package com.gcc_manpower.gccservicepaymenttracker.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import com.gcc_manpower.service.AppUserService; import javax.sql.DataSource; import jakarta.annotation.PostConstruct; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class GccServicePaymentTrackerService { private JdbcTemplate jdbcTemplate; @Autowired private AppUserService appUserService; @Autowired public void setDataSource(@Qualifier("mysqlItcellManagementDataSource") DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public Map getCountsByCategory() { Map counts = new HashMap<>(); counts.put("sd", 0); counts.put("tax invoice", 0); counts.put("emd", 0); counts.put("bank guarantee", 0); counts.put("delivery chellan", 0); counts.put("asd", 0); counts.put("agreement", 0); counts.put("total", 0); counts.put("status_not_updated", 0); String sql = "SELECT " + "CASE WHEN p.type REGEXP '^[0-9]+$' THEN t.type_name ELSE p.type END as type_resolved, " + "COUNT(*) as cnt " + "FROM payment_tracking_details p " + "LEFT JOIN payment_type_list t ON p.type REGEXP '^[0-9]+$' AND t.id = p.type " + "WHERE p.is_active = 1 AND p.is_delete = 0 " + "GROUP BY type_resolved"; try { // Count total active records String sqlTotal = "SELECT COUNT(*) FROM payment_tracking_details WHERE is_active = 1 AND is_delete = 0"; Integer totalCount = jdbcTemplate.queryForObject(sqlTotal, Integer.class); counts.put("total", totalCount != null ? totalCount : 0); // Count records where type is missing String sqlMissingType = "SELECT COUNT(*) FROM payment_tracking_details WHERE is_active = 1 AND is_delete = 0 AND (type IS NULL OR TRIM(type) = '')"; Integer missingTypeCount = jdbcTemplate.queryForObject(sqlMissingType, Integer.class); counts.put("status_not_updated", missingTypeCount != null ? missingTypeCount : 0); } catch (Exception e) { System.err.println("Error getting total or missing type counts: " + e.getMessage()); } try { List> rows = jdbcTemplate.queryForList(sql); for (Map row : rows) { String type = String.valueOf(row.get("type_resolved")).toLowerCase().trim(); Number countNum = (Number) row.get("cnt"); int count = countNum != null ? countNum.intValue() : 0; // Map database values back to categories if (type.equals("asd") || type.contains("asd") || type.contains("additional security")) { counts.put("asd", counts.getOrDefault("asd", 0) + count); } else if (type.equals("agreement") || type.contains("agreement")) { counts.put("agreement", counts.getOrDefault("agreement", 0) + count); } else if (type.equals("sd") || type.contains("security")) { counts.put("sd", counts.getOrDefault("sd", 0) + count); } else if (type.contains("invoice") || type.contains("bill")) { counts.put("tax invoice", counts.getOrDefault("tax invoice", 0) + count); } else if (type.equals("emd")) { counts.put("emd", counts.getOrDefault("emd", 0) + count); } else if (type.contains("guarantee") || type.contains("bg")) { counts.put("bank guarantee", counts.getOrDefault("bank guarantee", 0) + count); } else if (type.contains("chellan") || type.contains("challan") || type.contains("dc")) { counts.put("delivery chellan", counts.getOrDefault("delivery chellan", 0) + count); } } } catch (Exception e) { System.err.println("Error getting category counts: " + e.getMessage()); } return counts; } public List> searchDetails(String type, String fromDate, String toDate, String status) { StringBuilder sql = new StringBuilder( "SELECT p.*, " + "t.type_name as resolved_type, " + "v.vendor_name as resolved_vendor, " + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details p " + "LEFT JOIN payment_type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) " + "LEFT JOIN payment_vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) " + "LEFT JOIN payment_status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) " + "LEFT JOIN 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<>(); if (type != null && !type.trim().isEmpty()) { String category = type.trim().toLowerCase(); if (category.equals("status_not_updated")) { sql.append(" AND (p.type IS NULL OR TRIM(p.type) = '')"); } else if (category.equals("asd")) { sql.append( " AND (LOWER(p.type) = 'asd' OR LOWER(p.type) LIKE '%asd%' OR LOWER(t.type_name) LIKE '%asd%' OR LOWER(t.type_name) LIKE '%additional security%')"); } else if (category.equals("agreement")) { sql.append(" AND (LOWER(p.type) LIKE '%agreement%' OR LOWER(t.type_name) LIKE '%agreement%')"); } else if (category.equals("sd")) { sql.append( " AND ((LOWER(p.type) = 'sd' OR LOWER(p.type) LIKE '%security%' OR LOWER(t.type_name) LIKE '%security%') " + "AND LOWER(p.type) NOT LIKE '%additional security%' AND LOWER(p.type) NOT LIKE '%asd%' " + "AND (t.type_name IS NULL OR (LOWER(t.type_name) NOT LIKE '%additional security%' AND LOWER(t.type_name) NOT LIKE '%asd%')))"); } else if (category.equals("tax invoice")) { sql.append( " AND (LOWER(p.type) LIKE '%invoice%' OR LOWER(p.type) LIKE '%bill%' OR LOWER(t.type_name) LIKE '%invoice%' OR LOWER(t.type_name) LIKE '%bill%')"); } else if (category.equals("emd")) { sql.append( " AND (LOWER(p.type) = 'emd' OR LOWER(p.type) LIKE '%earnest%' OR LOWER(t.type_name) LIKE '%emd%' OR LOWER(t.type_name) LIKE '%earnest%')"); } else if (category.equals("bank guarantee")) { sql.append( " AND (LOWER(p.type) LIKE '%guarantee%' OR LOWER(p.type) = 'bg' OR LOWER(t.type_name) LIKE '%guarantee%' OR LOWER(t.type_name) = 'bg')"); } else if (category.equals("delivery chellan")) { sql.append( " AND (LOWER(p.type) LIKE '%chellan%' OR LOWER(p.type) LIKE '%challan%' OR LOWER(p.type) = 'dc' " + "OR LOWER(t.type_name) LIKE '%chellan%' OR LOWER(t.type_name) LIKE '%challan%' OR LOWER(t.type_name) = 'dc')"); } else { sql.append(" AND (LOWER(p.type) = ? OR LOWER(t.type_name) = ?)"); params.add(category); params.add(category); } } if (fromDate != null && !fromDate.trim().isEmpty()) { sql.append(" AND p.received_date >= ?"); params.add(parseDate(fromDate)); } if (toDate != null && !toDate.trim().isEmpty()) { sql.append(" AND p.received_date <= ?"); params.add(parseDate(toDate)); } if (status != null && !status.trim().isEmpty()) { if (status.trim().matches("^[0-9]+$")) { sql.append( " AND (p.status = ? OR (NOT p.status REGEXP '^[0-9]+$' AND p.status = (SELECT status_name FROM payment_status_list WHERE id = ? LIMIT 1)))"); params.add(status.trim()); params.add(status.trim()); } else { sql.append(" AND (LOWER(p.status) = ? OR (p.status REGEXP '^[0-9]+$' AND LOWER(s.status_name) = ?))"); params.add(status.trim().toLowerCase()); params.add(status.trim().toLowerCase()); } } sql.append(" ORDER BY p.order_by ASC, p.received_date DESC, p.id DESC"); try { return jdbcTemplate.queryForList(sql.toString(), params.toArray()); } catch (Exception e) { System.err.println("Error searching payment tracking details: " + e.getMessage()); return new ArrayList<>(); } } public Map getDetailById(int id) { String sql = "SELECT * FROM payment_tracking_details WHERE id = ? AND is_active = 1 AND is_delete = 0"; try { List> list = jdbcTemplate.queryForList(sql, id); if (list != null && !list.isEmpty()) { return list.get(0); } } catch (Exception e) { System.err.println("Error fetching payment tracking detail ID " + id + ": " + e.getMessage()); } return null; } public boolean saveOrUpdateDetail(Map payload, String updatedByUsername) { String userId = appUserService.getUserIdByName(updatedByUsername); if (userId == null) { userId = updatedByUsername; } Object idObj = payload.get("id"); java.sql.Date receivedDate = parseDate(payload.get("received_date")); String vendorName = (String) payload.get("vendor_name"); if (vendorName != null && !vendorName.trim().isEmpty() && !vendorName.trim().matches("^[0-9]+$")) { String trimmedVendorName = vendorName.trim(); String checkSql = "SELECT id FROM payment_vendors_list WHERE TRIM(LOWER(vendor_name)) = ? AND is_active = 1 AND is_delete = 0 LIMIT 1"; List existingIds = jdbcTemplate.query(checkSql, (rs, rowNum) -> rs.getInt("id"), trimmedVendorName.toLowerCase()); if (existingIds != null && !existingIds.isEmpty()) { vendorName = String.valueOf(existingIds.get(0)); } else { try { org.springframework.jdbc.support.GeneratedKeyHolder keyHolder = new org.springframework.jdbc.support.GeneratedKeyHolder(); final String finalVendorName = trimmedVendorName; jdbcTemplate.update(connection -> { java.sql.PreparedStatement ps = connection.prepareStatement( "INSERT INTO payment_vendors_list (vendor_name, is_active, is_delete, order_by, cdate) VALUES (?, 1, 0, '0', NOW())", java.sql.Statement.RETURN_GENERATED_KEYS); ps.setString(1, finalVendorName); return ps; }, keyHolder); Number key = keyHolder.getKey(); if (key != null) { vendorName = String.valueOf(key.intValue()); } } catch (Exception e) { System.err.println("Error inserting custom vendor '" + trimmedVendorName + "': " + e.getMessage()); } } } String subject = (String) payload.get("subject"); String amount = (String) payload.get("amount"); String invoiceNo = (String) payload.get("invoice_no"); String receiptNo = payload.get("receiptno") != null ? String.valueOf(payload.get("receiptno")) : null; java.sql.Date invoiceDate = parseDate(payload.get("invoice_date")); String type = (String) payload.get("type"); String physicalFileNo = (String) payload.get("physical_file_no"); String remarks = (String) payload.get("remarks"); String tat = (String) payload.get("tat"); String status = (String) payload.get("status"); String followup = (String) payload.get("followup"); String myfollowup = (String) payload.get("myfollowup"); String orderBy = (String) payload.get("order_by"); String filePath = (String) payload.get("file_path"); String paymentReview = (String) payload.get("payment_review"); if (idObj != null && !String.valueOf(idObj).trim().isEmpty()) { // Update int id = Integer.parseInt(String.valueOf(idObj).trim()); // Insert old record into history before update try { Map old = getDetailById(id); if (old != null) { String insertHistorySql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(insertHistorySql, old.get("id"), old.get("received_date"), old.get("vendor_name"), old.get("subject"), old.get("amount"), old.get("invoice_no"), old.get("receiptno"), old.get("invoice_date"), old.get("type"), old.get("physical_file_no"), old.get("remarks"), old.get("tat"), old.get("status"), old.get("followup"), old.get("myfollowup"), old.get("is_active"), old.get("is_delete"), old.get("order_by"), old.get("file_path"), old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), userId); } } catch (Exception e) { System.err.println("Error saving history for payment tracking detail ID " + id + ": " + e.getMessage()); } String sql = "UPDATE payment_tracking_details SET " + "received_date = ?, vendor_name = ?, subject = ?, amount = ?, invoice_no = ?, " + "receiptno = ?, invoice_date = ?, type = ?, physical_file_no = ?, remarks = ?, " + "tat = ?, status = ?, followup = ?, myfollowup = ?, order_by = ?, file_path = ?, " + "payment_review = ?, updated_by = ?, updated_date = ? " + "WHERE id = ? AND is_active = 1 AND is_delete = 0"; try { int rows = jdbcTemplate.update(sql, receivedDate, vendorName, subject, amount, invoiceNo, receiptNo, invoiceDate, type, physicalFileNo, remarks, tat, status, followup, myfollowup, orderBy, filePath, paymentReview, userId, new java.sql.Timestamp(System.currentTimeMillis()), id); return rows > 0; } catch (Exception e) { System.err.println("Error updating payment tracking detail ID " + id + ": " + e.getMessage()); return false; } } else { // Insert String sql = "INSERT INTO payment_tracking_details (" + "received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, " + "followup, myfollowup, is_active, is_delete, order_by, file_path, " + "payment_review, updated_by, updated_date, cdate" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?, ?, ?, ?, ?, ?)"; try { java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); int rows = jdbcTemplate.update(sql, receivedDate, vendorName, subject, amount, invoiceNo, receiptNo, invoiceDate, type, physicalFileNo, remarks, tat, status, followup, myfollowup, orderBy, filePath, paymentReview, userId, now, now); return rows > 0; } catch (Exception e) { System.err.println("Error saving payment tracking detail: " + e.getMessage()); return false; } } } public boolean deleteDetail(int id, String remarks, String updatedBy) { String sql = "UPDATE payment_tracking_details SET is_active = 0, is_delete = 1, remarks = ?, updated_by = ?, updated_date = ? WHERE id = ?"; try { // Retrieve old record first to populate history Map old = null; try { old = jdbcTemplate.queryForMap("SELECT * FROM payment_tracking_details WHERE id = ?", id); } catch (Exception e) { System.err.println("Could not find old record for history before delete: " + e.getMessage()); } int rows = jdbcTemplate.update(sql, remarks, updatedBy, new java.sql.Timestamp(System.currentTimeMillis()), id); if (rows > 0 && old != null) { try { String histSql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(histSql, id, old.get("received_date"), old.get("vendor_name"), old.get("subject"), old.get("amount"), old.get("invoice_no"), old.get("receiptno"), old.get("invoice_date"), old.get("type"), old.get("physical_file_no"), remarks, old.get("tat"), old.get("status"), old.get("followup"), old.get("myfollowup"), 0, // is_active = 0 1, // is_delete = 1 old.get("order_by"), old.get("file_path"), old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), updatedBy); } catch (Exception he) { System.err.println("Error saving history during delete: " + he.getMessage()); } } return rows > 0; } catch (Exception e) { System.err.println("Error soft-deleting payment tracking detail ID " + id + ": " + e.getMessage()); return false; } } private java.sql.Date parseDate(Object obj) { if (obj == null || String.valueOf(obj).trim().isEmpty()) { return null; } String str = String.valueOf(obj).trim(); try { // Support both yyyy-MM-dd and dd/MM/yyyy if (str.contains("/")) { String[] parts = str.split("/"); if (parts.length == 3) { // Convert dd/MM/yyyy to yyyy-MM-dd str = parts[2] + "-" + parts[1] + "-" + parts[0]; } } return java.sql.Date.valueOf(str); } catch (Exception e) { System.err.println("Error parsing date '" + obj + "': " + e.getMessage()); return null; } } public List> getActiveTypes() { String sql = "SELECT id, type_name FROM payment_type_list " + "WHERE is_active = 1 AND is_delete = 0 " + "ORDER BY order_by ASC, type_name ASC"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { System.err.println("Error fetching active types: " + e.getMessage()); return new ArrayList<>(); } } public List> getActiveVendors() { String sql = "SELECT id, vendor_name FROM payment_vendors_list " + "WHERE is_active = 1 AND is_delete = 0 " + "ORDER BY order_by ASC, vendor_name ASC"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { System.err.println("Error fetching active vendors: " + e.getMessage()); return new ArrayList<>(); } } public List> getStatusList() { String sql = "SELECT id, status_name FROM payment_status_list " + "WHERE is_active = 1 AND is_delete = 0 " + "ORDER BY id ASC, status_name ASC"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { System.err.println("Error fetching active vendors: " + e.getMessage()); return new ArrayList<>(); } } public List> getHistory(int detailId) { String sql = "SELECT h.*, " + "t.type_name as resolved_type, " + "v.vendor_name as resolved_vendor, " + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details_history h " + "LEFT JOIN payment_type_list t ON TRIM(h.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(h.type) AS UNSIGNED) " + "LEFT JOIN payment_vendors_list v ON TRIM(h.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(h.vendor_name) AS UNSIGNED) " + "LEFT JOIN payment_status_list s ON TRIM(h.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(h.status) AS UNSIGNED) " + "LEFT JOIN 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"; try { return jdbcTemplate.queryForList(sql, detailId); } catch (Exception e) { System.err.println( "Error fetching payment tracking history for detail ID " + detailId + ": " + e.getMessage()); return new ArrayList<>(); } } public List> getDeletedDetails() { String sql = "SELECT p.*, " + "t.type_name as resolved_type, " + "v.vendor_name as resolved_vendor, " + "s.status_name as resolved_status, " + "u.usr_name as updated_by_name " + "FROM payment_tracking_details p " + "LEFT JOIN payment_type_list t ON TRIM(p.type) REGEXP '^[0-9]+$' AND t.id = CAST(TRIM(p.type) AS UNSIGNED) " + "LEFT JOIN payment_vendors_list v ON TRIM(p.vendor_name) REGEXP '^[0-9]+$' AND v.id = CAST(TRIM(p.vendor_name) AS UNSIGNED) " + "LEFT JOIN payment_status_list s ON TRIM(p.status) REGEXP '^[0-9]+$' AND s.id = CAST(TRIM(p.status) AS UNSIGNED) " + "LEFT JOIN 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"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { System.err.println("Error fetching deleted payment tracking details: " + e.getMessage()); return new ArrayList<>(); } } public boolean restoreDetail(int id, String updatedBy) { String sql = "UPDATE payment_tracking_details SET is_active = 1, is_delete = 0, remarks = 'Record restored', updated_by = ?, updated_date = ? WHERE id = ?"; try { // Retrieve old record first to populate history Map old = null; try { old = jdbcTemplate.queryForMap("SELECT * FROM payment_tracking_details WHERE id = ?", id); } catch (Exception e) { System.err.println("Could not find old record for history before restore: " + e.getMessage()); } int rows = jdbcTemplate.update(sql, updatedBy, new java.sql.Timestamp(System.currentTimeMillis()), id); if (rows > 0 && old != null) { try { String histSql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(histSql, id, old.get("received_date"), old.get("vendor_name"), old.get("subject"), old.get("amount"), old.get("invoice_no"), old.get("receiptno"), old.get("invoice_date"), old.get("type"), old.get("physical_file_no"), "Record restored", // remarks old.get("tat"), old.get("status"), old.get("followup"), old.get("myfollowup"), 1, // is_active = 1 0, // is_delete = 0 old.get("order_by"), old.get("file_path"), old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), updatedBy); } catch (Exception he) { System.err.println("Error saving history during restore: " + he.getMessage()); } } return rows > 0; } catch (Exception e) { System.err.println("Error restoring payment tracking detail ID " + id + ": " + e.getMessage()); return false; } } }