From 4c0ed35aeb4d1a31cfd97511f751426893483a3a Mon Sep 17 00:00:00 2001 From: yoganandhank Date: Wed, 15 Jul 2026 18:07:24 +0530 Subject: [PATCH] changes in payment dashboard page and service --- .../GccServicePaymentTrackerService.java | 19 +- .../gccservicepaymenttracker/dashboard.html | 230 +++++++++++++++++- 2 files changed, 228 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java b/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java index 029fb71..a625a2a 100644 --- a/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java +++ b/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java @@ -97,9 +97,9 @@ public class GccServicePaymentTrackerService { + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details p " - + "LEFT JOIN type_list t ON p.type REGEXP '^[0-9]+$' AND t.id = p.type " - + "LEFT JOIN vendors_list v ON p.vendor_name REGEXP '^[0-9]+$' AND v.id = p.vendor_name " - + "LEFT JOIN status_list s ON p.status REGEXP '^[0-9]+$' AND s.id = p.status " + + "LEFT JOIN type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) " + + "LEFT JOIN vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) " + + "LEFT JOIN status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) " + "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) " + "WHERE p.is_active = 1 AND p.is_delete = 0"); List params = new ArrayList<>(); @@ -434,9 +434,9 @@ public class GccServicePaymentTrackerService { + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details_history h " - + "LEFT JOIN type_list t ON h.type REGEXP '^[0-9]+$' AND t.id = h.type " - + "LEFT JOIN vendors_list v ON h.vendor_name REGEXP '^[0-9]+$' AND v.id = h.vendor_name " - + "LEFT JOIN status_list s ON h.status REGEXP '^[0-9]+$' AND s.id = h.status " + + "LEFT JOIN type_list t ON TRIM(h.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(h.type) AS UNSIGNED) " + + "LEFT JOIN vendors_list v ON TRIM(h.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(h.vendor_name) AS UNSIGNED) " + + "LEFT JOIN status_list s ON TRIM(h.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(h.status) AS UNSIGNED) " + "LEFT JOIN gcc_manpower.app_user u ON (h.updated_by = CAST(u.usr_id AS CHAR) OR h.updated_by = u.usr_name) " + "WHERE h.detail_id = ? " + "ORDER BY h.updated_at DESC"; @@ -456,9 +456,9 @@ public class GccServicePaymentTrackerService { + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details p " - + "LEFT JOIN type_list t ON p.type REGEXP '^[0-9]+$' AND t.id = p.type " - + "LEFT JOIN vendors_list v ON p.vendor_name REGEXP '^[0-9]+$' AND v.id = p.vendor_name " - + "LEFT JOIN status_list s ON p.status REGEXP '^[0-9]+$' AND s.id = p.status " + + "LEFT JOIN type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) " + + "LEFT JOIN vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) " + + "LEFT JOIN status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) " + "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) " + "WHERE p.is_active = 0 AND p.is_delete = 1 " + "ORDER BY p.updated_date DESC"; @@ -523,4 +523,5 @@ public class GccServicePaymentTrackerService { return false; } } + } diff --git a/src/main/resources/templates/gccservicepaymenttracker/dashboard.html b/src/main/resources/templates/gccservicepaymenttracker/dashboard.html index a97122f..77d3a50 100644 --- a/src/main/resources/templates/gccservicepaymenttracker/dashboard.html +++ b/src/main/resources/templates/gccservicepaymenttracker/dashboard.html @@ -939,6 +939,19 @@ justify-content: center; } } + + /* Make modal-xl wider to fit multi-column data tables comfortably without squishing text */ + @media (min-width: 1200px) { + .modal-xl { + max-width: 94% !important; + } + } + + @media (min-width: 1600px) { + .modal-xl { + max-width: 1550px !important; + } + } @@ -1191,7 +1204,7 @@ Vendor Name Total No of files - Completed + Completed Files Awaiting Details Pending with Dept Pending @@ -1353,7 +1366,7 @@ Physical File No. eFile No./ Receipt No. Subject - Type (SD/ ASD) + Type of File Vendor Name Amount Age of Bill (Days) @@ -1716,7 +1729,7 @@ Physical File No. eFile No./ Receipt No. Subject - Type (SD/ ASD) + Type of File Vendor Name Amount Age of Bill (Days) @@ -1769,7 +1782,7 @@ Physical File No. eFile No./ Receipt No. Subject - Type (SD/ ASD) + Type of File Vendor Name Amount Age of Bill (Days) @@ -1804,6 +1817,7 @@ let activeCategory = 'all'; let allRecords = []; + let currentFilteredRecords = []; let recordModalObj = null; let historyModalObj = null; let deletedRecordsModalObj = null; @@ -1864,7 +1878,17 @@ ]); activeTypes = await typesRes.json(); activeVendors = await vendorsRes.json(); - activeStatus = await statusRes.json(); + const fetchedStatus = await statusRes.json(); + + // Sort activeStatus to have completed first, then pending, then others + const completedStatuses = fetchedStatus.filter(s => getStatusKey(s.status_name) === 'completed'); + const pendingStatuses = fetchedStatus.filter(s => getStatusKey(s.status_name) === 'pending'); + const otherStatuses = fetchedStatus.filter(s => + getStatusKey(s.status_name) !== 'completed' && + getStatusKey(s.status_name) !== 'pending' + ); + activeStatus = [...completedStatuses, ...pendingStatuses, ...otherStatuses]; + populateDropdowns(); } catch (err) { console.error("Error loading types, vendors or statuses:", err); @@ -1880,7 +1904,7 @@ if (name.includes('awaiting') || name.includes('details')) { return 'awaiting details'; } - if (name.includes('dept') || name.includes('uac') || name.includes('office')) { + if (name.includes('dept') || name.includes('uac') || name.includes('office') || name.includes('fm unit')) { return 'pending with dept'; } if (name.includes('pending') || name.includes('initiated')) { @@ -2005,11 +2029,22 @@ // Dynamically populate vendor breakdown table headers const vendorBreakdownTheadTr = document.getElementById('vendor-breakdown-thead-tr'); if (vendorBreakdownTheadTr) { + const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed'); + const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed'); + let vendorHeadHtml = ` Vendor Name Total No of files `; - activeStatus.forEach(s => { + completedStatuses.forEach(s => { + const style = getStatusStyle(s.status_name); + vendorHeadHtml += `${escapeHtml(s.status_name)}`; + }); + + // Custom Pending column header + vendorHeadHtml += `Pending files`; + + nonCompletedStatuses.forEach(s => { const style = getStatusStyle(s.status_name); vendorHeadHtml += `${escapeHtml(s.status_name)}`; }); @@ -2218,7 +2253,6 @@ if (activeClickFilter.col === 'completed' && status.toLowerCase() !== 'completed') return false; if (activeClickFilter.col === 'awaiting' && status.toLowerCase() !== 'awaiting details') return false; if (activeClickFilter.col === 'pending_dept' && status.toLowerCase() !== 'pending with dept') return false; - if (activeClickFilter.col === 'pending' && (status.toLowerCase() === 'completed' || status.toLowerCase() === 'awaiting details' || status.toLowerCase() === 'pending with dept')) return false; if (activeClickFilter.col === 'age_1_15' && (age < 1 || age > 15)) return false; if (activeClickFilter.col === 'age_16_30' && (age < 16 || age > 30)) return false; @@ -2249,6 +2283,8 @@ ); }); + currentFilteredRecords = filtered; + showLoader(false); const totalRecords = filtered.length; @@ -3077,8 +3113,11 @@ const filtered = vendorBreakdownData.filter(v => v.vendorName.toLowerCase().includes(query)); + const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed'); + const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed'); + if (filtered.length === 0) { - const colSpan = 2 + activeStatus.length + 6; + const colSpan = 2 + activeStatus.length + 1 + 6; tbody.innerHTML = `No matching vendors found`; return; } @@ -3116,7 +3155,21 @@ ${v.total} `; - activeStatus.forEach(s => { + // 1. Completed statuses + let totalCompleted = 0; + completedStatuses.forEach(s => { + const count = v.statusCounts[s.id] || 0; + totalCompleted += count; + const style = getStatusStyle(s.status_name); + trHtml += `${count}`; + }); + + // 2. Custom "Pending" column + const pendingCount = v.total - totalCompleted; + trHtml += `${pendingCount}`; + + // 3. Non-completed statuses + nonCompletedStatuses.forEach(s => { const count = v.statusCounts[s.id] || 0; const style = getStatusStyle(s.status_name); trHtml += `${count}`; @@ -3144,10 +3197,25 @@ TOTAL ${sumTotal} `; - activeStatus.forEach(s => { + + // 1. Completed statuses + let sumCompleted = 0; + completedStatuses.forEach(s => { const style = getStatusStyle(s.status_name); + sumCompleted += sumStatuses[s.id]; totalTrHtml += `${sumStatuses[s.id]}`; }); + + // 2. Custom "Pending" column + const sumPending = sumTotal - sumCompleted; + totalTrHtml += `${sumPending}`; + + // 3. Non-completed statuses + nonCompletedStatuses.forEach(s => { + const style = getStatusStyle(s.status_name); + totalTrHtml += `${sumStatuses[s.id]}`; + }); + totalTrHtml += ` ${sumAge1_15} ${sumAge16_30} @@ -3335,6 +3403,27 @@ } } + if (clickType === 'vendor_pending_custom') { + const rowVendor = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim(); + if (rowVendor !== vendorName) return false; + + const rowStatusStr = String(row.status || '').trim(); + const isCompleted = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed').some(s => + rowStatusStr === String(s.id) || + rowStatusStr.toLowerCase() === s.status_name.toLowerCase().trim() + ); + if (isCompleted) return false; + } + + if (clickType === 'pending_custom') { + const rowStatusStr = String(row.status || '').trim(); + const isCompleted = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed').some(s => + rowStatusStr === String(s.id) || + rowStatusStr.toLowerCase() === s.status_name.toLowerCase().trim() + ); + if (isCompleted) return false; + } + if (clickType === 'vendor_total') { const rowVendor = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim(); if (rowVendor !== vendorName) return false; @@ -3621,7 +3710,124 @@ } function exportMainTableToExcel() { - exportTableToExcelHelper('tracking-table', 'Payment_Tracking_Details_Main_Report.xlsx'); + if (!currentFilteredRecords || currentFilteredRecords.length === 0) { + alert("No records to export."); + return; + } + + // Create a temporary table element in memory + const tempTable = document.createElement('table'); + + // 1. Create head + const thead = document.createElement('thead'); + thead.innerHTML = ` + + Sr. No. + Received Date + Invoice Date + Physical File No. + eFile No./ Receipt No. + Subject + Type of File + Vendor Name + Amount + Age of Bill (Days) + Status (Pending / Completed) + Remarks + Last Updated At + Last Updated By + + `; + tempTable.appendChild(thead); + + // Helpers + const text = val => (val === null || val === undefined || String(val).trim() === '') ? '-' : val; + + const formatDate = dateStr => { + if (!dateStr) return '-'; + const parts = dateStr.split('-'); + if (parts.length === 3) { + return `${parts[2]}/${parts[1]}/${parts[0]}`; + } + return dateStr; + }; + + const formatDateTime = dateTimeStr => { + if (!dateTimeStr) return '-'; + try { + const d = new Date(dateTimeStr); + if (!isNaN(d.getTime())) { + const dd = String(d.getDate()).padStart(2, '0'); + const MM = String(d.getMonth() + 1).padStart(2, '0'); + const yyyy = d.getFullYear(); + const HH = String(d.getHours()).padStart(2, '0'); + const mm = String(d.getMinutes()).padStart(2, '0'); + const ss = String(d.getSeconds()).padStart(2, '0'); + return `${dd}/${MM}/${yyyy} ${HH}:${mm}:${ss}`; + } + } catch (e) { + console.error(e); + } + return dateTimeStr; + }; + + const getTypeLabel = typeVal => { + const cleanType = String(typeVal || '').trim().toLowerCase(); + let label = cleanType; + + if (cleanType === 'asd' || cleanType.includes('asd') || cleanType.includes('additional security')) { + label = 'ASD'; + } else if (cleanType.includes('agreement')) { + label = 'Agreement'; + } else if (cleanType === 'sd' || (cleanType.includes('security') && !cleanType.includes('additional') && !cleanType.includes('asd'))) { + label = 'Security Deposit'; + } else if (cleanType.includes('invoice') || cleanType.includes('bill')) { + label = 'Tax Invoice'; + } else if (cleanType === 'emd' || cleanType.includes('earnest')) { + label = 'EMD'; + } else if (cleanType.includes('guarantee') || cleanType === 'bg') { + label = 'Bank Guarantee'; + } else if (cleanType.includes('chellan') || cleanType.includes('challan') || cleanType === 'dc') { + label = 'Delivery Chellan'; + } else { + const typeObj = activeTypes.find(t => String(t.id) === cleanType); + if (typeObj) { + return typeObj.type_name; + } + label = typeVal || '-'; + } + return label; + }; + + // 2. Create body + const tbody = document.createElement('tbody'); + currentFilteredRecords.forEach((row, index) => { + const tr = document.createElement('tr'); + const dispStatus = String(row.resolved_status || getStatusName(row.status) || '').trim(); + + tr.innerHTML = ` + ${index + 1} + ${formatDate(row.received_date)} + ${formatDate(row.invoice_date)} + ${text(row.physical_file_no)} + ${text(row.receiptno)} + ${text(row.subject)} + ${getTypeLabel(row.resolved_type || row.type)} + ${text(row.resolved_vendor || row.vendor_name)} + ${text(row.amount)} + ${calculateAgeOfBill(row.received_date)} + ${text(dispStatus)} + ${text(row.remarks)} + ${formatDateTime(row.updated_date)} + ${text(row.updated_by_name || '-')} + `; + tbody.appendChild(tr); + }); + tempTable.appendChild(tbody); + + // Convert to Excel workbook and download + const wb = XLSX.utils.table_to_book(tempTable, { raw: true, sheet: "Sheet1" }); + XLSX.writeFile(wb, 'Payment_Tracking_Details_Main_Report.xlsx'); } // --- LATEST UPDATES CORNER BADGE LOGIC ---