From 8fab02e7c828212f3d280c80c4aa41ff11c1151c Mon Sep 17 00:00:00 2001 From: yoganandhank Date: Fri, 25 Sep 2026 10:54:30 +0530 Subject: [PATCH] changes in toner management pages and api's , adjustment management pages and apis's --- .../com/gcc_manpower/config/WebConfig.java | 3 + .../controller/AdjustmentController.java | 3 +- .../service/AdjustmentService.java | 11 +- .../controller/TonerController.java | 278 +- .../service/TonerService.java | 994 ++++-- .../gccadjustmentmanagement/dashboard.html | 1253 +++++++- .../gccadjustmentmanagement/details.html | 799 +++-- .../gcctapaldetailsmanagement/dashboard.html | 18 +- .../templates/gcctonermanagement/upload.html | 2706 ++++++++++++++--- src/main/resources/templates/home/login.html | 2 +- 10 files changed, 5038 insertions(+), 1029 deletions(-) diff --git a/src/main/java/com/gcc_manpower/config/WebConfig.java b/src/main/java/com/gcc_manpower/config/WebConfig.java index c181423..9a728a9 100644 --- a/src/main/java/com/gcc_manpower/config/WebConfig.java +++ b/src/main/java/com/gcc_manpower/config/WebConfig.java @@ -52,6 +52,9 @@ public class WebConfig implements WebMvcConfigurer { // Map URL path /gcctonermanagementfiles/** to the configured toner folder String tonerUploadPath = Paths.get(tonerUploadPathConfig).toAbsolutePath().toUri() .toString(); + if (!tonerUploadPath.endsWith("/")) { + tonerUploadPath += "/"; + } registry.addResourceHandler("/gcctonermanagementfiles/**") .addResourceLocations(tonerUploadPath); diff --git a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java index 8f65f7c..450d72e 100644 --- a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java +++ b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java @@ -388,6 +388,7 @@ public class AdjustmentController { public Map updateEJV( @RequestParam("id") Integer id, @RequestParam("ejv_num") String ejvNum, + @RequestParam(value = "ejv_date", required = false) String ejvDate, @RequestParam(value = "ejvFile", required = false) MultipartFile ejvFile, HttpSession session) { @@ -416,7 +417,7 @@ public class AdjustmentController { String ejvPath = saveFile(ejvFile, "ejv", fileDataNum, uploadDir, existing != null ? (String) existing.get("ejv_num_file_path") : null); - boolean updated = adjustmentService.updateEJV(id, ejvNum, ejvPath, username); + boolean updated = adjustmentService.updateEJV(id, ejvNum, ejvPath, ejvDate, username); if (updated) { response.put("success", true); response.put("message", "EJV details updated successfully."); diff --git a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java index 40b6dfb..06eff07 100644 --- a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java +++ b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java @@ -342,11 +342,18 @@ public class AdjustmentService { } public boolean updateEJV(int id, String ejvNum, String ejvFilePath, String username) { + return updateEJV(id, ejvNum, ejvFilePath, null, username); + } + + public boolean updateEJV(int id, String ejvNum, String ejvFilePath, String ejvDate, String username) { java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); - java.sql.Date today = new java.sql.Date(System.currentTimeMillis()); + java.sql.Date dateToSave = parseDate(ejvDate); + if (dateToSave == null) { + dateToSave = new java.sql.Date(System.currentTimeMillis()); + } String sql = "UPDATE adjustment_file_details SET ejv_num = ?, ejv_num_file_path = ?, ejv_num_file_gen_date = ?, updated_date = ?, updated_by = ? WHERE id = ?"; try { - int rows = jdbcTemplate.update(sql, ejvNum, ejvFilePath, today, now, username, id); + int rows = jdbcTemplate.update(sql, ejvNum, ejvFilePath, dateToSave, now, username, id); return rows > 0; } catch (Exception e) { System.err.println("Error updating EJV: " + e.getMessage()); diff --git a/src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java b/src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java index 02bc309..03ab898 100644 --- a/src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java +++ b/src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java @@ -234,7 +234,7 @@ public class TonerController { public Map uploadRequest( @RequestParam("itcellRefNo") String itcellRefNo, @RequestParam("deptId") String deptId, - @RequestParam("zoneId") String zoneId, + @RequestParam(value = "zoneId", required = false) String zoneId, @RequestParam("itemsJson") String itemsJson, @RequestParam(value = "userRequestFile", required = false) MultipartFile userRequestFile, HttpSession session) { @@ -282,8 +282,9 @@ public class TonerController { String urPath = saveUploadedFile(userRequestFile, "user_request", uploadDir); + String safeZoneId = (zoneId != null && !zoneId.trim().isEmpty()) ? zoneId.trim() : null; boolean saved = tonerService.saveUserRequest( - itcellRefNo.trim(), deptId.trim(), zoneId.trim(), items, urPath, updatedBy); + itcellRefNo.trim(), deptId.trim(), safeZoneId, items, urPath, updatedBy); if (saved) { response.put("success", true); @@ -301,12 +302,96 @@ public class TonerController { return response; } + @PostMapping("/api/request/update") + @ResponseBody + public Map updateRequest( + @RequestParam("id") int id, + @RequestParam("itcellRefNo") String itcellRefNo, + @RequestParam("deptId") String deptId, + @RequestParam(value = "zoneId", required = false) String zoneId, + @RequestParam("pid") int pid, + @RequestParam("tid") int tid, + @RequestParam("qtyRequested") int qtyRequested, + @RequestParam(value = "userRequestFile", required = false) MultipartFile userRequestFile, + HttpSession session) { + + Map response = new HashMap<>(); + String username = (String) session.getAttribute("toner_usrName"); + if (username == null) { + response.put("success", false); + response.put("error", "Unauthorized access. Please log in."); + return response; + } + + String updatedBy = appUserService.getUserIdByName(username); + if (updatedBy == null) + updatedBy = username; + + if (itcellRefNo == null || itcellRefNo.trim().isEmpty()) { + response.put("success", false); + response.put("error", "IT Cell Reference No. is required."); + return response; + } + + if (deptId == null || deptId.trim().isEmpty()) { + response.put("success", false); + response.put("error", "Department is required."); + return response; + } + + if (qtyRequested <= 0) { + response.put("success", false); + response.put("error", "Quantity requested must be greater than 0."); + return response; + } + + if (userRequestFile != null && !userRequestFile.isEmpty() && !isPdfFile(userRequestFile)) { + response.put("success", false); + response.put("error", "User Request file must be a PDF."); + return response; + } + + try { + File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile(); + if (!uploadDir.exists()) { + uploadDir.mkdirs(); + } + + String urPath = null; + if (userRequestFile != null && !userRequestFile.isEmpty()) { + urPath = saveUploadedFile(userRequestFile, "user_request", uploadDir); + } + + String safeZoneId = (zoneId != null && !zoneId.trim().isEmpty()) ? zoneId.trim() : null; + boolean updated = tonerService.updateUserRequest( + id, itcellRefNo.trim(), deptId.trim(), safeZoneId, pid, tid, qtyRequested, urPath, updatedBy); + + if (updated) { + response.put("success", true); + response.put("message", "User request details updated successfully."); + } else { + if (urPath != null) { + deleteFileIfExists(urPath, uploadDir); + } + response.put("success", false); + response.put("error", "Failed to update user request details in database."); + } + } catch (Exception e) { + response.put("success", false); + response.put("error", "An error occurred: " + e.getMessage()); + e.printStackTrace(); + } + return response; + } + @PostMapping("/api/outward") @ResponseBody public Map uploadOutward( @RequestParam("id") int id, @RequestParam("qtyIssued") String qtyIssued, @RequestParam(value = "finalDeliveredFile", required = false) MultipartFile finalDeliveredFile, + @RequestParam(value = "userPhotoFile", required = false) MultipartFile userPhotoFile, + @RequestParam(value = "userPhotoBase64", required = false) String userPhotoBase64, HttpSession session) { Map response = new HashMap<>(); @@ -321,12 +406,97 @@ public class TonerController { if (updatedBy == null) updatedBy = username; - if (finalDeliveredFile != null && !finalDeliveredFile.isEmpty() && !isPdfFile(finalDeliveredFile)) { + if (finalDeliveredFile != null && !finalDeliveredFile.isEmpty()) { + if (!isPdfFile(finalDeliveredFile)) { + response.put("success", false); + response.put("error", "Final Delivered file must be a PDF."); + return response; + } + boolean hasPhoto = (userPhotoFile != null && !userPhotoFile.isEmpty()) + || (userPhotoBase64 != null && !userPhotoBase64.trim().isEmpty()); + if (!hasPhoto) { + response.put("success", false); + response.put("error", "Receiver / User photo is mandatory."); + return response; + } + } + + if (userPhotoFile != null && !userPhotoFile.isEmpty() && !isImageFile(userPhotoFile)) { + response.put("success", false); + response.put("error", "User photo must be a valid image file (JPG, PNG, WEBP)."); + return response; + } + + Map req = tonerService.getUserRequestById(id); + if (req == null) { response.put("success", false); - response.put("error", "Final Delivered file must be a PDF."); + response.put("error", "Invalid or non-existent request ID: " + id); return response; } + boolean isInitialDelivery = (finalDeliveredFile == null || finalDeliveredFile.isEmpty()) && + (userPhotoFile == null || userPhotoFile.isEmpty()) && + (userPhotoBase64 == null || userPhotoBase64.trim().isEmpty()); + + if (isInitialDelivery) { + int issueQty = 0; + try { + issueQty = Integer.parseInt(qtyIssued.trim()); + } catch (Exception e) { + response.put("success", false); + response.put("error", "Invalid quantity issued value."); + return response; + } + + if (issueQty <= 0) { + response.put("success", false); + response.put("error", "Quantity issued must be greater than zero."); + return response; + } + + String pid = String.valueOf(req.get("pid")); + String tid = String.valueOf(req.get("tid")); + String modelName = req.get("model_name") != null ? String.valueOf(req.get("model_name")) : "Toner"; + String printerName = req.get("printer_name") != null ? String.valueOf(req.get("printer_name")) : "Printer"; + + int availableStock = tonerService.getAvailableStockForModel(pid, tid); + + if (availableStock <= 0) { + response.put("success", false); + response.put("error", "Cannot issue: No inward stock available for " + printerName + " - " + modelName + + ". Please register inward stock before issuing outward delivery."); + return response; + } + + if (issueQty > availableStock) { + response.put("success", false); + response.put("error", + "Cannot issue: Quantity to issue (" + issueQty + ") exceeds available inward stock (" + + availableStock + ") for " + printerName + " - " + modelName + "."); + return response; + } + + int requestedQty = 0; + int alreadyDelivered = 0; + try { + if (req.get("qty_requested") != null) { + requestedQty = Integer.parseInt(String.valueOf(req.get("qty_requested"))); + } + if (req.get("qty_delivered") != null) { + alreadyDelivered = Integer.parseInt(String.valueOf(req.get("qty_delivered"))); + } + } catch (Exception ignored) { + } + + int remainingReq = Math.max(0, requestedQty - alreadyDelivered); + if (issueQty > remainingReq) { + response.put("success", false); + response.put("error", "Cannot issue: Quantity to issue (" + issueQty + + ") exceeds remaining requested quantity (" + remainingReq + ")."); + return response; + } + } + try { File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile(); if (!uploadDir.exists()) { @@ -334,15 +504,22 @@ public class TonerController { } String fdPath = saveUploadedFile(finalDeliveredFile, "final_delivered", uploadDir); + String photoPath = null; + if (userPhotoFile != null && !userPhotoFile.isEmpty()) { + photoPath = saveUploadedFile(userPhotoFile, "receiver_photo", uploadDir); + } else if (userPhotoBase64 != null && !userPhotoBase64.trim().isEmpty()) { + photoPath = saveBase64Image(userPhotoBase64, "receiver_photo", uploadDir); + } boolean saved = tonerService.saveOutwardRegister( - id, qtyIssued.trim(), fdPath, updatedBy); + id, qtyIssued.trim(), fdPath, photoPath, updatedBy); if (saved) { response.put("success", true); response.put("message", "Outward delivery recorded successfully."); } else { deleteFileIfExists(fdPath, uploadDir); + deleteFileIfExists(photoPath, uploadDir); response.put("success", false); response.put("error", "Failed to update outward details in the database."); } @@ -493,12 +670,24 @@ public class TonerController { return originalFilename.toLowerCase().endsWith(".pdf"); } + private boolean isImageFile(MultipartFile file) { + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null) + return false; + String lower = originalFilename.toLowerCase(); + return lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".png") || lower.endsWith(".webp") + || lower.endsWith(".gif"); + } + private String saveUploadedFile(MultipartFile file, String prefix, File uploadDir) throws Exception { if (file == null || file.isEmpty()) { return null; } String originalFilename = file.getOriginalFilename(); - String ext = ".pdf"; // Since we only allow PDFs + String ext = ".pdf"; + if (originalFilename != null && originalFilename.lastIndexOf('.') > 0) { + ext = originalFilename.substring(originalFilename.lastIndexOf('.')).toLowerCase(); + } // Clean filename, make it unique String savedFilename = prefix + "_" + System.currentTimeMillis() + ext; @@ -508,6 +697,34 @@ public class TonerController { return "gcctonermanagementfiles/" + savedFilename; } + private String saveBase64Image(String base64Data, String prefix, File uploadDir) throws Exception { + if (base64Data == null || base64Data.trim().isEmpty()) { + return null; + } + String cleanData = base64Data.trim(); + String ext = ".jpg"; + if (cleanData.contains("data:image/png;base64,")) { + ext = ".png"; + cleanData = cleanData.replace("data:image/png;base64,", ""); + } else if (cleanData.contains("data:image/jpeg;base64,")) { + ext = ".jpg"; + cleanData = cleanData.replace("data:image/jpeg;base64,", ""); + } else if (cleanData.contains("data:image/webp;base64,")) { + ext = ".webp"; + cleanData = cleanData.replace("data:image/webp;base64,", ""); + } else if (cleanData.startsWith("data:") && cleanData.contains(";base64,")) { + cleanData = cleanData.substring(cleanData.indexOf(";base64,") + 8); + } + + byte[] decodedBytes = java.util.Base64.getDecoder().decode(cleanData); + String savedFilename = prefix + "_" + System.currentTimeMillis() + ext; + File destFile = new File(uploadDir, savedFilename).getAbsoluteFile(); + try (java.io.FileOutputStream fos = new java.io.FileOutputStream(destFile)) { + fos.write(decodedBytes); + } + return "gcctonermanagementfiles/" + savedFilename; + } + private void deleteFileIfExists(String webPath, File uploadDir) { if (webPath == null || webPath.isEmpty()) return; @@ -531,6 +748,26 @@ public class TonerController { return tonerService.getActiveWorkorderRefNos(); } + @GetMapping("/api/workorders/items") + @ResponseBody + public List> getWorkorderRegisteredItems( + @RequestParam("refNo") String refNo, + HttpSession session) { + if (session.getAttribute("toner_usrName") == null) { + return new ArrayList<>(); + } + return tonerService.getWorkorderRegisteredItems(refNo); + } + + @GetMapping("/api/workorder/history") + @ResponseBody + public List> getWorkorderRegisterHistory(HttpSession session) { + if (session.getAttribute("toner_usrName") == null) { + return new ArrayList<>(); + } + return tonerService.getWorkorderRegisterHistory(); + } + @PostMapping("/api/workorder-request") @ResponseBody public Map uploadWorkorderModelRequest( @@ -582,18 +819,21 @@ public class TonerController { uploadDir.mkdirs(); } - String ecPath = saveUploadedFile(emailCopyFile, "workorder_request_email", uploadDir); + String ecPath = saveUploadedFile(emailCopyFile, "workorder_request_email", + uploadDir); boolean saved = tonerService.saveWorkorderModelRequest( refNo.trim(), companyName.trim(), pid.trim(), items, ecPath, updatedBy); if (saved) { response.put("success", true); - response.put("message", "Workorder model request details saved successfully."); + response.put("message", + "Workorder model request details saved successfully."); } else { deleteFileIfExists(ecPath, uploadDir); response.put("success", false); - response.put("error", "Failed to save workorder request details in the database."); + response.put("error", + "Failed to save workorder request details in the database."); } } catch (Exception e) { response.put("success", false); @@ -603,17 +843,6 @@ public class TonerController { return response; } - @GetMapping("/api/workorders/items") - @ResponseBody - public List> getWorkorderRegisteredItems( - @RequestParam("refNo") String refNo, - HttpSession session) { - if (session.getAttribute("toner_usrName") == null) { - return new ArrayList<>(); - } - return tonerService.getWorkorderRegisteredItems(refNo); - } - @GetMapping("/api/workorders/active-requests") @ResponseBody public List getActiveWorkorderRequestRefNos(HttpSession session) { @@ -634,15 +863,6 @@ public class TonerController { return tonerService.getWorkorderRequests(refNo); } - @GetMapping("/api/workorder/history") - @ResponseBody - public List> getWorkorderRegisterHistory(HttpSession session) { - if (session.getAttribute("toner_usrName") == null) { - return new ArrayList<>(); - } - return tonerService.getWorkorderRegisterHistory(); - } - @GetMapping("/api/workorder-request/history") @ResponseBody public List> getWorkorderModelRequestHistory(HttpSession session) { diff --git a/src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java b/src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java index 29ba0c3..286a4d1 100644 --- a/src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java +++ b/src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java @@ -1,22 +1,23 @@ package com.gcc_manpower.gcctonermanagement.service; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; -import jakarta.annotation.PostConstruct; 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 org.springframework.transaction.annotation.Transactional; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; -import java.sql.PreparedStatement; -import java.sql.Statement; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class TonerService { @@ -28,27 +29,225 @@ public class TonerService { this.jdbcTemplate = new JdbcTemplate(dataSource); } + // ================= FAST IN-MEMORY LOOKUP MAPS (Zero N+1 DB Calls) + // ================= + private Map getPrinterMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT id, printer_name FROM toner_printer_model_list WHERE is_active = 1 AND is_delete = 0"); + for (Map row : list) { + if (row.get("id") != null && row.get("printer_name") != null) { + map.put(String.valueOf(row.get("id")).trim(), String.valueOf(row.get("printer_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getTonerMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT id, model_name FROM toner_model_list WHERE is_active = 1 AND is_delete = 0"); + for (Map row : list) { + if (row.get("id") != null && row.get("model_name") != null) { + map.put(String.valueOf(row.get("id")).trim(), String.valueOf(row.get("model_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getDepartmentMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT id, dept_name FROM toner_department_list WHERE is_active = 1 AND is_delete = 0"); + for (Map row : list) { + if (row.get("id") != null && row.get("dept_name") != null) { + map.put(String.valueOf(row.get("id")).trim(), String.valueOf(row.get("dept_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getZoneMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT id, zone_name FROM toner_zone_list WHERE is_active = 1 AND is_delete = 0"); + for (Map row : list) { + if (row.get("id") != null && row.get("zone_name") != null) { + map.put(String.valueOf(row.get("id")).trim(), String.valueOf(row.get("zone_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getUserMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList("SELECT usr_id, usr_name FROM app_user"); + for (Map row : list) { + if (row.get("usr_id") != null && row.get("usr_name") != null) { + map.put(String.valueOf(row.get("usr_id")).trim(), String.valueOf(row.get("usr_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getVendorMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT id, vendor_name FROM payment_vendors_list WHERE is_active = 1 AND is_delete = 0"); + for (Map row : list) { + if (row.get("id") != null && row.get("vendor_name") != null) { + map.put(String.valueOf(row.get("id")).trim(), String.valueOf(row.get("vendor_name")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getWorkorderFileNumberMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT ref_no, file_number FROM toner_workorder_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"); + for (Map row : list) { + if (row.get("ref_no") != null && row.get("file_number") != null) { + map.put(String.valueOf(row.get("ref_no")).trim(), String.valueOf(row.get("file_number")).trim()); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getWorkorderRegisteredQtyMap() { + Map map = new HashMap<>(); + try { + List> list = jdbcTemplate.queryForList( + "SELECT ref_no, pid, tid, qty_registered FROM toner_workorder_printermodel_qty WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"); + for (Map row : list) { + if (row.get("ref_no") != null && row.get("pid") != null && row.get("tid") != null) { + String refNo = String.valueOf(row.get("ref_no")).trim(); + String pid = String.valueOf(row.get("pid")).trim(); + String tid = String.valueOf(row.get("tid")).trim(); + Integer qty = parseIntegerSafely(row.get("qty_registered")); + map.put(refNo + "_" + pid + "_" + tid, qty != null ? qty : 0); + } + } + } catch (Exception ignored) { + } + return map; + } + + private Map getInwardStockMap() { + Map map = new HashMap<>(); + try { + String sql = "SELECT ref_no, pid, tid, SUM(CAST(qty_registered AS SIGNED)) AS total_qty FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY ref_no, pid, tid"; + List> list = jdbcTemplate.queryForList(sql); + for (Map row : list) { + String ref = row.get("ref_no") != null ? String.valueOf(row.get("ref_no")).trim() : ""; + String pid = row.get("pid") != null ? String.valueOf(row.get("pid")).trim() : ""; + String tid = row.get("tid") != null ? String.valueOf(row.get("tid")).trim() : ""; + Integer qty = parseIntegerSafely(row.get("total_qty")); + map.put(ref + "_" + pid + "_" + tid, qty != null ? qty : 0); + } + } catch (Exception ignored) { + } + return map; + } + + private Map getInwardStockByRequestMap() { + Map map = new HashMap<>(); + try { + String sql = "SELECT ref_no, pid, tid, request_id, SUM(CAST(qty_registered AS SIGNED)) AS total_qty FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY ref_no, pid, tid, request_id"; + List> list = jdbcTemplate.queryForList(sql); + for (Map row : list) { + String ref = row.get("ref_no") != null ? String.valueOf(row.get("ref_no")).trim() : ""; + String pid = row.get("pid") != null ? String.valueOf(row.get("pid")).trim() : ""; + String tid = row.get("tid") != null ? String.valueOf(row.get("tid")).trim() : ""; + String reqId = row.get("request_id") != null ? String.valueOf(row.get("request_id")).trim() : ""; + Integer qty = parseIntegerSafely(row.get("total_qty")); + map.put(ref + "_" + pid + "_" + tid + "_" + reqId, qty != null ? qty : 0); + } + } catch (Exception ignored) { + } + return map; + } + + private Map getAvailableStockMap() { + Map map = new HashMap<>(); + try { + Map inwardMap = new HashMap<>(); + String inSql = "SELECT pid, tid, SUM(CAST(qty_registered AS SIGNED)) AS total_in FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY pid, tid"; + List> inList = jdbcTemplate.queryForList(inSql); + for (Map row : inList) { + String pid = row.get("pid") != null ? String.valueOf(row.get("pid")).trim() : ""; + String tid = row.get("tid") != null ? String.valueOf(row.get("tid")).trim() : ""; + Integer q = parseIntegerSafely(row.get("total_in")); + inwardMap.put(pid + "_" + tid, q != null ? q : 0); + } + + Map outwardMap = new HashMap<>(); + String outSql = "SELECT pid, tid, SUM(CAST(qty_delivered AS SIGNED)) AS total_out FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY pid, tid"; + List> outList = jdbcTemplate.queryForList(outSql); + for (Map row : outList) { + String pid = row.get("pid") != null ? String.valueOf(row.get("pid")).trim() : ""; + String tid = row.get("tid") != null ? String.valueOf(row.get("tid")).trim() : ""; + Integer q = parseIntegerSafely(row.get("total_out")); + outwardMap.put(pid + "_" + tid, q != null ? q : 0); + } + + for (Map.Entry entry : inwardMap.entrySet()) { + int inQ = entry.getValue(); + int outQ = outwardMap.getOrDefault(entry.getKey(), 0); + map.put(entry.getKey(), Math.max(0, inQ - outQ)); + } + } catch (Exception ignored) { + } + return map; + } + + // ================= PRINTERS & TONERS ================= public List> getActivePrinters() { - String sql = "SELECT id, printer_name FROM toner_printer_model_list where is_active = 1 AND is_delete = 0 "; + String sql = "SELECT id, printer_name FROM toner_printer_model_list WHERE is_active = 1 AND is_delete = 0"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { - System.err.println("Error fetching printers list: " + e.getMessage()); return new ArrayList<>(); } } public List> getActiveTonersByPrinter(int pid) { - String sql = "SELECT t.id, t.model_name, t.part_code, " + - "(COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM toner_stock_register_details sr WHERE CAST(sr.pid AS SIGNED) = t.pid AND CAST(sr.tid AS SIGNED) = t.id AND sr.is_active = 1 AND sr.is_delete = 0), 0) - " - + - "COALESCE((SELECT SUM(CAST(dr.qty_delivered AS SIGNED)) FROM toner_department_requested_details dr WHERE CAST(dr.pid AS SIGNED) = t.pid AND CAST(dr.tid AS SIGNED) = t.id AND dr.is_active = 1 AND dr.is_delete = 0), 0)) AS available_stock " - + - "FROM toner_model_list t WHERE t.pid = ? AND t.is_active = 1 AND t.is_delete = 0"; try { - return jdbcTemplate.queryForList(sql, pid); + String sql = "SELECT id, pid, model_name, part_code FROM toner_model_list WHERE pid = ? AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + List> list = jdbcTemplate.queryForList(sql, pid); + Map availMap = getAvailableStockMap(); + + List> result = new ArrayList<>(); + for (Map map : list) { + Map row = new HashMap<>(map); + String tidStr = String.valueOf(map.get("id")).trim(); + String pidStr = String.valueOf(map.get("pid")).trim(); + row.put("available_stock", availMap.getOrDefault(pidStr + "_" + tidStr, 0)); + result.add(row); + } + return result; } catch (Exception e) { - System.err.println("Error fetching toners list: " + e.getMessage()); return new ArrayList<>(); } } @@ -58,26 +257,35 @@ public class TonerService { try { return jdbcTemplate.queryForObject(sql, String.class, tid); } catch (Exception e) { - System.err.println("Error fetching toner model name for id " + tid + ": " + e.getMessage()); return "UNKNOWN"; } } public int getTonerAvailableStock(int tid) { - String sql = "SELECT " + - "(COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM toner_stock_register_details sr WHERE CAST(sr.tid AS SIGNED) = t.id AND sr.is_active = 1 AND sr.is_delete = 0), 0) - " - + - "COALESCE((SELECT SUM(CAST(dr.qty_delivered AS SIGNED)) FROM toner_department_requested_details dr WHERE CAST(dr.tid AS SIGNED) = t.id AND dr.is_active = 1 AND dr.is_delete = 0), 0)) AS available_stock " - + - "FROM toner_model_list t WHERE t.id = ?"; try { - return jdbcTemplate.queryForObject(sql, Integer.class, tid); + String sql = "SELECT pid FROM toner_model_list WHERE id = ?"; + Integer pid = jdbcTemplate.queryForObject(sql, Integer.class, tid); + if (pid == null) + return 0; + return getAvailableStockForModel(String.valueOf(pid), String.valueOf(tid)); } catch (Exception e) { - System.err.println("Error fetching toner available stock for id " + tid + ": " + e.getMessage()); return 0; } } + public int getAvailableStockForModel(String pid, String tid) { + String sqlInward = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM toner_stock_register_details WHERE TRIM(pid) = TRIM(?) AND TRIM(tid) = TRIM(?) AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + String sqlOutward = "SELECT COALESCE(SUM(CAST(qty_delivered AS SIGNED)), 0) FROM toner_department_requested_details WHERE TRIM(pid) = TRIM(?) AND TRIM(tid) = TRIM(?) AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + try { + int inVal = jdbcTemplate.queryForObject(sqlInward, Integer.class, pid, tid); + int outVal = jdbcTemplate.queryForObject(sqlOutward, Integer.class, pid, tid); + return Math.max(0, inVal - outVal); + } catch (Exception e) { + return 0; + } + } + + // ================= INWARD STOCK REGISTER ================= @Transactional public boolean saveInwardRegister(String refNo, String companyName, List> items, String dcUpload, String username) { @@ -85,46 +293,99 @@ public class TonerService { "(ref_no, company_name, pid, tid, qty_registered, dc_upload, cdate, updated_date, updated_by, is_active, is_delete, request_id) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?)"; - java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Timestamp now = new Timestamp(System.currentTimeMillis()); try { for (Map item : items) { Integer pidVal = parseIntegerSafely(item.get("pid")); Integer tidVal = parseIntegerSafely(item.get("tid")); if (pidVal == null || tidVal == null) { - continue; // Skip invalid item + continue; } int pid = pidVal; int tid = tidVal; - String qty = String.valueOf(item.get("qty")); - Integer requestId = parseIntegerSafely(item.get("requestId")); - - int qtyInt = 0; - try { - qtyInt = Integer.parseInt(qty.trim()); - } catch (Exception ignored) { - } - if (qtyInt <= 0) + Integer qtyVal = parseIntegerSafely(item.get("qty")); + if (qtyVal == null || qtyVal <= 0) continue; + Integer requestId = parseIntegerSafely(item.get("requestId")); + jdbcTemplate.update(sql, refNo, companyName, String.valueOf(pid), String.valueOf(tid), - String.valueOf(qtyInt), - dcUpload, now, now, username, requestId); + String.valueOf(qtyVal), dcUpload, now, now, username, requestId); } return true; } catch (Exception e) { - System.err.println("Error saving inward register: " + e.getMessage()); - e.printStackTrace(); throw new RuntimeException("Rollback saveInwardRegister due to: " + e.getMessage()); } } + public List> getInwardHistory() { + try { + String sql = "SELECT * FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map userMap = getUserMap(); + Map vendorMap = getVendorMap(); + Map fileNumberMap = getWorkorderFileNumberMap(); + Map woQtyMap = getWorkorderRegisteredQtyMap(); + + Map> grouped = new HashMap<>(); + for (Map raw : list) { + String refNo = raw.get("ref_no") != null ? String.valueOf(raw.get("ref_no")).trim() : ""; + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String key = refNo + "_" + pid + "_" + tid; + + Integer qtyIn = parseIntegerSafely(raw.get("qty_registered")); + int qty = qtyIn != null ? qtyIn : 0; + + String dc = raw.get("dc_upload") != null ? String.valueOf(raw.get("dc_upload")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + String companyName = raw.get("company_name") != null ? String.valueOf(raw.get("company_name")).trim() + : ""; + + if (!grouped.containsKey(key)) { + Map row = new HashMap<>(raw); + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("order_by", userMap.getOrDefault(updatedBy, updatedBy)); + row.put("file_number", fileNumberMap.getOrDefault(refNo, "")); + row.put("vendor_name", vendorMap.getOrDefault(companyName, companyName)); + row.put("qty_registered", qty); + row.put("qty_workorder_registered", woQtyMap.getOrDefault(refNo + "_" + pid + "_" + tid, 0)); + row.put("qty_workorder_requested", 0); + row.put("dc_uploads", dc); + grouped.put(key, row); + } else { + Map existing = grouped.get(key); + Integer curQ = parseIntegerSafely(existing.get("qty_registered")); + int currentQty = curQ != null ? curQ : 0; + existing.put("qty_registered", currentQty + qty); + + String currentDcs = existing.get("dc_uploads") != null ? String.valueOf(existing.get("dc_uploads")) + : ""; + if (!dc.isEmpty() && !currentDcs.contains(dc)) { + existing.put("dc_uploads", currentDcs.isEmpty() ? dc : (currentDcs + "," + dc)); + } + } + } + return new ArrayList<>(grouped.values()); + } catch (Exception e) { + return new ArrayList<>(); + } + } + + // ================= USER REQUESTS ================= public int getMaxRequestId() { String sql = "SELECT COALESCE(MAX(id), 0) FROM toner_department_requested_details"; try { return jdbcTemplate.queryForObject(sql, Integer.class); } catch (Exception e) { - System.err.println("Error fetching max id from toner_department_requested_details: " + e.getMessage()); return 0; } } @@ -132,75 +393,215 @@ public class TonerService { @Transactional public boolean saveUserRequest(String itCellRefNo, String deptId, String zoneId, List> items, String userRequestUpload, String username) { - java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Timestamp now = new Timestamp(System.currentTimeMillis()); String sql = "INSERT INTO toner_department_requested_details " + "(req_no, itcell_ref_no, dept_id, zone_id, pid, tid, qty_requested, qty_delivered, user_request_upload, cdate, updated_date, updated_by, is_active, is_delete) " + "VALUES (?, ?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?, 1, 0)"; try { + int baseMaxId = getMaxRequestId(); + int year = Calendar.getInstance().get(Calendar.YEAR); + int seqCounter = 0; + for (Map item : items) { Integer pidVal = parseIntegerSafely(item.get("pid")); Integer tidVal = parseIntegerSafely(item.get("tid")); if (pidVal == null || tidVal == null) { - continue; // Skip invalid item + continue; } int pid = pidVal; int tid = tidVal; - String qtyRequested = String.valueOf(item.get("qty")); + + Integer qtyVal = parseIntegerSafely(item.get("qty")); + int qtyRequested = (qtyVal != null && qtyVal > 0) ? qtyVal : 1; String tonerModel = getTonerModelName(tid); - String cleanModel = tonerModel.trim().replaceAll("\\s+", "-").replaceAll("[^a-zA-Z0-9-]", ""); - int maxId = getMaxRequestId(); - int seqNum = 1000 + maxId + 1; - int year = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR); + String cleanModel = (tonerModel != null && !tonerModel.trim().isEmpty()) + ? tonerModel.trim().replaceAll("\\s+", "-").replaceAll("[^a-zA-Z0-9-]", "") + : "REQ"; + if (cleanModel.isEmpty()) { + cleanModel = "REQ"; + } + + seqCounter++; + int seqNum = 1000 + baseMaxId + seqCounter; String reqNo = String.format("%s-%d-%04d", cleanModel, year, seqNum); jdbcTemplate.update(sql, reqNo, itCellRefNo, deptId, zoneId, String.valueOf(pid), String.valueOf(tid), - qtyRequested, userRequestUpload, now, now, username); + String.valueOf(qtyRequested), userRequestUpload, now, now, username); } return true; } catch (Exception e) { - System.err.println("Error saving user requests: " + e.getMessage()); - e.printStackTrace(); throw new RuntimeException("Rollback saveUserRequest due to: " + e.getMessage()); } } + @Transactional + public boolean updateUserRequest(int id, String itcellRefNo, String deptId, String zoneId, + int pid, int tid, int qtyRequested, String newUploadPath, String username) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + try { + String safeZoneId = (zoneId != null && !zoneId.trim().isEmpty() && !"null".equalsIgnoreCase(zoneId)) ? zoneId.trim() : null; + if (newUploadPath != null && !newUploadPath.trim().isEmpty()) { + String sql = "UPDATE toner_department_requested_details SET " + + "itcell_ref_no = ?, dept_id = ?, zone_id = ?, pid = ?, tid = ?, " + + "qty_requested = ?, user_request_upload = ?, updated_date = ?, updated_by = ? " + + "WHERE id = ?"; + int rows = jdbcTemplate.update(sql, itcellRefNo.trim(), deptId.trim(), safeZoneId, + String.valueOf(pid), String.valueOf(tid), String.valueOf(qtyRequested), + newUploadPath, now, username, id); + return rows > 0; + } else { + String sql = "UPDATE toner_department_requested_details SET " + + "itcell_ref_no = ?, dept_id = ?, zone_id = ?, pid = ?, tid = ?, " + + "qty_requested = ?, updated_date = ?, updated_by = ? " + + "WHERE id = ?"; + int rows = jdbcTemplate.update(sql, itcellRefNo.trim(), deptId.trim(), safeZoneId, + String.valueOf(pid), String.valueOf(tid), String.valueOf(qtyRequested), + now, username, id); + return rows > 0; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public List> getAllUserRequests() { + try { + String sql = "SELECT * FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map deptMap = getDepartmentMap(); + Map zoneMap = getZoneMap(); + Map userMap = getUserMap(); + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String deptId = raw.get("dept_id") != null ? String.valueOf(raw.get("dept_id")).trim() : ""; + String zoneId = raw.get("zone_id") != null ? String.valueOf(raw.get("zone_id")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("dept_name", deptMap.getOrDefault(deptId, deptId)); + row.put("zone_name", zoneMap.getOrDefault(zoneId, zoneId)); + row.put("updated_by", userMap.getOrDefault(updatedBy, updatedBy)); + + result.add(row); + } + // System.out.println("result:" + result); + return result; + } catch (Exception e) { + return new ArrayList<>(); + } + } + + public Map getUserRequestById(int id) { + try { + String sql = "SELECT * FROM toner_department_requested_details WHERE id = ? AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + List> list = jdbcTemplate.queryForList(sql, id); + if (list == null || list.isEmpty()) { + return null; + } + Map raw = list.get(0); + Map row = new HashMap<>(raw); + + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String deptId = raw.get("dept_id") != null ? String.valueOf(raw.get("dept_id")).trim() : ""; + + row.put("printer_name", getPrinterMap().getOrDefault(pid, "")); + row.put("model_name", getTonerMap().getOrDefault(tid, "")); + row.put("dept_name", getDepartmentMap().getOrDefault(deptId, deptId)); + return row; + } catch (Exception e) { + return null; + } + } + public List> getPendingRequests() { - String sql = "SELECT r.id, r.req_no, COALESCE(d.dept_name, r.dept_id) AS dept_id, COALESCE(z.zone_name, r.zone_id) AS zone_name, r.pid, r.tid, r.qty_requested, r.qty_delivered, " - + - "p.printer_name, t.model_name " + - "FROM toner_department_requested_details r " + - "LEFT JOIN toner_printer_model_list p ON r.pid = p.id " + - "LEFT JOIN toner_model_list t ON r.tid = t.id " + - "LEFT JOIN toner_department_list d ON CAST(r.dept_id AS SIGNED) = d.id " + - "LEFT JOIN toner_zone_list z ON CAST(r.zone_id AS SIGNED) = z.id " + - "WHERE r.is_active = 1 AND r.is_delete = 0 " + - "AND (r.qty_delivered IS NULL OR CAST(r.qty_delivered AS SIGNED) < CAST(r.qty_requested AS SIGNED)) " + - "ORDER BY r.id DESC"; try { - return jdbcTemplate.queryForList(sql); + String sql = "SELECT * FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map deptMap = getDepartmentMap(); + Map zoneMap = getZoneMap(); + Map availStockMap = getAvailableStockMap(); + + List> result = new ArrayList<>(); + for (Map raw : list) { + Integer reqQty = parseIntegerSafely(raw.get("qty_requested")); + Integer delQty = parseIntegerSafely(raw.get("qty_delivered")); + int requested = reqQty != null ? reqQty : 0; + int delivered = delQty != null ? delQty : 0; + + if (delivered >= requested) { + continue; // Already fulfilled + } + + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + int availStock = availStockMap.getOrDefault(pid + "_" + tid, 0); + if (availStock <= 0) { + continue; // Cannot issue without available stock + } + + Map row = new HashMap<>(raw); + String deptId = raw.get("dept_id") != null ? String.valueOf(raw.get("dept_id")).trim() : ""; + String zoneId = raw.get("zone_id") != null ? String.valueOf(raw.get("zone_id")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("dept_name", deptMap.getOrDefault(deptId, deptId)); + row.put("zone_name", zoneMap.getOrDefault(zoneId, zoneId)); + row.put("available_stock", availStock); + + result.add(row); + } + return result; } catch (Exception e) { - System.err.println("Error fetching pending requests: " + e.getMessage()); return new ArrayList<>(); } } - public boolean saveOutwardRegister(int id, String qtyIssued, String finalDeliveredUpload, String username) { + // ================= OUTWARD REGISTER ================= + public boolean saveOutwardRegister(int id, String qtyIssued, String finalDeliveredUpload, String userPhotoUpload, + String username) { String sql; int rows; - java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Timestamp now = new Timestamp(System.currentTimeMillis()); try { - if (finalDeliveredUpload != null) { - sql = "UPDATE toner_department_requested_details " + - "SET final_delivered_upload = CASE WHEN final_delivered_upload IS NULL OR final_delivered_upload = '' THEN ? ELSE CONCAT(final_delivered_upload, ',', ?) END, " - + + if (finalDeliveredUpload != null || userPhotoUpload != null) { + sql = "UPDATE toner_department_requested_details SET " + + "final_delivered_upload = CASE WHEN ? IS NULL OR ? = '' THEN final_delivered_upload " + + " WHEN final_delivered_upload IS NULL OR final_delivered_upload = '' THEN ? " + + " ELSE CONCAT(final_delivered_upload, ',', ?) END, " + + "receiver_photo = CASE WHEN ? IS NULL OR ? = '' THEN receiver_photo " + + " WHEN receiver_photo IS NULL OR receiver_photo = '' THEN ? " + + " ELSE CONCAT(receiver_photo, ',', ?) END, " + "qty_delivered = CASE WHEN qty_delivered IS NULL OR CAST(qty_delivered AS SIGNED) = 0 THEN ? ELSE qty_delivered END, " + "updated_date = ?, updated_by = ? " + "WHERE id = ?"; - rows = jdbcTemplate.update(sql, finalDeliveredUpload, finalDeliveredUpload, qtyIssued, now, username, - id); + rows = jdbcTemplate.update(sql, + finalDeliveredUpload, finalDeliveredUpload, finalDeliveredUpload, finalDeliveredUpload, + userPhotoUpload, userPhotoUpload, userPhotoUpload, userPhotoUpload, + qtyIssued, now, username, id); } else { sql = "UPDATE toner_department_requested_details " + "SET qty_delivered = CAST(COALESCE(qty_delivered, 0) AS SIGNED) + CAST(? AS SIGNED), " + @@ -210,37 +611,74 @@ public class TonerService { } return rows > 0; } catch (Exception e) { - System.err.println("Error updating outward register: " + e.getMessage()); - e.printStackTrace(); throw new RuntimeException("Rollback saveOutwardRegister due to: " + e.getMessage()); } } + public boolean saveOutwardRegister(int id, String qtyIssued, String finalDeliveredUpload, String username) { + return saveOutwardRegister(id, qtyIssued, finalDeliveredUpload, null, username); + } + + public List> getOutwardHistory() { + try { + String sql = "SELECT * FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) AND qty_delivered IS NOT NULL AND CAST(qty_delivered AS SIGNED) > 0 ORDER BY updated_date DESC, id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map deptMap = getDepartmentMap(); + Map zoneMap = getZoneMap(); + Map userMap = getUserMap(); + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String deptId = raw.get("dept_id") != null ? String.valueOf(raw.get("dept_id")).trim() : ""; + String zoneId = raw.get("zone_id") != null ? String.valueOf(raw.get("zone_id")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("dept_name", deptMap.getOrDefault(deptId, deptId)); + row.put("zone_name", zoneMap.getOrDefault(zoneId, zoneId)); + row.put("updated_by", userMap.getOrDefault(updatedBy, updatedBy)); + + result.add(row); + } + return result; + } catch (Exception e) { + return new ArrayList<>(); + } + } + + // ================= DASHBOARD & INVENTORY ================= public Map getDashboardMetrics() { Map metrics = new HashMap<>(); - - String sqlRaised = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM toner_department_requested_details WHERE is_active = 1 AND is_delete = 0"; - String sqlDistributed = "SELECT COALESCE(SUM(CAST(qty_delivered AS SIGNED)), 0) FROM toner_department_requested_details WHERE is_active = 1 AND is_delete = 0"; - String sqlInward = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM toner_stock_register_details WHERE is_active = 1 AND is_delete = 0"; - String sqlWorkorderRegistered = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM toner_workorder_printermodel_qty WHERE is_active = 1 AND is_delete = 0"; - String sqlWorkorderRequested = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM toner_workorder_model_request_details WHERE is_active = 1 AND is_delete = 0"; + String sqlRaised = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + String sqlDistributed = "SELECT COALESCE(SUM(CAST(qty_delivered AS SIGNED)), 0) FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + String sqlInward = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + String sqlWorkorderRegistered = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM toner_workorder_printermodel_qty WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; try { int totalRaised = jdbcTemplate.queryForObject(sqlRaised, Integer.class); int totalDistributed = jdbcTemplate.queryForObject(sqlDistributed, Integer.class); int inwardStock = jdbcTemplate.queryForObject(sqlInward, Integer.class); - int availableStock = inwardStock - totalDistributed; + int availableStock = Math.max(0, inwardStock - totalDistributed); int workorderRegistered = jdbcTemplate.queryForObject(sqlWorkorderRegistered, Integer.class); - int workorderRequested = jdbcTemplate.queryForObject(sqlWorkorderRequested, Integer.class); metrics.put("totalRaised", totalRaised); metrics.put("totalDistributed", totalDistributed); metrics.put("inwardStock", inwardStock); metrics.put("availableStock", availableStock); metrics.put("workorderRegistered", workorderRegistered); - metrics.put("workorderRequested", workorderRequested); + metrics.put("workorderRequested", 0); } catch (Exception e) { - System.err.println("Error computing dashboard metrics: " + e.getMessage()); metrics.put("totalRaised", 0); metrics.put("totalDistributed", 0); metrics.put("inwardStock", 0); @@ -252,96 +690,59 @@ public class TonerService { } public List> getInventorySummary() { - String safeSql = "SELECT p.id AS pid, p.printer_name, t.id AS tid, t.model_name, " + - "COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM toner_stock_register_details sr WHERE CAST(sr.pid AS SIGNED) = t.pid AND CAST(sr.tid AS SIGNED) = t.id AND sr.is_active = 1 AND sr.is_delete = 0), 0) AS inward_qty, " - + - "COALESCE((SELECT SUM(CAST(dr.qty_delivered AS SIGNED)) FROM toner_department_requested_details dr WHERE CAST(dr.pid AS SIGNED) = t.pid AND CAST(dr.tid AS SIGNED) = t.id AND dr.is_active = 1 AND dr.is_delete = 0), 0) AS outward_qty " - + - "FROM toner_model_list t " + - "JOIN toner_printer_model_list p ON t.pid = p.id " + - "WHERE t.is_active = 1 AND t.is_delete = 0 " + - "ORDER BY p.printer_name, t.model_name"; try { - List> list = jdbcTemplate.queryForList(safeSql); - for (Map map : list) { - Number inward = (Number) map.get("inward_qty"); - Number outward = (Number) map.get("outward_qty"); - long avail = (inward != null ? inward.longValue() : 0) - (outward != null ? outward.longValue() : 0); - map.put("available_qty", avail); + String sql = "SELECT t.id AS tid, t.pid, t.model_name, p.printer_name FROM toner_model_list t " + + "LEFT JOIN toner_printer_model_list p ON t.pid = p.id " + + "WHERE (t.is_active IS NULL OR t.is_active = 1) AND (t.is_delete IS NULL OR t.is_delete = 0) " + + "ORDER BY p.printer_name, t.model_name"; + List> list = jdbcTemplate.queryForList(sql); + + Map inwardMap = new HashMap<>(); + try { + String inSql = "SELECT pid, tid, SUM(CAST(qty_registered AS SIGNED)) AS total_in FROM toner_stock_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY pid, tid"; + for (Map r : jdbcTemplate.queryForList(inSql)) { + String k = String.valueOf(r.get("pid")).trim() + "_" + String.valueOf(r.get("tid")).trim(); + inwardMap.put(k, + parseIntegerSafely(r.get("total_in")) != null ? parseIntegerSafely(r.get("total_in")) : 0); + } + } catch (Exception ignored) { } - return list; - } catch (Exception e) { - System.err.println("Error fetching inventory summary: " + e.getMessage()); - e.printStackTrace(); - return new ArrayList<>(); - } - } - public List> getInwardHistory() { - String sql = "SELECT MAX(s.id) AS id, s.ref_no, s.pid, s.tid, s.company_name, " - + "SUM(CAST(s.qty_registered AS SIGNED)) AS qty_registered, MAX(s.dc_upload) AS dc_upload, " - + "MAX(s.cdate) AS cdate, COALESCE(MAX(u.usr_name), MAX(s.updated_by)) AS order_by, " - + "p.printer_name, t.model_name, " - + "COALESCE((SELECT w.qty_registered FROM toner_workorder_printermodel_qty w WHERE w.ref_no = s.ref_no AND w.pid = s.pid AND w.tid = s.tid AND w.is_active = 1 AND w.is_delete = 0 LIMIT 1), 0) AS qty_workorder_registered, " - + "COALESCE((SELECT SUM(CAST(mr.qty_registered AS SIGNED)) FROM toner_workorder_model_multiple_request_qty mr INNER JOIN toner_workorder_model_request_details r ON mr.ref_no = r.ref_no AND mr.cdate = r.cdate WHERE r.ref_no = s.ref_no AND mr.pid = s.pid AND mr.tid = s.tid AND mr.is_active = 1 AND mr.is_delete = 0 AND r.is_active = 1 AND r.is_delete = 0), 0) AS qty_workorder_requested " - + "FROM toner_stock_register_details s " - + "LEFT JOIN toner_printer_model_list p ON CAST(s.pid AS SIGNED) = p.id " - + "LEFT JOIN toner_model_list t ON CAST(s.tid AS SIGNED) = t.id " - + "LEFT JOIN app_user u ON CAST(s.updated_by AS SIGNED) = u.usr_id " - + "WHERE s.is_active = 1 AND s.is_delete = 0 " - + "GROUP BY s.ref_no, s.pid, s.tid, s.company_name, p.printer_name, t.model_name " - + "ORDER BY MAX(s.id) DESC"; - try { - return jdbcTemplate.queryForList(sql); - } catch (Exception e) { - System.err.println("Error fetching inward history: " + e.getMessage()); - return new ArrayList<>(); - } - } + Map outwardMap = new HashMap<>(); + try { + String outSql = "SELECT pid, tid, SUM(CAST(qty_delivered AS SIGNED)) AS total_out FROM toner_department_requested_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY pid, tid"; + for (Map r : jdbcTemplate.queryForList(outSql)) { + String k = String.valueOf(r.get("pid")).trim() + "_" + String.valueOf(r.get("tid")).trim(); + outwardMap.put(k, + parseIntegerSafely(r.get("total_out")) != null ? parseIntegerSafely(r.get("total_out")) + : 0); + } + } catch (Exception ignored) { + } - public List> getAllUserRequests() { - String sql = "SELECT r.id, r.pid, r.tid, r.req_no, r.itcell_ref_no, COALESCE(d.dept_name, r.dept_id) AS dept_id, COALESCE(z.zone_name, r.zone_id) AS zone_name, r.qty_requested, r.qty_delivered, " - + "r.user_request_upload, r.cdate, r.updated_date, COALESCE(u.usr_name, r.updated_by) AS updated_by, " - + "p.printer_name, t.model_name " - + "FROM toner_department_requested_details r " - + "LEFT JOIN toner_printer_model_list p ON CAST(r.pid AS SIGNED) = p.id " - + "LEFT JOIN toner_model_list t ON CAST(r.tid AS SIGNED) = t.id " - + "LEFT JOIN app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id " - + "LEFT JOIN toner_department_list d ON CAST(r.dept_id AS SIGNED) = d.id " - + "LEFT JOIN toner_zone_list z ON CAST(r.zone_id AS SIGNED) = z.id " - + "WHERE r.is_active = 1 AND r.is_delete = 0 " - + "ORDER BY r.id DESC"; - try { - return jdbcTemplate.queryForList(sql); - } catch (Exception e) { - System.err.println("Error fetching user requests: " + e.getMessage()); - return new ArrayList<>(); - } - } + List> result = new ArrayList<>(); + for (Map map : list) { + Map row = new HashMap<>(map); + String k = String.valueOf(map.get("pid")).trim() + "_" + String.valueOf(map.get("tid")).trim(); + int inward = inwardMap.getOrDefault(k, 0); + int outward = outwardMap.getOrDefault(k, 0); - public List> getOutwardHistory() { - String sql = "SELECT r.id, r.pid, r.tid, r.req_no, r.itcell_ref_no, COALESCE(d.dept_name, r.dept_id) AS dept_id, COALESCE(z.zone_name, r.zone_id) AS zone_name, r.qty_requested, r.qty_delivered, r.final_delivered_upload, r.user_request_upload, r.cdate, r.updated_date, " - + - "p.printer_name, t.model_name " + - "FROM toner_department_requested_details r " + - "LEFT JOIN toner_printer_model_list p ON CAST(r.pid AS SIGNED) = p.id " + - "LEFT JOIN toner_model_list t ON CAST(r.tid AS SIGNED) = t.id " + - "LEFT JOIN toner_department_list d ON CAST(r.dept_id AS SIGNED) = d.id " + - "LEFT JOIN toner_zone_list z ON CAST(r.zone_id AS SIGNED) = z.id " + - "WHERE r.is_active = 1 AND r.is_delete = 0 " + - "AND r.qty_delivered IS NOT NULL AND CAST(r.qty_delivered AS SIGNED) > 0 " + - "ORDER BY r.updated_date DESC"; - try { - return jdbcTemplate.queryForList(sql); + row.put("inward_qty", inward); + row.put("outward_qty", outward); + row.put("available_qty", Math.max(0, inward - outward)); + result.add(row); + } + return result; } catch (Exception e) { return new ArrayList<>(); } } + // ================= WORKORDER REGISTRATION & REQUESTS ================= public boolean saveWorkorder(String refNo, String fileNumber, String vendorName, String startDate, String endDate, String dcUpload, String totalQty, List> items, String username) { - java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Timestamp now = new Timestamp(System.currentTimeMillis()); String sqlMain = "INSERT INTO toner_workorder_register_details " + "(ref_no, file_number, vendor_name, workorder_start_date, workorder_end_date, pid, tid, total_qty_registered, dc_upload, cdate, updated_date, updated_by, is_active, is_delete) " @@ -363,9 +764,8 @@ public class TonerService { refNo, fileNumber, vendorName, startDate, endDate, firstPid, firstTid, totalQty, dcUpload, now, now, username); - if (mainRows <= 0) { + if (mainRows <= 0) return false; - } String sqlSub = "INSERT INTO toner_workorder_printermodel_qty " + "(ref_no, pid, tid, qty_registered, cdate, updated_date, updated_by, is_active, is_delete) " + @@ -381,30 +781,134 @@ public class TonerService { return true; } catch (Exception e) { - System.err.println("Error saving workorder: " + e.getMessage()); - e.printStackTrace(); throw new RuntimeException("Rollback saveWorkorder due to: " + e.getMessage()); } } public List getActiveWorkorderRefNos() { - String sql = "SELECT ref_no FROM toner_workorder_register_details WHERE is_active = 1 AND is_delete = 0 ORDER BY id DESC"; + String sql = "SELECT ref_no FROM toner_workorder_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; try { return jdbcTemplate.queryForList(sql, String.class); } catch (Exception e) { - System.err.println("Error fetching workorder ref nos: " + e.getMessage()); + return new ArrayList<>(); + } + } + + public List> getWorkorderRegisteredItems(String refNo) { + try { + String sql = "SELECT * FROM toner_workorder_printermodel_qty WHERE TRIM(ref_no) = TRIM(?) AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + List> list = jdbcTemplate.queryForList(sql, refNo); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map vendorMap = getVendorMap(); + Map fileNumberMap = getWorkorderFileNumberMap(); + Map inStockMap = getInwardStockMap(); + + String vendorId = ""; + try { + String vSql = "SELECT vendor_name FROM toner_workorder_register_details WHERE TRIM(ref_no) = TRIM(?) LIMIT 1"; + vendorId = jdbcTemplate.queryForObject(vSql, String.class, refNo); + } catch (Exception ignored) { + } + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String cleanRef = refNo != null ? refNo.trim() : ""; + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("vendor_name", vendorMap.getOrDefault(vendorId, vendorId)); + row.put("vendor_id", vendorId); + row.put("file_number", fileNumberMap.getOrDefault(cleanRef, "")); + row.put("qty_inward_received", inStockMap.getOrDefault(cleanRef + "_" + pid + "_" + tid, 0)); + + result.add(row); + } + return result; + } catch (Exception e) { + return new ArrayList<>(); + } + } + + public List> getWorkorderRegisterHistory() { + try { + String sql = "SELECT * FROM toner_workorder_printermodel_qty WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map userMap = getUserMap(); + Map vendorMap = getVendorMap(); + Map fileNumberMap = getWorkorderFileNumberMap(); + + Map> mainRegMap = new HashMap<>(); + try { + List> mains = jdbcTemplate.queryForList( + "SELECT * FROM toner_workorder_register_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"); + for (Map m : mains) { + if (m.get("ref_no") != null) { + mainRegMap.put(String.valueOf(m.get("ref_no")).trim(), m); + } + } + } catch (Exception ignored) { + } + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String refNo = raw.get("ref_no") != null ? String.valueOf(raw.get("ref_no")).trim() : ""; + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + + Map mainRow = mainRegMap.get(refNo); + String vendorStr = ""; + if (mainRow != null) { + vendorStr = mainRow.get("vendor_name") != null ? String.valueOf(mainRow.get("vendor_name")).trim() + : ""; + row.put("file_number", mainRow.get("file_number")); + row.put("workorder_start_date", mainRow.get("workorder_start_date")); + row.put("workorder_end_date", mainRow.get("workorder_end_date")); + row.put("dc_upload", mainRow.get("dc_upload")); + row.put("wo_cdate", mainRow.get("cdate")); + } else { + row.put("file_number", fileNumberMap.getOrDefault(refNo, "")); + } + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("updated_by", userMap.getOrDefault(updatedBy, updatedBy)); + row.put("vendor_name", vendorMap.getOrDefault(vendorStr, vendorStr)); + row.put("total_qty_registered", raw.get("qty_registered")); + + result.add(row); + } + return result; + } catch (Exception e) { return new ArrayList<>(); } } @Transactional - public boolean saveWorkorderModelRequest(String refNo, String companyName, String pid, + public boolean saveWorkorderModelRequest(String refNo, String companyName, + String pid, List> items, String emailCopyUpload, String username) { if (items == null || items.isEmpty()) { return false; } - // Calculate sum of qty_requested int totalQtyRequested = 0; for (Map item : items) { Integer q = parseIntegerSafely(item.get("qty")); @@ -414,14 +918,11 @@ public class TonerService { } final int finalTotalQty = totalQtyRequested; - // Use first item's pid and tid for the summary row to maintain compatibility String firstPid = (pid != null && !pid.trim().isEmpty()) ? pid : String.valueOf(items.get(0).get("pid")); String firstTid = String.valueOf(items.get(0).get("tid")); - java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Timestamp now = new Timestamp(System.currentTimeMillis()); - // Insert into toner_workorder_model_request_details using KeyHolder to get the - // generated id String sqlMain = "INSERT INTO toner_workorder_model_request_details " + "(ref_no, company_name, pid, tid, qty_requested, email_copy_upload, cdate, updated_date, updated_by, is_active, is_delete) " + @@ -443,17 +944,11 @@ public class TonerService { return ps; }, keyHolder); - if (mainRows <= 0) { + if (mainRows <= 0) return false; - } - Number key = keyHolder.getKey(); - int requestId = key != null ? key.intValue() : 0; - - // Insert into toner_workorder_model_multiple_request_qty in loop String sqlSub = "INSERT INTO toner_workorder_model_multiple_request_qty " + - "(ref_no, pid, tid, qty_registered, cdate, updated_date, updated_by, is_active, is_delete) " - + + "(ref_no, pid, tid, qty_registered, cdate, updated_date, updated_by, is_active, is_delete) " + "VALUES (?, ?, ?, ?, ?, ?, ?, 1, 0)"; for (Map item : items) { @@ -466,117 +961,110 @@ public class TonerService { return true; } catch (Exception e) { - System.err.println("Error saving workorder model multiple request: " + e.getMessage()); - e.printStackTrace(); throw new RuntimeException("Rollback saveWorkorderModelRequest due to: " + e.getMessage()); } } - public List> getWorkorderRegisteredItems(String refNo) { - String sql = "SELECT w.pid, w.tid, w.qty_registered, p.printer_name, t.model_name, t.part_code, COALESCE(vl.vendor_name, r.vendor_name) AS vendor_name, " - + "COALESCE((SELECT SUM(CAST(mr.qty_registered AS SIGNED)) FROM toner_workorder_model_multiple_request_qty mr " - + "WHERE mr.ref_no = w.ref_no AND mr.pid = w.pid AND mr.tid = w.tid AND mr.is_active = 1 AND mr.is_delete = 0), 0) AS qty_already_requested " - + "FROM toner_workorder_printermodel_qty w " - + "LEFT JOIN toner_workorder_register_details r ON w.ref_no = r.ref_no " - + "LEFT JOIN payment_vendors_list vl ON CAST(r.vendor_name AS SIGNED) = vl.id " - + "LEFT JOIN toner_printer_model_list p ON CAST(w.pid AS SIGNED) = p.id " - + "LEFT JOIN toner_model_list t ON CAST(w.tid AS SIGNED) = t.id " - + "WHERE w.ref_no = ? AND w.is_active = 1 AND w.is_delete = 0"; - try { - return jdbcTemplate.queryForList(sql, refNo); - } catch (Exception e) { - System.err.println("Error fetching workorder registered items for ref " + refNo + ": " + e.getMessage()); - return new ArrayList<>(); - } - } - public List getActiveWorkorderRequestRefNos() { - String sql = "SELECT ref_no FROM toner_workorder_model_request_details WHERE is_active = 1 AND is_delete = 0 GROUP BY ref_no ORDER BY MAX(id) DESC"; + String sql = "SELECT ref_no FROM toner_workorder_model_request_details WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) GROUP BY ref_no ORDER BY MAX(id) DESC"; try { return jdbcTemplate.queryForList(sql, String.class); } catch (Exception e) { - System.err.println("Error fetching workorder request ref nos: " + e.getMessage()); return new ArrayList<>(); } } public List> getWorkorderRequests(String refNo) { - String sql = "SELECT mr.id, r.id AS main_request_id, mr.pid, mr.tid, mr.qty_registered AS qty_requested, r.company_name AS vendor_id, r.cdate, " - + "COALESCE(u.usr_name, r.updated_by) AS updated_by, r.email_copy_upload, p.printer_name, t.model_name, t.part_code, " - + "COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM toner_stock_register_details sr " - + "WHERE sr.ref_no = r.ref_no AND sr.pid = mr.pid AND sr.tid = mr.tid AND sr.request_id = mr.id AND sr.is_active = 1 AND sr.is_delete = 0), 0) AS qty_inward_received " - + "FROM toner_workorder_model_multiple_request_qty mr " - + "LEFT JOIN toner_workorder_model_request_details r ON mr.ref_no = r.ref_no AND mr.cdate = r.cdate " - + "LEFT JOIN toner_printer_model_list p ON CAST(mr.pid AS SIGNED) = p.id " - + "LEFT JOIN toner_model_list t ON CAST(mr.tid AS SIGNED) = t.id " - + "LEFT JOIN app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id " - + "WHERE r.ref_no = ? AND mr.is_active = 1 AND mr.is_delete = 0 AND r.is_active = 1 AND r.is_delete = 0"; - try { - return jdbcTemplate.queryForList(sql, refNo); - } catch (Exception e) { - System.err.println("Error fetching workorder requests for ref " + refNo + ": " + e.getMessage()); - return new ArrayList<>(); - } - } - - public List> getWorkorderRegisterHistory() { - String sql = "SELECT w.id, w.ref_no, w.pid, w.tid, w.qty_registered AS total_qty_registered, " + - "r.file_number, COALESCE(vl.vendor_name, r.vendor_name) AS vendor_name, r.workorder_start_date, r.workorder_end_date, " - + - "r.dc_upload, r.cdate, COALESCE(u.usr_name, r.updated_by) AS updated_by, " + - "p.printer_name, t.model_name " + - "FROM toner_workorder_printermodel_qty w " + - "LEFT JOIN toner_workorder_register_details r ON w.ref_no = r.ref_no " + - "LEFT JOIN payment_vendors_list vl ON CAST(r.vendor_name AS SIGNED) = vl.id " + - "LEFT JOIN toner_printer_model_list p ON CAST(w.pid AS SIGNED) = p.id " + - "LEFT JOIN toner_model_list t ON CAST(w.tid AS SIGNED) = t.id " + - "LEFT JOIN app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id " + - "WHERE w.is_active = 1 AND w.is_delete = 0 AND r.is_active = 1 AND r.is_delete = 0 " + - "ORDER BY w.id DESC"; try { - return jdbcTemplate.queryForList(sql); + String sql = "SELECT * FROM toner_workorder_model_multiple_request_qty WHERE TRIM(ref_no) = TRIM(?) AND (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; + List> list = jdbcTemplate.queryForList(sql, refNo); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map userMap = getUserMap(); + Map inByReqMap = getInwardStockByRequestMap(); + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String cleanRef = refNo != null ? refNo.trim() : ""; + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + String mrId = raw.get("id") != null ? String.valueOf(raw.get("id")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("updated_by", userMap.getOrDefault(updatedBy, updatedBy)); + row.put("qty_requested", raw.get("qty_registered")); + row.put("qty_inward_received", + inByReqMap.getOrDefault(cleanRef + "_" + pid + "_" + tid + "_" + mrId, 0)); + + result.add(row); + } + return result; } catch (Exception e) { - System.err.println("Error fetching workorder register history: " + e.getMessage()); return new ArrayList<>(); } } public List> getWorkorderModelRequestHistory() { - String sql = "SELECT mr.id, r.id AS main_request_id, r.ref_no, mr.pid, mr.tid, r.company_name AS vendor_id, " - + "CAST(mr.qty_registered AS SIGNED) AS qty_requested, r.email_copy_upload, " - + "r.cdate, COALESCE(u.usr_name, r.updated_by) AS order_by, p.printer_name, t.model_name, " - + "COALESCE((SELECT w.qty_registered FROM toner_workorder_printermodel_qty w WHERE w.ref_no = r.ref_no AND w.pid = mr.pid AND w.tid = mr.tid AND w.is_active = 1 AND w.is_delete = 0 LIMIT 1), 0) AS qty_registered " - + "FROM toner_workorder_model_multiple_request_qty mr " - + "LEFT JOIN toner_workorder_model_request_details r ON mr.ref_no = r.ref_no AND mr.cdate = r.cdate " - + "LEFT JOIN toner_printer_model_list p ON CAST(mr.pid AS SIGNED) = p.id " - + "LEFT JOIN toner_model_list t ON CAST(mr.tid AS SIGNED) = t.id " - + "LEFT JOIN app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id " - + "WHERE mr.is_active = 1 AND mr.is_delete = 0 AND r.is_active = 1 AND r.is_delete = 0 " - + "ORDER BY mr.id DESC"; try { - return jdbcTemplate.queryForList(sql); + String sql = "SELECT * FROM toner_workorder_model_multiple_request_qty WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY id DESC"; + List> list = jdbcTemplate.queryForList(sql); + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + + Map printerMap = getPrinterMap(); + Map tonerMap = getTonerMap(); + Map userMap = getUserMap(); + Map woRegisteredQtyMap = getWorkorderRegisteredQtyMap(); + + List> result = new ArrayList<>(); + for (Map raw : list) { + Map row = new HashMap<>(raw); + + String pid = raw.get("pid") != null ? String.valueOf(raw.get("pid")).trim() : ""; + String tid = raw.get("tid") != null ? String.valueOf(raw.get("tid")).trim() : ""; + String updatedBy = raw.get("updated_by") != null ? String.valueOf(raw.get("updated_by")).trim() : ""; + String refNo = raw.get("ref_no") != null ? String.valueOf(raw.get("ref_no")).trim() : ""; + + row.put("printer_name", printerMap.getOrDefault(pid, pid)); + row.put("model_name", tonerMap.getOrDefault(tid, tid)); + row.put("order_by", userMap.getOrDefault(updatedBy, updatedBy)); + row.put("qty_requested", raw.get("qty_registered")); + + int woQty = woRegisteredQtyMap.getOrDefault(refNo + "_" + pid + "_" + tid, 0); + row.put("qty_registered", woQty); + + result.add(row); + } + return result; } catch (Exception e) { - System.err.println("Error fetching workorder model request history: " + e.getMessage()); return new ArrayList<>(); } } + // ================= DEPARTMENTS & ZONES ================= public List> getActiveDepartments() { - String sql = "SELECT id, dept_name FROM toner_department_list WHERE is_active = 1 AND is_delete = 0 ORDER BY dept_name ASC"; + String sql = "SELECT id, dept_name FROM toner_department_list WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0) ORDER BY dept_name ASC"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { - System.err.println("Error fetching active departments: " + e.getMessage()); return new ArrayList<>(); } } public List> getActiveZones() { - String sql = "SELECT id, zone_name FROM toner_zone_list WHERE is_active = 1 AND is_delete = 0"; + String sql = "SELECT id, zone_name FROM toner_zone_list WHERE (is_active IS NULL OR is_active = 1) AND (is_delete IS NULL OR is_delete = 0)"; try { return jdbcTemplate.queryForList(sql); } catch (Exception e) { - System.err.println("Error fetching active zones: " + e.getMessage()); return new ArrayList<>(); } } diff --git a/src/main/resources/templates/gccadjustmentmanagement/dashboard.html b/src/main/resources/templates/gccadjustmentmanagement/dashboard.html index 4f1d5cc..a4a8fc0 100644 --- a/src/main/resources/templates/gccadjustmentmanagement/dashboard.html +++ b/src/main/resources/templates/gccadjustmentmanagement/dashboard.html @@ -33,6 +33,9 @@ + + + @@ -1145,6 +1374,7 @@ FI Amount + Days Flow @@ -1268,18 +1498,19 @@ File Available - - Bal Amount - PI Amount + + Bal Amount + FI Amount + Days Flow Remarks Created Date + +
+ + +
+
- -
- -
-
- Choose File -
- -
-
-
-
- - -
- - -
-
@@ -1532,6 +1749,27 @@ class="form-control-custom">
+ +
+ +
+
+ Choose File +
+ +
+
+
+
+ + +
+ + +
+
@@ -1826,6 +2064,165 @@
+ + + @@ -1854,6 +2251,10 @@ let listPendingReasonFilter = 'All'; let detailsModalObj = null; let registrationModalObj = null; + let statusPieChartModalObj = null; + let statusPieChartInstance = null; + let currentPieModalStatus = 'All'; + let activePieModalCategories = []; let currentPage = 1; let pageSize = 15; let sortField = 'fin_year'; @@ -1876,6 +2277,10 @@ }); detailsModalObj = new bootstrap.Modal(document.getElementById('viewDetailsModal')); registrationModalObj = new bootstrap.Modal(document.getElementById('registrationModal')); + const pieModalEl = document.getElementById('statusPieChartModal'); + if (pieModalEl) { + statusPieChartModalObj = new bootstrap.Modal(pieModalEl); + } }); function showLoader(show) { @@ -1984,6 +2389,7 @@ const statusInput = document.getElementById('ejv-adj-order-status'); const fileInput = document.getElementById('ejv-num-file'); const numInput = document.getElementById('ejv-number-input'); + const dateInput = document.getElementById('ejv-date-input'); if (!input || !hiddenId || !statusInput || !fileInput || !numInput) return; @@ -1996,6 +2402,10 @@ fileInput.required = false; numInput.value = ''; numInput.setAttribute('disabled', 'true'); + if (dateInput) { + dateInput.value = ''; + dateInput.setAttribute('disabled', 'true'); + } const container = fileInput.closest('.file-upload-wrapper'); if (container) { @@ -2014,6 +2424,10 @@ fileInput.required = false; numInput.value = ''; numInput.setAttribute('disabled', 'true'); + if (dateInput) { + dateInput.value = ''; + dateInput.setAttribute('disabled', 'true'); + } const container = fileInput.closest('.file-upload-wrapper'); if (container) { @@ -2036,6 +2450,10 @@ statusInput.value = 'Yes'; fileInput.removeAttribute('disabled'); numInput.removeAttribute('disabled'); + if (dateInput) { + dateInput.removeAttribute('disabled'); + dateInput.value = r.ejv_num_file_gen_date ? String(r.ejv_num_file_gen_date).split('T')[0] : new Date().toISOString().split('T')[0]; + } numInput.value = (r.ejv_num && r.ejv_num !== '-') ? r.ejv_num : ''; @@ -2062,6 +2480,10 @@ fileInput.required = false; numInput.value = ''; numInput.setAttribute('disabled', 'true'); + if (dateInput) { + dateInput.value = ''; + dateInput.setAttribute('disabled', 'true'); + } const container = fileInput.closest('.file-upload-wrapper'); if (container) { @@ -2170,6 +2592,11 @@ if (input) input.value = ''; const hiddenId = document.getElementById('ejv-file-id'); if (hiddenId) hiddenId.value = ''; + const dateInput = document.getElementById('ejv-date-input'); + if (dateInput) { + dateInput.value = ''; + dateInput.setAttribute('disabled', 'true'); + } handleEJVFileNumberInput(); } @@ -2263,21 +2690,26 @@ sessionStorage.setItem('dashboard_filter_to_date', toDateVal); } - // Restore dashboard filters from sessionStorage + // Restore dashboard filters from sessionStorage / URL function restoreDashboardFiltersFromSession() { - const savedFy = sessionStorage.getItem('dashboard_filter_fin_year'); - const savedFromDate = sessionStorage.getItem('dashboard_filter_from_date'); - const savedToDate = sessionStorage.getItem('dashboard_filter_to_date'); + const params = new URLSearchParams(window.location.search); + const urlFy = params.get('fy'); + const urlFromDate = params.get('fromDate'); + const urlToDate = params.get('toDate'); + + const savedFy = urlFy || sessionStorage.getItem('dashboard_filter_fin_year'); + const savedFromDate = urlFromDate || sessionStorage.getItem('dashboard_filter_from_date'); + const savedToDate = urlToDate || sessionStorage.getItem('dashboard_filter_to_date'); if (savedFy !== null) { const fySelect = document.getElementById('filter-fin-year'); if (fySelect) fySelect.value = savedFy; } - if (savedFromDate !== null) { + if (savedFromDate !== null && savedFromDate !== '') { const fromDateInput = document.getElementById('filter-from-date'); if (fromDateInput) fromDateInput.value = savedFromDate; } - if (savedToDate !== null) { + if (savedToDate !== null && savedToDate !== '') { const toDateInput = document.getElementById('filter-to-date'); if (toDateInput) toDateInput.value = savedToDate; } @@ -2316,6 +2748,15 @@ if (editId) { editRecord(parseInt(editId, 10)); } + + // Check for openPieModal in URL and auto-open the pie chart modal + const openPieModal = params.get('openPieModal'); + if (openPieModal) { + openStatusPieChartModal(openPieModal); + const cleanUrl = new URL(window.location); + cleanUrl.searchParams.delete('openPieModal'); + window.history.replaceState({}, '', cleanUrl); + } }) .catch(err => { showLoader(false); @@ -2502,7 +2943,7 @@ html += `
+ id="card-total-files" style="cursor: pointer;" onclick="openStatusPieChartModal('All')" title="Click to view pie chart distribution"> Total Files
${displayTotal} @@ -2516,7 +2957,7 @@ html += `
+ id="card-adjusted-files" style="cursor: pointer;" onclick="openStatusPieChartModal('Adjusted')" title="Click to view pie chart distribution"> Adjusted Files
${adjustedCount} @@ -2530,7 +2971,7 @@ html += `
+ id="card-pending-files" style="cursor: pointer;" onclick="openStatusPieChartModal('Pending')" title="Click to view pie chart distribution"> Pending / Not Adjusted
${pendingCount} @@ -2755,6 +3196,359 @@ window.location.href = url; } + function openStatusPieChartModal(statusType) { + currentPieModalStatus = statusType; + const fyFilter = document.getElementById('filter-fin-year') ? document.getElementById('filter-fin-year').value : 'All'; + const fromDateVal = document.getElementById('filter-from-date') ? document.getElementById('filter-from-date').value : ''; + const toDateVal = document.getElementById('filter-to-date') ? document.getElementById('filter-to-date').value : ''; + const cleanFy = str => String(str || '').replace(/\s+/g, ''); + + const isNullOrEmpty = val => !val || String(val).trim() === '' || String(val).trim() === '-' || String(val).trim() === 'null'; + const isAdjusted = r => { + const ejvNum = String(r.ejv_num || '').trim(); + const ejvPath = String(r.ejv_num_file_path || '').trim(); + return (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'); + }; + + // 1. Filter records by FY and Dates + let baseRecords = allRecords; + if (fyFilter !== 'All') { + baseRecords = baseRecords.filter(r => cleanFy(r.fin_year) === cleanFy(fyFilter)); + } + + if (fromDateVal) { + const fromDate = new Date(fromDateVal); + fromDate.setHours(0, 0, 0, 0); + baseRecords = baseRecords.filter(r => { + const recordDate = r.cdate ? new Date(r.cdate) : null; + return recordDate && recordDate >= fromDate; + }); + } + + if (toDateVal) { + const toDate = new Date(toDateVal); + toDate.setHours(23, 59, 59, 999); + baseRecords = baseRecords.filter(r => { + const recordDate = r.cdate ? new Date(r.cdate) : null; + return recordDate && recordDate <= toDate; + }); + } + + // 2. Filter records by Status (All, Adjusted, Pending) + let statusRecords = baseRecords; + let statusTitle = 'Total Files Distribution'; + let statusSubtitle = 'Advance Type breakdown across all registered files'; + let headerGradient = 'linear-gradient(135deg, #06232e, #0a4b64)'; + let iconClass = 'fa-solid fa-folder'; + let btnViewAllText = 'View All Total Files in Details'; + let countColor = '#0a4b64'; + + if (statusType === 'Adjusted') { + statusRecords = baseRecords.filter(r => isAdjusted(r)); + statusTitle = 'Adjusted Files Distribution'; + statusSubtitle = 'Advance Type breakdown for all adjusted (EJV completed) files'; + headerGradient = 'linear-gradient(135deg, #064e3b, #059669)'; + iconClass = 'fa-solid fa-circle-check'; + btnViewAllText = 'View All Adjusted Files in Details'; + countColor = '#059669'; + } else if (statusType === 'Pending') { + statusRecords = baseRecords.filter(r => !isAdjusted(r)); + statusTitle = 'Pending / Not Adjusted Files Distribution'; + statusSubtitle = 'Advance Type breakdown for all pending (non-adjusted) files'; + headerGradient = 'linear-gradient(135deg, #7f1d1d, #dc2626)'; + iconClass = 'fa-solid fa-circle-xmark'; + btnViewAllText = 'View All Pending Files in Details'; + countColor = '#dc2626'; + } + + // Update modal header UI + const headerEl = document.getElementById('pie-modal-header'); + if (headerEl) headerEl.style.background = headerGradient; + + const titleEl = document.getElementById('statusPieChartModalLabel'); + if (titleEl) titleEl.textContent = statusTitle; + + const subEl = document.getElementById('pie-modal-subtitle'); + if (subEl) subEl.textContent = statusSubtitle; + + const iconEl = document.getElementById('pie-modal-icon'); + if (iconEl) iconEl.className = iconClass; + + const btnViewAll = document.getElementById('btn-pie-view-all'); + if (btnViewAll) { + btnViewAll.innerHTML = ` ${btnViewAllText}`; + } + + // Total summary figures + const totalCount = statusRecords.length; + const totalAmount = statusRecords.reduce((sum, r) => sum + (r.pi_amt ? Number(r.pi_amt) : 0), 0); + + // Filter badge string + let filterText = `FY: ${fyFilter}`; + if (fromDateVal && toDateVal) { + filterText += ` (${fromDateVal} to ${toDateVal})`; + } else if (fromDateVal) { + filterText += ` (From ${fromDateVal})`; + } else if (toDateVal) { + filterText += ` (To ${toDateVal})`; + } + const filterSummaryEl = document.getElementById('pie-summary-filter'); + if (filterSummaryEl) filterSummaryEl.textContent = filterText; + + const countSummaryEl = document.getElementById('pie-summary-count'); + if (countSummaryEl) { + countSummaryEl.textContent = `${totalCount} File${totalCount === 1 ? '' : 's'}`; + countSummaryEl.style.color = countColor; + } + + const amountSummaryEl = document.getElementById('pie-summary-amount'); + if (amountSummaryEl) amountSummaryEl.textContent = formatIndianCurrency(totalAmount); + + // 3. Compute Category breakdown + const paletteList = [ + '#ea580c', '#0284c7', '#7c3aed', '#16a34a', '#e11d48', + '#0d9488', '#c026d3', '#f59e0b', '#475569', '#2563eb', + '#059669', '#d97706', '#9333ea', '#64748b' + ]; + + const categories = []; + + // Add standard advance types + if (allAdvanceTypes && allAdvanceTypes.length > 0) { + allAdvanceTypes.forEach((t, idx) => { + const matched = statusRecords.filter(r => String(r.adv_type_id) === String(t.id) && r.file_available_status !== 'No'); + const count = matched.length; + const amount = matched.reduce((sum, r) => sum + (r.pi_amt ? Number(r.pi_amt) : 0), 0); + const color = paletteList[idx % paletteList.length]; + + categories.push({ + catId: String(t.id), + name: t.adv_type, + count: count, + amount: amount, + color: color + }); + }); + } + + // Add Not Available Files category + const notAvailMatched = statusRecords.filter(r => r.file_available_status === 'No'); + const notAvailCount = notAvailMatched.length; + const notAvailAmount = notAvailMatched.reduce((sum, r) => sum + (r.pi_amt ? Number(r.pi_amt) : 0), 0); + categories.push({ + catId: 'NotAvailable', + name: 'Not Available Files', + count: notAvailCount, + amount: notAvailAmount, + color: '#64748b' + }); + + // Filter for chart (slices with count > 0) + const chartCategories = categories.filter(c => c.count > 0); + activePieModalCategories = chartCategories; + + // Render table breakdown + renderPieModalTable(categories, totalCount, totalAmount); + + // Render chart + renderStatusDistributionChart(chartCategories, totalCount, totalAmount); + + // Show modal + if (statusPieChartModalObj) { + statusPieChartModalObj.show(); + } + } + + function renderPieModalTable(categories, totalCount, totalAmount) { + const tbody = document.getElementById('pie-category-breakdown-body'); + const countLabel = document.getElementById('pie-categories-count'); + if (countLabel) { + const activeCount = categories.filter(c => c.count > 0).length; + countLabel.textContent = `${activeCount} Active / ${categories.length} Total`; + } + + if (!tbody) return; + + // Sort categories descending by count, then by amount + const sorted = [...categories].sort((a, b) => b.count - a.count || b.amount - a.amount); + + let html = ''; + sorted.forEach(cat => { + const percentage = totalCount > 0 ? ((cat.count / totalCount) * 100).toFixed(1) : '0.0'; + const isZero = cat.count === 0; + + html += ` + + +
+ + ${cat.name} +
+ + + + ${cat.count} + + + + ${formatIndianCurrency(cat.amount)} + + + + ${percentage}% + + + + ${cat.count > 0 ? ` + + ` : `-`} + + + `; + }); + + tbody.innerHTML = html; + } + + function renderStatusDistributionChart(chartCategories, totalCount, totalAmount) { + const canvas = document.getElementById('statusDistributionPieChart'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + + if (statusPieChartInstance) { + statusPieChartInstance.destroy(); + statusPieChartInstance = null; + } + + if (!chartCategories || chartCategories.length === 0 || totalCount === 0) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#64748b'; + ctx.font = '14px Poppins, sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('No files matching selected criteria', canvas.width / 2, canvas.height / 2); + return; + } + + const labels = chartCategories.map(c => c.name); + const data = chartCategories.map(c => c.count); + const amounts = chartCategories.map(c => c.amount); + const backgroundColors = chartCategories.map(c => c.color); + + statusPieChartInstance = new Chart(ctx, { + type: 'doughnut', + data: { + labels: labels, + datasets: [{ + data: data, + backgroundColor: backgroundColors, + borderWidth: 2, + borderColor: '#ffffff', + hoverOffset: 8 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + position: 'bottom', + labels: { + boxWidth: 14, + padding: 14, + font: { + size: 12, + family: 'Poppins' + }, + generateLabels: function (chart) { + const d = chart.data; + if (d.labels.length && d.datasets.length) { + return d.labels.map((label, i) => { + const count = d.datasets[0].data[i]; + const percentage = totalCount > 0 ? ((count / totalCount) * 100).toFixed(1) : 0; + const color = d.datasets[0].backgroundColor[i]; + return { + text: `${label}: ${count} (${percentage}%)`, + fillStyle: color, + strokeStyle: color, + lineWidth: 0, + hidden: isNaN(d.datasets[0].data[i]) || chart.getDatasetMeta(0).data[i].hidden, + index: i + }; + }); + } + return []; + } + } + }, + tooltip: { + backgroundColor: 'rgba(15, 23, 42, 0.95)', + titleFont: { + size: 14, + family: 'Outfit', + weight: '700' + }, + bodyFont: { + size: 13, + family: 'Poppins' + }, + padding: 14, + cornerRadius: 10, + callbacks: { + label: function (context) { + const label = context.label || ''; + const value = context.raw || 0; + const amt = amounts[context.dataIndex] || 0; + const percentage = totalCount > 0 ? ((value / totalCount) * 100).toFixed(1) : 0; + return [ + ` Files: ${value} (${percentage}%)`, + ` Amount: ${formatIndianCurrency(amt)}`, + ` 👉 Click to view details` + ]; + } + } + } + }, + onClick: (event, elements) => { + if (elements && elements.length > 0) { + const index = elements[0].index; + const item = activePieModalCategories[index]; + if (item) { + navigateFromPieModal(item.catId); + } + } + }, + onHover: (event, chartElement) => { + event.native.target.style.cursor = chartElement[0] ? 'pointer' : 'default'; + }, + cutout: '50%', + animation: { + animateScale: true, + animateRotate: true, + duration: 800 + } + } + }); + } + + function navigateFromPieModal(catId) { + if (statusPieChartModalObj) { + statusPieChartModalObj.hide(); + } + const fy = document.getElementById('filter-fin-year') ? document.getElementById('filter-fin-year').value : 'All'; + const fromDate = document.getElementById('filter-from-date') ? document.getElementById('filter-from-date').value : ''; + const toDate = document.getElementById('filter-to-date') ? document.getElementById('filter-to-date').value : ''; + + let url = `${contextPath}gccadjustmentmanagement/details?status=${currentPieModalStatus}&catId=${catId}&reason=Total&fy=${fy}&fromPieModal=${currentPieModalStatus}`; + if (fromDate) url += `&fromDate=${fromDate}`; + if (toDate) url += `&toDate=${toDate}`; + window.location.href = url; + } + function goToListViewWithFilter(vendor) { const select = document.getElementById('filter-vendor'); if (select) { @@ -3109,13 +3903,232 @@ } } + function parseValidDate(dateVal) { + if (!dateVal || dateVal === '-' || dateVal === 'null' || dateVal === '1970-01-01' || String(dateVal).startsWith('1970-01-01')) return null; + const d = new Date(dateVal); + if (isNaN(d.getTime()) || d.getFullYear() <= 1970) return null; + return d; + } + + function diffInDays(dStart, dEnd) { + if (!dStart || !dEnd) return null; + const s = new Date(dStart); + const e = new Date(dEnd); + s.setHours(0, 0, 0, 0); + e.setHours(0, 0, 0, 0); + const diffMs = e - s; + const days = Math.round(diffMs / (1000 * 60 * 60 * 24)); + return days >= 0 ? days : 0; + } + + function calculateDaysFlow(r) { + if (!r) return null; + + const ejvNum = String(r.ejv_num || '').trim(); + const ejvPath = String(r.ejv_num_file_path || '').trim(); + const isAdjustedStatus = (ejvNum !== '' && ejvNum !== '-') || (ejvPath !== '' && ejvPath !== '-'); + + // 1. PI Date (First milestone) + const piDate = parseValidDate(r.pi_file_gen_date); + + // 2. AS Signed Date (File Date) - as_file_gen_date with fallback to uploadDate (cdate) + const fileDate = parseValidDate(r.as_file_gen_date); + const uploadDate = parseValidDate(r.cdate); + const asBaseDate = fileDate || uploadDate; + + // Base start date for overall turnaround / age + const baseDate = piDate || asBaseDate; + + // 3. DC Completed Date + const dcDate = parseValidDate(r.dc_completed_date); + + // 4. Final Invoice Date + const fiDate = parseValidDate(r.fi_file_gen_date); + + // 5. AS Order Signed Date + const asOrderDate = parseValidDate(r.as_final_file_gen_date); + + // 6. EJV Date + const ejvDate = parseValidDate(r.ejv_num_file_gen_date); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // Inter-stage days flow + const daysPiToAs = (piDate && asBaseDate) ? diffInDays(piDate, asBaseDate) : null; + const daysAsToDc = (asBaseDate && dcDate) ? diffInDays(asBaseDate, dcDate) : ((piDate && dcDate) ? diffInDays(piDate, dcDate) : null); + const daysDcToFi = (dcDate && fiDate) ? diffInDays(dcDate, fiDate) : (asBaseDate && fiDate ? diffInDays(asBaseDate, fiDate) : ((piDate && fiDate) ? diffInDays(piDate, fiDate) : null)); + const daysFiToAsOrder = (fiDate && asOrderDate) ? diffInDays(fiDate, asOrderDate) : null; + const daysAsOrderToEjv = (asOrderDate && ejvDate) ? diffInDays(asOrderDate, ejvDate) : null; + + // Total Turnaround / Age + let totalDays = null; + let isCompleted = false; + + if (isAdjustedStatus && ejvDate && baseDate) { + totalDays = diffInDays(baseDate, ejvDate); + isCompleted = true; + } else if (isAdjustedStatus && baseDate) { + const completionDate = parseValidDate(r.updated_date) || today; + totalDays = diffInDays(baseDate, completionDate); + isCompleted = true; + } else if (baseDate) { + totalDays = diffInDays(baseDate, today); + isCompleted = false; + } + + return { + piDate, + fileDate, + uploadDate, + asBaseDate, + dcDate, + fiDate, + asOrderDate, + ejvDate, + daysPiToAs, + daysAsToDc, + daysDcToFi, + daysFiToAsOrder, + daysAsOrderToEjv, + totalDays, + isCompleted, + hasPi: !!(r.pi_file_path && r.pi_file_path !== '-' && r.pi_file_path !== 'null'), + hasAs: !!(r.as_file_path && r.as_file_path !== '-' && r.as_file_path !== 'null'), + hasDc: !!(r.dc_file_path && r.dc_file_path !== '-' && r.dc_file_path !== 'null'), + hasFi: !!(r.fi_file_path && r.fi_file_path !== '-' && r.fi_file_path !== 'null'), + hasAsOrder: !!(r.as_final_file_path && r.as_final_file_path !== '-' && r.as_final_file_path !== 'null'), + hasEjv: isAdjustedStatus + }; + } + + function renderMilestoneTimeline(flow, r) { + if (!flow) return ''; + + const formatDateOnly = (d) => { + if (!d) return 'Not Recorded'; + const pad = n => String(n).padStart(2, '0'); + return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()}`; + }; + + const renderDaysPill = (days, label) => { + if (days === null || days === undefined) return ''; + let bg = '#dcfce7', text = '#15803d'; + if (days > 60) { bg = '#ffe4e6'; text = '#be123c'; } + else if (days > 30) { bg = '#ffedd5'; text = '#c2410c'; } + return `+${days} Days ${label ? '(' + label + ')' : ''}`; + }; + + const step1Status = flow.piDate ? 'completed' : (flow.hasPi ? 'in-progress' : 'pending'); + const step2Status = (flow.fileDate || flow.uploadDate) ? 'completed' : (flow.hasAs ? 'in-progress' : 'pending'); + const step3Status = flow.dcDate ? 'completed' : (flow.hasDc ? 'in-progress' : 'pending'); + const step4Status = flow.fiDate ? 'completed' : (flow.hasFi ? 'in-progress' : 'pending'); + const step5Status = flow.asOrderDate ? 'completed' : (flow.hasAsOrder ? 'in-progress' : 'pending'); + const step6Status = flow.isCompleted ? 'completed' : (flow.hasEjv ? 'in-progress' : 'pending'); + + return ` +
+
+
+ Milestone Dates & Days Flow Tracker +
+
+ + + ${flow.isCompleted ? 'Adjusted (' + flow.totalDays + ' Days Turnaround)' : 'Pending (' + flow.totalDays + ' Days Elapsed)'} + +
+
+
+
+ +
+
+ Step 1 + +
+
PI Gen Date
+
${formatDateOnly(flow.piDate)}
+
Start (Day 0)
+
+ +
+ + +
+
+ Step 2 + +
+
AS Signed Date (File Date)
+
${formatDateOnly(flow.fileDate || flow.uploadDate)}
+
${renderDaysPill(flow.daysPiToAs, 'from PI')}
+
+ +
+ + +
+
+ Step 3 + +
+
DC Completed
+
${formatDateOnly(flow.dcDate)}
+
${renderDaysPill(flow.daysAsToDc, 'from AS Signed Date')}
+
+ +
+ + +
+
+ Step 4 + +
+
Final Invoice
+
${formatDateOnly(flow.fiDate)}
+
${renderDaysPill(flow.daysDcToFi, 'from DC')}
+
+ +
+ + +
+
+ Step 5 + +
+
AS Order Date
+
${formatDateOnly(flow.asOrderDate)}
+
${renderDaysPill(flow.daysFiToAsOrder, 'from FI')}
+
+ +
+ + +
+
+ Step 6 + +
+
EJV Completed
+
${formatDateOnly(flow.ejvDate)}
+
${flow.isCompleted ? renderDaysPill(flow.daysAsOrderToEjv, 'from AS Order') : 'Pending'}
+
+
+
+
+ `; + } + // Render main table rows function renderTableData(records, startSNo = 1) { const tbody = document.getElementById('records-table-body'); tbody.innerHTML = ''; if (records.length === 0) { - tbody.innerHTML = 'No files found matching criteria.'; + tbody.innerHTML = 'No files found matching criteria.'; return; } @@ -3124,8 +4137,27 @@ const balAmt = r.bal_amt ? Number(r.bal_amt).toLocaleString('en-IN') : '-'; const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-'; const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-'; - // ${escapeHtml(r.resolved_fund_type || '-')} - // ${escapeHtml(r.ac_code || '-')} + + const flow = calculateDaysFlow(r); + let daysFlowHtml = '-'; + if (flow && flow.totalDays !== null) { + const days = flow.totalDays; + let badgeClass = 'days-flow-green'; + if (days > 60) badgeClass = 'days-flow-red'; + else if (days > 30) badgeClass = 'days-flow-orange'; + + const icon = flow.isCompleted ? '' : ''; + const subText = flow.isCompleted ? 'Turnaround' : 'Age (Pending)'; + + daysFlowHtml = ` +
+ + ${icon} ${days} Days + + ${subText} +
+ `; + } tbody.innerHTML += ` @@ -3141,9 +4173,10 @@ ? `No` : '-'} - ₹ ${balAmt} ₹ ${piAmt} + ₹ ${balAmt} ₹ ${fiAmt} + ${daysFlowHtml} ${escapeHtml(r.file_remark || '-')} ${formatDateTime(r.cdate)} ${formatDateTime(r.updated_date)} @@ -3224,20 +4257,24 @@ // Define checklist document array const docs = [ { name: "Original / Main Registration File", path: r.file_path, extra: `Ref Num: ${r.file_data_num || '-'}` }, + { name: "Proforma Invoice (PI) Document", path: r.pi_file_path, extra: `PI Amount: ₹ ${piAmt} | Date: ${formatDate(r.pi_file_gen_date)}` }, + { name: "AS Sanction Copy", path: r.as_file_path, extra: `Date: ${formatDate(r.as_file_gen_date)}` }, { name: "Purchase Order (PO) Document", path: r.po_file_path, extra: null }, { name: "Delivery Challan (DC) Document", path: r.dc_file_path, extra: r.dc_completed_date ? `Completed Date: ${formatDate(r.dc_completed_date)}` : null }, { name: "Inward Register Document", path: r.ir_file_path, extra: null }, { name: "Stock Register (SR) Document", path: r.sr_filepath, extra: null }, - { name: "Proforma Invoice (PI) Document", path: r.pi_file_path, extra: `PI Amount: ₹ ${piAmt} | Date: ${formatDate(r.pi_file_gen_date)}` }, { name: "Final Invoice (FI) Document", path: r.fi_file_path, extra: `FI Amount: ₹ ${fiAmt} | Date: ${formatDate(r.fi_file_gen_date)}` }, { name: "Temporary Bill (TC) Document", path: r.tc_file_path, extra: `Date: ${formatDate(r.tc_file_gen_date)}` }, { name: "RTGS Bank Payment Proof", path: r.rtgs_file_path, extra: r.rtgs_date ? `Upload Date: ${formatDate(r.rtgs_date)}` : null }, { name: "EJV Adjustment Bill Copy", path: r.ejv_num_file_path, extra: `EJV Num: ${r.ejv_num || '-'} | Date: ${formatDate(r.ejv_num_file_gen_date)}` }, - { name: "AS Sanction Copy", path: r.as_file_path, extra: `Date: ${formatDate(r.as_file_gen_date)}` }, { name: "Final AS Copy", path: r.as_final_file_path, extra: `Date: ${r.as_final_file_gen_date || '-'}` } ]; + const flow = calculateDaysFlow(r); + const timelineHtml = renderMilestoneTimeline(flow, r); + let html = ` + ${timelineHtml}
Vendor Name: ${escapeHtml(r.vend_name)}
@@ -3440,6 +4477,18 @@ } } + function resolveAdjustmentFileUrl(path) { + if (!isValidFilePath(path)) return '#'; + let clean = String(path).trim().replace(/\\/g, '/'); + while (clean.startsWith('/')) { + clean = clean.substring(1); + } + if (clean.startsWith('http://') || clean.startsWith('https://')) { + return clean; + } + return contextPath + clean; + } + function setupExistingFileLink(inputId, filePath) { const input = document.getElementById(inputId); if (!input) return; @@ -3454,13 +4503,14 @@ container.appendChild(linkDiv); } - if (filePath && filePath !== '-' && filePath !== 'null') { - let cleanPath = String(filePath).trim(); - if (cleanPath.startsWith('/')) { + if (isValidFilePath(filePath)) { + let cleanPath = String(filePath).trim().replace(/\\/g, '/'); + while (cleanPath.startsWith('/')) { cleanPath = cleanPath.substring(1); } - const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1); - linkDiv.innerHTML = ` View Existing: ${fileName}`; + const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1) || 'Document.pdf'; + const fileUrl = resolveAdjustmentFileUrl(filePath); + linkDiv.innerHTML = ` View Existing: ${escapeHtml(fileName)}`; linkDiv.style.display = 'block'; } else { linkDiv.style.display = 'none'; @@ -3638,10 +4688,24 @@ Fund Type A/C Code File Available Status - Bal Amount PI Amount + Bal Amount FI Amount + Status + PI Gen Date + AS Signed Date (File Date) + Days (PI to AS Signed Date) + DC Completed Date + Days (AS Signed to DC) + Final Invoice Date + Days (DC to FI) + AS Order Date + Days (FI to AS Order) EJV Number + EJV Gen Date + Days (AS Order to EJV) + Total Flow (Days) + File Remarks Created Date Last Updated Updated By @@ -3657,6 +4721,15 @@ const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-'; const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-'; + const flow = calculateDaysFlow(r); + const statusStr = (flow && flow.isCompleted) ? 'Adjusted' : 'Pending'; + + const formatDateCell = (d) => { + if (!d) return '-'; + const pad = n => String(n).padStart(2, '0'); + return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()}`; + }; + tbody.innerHTML += ` ${currentSNo++} @@ -3668,10 +4741,24 @@ ${escapeHtml(r.resolved_fund_type || '-')} ${escapeHtml(r.ac_code || '-')} ${escapeHtml(r.file_available_status || '-')} - ₹ ${balAmt} ₹ ${piAmt} + ₹ ${balAmt} ₹ ${fiAmt} - ${escapeHtml(r.ejv_num || '-')} + ${statusStr} + ${formatDateCell(flow ? flow.piDate : null)} + ${formatDateCell(flow ? (flow.fileDate || flow.uploadDate) : null)} + ${flow && flow.daysPiToAs !== null ? flow.daysPiToAs : '-'} + ${formatDateCell(flow ? flow.dcDate : null)} + ${flow && flow.daysAsToDc !== null ? flow.daysAsToDc : '-'} + ${formatDateCell(flow ? flow.fiDate : null)} + ${flow && flow.daysDcToFi !== null ? flow.daysDcToFi : '-'} + ${formatDateCell(flow ? flow.asOrderDate : null)} + ${flow && flow.daysFiToAsOrder !== null ? flow.daysFiToAsOrder : '-'} + ${escapeHtml(r.ejv_num || '-')} + ${formatDateCell(flow ? flow.ejvDate : null)} + ${flow && flow.daysAsOrderToEjv !== null ? flow.daysAsOrderToEjv : '-'} + ${flow && flow.totalDays !== null ? flow.totalDays + (flow.isCompleted ? ' (Turnaround)' : ' (Pending)') : '-'} + ${escapeHtml(r.file_remark || '-')} ${formatDateTime(r.cdate)} ${formatDateTime(r.updated_date)} ${escapeHtml(r.updated_by_name || r.updated_by || '-')} @@ -3915,7 +5002,7 @@ tbody.innerHTML = ''; if (paginatedRecords.length === 0) { - tbody.innerHTML = 'No files found matching criteria.'; + tbody.innerHTML = 'No files found matching criteria.'; renderDashPagination(0); return; } @@ -3948,8 +5035,26 @@ statusBadge = `
Adjusted`; } - // ${escapeHtml(r.resolved_fund_type || '-')} - // ${escapeHtml(r.ac_code || '-')} + const flow = calculateDaysFlow(r); + let daysFlowHtml = '-'; + if (flow && flow.totalDays !== null) { + const days = flow.totalDays; + let badgeClass = 'days-flow-green'; + if (days > 60) badgeClass = 'days-flow-red'; + else if (days > 30) badgeClass = 'days-flow-orange'; + + const icon = flow.isCompleted ? '' : ''; + const subText = flow.isCompleted ? 'Turnaround' : 'Age (Pending)'; + + daysFlowHtml = ` +
+ + ${icon} ${days} Days + + ${subText} +
+ `; + } tbody.innerHTML += ` @@ -3964,9 +5069,10 @@ ? `No` : '-'} - ₹ ${balAmt} ₹ ${piAmt} + ₹ ${balAmt} ₹ ${fiAmt} + ${daysFlowHtml} ${escapeHtml(r.file_remark || '-')} ${formatDateTime(r.cdate)} ${formatDateTime(r.updated_date)} @@ -4091,10 +5197,24 @@ Fund Type A/C Code File Available Status - Bal Amount PI Amount + Bal Amount FI Amount + Status + PI Gen Date + AS Signed Date (File Date) + Days (PI to AS Signed Date) + DC Completed Date + Days (AS Signed to DC) + Final Invoice Date + Days (DC to FI) + AS Order Date + Days (FI to AS Order) EJV Number + EJV Gen Date + Days (AS Order to EJV) + Total Flow (Days) + File Remarks Created Date Last Updated Updated By @@ -4110,6 +5230,15 @@ const piAmt = r.pi_amt ? Number(r.pi_amt).toLocaleString('en-IN') : '-'; const fiAmt = r.fi_amt ? Number(r.fi_amt).toLocaleString('en-IN') : '-'; + const flow = calculateDaysFlow(r); + const statusStr = (flow && flow.isCompleted) ? 'Adjusted' : 'Pending'; + + const formatDateCell = (d) => { + if (!d) return '-'; + const pad = n => String(n).padStart(2, '0'); + return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()}`; + }; + tbody.innerHTML += ` ${currentSNo++} @@ -4121,10 +5250,24 @@ ${escapeHtml(r.resolved_fund_type || '-')} ${escapeHtml(r.ac_code || '-')} ${escapeHtml(r.file_available_status || '-')} - ₹ ${balAmt} ₹ ${piAmt} + ₹ ${balAmt} ₹ ${fiAmt} + ${statusStr} + ${formatDateCell(flow ? flow.piDate : null)} + ${formatDateCell(flow ? (flow.fileDate || flow.uploadDate) : null)} + ${flow && flow.daysPiToAs !== null ? flow.daysPiToAs : '-'} + ${formatDateCell(flow ? flow.dcDate : null)} + ${flow && flow.daysAsToDc !== null ? flow.daysAsToDc : '-'} + ${formatDateCell(flow ? flow.fiDate : null)} + ${flow && flow.daysDcToFi !== null ? flow.daysDcToFi : '-'} + ${formatDateCell(flow ? flow.asOrderDate : null)} + ${flow && flow.daysFiToAsOrder !== null ? flow.daysFiToAsOrder : '-'} ${escapeHtml(r.ejv_num || '-')} + ${formatDateCell(flow ? flow.ejvDate : null)} + ${flow && flow.daysAsOrderToEjv !== null ? flow.daysAsOrderToEjv : '-'} + ${flow && flow.totalDays !== null ? flow.totalDays + (flow.isCompleted ? ' (Turnaround)' : ' (Pending)') : '-'} + ${escapeHtml(r.file_remark || '-')} ${formatDateTime(r.cdate)} ${formatDateTime(r.updated_date)} ${escapeHtml(r.updated_by_name || r.updated_by || '-')} diff --git a/src/main/resources/templates/gccadjustmentmanagement/details.html b/src/main/resources/templates/gccadjustmentmanagement/details.html index acbee9f..de8e78e 100644 --- a/src/main/resources/templates/gccadjustmentmanagement/details.html +++ b/src/main/resources/templates/gccadjustmentmanagement/details.html @@ -105,9 +105,10 @@ top: 0; left: 0; width: 100%; - height: 100%; + height: 46px; opacity: 0; cursor: pointer; + z-index: 2; } .file-upload-wrapper:hover .file-upload-btn-custom { @@ -126,6 +127,18 @@ gap: 4px; } + .existing-file-link { + position: relative; + z-index: 10; + margin-top: 6px; + } + + .existing-file-link a { + position: relative; + z-index: 10; + cursor: pointer; + } + .table-custom { font-size: 0.85rem; border-collapse: separate; @@ -263,6 +276,133 @@ text-decoration: underline; color: #172554; } + + /* Days Flow & Milestone Timeline Tracker Styles */ + .days-flow-badge-wrap { + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 2px; + } + + .days-flow-badge { + font-family: 'Outfit', sans-serif; + font-weight: 700; + padding: 4px 10px; + border-radius: 20px; + font-size: 0.78rem; + display: inline-flex; + align-items: center; + gap: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + transition: all 0.2s; + } + + .days-flow-badge:hover { + transform: scale(1.04); + } + + .days-flow-green { + background-color: #dcfce7 !important; + color: #15803d !important; + border: 1px solid #86efac; + } + + .days-flow-orange { + background-color: #ffedd5 !important; + color: #c2410c !important; + border: 1px solid #fdba74; + } + + .days-flow-red { + background-color: #ffe4e6 !important; + color: #be123c !important; + border: 1px solid #fda4af; + } + + .timeline-stepper { + display: flex; + align-items: stretch; + justify-content: space-between; + position: relative; + margin: 8px 0; + gap: 8px; + overflow-x: auto; + padding: 6px 2px; + } + + .timeline-step { + flex: 1; + min-width: 140px; + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 10px; + position: relative; + display: flex; + flex-direction: column; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.03); + transition: all 0.2s; + } + + .timeline-step.completed { + border-color: #10b981; + background: #f0fdf4; + } + + .timeline-step.in-progress { + border-color: #3b82f6; + background: #eff6ff; + } + + .timeline-step.pending { + border-color: #e2e8f0; + background: #f8fafc; + opacity: 0.85; + } + + .timeline-step-header { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.75rem; + font-weight: 700; + margin-bottom: 4px; + } + + .timeline-step-title { + font-size: 0.82rem; + font-weight: 700; + color: #1e293b; + display: flex; + align-items: center; + gap: 6px; + } + + .timeline-step-date { + font-family: 'Outfit', sans-serif; + font-size: 0.8rem; + font-weight: 600; + color: #334155; + margin-top: 4px; + } + + .timeline-step-days { + display: inline-block; + margin-top: 6px; + font-size: 0.72rem; + font-weight: 700; + } + + .timeline-connector { + display: flex; + align-items: center; + justify-content: center; + color: #94a3b8; + font-size: 0.85rem; + flex-shrink: 0; + padding: 0 2px; + } @@ -292,7 +432,8 @@ class="btn-back d-flex align-items-center gap-2"> Back to Dashboard --> - + Back to Dashboard
@@ -376,12 +517,12 @@ File Num Advance Type - Bal Amount PI Amount + Bal Amount FI Amount Adjustment Status File Status - Days Past + Days Flow Remarks Updated Date @@ -499,27 +640,6 @@ placeholder="Enter Subject">
- -
- -
-
- Choose File -
- -
-
-
-
- - -
- - -
-
@@ -550,6 +670,27 @@ class="form-control-custom">
+ +
+ +
+
+ Choose File +
+ +
+
+
+
+ + +
+ + +
+
@@ -914,6 +1055,24 @@ } } + function isValidFilePath(path) { + if (!path) return false; + const p = String(path).trim(); + return p !== '' && p !== '-' && p !== 'null' && p !== 'undefined'; + } + + function resolveAdjustmentFileUrl(path) { + if (!isValidFilePath(path)) return '#'; + let clean = String(path).trim().replace(/\\/g, '/'); + while (clean.startsWith('/')) { + clean = clean.substring(1); + } + if (clean.startsWith('http://') || clean.startsWith('https://')) { + return clean; + } + return contextPath + clean; + } + function setupExistingFileLink(inputId, filePath) { const input = document.getElementById(inputId); if (!input) return; @@ -928,13 +1087,14 @@ container.appendChild(linkDiv); } - if (filePath && filePath !== '-' && filePath !== 'null') { - let cleanPath = String(filePath).trim(); - if (cleanPath.startsWith('/')) { + if (isValidFilePath(filePath)) { + let cleanPath = String(filePath).trim().replace(/\\/g, '/'); + while (cleanPath.startsWith('/')) { cleanPath = cleanPath.substring(1); } - const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1); - linkDiv.innerHTML = ` View Existing: ${fileName}`; + const fileName = cleanPath.substring(cleanPath.lastIndexOf('/') + 1) || 'Document.pdf'; + const fileUrl = resolveAdjustmentFileUrl(filePath); + linkDiv.innerHTML = ` View Existing: ${escapeHtml(fileName)}`; linkDiv.style.display = 'block'; } else { linkDiv.style.display = 'none'; @@ -1002,6 +1162,8 @@ }); } + let fromPieModal = ''; + function parseUrlParams() { const params = new URLSearchParams(window.location.search); filterStatus = params.get('status') || 'All'; @@ -1010,6 +1172,7 @@ filterFy = params.get('fy') || 'All'; filterFromDate = params.get('fromDate') || ''; filterToDate = params.get('toDate') || ''; + fromPieModal = params.get('fromPieModal') || ''; activeAdvTypeFilter = filterCatId; @@ -1023,6 +1186,35 @@ updatePageTitle(); } + function goBackToDashboard() { + const params = new URLSearchParams(window.location.search); + const pieStatus = params.get('fromPieModal') || fromPieModal || filterStatus; + const fy = filterFy || params.get('fy') || 'All'; + const fromDate = filterFromDate || params.get('fromDate') || ''; + const toDate = filterToDate || params.get('toDate') || ''; + + let url = `${contextPath}gccadjustmentmanagement/dashboard`; + const q = new URLSearchParams(); + if (pieStatus) { + q.set('openPieModal', pieStatus); + } + if (fy && fy !== 'All') { + q.set('fy', fy); + } + if (fromDate) { + q.set('fromDate', fromDate); + } + if (toDate) { + q.set('toDate', toDate); + } + + const qs = q.toString(); + if (qs) { + url += '?' + qs; + } + window.location.href = url; + } + function updatePageTitle() { const titleEl = document.getElementById('details-page-title'); if (!titleEl) return; @@ -1473,11 +1665,23 @@ `; }); + // Static 'Status Not Updated' button for records with empty / null / '-' file_status_id + const isNotUpdatedActive = String(activeFileStatusFilter) === 'NotUpdated'; + const notUpdatedCount = records.filter(r => !r.file_status_id || String(r.file_status_id).trim() === '' || String(r.file_status_id).trim() === '-' || String(r.file_status_id).trim() === 'null' || String(r.file_status_id).trim() === 'undefined').length; + fsHtml += ` + + `; + fileStatusContainer.innerHTML = fsHtml; } // Apply File Status filter - if (activeFileStatusFilter !== 'All') { + if (activeFileStatusFilter === 'NotUpdated') { + records = records.filter(r => !r.file_status_id || String(r.file_status_id).trim() === '' || String(r.file_status_id).trim() === '-' || String(r.file_status_id).trim() === 'null' || String(r.file_status_id).trim() === 'undefined'); + } else if (activeFileStatusFilter !== 'All') { records = records.filter(r => String(r.file_status_id) === String(activeFileStatusFilter)); } @@ -1575,6 +1779,223 @@ renderTableData(); } + function parseValidDate(dateVal) { + if (!dateVal || dateVal === '-' || dateVal === 'null' || dateVal === '1970-01-01' || String(dateVal).startsWith('1970-01-01')) return null; + const d = new Date(dateVal); + if (isNaN(d.getTime()) || d.getFullYear() <= 1970) return null; + return d; + } + + function diffInDays(dStart, dEnd) { + if (!dStart || !dEnd) return null; + const s = new Date(dStart); + const e = new Date(dEnd); + s.setHours(0, 0, 0, 0); + e.setHours(0, 0, 0, 0); + const diffMs = e - s; + const days = Math.round(diffMs / (1000 * 60 * 60 * 24)); + return days >= 0 ? days : 0; + } + + function calculateDaysFlow(r) { + if (!r) return null; + + const isAdjustedStatus = isAdjusted(r); + + // 1. PI Date (First milestone) + const piDate = parseValidDate(r.pi_file_gen_date); + + // 2. AS Signed Date (File Date) - as_file_gen_date with fallback to uploadDate (cdate) + const fileDate = parseValidDate(r.as_file_gen_date); + const uploadDate = parseValidDate(r.cdate); + const asBaseDate = fileDate || uploadDate; + + // Base start date for overall turnaround / age + const baseDate = piDate || asBaseDate; + + // 3. DC Completed Date + const dcDate = parseValidDate(r.dc_completed_date); + + // 4. Final Invoice Date + const fiDate = parseValidDate(r.fi_file_gen_date); + + // 5. AS Order Signed Date + const asOrderDate = parseValidDate(r.as_final_file_gen_date); + + // 6. EJV Date + const ejvDate = parseValidDate(r.ejv_num_file_gen_date); + + const today = new Date(); + today.setHours(0, 0, 0, 0); + + // Inter-stage days flow + const daysPiToAs = (piDate && asBaseDate) ? diffInDays(piDate, asBaseDate) : null; + const daysAsToDc = (asBaseDate && dcDate) ? diffInDays(asBaseDate, dcDate) : ((piDate && dcDate) ? diffInDays(piDate, dcDate) : null); + const daysDcToFi = (dcDate && fiDate) ? diffInDays(dcDate, fiDate) : (asBaseDate && fiDate ? diffInDays(asBaseDate, fiDate) : ((piDate && fiDate) ? diffInDays(piDate, fiDate) : null)); + const daysFiToAsOrder = (fiDate && asOrderDate) ? diffInDays(fiDate, asOrderDate) : null; + const daysAsOrderToEjv = (asOrderDate && ejvDate) ? diffInDays(asOrderDate, ejvDate) : null; + + // Total Turnaround / Age + let totalDays = null; + let isCompleted = false; + + if (isAdjustedStatus && ejvDate && baseDate) { + totalDays = diffInDays(baseDate, ejvDate); + isCompleted = true; + } else if (isAdjustedStatus && baseDate) { + const completionDate = parseValidDate(r.updated_date) || today; + totalDays = diffInDays(baseDate, completionDate); + isCompleted = true; + } else if (baseDate) { + totalDays = diffInDays(baseDate, today); + isCompleted = false; + } + + return { + piDate, + fileDate, + uploadDate, + asBaseDate, + dcDate, + fiDate, + asOrderDate, + ejvDate, + daysPiToAs, + daysAsToDc, + daysDcToFi, + daysFiToAsOrder, + daysAsOrderToEjv, + totalDays, + isCompleted, + hasPi: !!(r.pi_file_path && r.pi_file_path !== '-' && r.pi_file_path !== 'null'), + hasAs: !!(r.as_file_path && r.as_file_path !== '-' && r.as_file_path !== 'null'), + hasDc: !!(r.dc_file_path && r.dc_file_path !== '-' && r.dc_file_path !== 'null'), + hasFi: !!(r.fi_file_path && r.fi_file_path !== '-' && r.fi_file_path !== 'null'), + hasAsOrder: !!(r.as_final_file_path && r.as_final_file_path !== '-' && r.as_final_file_path !== 'null'), + hasEjv: isAdjustedStatus + }; + } + + function renderMilestoneTimeline(flow, r) { + if (!flow) return ''; + + const formatDateOnly = (d) => { + if (!d) return 'Not Recorded'; + const pad = n => String(n).padStart(2, '0'); + return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()}`; + }; + + const renderDaysPill = (days, label) => { + if (days === null || days === undefined) return ''; + let bg = '#dcfce7', text = '#15803d'; + if (days > 60) { bg = '#ffe4e6'; text = '#be123c'; } + else if (days > 30) { bg = '#ffedd5'; text = '#c2410c'; } + return `+${days} Days ${label ? '(' + label + ')' : ''}`; + }; + + const step1Status = flow.piDate ? 'completed' : (flow.hasPi ? 'in-progress' : 'pending'); + const step2Status = (flow.fileDate || flow.uploadDate) ? 'completed' : (flow.hasAs ? 'in-progress' : 'pending'); + const step3Status = flow.dcDate ? 'completed' : (flow.hasDc ? 'in-progress' : 'pending'); + const step4Status = flow.fiDate ? 'completed' : (flow.hasFi ? 'in-progress' : 'pending'); + const step5Status = flow.asOrderDate ? 'completed' : (flow.hasAsOrder ? 'in-progress' : 'pending'); + const step6Status = flow.isCompleted ? 'completed' : (flow.hasEjv ? 'in-progress' : 'pending'); + + return ` +
+
+
+ Milestone Dates & Days Flow Tracker +
+
+ + + ${flow.isCompleted ? 'Adjusted (' + flow.totalDays + ' Days Turnaround)' : 'Pending (' + flow.totalDays + ' Days Elapsed)'} + +
+
+
+
+ +
+
+ Step 1 + +
+
PI Gen Date
+
${formatDateOnly(flow.piDate)}
+
Start (Day 0)
+
+ +
+ + +
+
+ Step 2 + +
+
AS Signed Date (File Date)
+
${formatDateOnly(flow.fileDate || flow.uploadDate)}
+
${renderDaysPill(flow.daysPiToAs, 'from PI')}
+
+ +
+ + +
+
+ Step 3 + +
+
DC Completed
+
${formatDateOnly(flow.dcDate)}
+
${renderDaysPill(flow.daysAsToDc, 'from AS Signed Date')}
+
+ +
+ + +
+
+ Step 4 + +
+
Final Invoice
+
${formatDateOnly(flow.fiDate)}
+
${renderDaysPill(flow.daysDcToFi, 'from DC')}
+
+ +
+ + +
+
+ Step 5 + +
+
AS Order Date
+
${formatDateOnly(flow.asOrderDate)}
+
${renderDaysPill(flow.daysFiToAsOrder, 'from FI')}
+
+ +
+ + +
+
+ Step 6 + +
+
EJV Completed
+
${formatDateOnly(flow.ejvDate)}
+
${flow.isCompleted ? renderDaysPill(flow.daysAsOrderToEjv, 'from AS Order') : 'Pending'}
+
+
+
+
+ `; + } + function renderTableData() { const tbody = document.getElementById('details-table-body'); if (!tbody) return; @@ -1629,62 +2050,27 @@ const sNo = start + idx + 1; const status = isAdjusted(r) ? 'Adjusted' : 'Pending'; - let daysPastHtml = '-'; - if (r.fi_file_gen_date) { - const genDate = new Date(r.fi_file_gen_date); - if (!isNaN(genDate.getTime())) { - const today = new Date(); - genDate.setHours(0, 0, 0, 0); - today.setHours(0, 0, 0, 0); - const diffTime = today - genDate; - const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); - const daysVal = diffDays >= 0 ? diffDays : 0; - - let badgeBg = '#f1f5f9'; - let badgeText = '#475569'; - if (daysVal > 60) { - badgeBg = '#ffe4e6'; // Rose/Red - badgeText = '#e11d48'; - } else if (daysVal > 30) { - badgeBg = '#ffedd5'; // Orange - badgeText = '#ea580c'; - } else { - badgeBg = '#dcfce7'; // Green - badgeText = '#16a34a'; - } - daysPastHtml = `${daysVal} Days`; - } + const flow = calculateDaysFlow(r); + let daysFlowHtml = '-'; + if (flow && flow.totalDays !== null) { + const days = flow.totalDays; + let badgeClass = 'days-flow-green'; + if (days > 60) badgeClass = 'days-flow-red'; + else if (days > 30) badgeClass = 'days-flow-orange'; + + const icon = flow.isCompleted ? '' : ''; + const subText = flow.isCompleted ? 'Turnaround' : 'Age (Pending)'; + + daysFlowHtml = ` +
+ + ${icon} ${days} Days + + ${subText} +
+ `; } - // let fileAvailStr = '-'; - // if (r.file_available_status === 'Yes') { - // fileAvailStr = ' Yes'; - // } else if (r.file_available_status === 'No') { - // fileAvailStr = ' No'; - // } - - // Render Documents column cell dynamically - // let docsHtml = ''; - // const renderDocBadge = (path, label, color) => { - // if (!path || path === '-' || path === 'null') return ''; - // return ` - // - // `; - // }; - - // docsHtml += renderDocBadge(r.po_file_path, 'PO', 'primary'); - // docsHtml += renderDocBadge(r.pi_file_path, 'PI', 'info text-white'); - // docsHtml += renderDocBadge(r.fi_file_path, 'FI', 'success'); - // docsHtml += renderDocBadge(r.ejv_num_file_path, 'EJV', 'dark'); - - // if (docsHtml === '') docsHtml = '-'; - - // ${docsHtml} - // ${fileAvailStr} html += ` ${sNo} @@ -1695,8 +2081,8 @@ ${r.adv_type || '-'} - ${formatAmount(r.bal_amt)} ${formatAmount(r.pi_amt)} + ${formatAmount(r.bal_amt)} ${formatAmount(r.fi_amt)} ${status} @@ -1704,7 +2090,7 @@ ${r.file_status || '-'} - ${daysPastHtml} + ${daysFlowHtml} ${r.file_remark || '-'} ${formatDate(r.updated_date || r.cdate)} @@ -1774,16 +2160,12 @@ } function viewDocument(filePath, docName, file_data_num) { - if (!filePath || filePath === '-' || filePath === 'null') { + if (!isValidFilePath(filePath)) { Swal.fire({ icon: 'warning', title: 'Not Uploaded', text: 'This file has not been uploaded yet.' }); return; } - let cleanPath = String(filePath).trim(); - if (cleanPath.startsWith('/')) { - cleanPath = cleanPath.substring(1); - } - const fileUrl = contextPath + cleanPath; + const fileUrl = resolveAdjustmentFileUrl(filePath); const titleEl = document.getElementById('docViewerModalLabel'); if (titleEl) { @@ -1803,8 +2185,6 @@ const fileObj = allRecords.find(r => r.id === id); if (!fileObj) return; - console.log(fileObj); - const formatAmount = amt => amt ? 'Rs. ' + new Intl.NumberFormat('en-IN', { minimumFractionDigits: 2 }).format(amt) : '-'; const renderViewIcon = (filePath, fileName, file_data_num) => { if (!filePath || filePath === '-' || filePath === 'null') return 'Not uploaded'; @@ -1814,16 +2194,15 @@ onclick="viewDocument('${filePath}', '${fileName}', '${file_data_num}')"> View File -
`; }; const formatDate = dateStr => { - if (!dateStr) return '-'; + if (!dateStr || dateStr === '-' || dateStr === 'null' || dateStr === '1970-01-01' || String(dateStr).startsWith('1970-01-01')) return '-'; try { const date = new Date(dateStr); - if (isNaN(date.getTime())) { + if (isNaN(date.getTime()) || date.getFullYear() <= 1970) { return dateStr; } const pad = num => String(num).padStart(2, '0'); @@ -1831,15 +2210,21 @@ const mm = pad(date.getMonth() + 1); const yyyy = date.getFullYear(); - return `${dd}/${mm}/${yyyy} `; + return `${dd}/${mm}/${yyyy}`; } catch (e) { return dateStr; } }; + const flow = calculateDaysFlow(fileObj); + const timelineHtml = renderMilestoneTimeline(flow, fileObj); + const modalBody = document.getElementById('fileInfoModalBody'); if (modalBody) { modalBody.innerHTML = ` + + ${timelineHtml} +
Basic Details
@@ -1851,124 +2236,126 @@

File Status: ${fileObj.file_available_status || '-'}

File Status (Category): ${fileObj.file_status || '-'}

Subject: ${fileObj.subject || '-'}

+

Uploaded Date: ${formatDate(fileObj.cdate)}

-
Amounts & Files
+
Milestone Files & Dates
- + +
+
+ PI Amount: +
${formatAmount(fileObj.pi_amt)}
+
+
+ PI File: +
${renderViewIcon(fileObj.pi_file_path, 'PI File', fileObj.file_data_num)}
+
+
+ PI File Gen Date: +
${formatDate(fileObj.pi_file_gen_date)}
+
+
+ +
Bal Amount:
${formatAmount(fileObj.bal_amt)}
- AS File: + AS File (Initial):
${renderViewIcon(fileObj.as_file_path, 'AS File', fileObj.file_data_num)}
- AS Signed Date: + AS Signed Date (File Date):
${formatDate(fileObj.as_file_gen_date)}
- - - +
- PI Amount: -
${formatAmount(fileObj.pi_amt)}
+ DC File: +
${renderViewIcon(fileObj.dc_file_path, 'DC File', fileObj.file_data_num)}
- PI File: -
${renderViewIcon(fileObj.pi_file_path, 'PI File', fileObj.file_data_num)}
+ DC Completed Date: +
${formatDate(fileObj.dc_completed_date)}
- PI completed date: -
${formatDate(fileObj.pi_file_gen_date)}
+ Stock Register File: +
${renderViewIcon(fileObj.sr_filepath, 'SR File', fileObj.file_data_num)}
- +
FI Amount:
${formatAmount(fileObj.fi_amt)}
- FI File: + Final Invoice File:
${renderViewIcon(fileObj.fi_file_path, 'FI File', fileObj.file_data_num)}
- FI completed date: + Final Invoice Date:
${formatDate(fileObj.fi_file_gen_date)}
- +
- RTGS File: -
${renderViewIcon(fileObj.rtgs_file_path, 'RTGS File', fileObj.file_data_num)}
+ AS Order Final File: +
${renderViewIcon(fileObj.as_final_file_path, 'AS Final File', fileObj.file_data_num)}
- RTGS Date: -
${formatDate(fileObj.rtgs_date)}
+ AS Order Date: +
${formatDate(fileObj.as_final_file_gen_date)}
- Stock registry File: -
${renderViewIcon(fileObj.sr_filepath, 'SR File', fileObj.file_data_num)}
+ TC Date: +
${formatDate(fileObj.tc_file_gen_date)}
- +
-
- DC file: -
${renderViewIcon(fileObj.dc_file_path, 'DC File', fileObj.file_data_num)}
-
-
- DC completed date: -
${formatDate(fileObj.dc_completed_date)}
+
+ EJV Number: +
${fileObj.ejv_num || '-'}
-
+
EJV File:
${renderViewIcon(fileObj.ejv_num_file_path, 'EJV File', fileObj.file_data_num)}
-
- EJV date: +
+ EJV Date:
${formatDate(fileObj.ejv_num_file_gen_date)}
- -
+ +
- Adjustment Order Final File: -
${renderViewIcon(fileObj.as_final_file_path, 'AS Final File', fileObj.file_data_num)}
+ RTGS File: +
${renderViewIcon(fileObj.rtgs_file_path, 'RTGS File', fileObj.file_data_num)}
- AS Final Date: -
${formatDate(fileObj.as_final_file_gen_date)}
+ RTGS Date: +
${formatDate(fileObj.rtgs_date)}
- TC File: -
${renderViewIcon(fileObj.tc_file_path, 'TC File', fileObj.file_data_num)}
+ PO File: +
${renderViewIcon(fileObj.po_file_path, 'PO File', fileObj.file_data_num)}
- TC Date: -
${formatDate(fileObj.tc_file_gen_date)}
+ TC File: +
${renderViewIcon(fileObj.tc_file_path, 'TC File', fileObj.file_data_num)}
- - -
-
- PurchaseOrder File: -
${renderViewIcon(fileObj.po_file_path, 'PO File', fileObj.file_data_num)}
-
- -
@@ -1991,10 +2378,10 @@ } const formatDateForExcel = dateStr => { - if (!dateStr || dateStr === '-' || dateStr === 'null') return ''; + if (!dateStr || dateStr === '-' || dateStr === 'null' || dateStr === '1970-01-01' || String(dateStr).startsWith('1970-01-01')) return ''; try { const date = new Date(dateStr); - if (isNaN(date.getTime())) { + if (isNaN(date.getTime()) || date.getFullYear() <= 1970) { return dateStr; } const pad = num => String(num).padStart(2, '0'); @@ -2007,48 +2394,55 @@ } }; - const data = filteredRecords.map((r, index) => ({ - 'S.No': index + 1, - 'File Number': r.file_data_num || '', - 'Financial Year': r.fin_year || '', - 'Subject': r.subject || '', - 'Vendor Name': r.vend_name || '', - 'Advance Type': r.adv_type || '', - 'Fund Type': r.fund_type || '', - 'A/C Head Code': r.ac_code || '', - 'File Available Status': r.file_available_status || '', - 'File Status (Category)': r.file_status || '', - 'Bal Amount': r.bal_amt || 0, - 'AS Signed Date': formatDateForExcel(r.as_file_gen_date), - 'PO Amount': r.po_amt || 0, - 'PO Completed Date': formatDateForExcel(r.po_file_gen_date), - 'PI Amount': r.pi_amt || 0, - 'PI Completed Date': formatDateForExcel(r.pi_file_gen_date), - 'RTGS Date': formatDateForExcel(r.rtgs_date), - 'FI Amount': r.fi_amt || 0, - 'FI Completed Date': formatDateForExcel(r.fi_file_gen_date), - 'DC Completed Date': formatDateForExcel(r.dc_completed_date), - 'EJV Num': r.ejv_num || '', - 'EJV Date': formatDateForExcel(r.ejv_num_file_gen_date), - 'AS Final Date': formatDateForExcel(r.as_final_file_gen_date), - 'TC Date': formatDateForExcel(r.tc_file_gen_date), - 'Status': isAdjusted(r) ? 'Adjusted' : 'Pending', - 'Remarks': r.file_remark || '', - 'Created Date': formatDateForExcel(r.cdate), - 'Updated Date': formatDateForExcel(r.updated_date), - 'AS File Path': r.as_file_path || '', - 'PI File Path': r.pi_file_path || '', - 'RTGS File Path': r.rtgs_file_path || '', - 'PO File Path': r.po_file_path || '', - 'DC File Path': r.dc_file_path || '', - 'SR File Path': r.sr_filepath || '', - 'IR File Path': r.ir_file_path || '', - 'FI File Path': r.fi_file_path || '', - 'AS Final File Path': r.as_final_file_path || '', - 'TC File Path': r.tc_file_path || '', - 'EJV File Path': r.ejv_num_file_path || '', - 'Main File Path': r.file_path || '' - })); + const data = filteredRecords.map((r, index) => { + const flow = calculateDaysFlow(r); + return { + 'S.No': index + 1, + 'File Number': r.file_data_num || '', + 'Financial Year': r.fin_year || '', + 'Subject': r.subject || '', + 'Vendor Name': r.vend_name || '', + 'Advance Type': r.adv_type || '', + 'Fund Type': r.fund_type || '', + 'A/C Head Code': r.ac_code || '', + 'File Available Status': r.file_available_status || '', + 'File Status (Category)': r.file_status || 'Not Updated', + 'PI Amount': r.pi_amt || 0, + 'PI File Gen Date': formatDateForExcel(r.pi_file_gen_date), + 'Bal Amount': r.bal_amt || 0, + 'AS Signed Date (File Date)': formatDateForExcel(r.as_file_gen_date), + 'Days (PI to AS Signed)': flow && flow.daysPiToAs !== null ? flow.daysPiToAs : '', + 'DC Completed Date': formatDateForExcel(r.dc_completed_date), + 'Days (AS Signed to DC)': flow && flow.daysAsToDc !== null ? flow.daysAsToDc : '', + 'Final Invoice Amount': r.fi_amt || 0, + 'Final Invoice Date': formatDateForExcel(r.fi_file_gen_date), + 'Days (DC to FI)': flow && flow.daysDcToFi !== null ? flow.daysDcToFi : '', + 'AS Order Date (Final AS Date)': formatDateForExcel(r.as_final_file_gen_date), + 'Days (FI to AS Order)': flow && flow.daysFiToAsOrder !== null ? flow.daysFiToAsOrder : '', + 'EJV Num': r.ejv_num || '', + 'EJV Date': formatDateForExcel(r.ejv_num_file_gen_date), + 'Days (AS Order to EJV)': flow && flow.daysAsOrderToEjv !== null ? flow.daysAsOrderToEjv : '', + 'Total Days Flow': flow && flow.totalDays !== null ? flow.totalDays : '', + 'Days Flow Type': flow && flow.isCompleted ? 'Turnaround (Adjusted)' : 'Age (Pending)', + 'RTGS Date': formatDateForExcel(r.rtgs_date), + 'TC Date': formatDateForExcel(r.tc_file_gen_date), + 'Status': isAdjusted(r) ? 'Adjusted' : 'Pending', + 'Remarks': r.file_remark || '', + 'Last Updated Date': formatDateForExcel(r.updated_date), + 'PI File Path': r.pi_file_path || '', + 'AS File Path': r.as_file_path || '', + 'RTGS File Path': r.rtgs_file_path || '', + 'PO File Path': r.po_file_path || '', + 'DC File Path': r.dc_file_path || '', + 'SR File Path': r.sr_filepath || '', + 'IR File Path': r.ir_file_path || '', + 'FI File Path': r.fi_file_path || '', + 'AS Final File Path': r.as_final_file_path || '', + 'TC File Path': r.tc_file_path || '', + 'EJV File Path': r.ejv_num_file_path || '', + 'Main File Path': r.file_path || '' + }; + }); const ws = XLSX.utils.json_to_sheet(data); const wb = XLSX.utils.book_new(); @@ -2059,9 +2453,16 @@ XLSX.writeFile(wb, `gcc_adjustment_details_${dateStr}.xlsx`); } - - - + // HTML escaping helper + function escapeHtml(str) { + if (!str) return ''; + return String(str) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } diff --git a/src/main/resources/templates/gcctapaldetailsmanagement/dashboard.html b/src/main/resources/templates/gcctapaldetailsmanagement/dashboard.html index 61aa067..6937c47 100644 --- a/src/main/resources/templates/gcctapaldetailsmanagement/dashboard.html +++ b/src/main/resources/templates/gcctapaldetailsmanagement/dashboard.html @@ -346,6 +346,21 @@ background-color: #e2e8f0; } + + /* Interactive Summary Cell Styles */ + .cursor-pointer { + cursor: pointer; + transition: all 0.2s ease; + } + + .cursor-pointer:hover { + background-color: #cbd5e1 !important; + transform: scale(1.08); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08); + z-index: 5; + position: relative; + } + /* Table card styling */ .table-card { background: #fff; @@ -761,7 +776,8 @@ - + 0 diff --git a/src/main/resources/templates/gcctonermanagement/upload.html b/src/main/resources/templates/gcctonermanagement/upload.html index 2c61d73..d5ab1ec 100644 --- a/src/main/resources/templates/gcctonermanagement/upload.html +++ b/src/main/resources/templates/gcctonermanagement/upload.html @@ -559,6 +559,17 @@ color: #ffffff; } + .btn-view-photo { + background-color: #f5f3ff; + color: #6d28d9; + border: 1px solid #ede9fe; + } + + .btn-view-photo:hover { + background-color: #6d28d9; + color: #ffffff; + } + /* Prominent Toner Model Badge */ .badge-toner-model { background-color: #475569 !important; @@ -832,10 +843,12 @@ Workorder Register + Inward Register @@ -1006,7 +1019,7 @@
- +
-
+
@@ -1076,15 +1090,36 @@
-
- Category & Workorder Wise Stock Breakdown -
- Search: - +
+ Category & Workorder Wise Stock Breakdown +
+ 0 + records +
+
+ +
+ + Breakdown View: + + +
+
+ Search: + +
@@ -1093,9 +1128,10 @@ S.No Printer Model (Make) - Work Order Ref + Work Order Ref + ITCell Ref No Workorder Registered Qty - Workorder Request Qty + Inward Qty User Request Qty Outward/Issued Qty @@ -1104,7 +1140,7 @@ - Click "Fetch Data" to view + Click "Fetch Data" to view dashboard records. @@ -1135,7 +1171,7 @@ style="width: auto; height: 36px; font-size: 0.85rem;" onchange="onDashboardDetailsTypeChange()"> - + @@ -1243,7 +1279,7 @@ + onchange="onReportTonerFilterChange()" disabled>
+ + + + +
+ + +
+
- -
+ +
+ + +
+ + + + + + + + +
+
@@ -1616,7 +1711,8 @@
- + +
-
-
-
-
-
-
-
@@ -1727,7 +1815,6 @@
- @@ -1737,7 +1824,6 @@
-
    -
+ --> @@ -1841,7 +1927,7 @@ - Please select a Workorder Reference to view requested models. + Please select a Workorder Reference to view registered models. @@ -1943,11 +2029,16 @@ +
+ + +
- +
@@ -2105,7 +2196,42 @@
-
+ + + + + + + + + + + + + + + +
@@ -2114,6 +2240,11 @@
0 Records +
@@ -2152,7 +2283,7 @@
-
+
@@ -2183,6 +2314,13 @@
+
+ + +
@@ -2199,24 +2337,89 @@ Save Delivery & Download Acknowledgement - +
- -
- - Choose PDF File - +
+ Delivery Proof & Acknowledgement +
+ +
+ +
+ +
+ + Choose Signed PDF + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ Receiver Photo Preview +
+ + +
+
+ + +
+
@@ -2317,6 +2520,8 @@ // Caching datasets let printersCached = []; + let departmentsCached = []; + let zonesCached = []; let pendingRequestsCached = []; let userRequestsCached = []; let inwardHistoryCached = []; @@ -2327,6 +2532,10 @@ // Signature Pad & Last Saved state let lastSavedOutward = null; + let outwardCameraStream = null; + let outwardCameraFacing = 'environment'; + let outwardCapturedPhotoBlob = null; + let outwardCapturedPhotoBase64 = null; let drawing = false; let hasSigned = false; let lastX = 0; @@ -2335,6 +2544,8 @@ document.addEventListener('DOMContentLoaded', () => { // Load base list of printers on startup fetchPrinters(); + fetchDepartmentsList(); + fetchZonesList(); // Load vendors map, then default to Dashboard View Data loadVendorsMap(() => { loadDashboardPage(); @@ -2404,9 +2615,9 @@ resetForm('inward'); const tbody = document.getElementById('inward-items-tbody'); if (tbody) { - tbody.innerHTML = 'Please select a Workorder Reference to view requested models.'; + tbody.innerHTML = 'Please select a Workorder Reference to view registered models.'; } - fetchActiveWorkorderRequests(); + fetchActiveWorkordersForInward(); loadVendorsMap(() => { fetchInwardHistory(); }); @@ -2439,6 +2650,9 @@ printersCached = data; populateReportPrinterFilters(); populateDashboardPrinterFilters(); + if (userRequestsCached && userRequestsCached.length > 0) { + populateUserReqFilters(userRequestsCached); + } }) .catch(err => console.error("Error fetching printers: ", err)); } @@ -2749,16 +2963,30 @@ function populateDashboardWorkOrders() { const select = document.getElementById('dashboard-workorder-filter'); if (!select) return; - select.innerHTML = ''; - - // Get unique ref_no from workorderHistoryCached - const refNos = [...new Set(workorderHistoryCached.map(item => item.ref_no).filter(Boolean))]; - refNos.sort(); + const currentVal = select.value; + select.innerHTML = ''; + + const refMap = new Map(); + (workorderHistoryCached || []).forEach(item => { + if (item.ref_no) { + const label = item.file_number ? `${item.ref_no} (WO: ${item.file_number})` : item.ref_no; + refMap.set(String(item.ref_no).trim(), label); + } + }); + (userRequestsCached || []).forEach(item => { + if (item.itcell_ref_no) { + const ref = String(item.itcell_ref_no).trim(); + if (!refMap.has(ref)) { + refMap.set(ref, `Req: ${ref}`); + } + } + }); - refNos.forEach(ref => { + Array.from(refMap.entries()).sort((a, b) => a[1].localeCompare(b[1])).forEach(([val, label]) => { const opt = document.createElement('option'); - opt.value = ref; - opt.textContent = ref; + opt.value = val; + opt.textContent = label; + if (val === currentVal) opt.selected = true; select.appendChild(opt); }); } @@ -2831,14 +3059,18 @@ // Show loading indicators const tbody = document.getElementById('dashboard-tbody'); if (tbody) { - tbody.innerHTML = `Loading dashboard data...`; + const viewFilter = document.getElementById('dashboard-breakdown-view-filter')?.value || 'user_request'; + const showWoRef = viewFilter !== 'user_request'; + const showItcellRef = viewFilter !== 'workorder'; + const totalCols = 7 + (showWoRef ? 1 : 0) + (showItcellRef ? 1 : 0); + tbody.innerHTML = `Loading dashboard data...`; } // Fetch datasets for dashboard calculations const fetch1 = fetch(contextPath + 'gcctonermanagement/api/requests/list').then(res => res.json()); const fetch2 = fetch(contextPath + 'gcctonermanagement/api/inward/list').then(res => res.json()); const fetch3 = fetch(contextPath + 'gcctonermanagement/api/workorder/history').then(res => res.json()); - const fetch4 = fetch(contextPath + 'gcctonermanagement/api/workorder-request/history').then(res => res.json()); + const fetch4 = Promise.resolve([]); // Bypassed Workorder Model Request const fetch5 = fetch(contextPath + 'gcctonermanagement/api/outward/list').then(res => res.json()); const fetch6 = fetch(contextPath + 'gcctonermanagement/api/report').then(res => res.json()); @@ -2864,7 +3096,7 @@ .catch(err => { console.error("Error loading dashboard datasets:", err); if (tbody) { - tbody.innerHTML = `Failed to load dashboard data.`; + tbody.innerHTML = `Failed to load dashboard data.`; } }); } @@ -2876,18 +3108,35 @@ const groups = {}; - function getGroupKey(pid, refNo) { - const cleanRef = (refNo || '').trim(); - return `${pid || 0}_${cleanRef || 'general'}`; + function initTonerSplit(g, tid, tonerModel) { + const cleanTid = tid || 0; + if (!g.tonerSplits[cleanTid]) { + g.tonerSplits[cleanTid] = { + tid: cleanTid, + tonerModel: tonerModel || 'Unknown Toner', + workorderQty: 0, + workorderReqQty: 0, + inwardQty: 0, + outwardQty: 0, + requestedQty: 0 + }; + } + return g.tonerSplits[cleanTid]; } - function initGroup(pid, printerName, refNo) { - const key = getGroupKey(pid, refNo); + function getWorkorderGroup(pid, printerName, refNo, fileNumber) { + const cleanPid = pid || 0; + const cleanRef = (refNo || '').trim(); + const cleanFile = (fileNumber || '').trim(); + const key = `${cleanPid}_WO_${cleanRef || 'general'}`; if (!groups[key]) { groups[key] = { - pid: pid || '', + key: key, + type: 'workorder', + pid: cleanPid, printerName: printerName || 'Unknown Printer', - refNo: (refNo || '').trim() || 'General / Direct', + workOrderRef: cleanFile || '-', + itcellRefNo: cleanRef || 'General / Direct', workorderQty: 0, workorderReqQty: 0, inwardQty: 0, @@ -2896,81 +3145,85 @@ tonerSplits: {} }; } + if (cleanFile && (!groups[key].workOrderRef || groups[key].workOrderRef === '-')) { + groups[key].workOrderRef = cleanFile; + } return groups[key]; } - function initTonerSplit(g, tid, tonerModel) { - const cleanTid = tid || 0; - if (!g.tonerSplits[cleanTid]) { - g.tonerSplits[cleanTid] = { - tid: cleanTid, - tonerModel: tonerModel || 'Unknown Toner', + function getUserRequestGroup(pid, printerName, itcellRefNo) { + const cleanPid = pid || 0; + const cleanRef = (itcellRefNo || '').trim(); + const woKey = `${cleanPid}_WO_${cleanRef || 'general'}`; + if (groups[woKey]) { + return groups[woKey]; + } + const key = `${cleanPid}_REQ_${cleanRef || 'general'}`; + if (!groups[key]) { + groups[key] = { + key: key, + type: 'user_request', + pid: cleanPid, + printerName: printerName || 'Unknown Printer', + workOrderRef: '-', + itcellRefNo: cleanRef || 'General / Direct', workorderQty: 0, workorderReqQty: 0, inwardQty: 0, outwardQty: 0, - requestedQty: 0 + requestedQty: 0, + tonerSplits: {} }; } - return g.tonerSplits[cleanTid]; + return groups[key]; } // 1. Process Workorder Registered Qty - workorderHistoryCached.forEach(item => { - if (!matchesPeriod(item.cdate)) return; - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return; - if (selectedToner && String(item.tid) !== String(selectedToner)) return; - if (selectedWorkorder && String(item.ref_no) !== String(selectedWorkorder)) return; - - const g = initGroup(item.pid, item.printer_name, item.ref_no); - g.workorderQty += parseInt(item.total_qty_registered || 0); - - const ts = initTonerSplit(g, item.tid, item.model_name); - ts.workorderQty += parseInt(item.total_qty_registered || 0); - }); - - // 2. Process Workorder Requested Qty - workorderRequestHistoryCached.forEach(item => { + (workorderHistoryCached || []).forEach(item => { if (!matchesPeriod(item.cdate)) return; if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return; if (selectedToner && String(item.tid) !== String(selectedToner)) return; - if (selectedWorkorder && String(item.ref_no) !== String(selectedWorkorder)) return; + if (selectedWorkorder && String(item.ref_no) !== String(selectedWorkorder) && String(item.file_number) !== String(selectedWorkorder)) return; - const g = initGroup(item.pid, item.printer_name, item.ref_no); - g.workorderReqQty += parseInt(item.qty_requested || 0); + const g = getWorkorderGroup(item.pid, item.printer_name, item.ref_no, item.file_number); + const qty = parseInt(item.total_qty_registered || 0) || 0; + g.workorderQty += qty; const ts = initTonerSplit(g, item.tid, item.model_name); - ts.workorderReqQty += parseInt(item.qty_requested || 0); + ts.workorderQty += qty; }); - // 3. Process Inward stock - inwardHistoryCached.forEach(item => { + // 2. Process Inward stock + (inwardHistoryCached || []).forEach(item => { if (!matchesPeriod(item.cdate)) return; if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return; if (selectedToner && String(item.tid) !== String(selectedToner)) return; - if (selectedWorkorder && String(item.ref_no) !== String(selectedWorkorder)) return; + if (selectedWorkorder && String(item.ref_no) !== String(selectedWorkorder) && String(item.file_number) !== String(selectedWorkorder)) return; - const g = initGroup(item.pid, item.printer_name, item.ref_no); - g.inwardQty += parseInt(item.qty_registered || 0); + const g = getWorkorderGroup(item.pid, item.printer_name, item.ref_no, item.file_number); + const qty = parseInt(item.qty_registered || 0) || 0; + g.inwardQty += qty; const ts = initTonerSplit(g, item.tid, item.model_name); - ts.inwardQty += parseInt(item.qty_registered || 0); + ts.inwardQty += qty; }); - // 4. Process Outward (User Requests delivered) - userRequestsCached.forEach(item => { + // 3. Process Outward (User Requests & Deliveries) + (userRequestsCached || []).forEach(item => { if (!matchesPeriod(item.cdate)) return; if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return; if (selectedToner && String(item.tid) !== String(selectedToner)) return; if (selectedWorkorder && String(item.itcell_ref_no) !== String(selectedWorkorder)) return; - const g = initGroup(item.pid, item.printer_name, item.itcell_ref_no); - g.requestedQty += parseInt(item.qty_requested || 0); - g.outwardQty += parseInt(item.qty_delivered || 0); + const g = getUserRequestGroup(item.pid, item.printer_name, item.itcell_ref_no); + const reqQty = parseInt(item.qty_requested || 0) || 0; + const delQty = parseInt(item.qty_delivered || 0) || 0; + g.requestedQty += reqQty; + g.outwardQty += delQty; const ts = initTonerSplit(g, item.tid, item.model_name); - ts.requestedQty += parseInt(item.qty_requested || 0); - ts.outwardQty += parseInt(item.qty_delivered || 0); + ts.requestedQty += reqQty; + ts.outwardQty += delQty; }); let list = Object.values(groups); @@ -3204,7 +3457,7 @@ } else if (activeDashboardDetailsType === 'available_stock') { // stock_summary doesn't have workorder, ignore } else { - if (String(item.ref_no) !== String(selectedWorkorder)) return false; + if (String(item.ref_no) !== String(selectedWorkorder) && String(item.file_number) !== String(selectedWorkorder)) return false; } } return true; @@ -3278,7 +3531,7 @@ // Calculate counts dynamically from current dashboard details dataset const deptCounts = {}; data.forEach(item => { - const dept = item.dept_id; + const dept = item.dept_name; if (dept) { deptCounts[dept] = (deptCounts[dept] || 0) + 1; } @@ -3307,7 +3560,7 @@ // Apply categorywise (Printer Make), department, stock status, and text search filters let filtered = dashboardDetailsFilteredDataset.filter(item => { if (selectedCategory && item.printer_name !== selectedCategory) return false; - if (selectedDept && item.dept_id !== selectedDept) return false; + if (selectedDept && item.dept_name !== selectedDept) return false; if (activeDashboardDetailsType === 'available_stock' && selectedStockStatus) { const available = parseInt(item.available_qty || 0); @@ -3324,7 +3577,7 @@ item.printer_name, item.model_name, item.part_code, - item.dept_id, + item.dept_name, item.req_no, item.company_name, item.updated_by, @@ -3517,7 +3770,7 @@ ${globalIdx} ${escapeHtml(item.req_no)} ${escapeHtml(item.itcell_ref_no || '-')} - ${escapeHtml(item.dept_id)} + ${escapeHtml(item.dept_name)} ${escapeHtml(item.printer_name || '-')} ${escapeHtml(item.model_name || '-')} ${item.qty_requested} @@ -3559,19 +3812,29 @@ return `
${label}`; }).join(' '); } + let photoBtn = ''; + if (item.receiver_photo) { + const photos = item.receiver_photo.split(','); + photoBtn = photos.map((p, pIdx) => { + const trimmed = p.trim(); + const url = trimmed.startsWith('.') ? trimmed.substring(1) : trimmed; + const label = photos.length > 1 ? `View Photo ${pIdx + 1}` : `View Photo`; + return ` ${label}`; + }).join(' '); + } const pdfBtn = ``; tr.innerHTML = ` ${globalIdx} ${escapeHtml(item.req_no)} - ${escapeHtml(item.dept_id)} + ${escapeHtml(item.dept_name)} ${escapeHtml(item.printer_name)} ${escapeHtml(item.model_name)} ${item.qty_requested} ${item.qty_delivered} ${reqDateFormatted} ${dateFormatted} - ${docBtn}${pdfBtn} + ${docBtn}${photoBtn}${pdfBtn} `; tbody.appendChild(tr); }); @@ -3671,6 +3934,10 @@ applyDashboardDetailsFilters(1); } + function onDashboardBreakdownViewChange(val) { + renderDashboardTable(dashboardGroupsCached); + } + function onDashboardStockBreakdownSearchInput(val) { renderDashboardTable(dashboardGroupsCached); } @@ -3681,80 +3948,177 @@ tbody.innerHTML = ''; + const viewFilter = document.getElementById('dashboard-breakdown-view-filter')?.value || 'user_request'; const searchQuery = (document.getElementById('dashboard-stock-breakdown-search')?.value || '').toLowerCase().trim(); - let filtered = data; + let filtered = data || []; + + // 1. Determine column visibility based on viewFilter + const showWoRef = viewFilter !== 'user_request'; + const showItcellRef = viewFilter !== 'workorder'; + + const thWoRef = document.getElementById('th-dashboard-wo-ref'); + const thItcellRef = document.getElementById('th-dashboard-itcell-ref'); + if (thWoRef) thWoRef.style.display = showWoRef ? '' : 'none'; + if (thItcellRef) thItcellRef.style.display = showItcellRef ? '' : 'none'; + + const totalCols = 7 + (showWoRef ? 1 : 0) + (showItcellRef ? 1 : 0); + + // 2. Filter by view mode (User Request Wise by default, Work Order Registered Wise, or All Combined) + if (viewFilter === 'user_request') { + filtered = filtered.filter(item => item.type === 'user_request' || (item.requestedQty > 0 || item.outwardQty > 0)); + } else if (viewFilter === 'workorder') { + filtered = filtered.filter(item => item.type === 'workorder' || (item.workorderQty > 0 || item.inwardQty > 0)); + } + + // 3. Filter by search query if (searchQuery) { - filtered = data.filter(item => { + filtered = filtered.filter(item => { return (item.printerName && item.printerName.toLowerCase().includes(searchQuery)) || - (item.refNo && item.refNo.toLowerCase().includes(searchQuery)); + (item.workOrderRef && item.workOrderRef.toLowerCase().includes(searchQuery)) || + (item.itcellRefNo && item.itcellRefNo.toLowerCase().includes(searchQuery)); }); } + // Update records count badge + const countBadge = document.getElementById('dashboard-breakdown-count-badge'); + if (countBadge) { + countBadge.textContent = `${filtered.length} records`; + } + if (filtered.length === 0) { - tbody.innerHTML = `No matching records found for the search query.`; + const emptyMsg = viewFilter === 'user_request' + ? 'No user request breakdown records found.' + : (viewFilter === 'workorder' ? 'No work order breakdown records found.' : 'No matching records found.'); + tbody.innerHTML = `${emptyMsg}`; return; } filtered.sort((a, b) => { - const printerCompare = a.printerName.localeCompare(b.printerName); + const printerCompare = (a.printerName || '').localeCompare(b.printerName || ''); if (printerCompare !== 0) return printerCompare; - return a.refNo.localeCompare(b.refNo); + const itcellCompare = (a.itcellRefNo || '').localeCompare(b.itcellRefNo || ''); + if (itcellCompare !== 0) return itcellCompare; + return (a.workOrderRef || '').localeCompare(b.workOrderRef || ''); }); filtered.forEach((item, index) => { const tr = document.createElement('tr'); - const available = Math.max(0, item.inwardQty - item.outwardQty); - let availableBadge = `${available}`; - if (available === 0) { - availableBadge = `Out of stock`; - } else if (available <= 5) { - availableBadge = `${available} (Low)`; + let availableBadge = ''; + if (item.workorderQty > 0 || item.inwardQty > 0) { + const available = Math.max(0, item.inwardQty - item.outwardQty); + if (available === 0) { + availableBadge = `0 (Out of stock)`; + } else if (available <= 5) { + availableBadge = `${available} (Low)`; + } else { + availableBadge = `${available}`; + } + } else { + if (item.outwardQty >= item.requestedQty && item.requestedQty > 0) { + availableBadge = `Delivered (${item.outwardQty})`; + } else if (item.outwardQty > 0) { + availableBadge = `Partial (${item.outwardQty}/${item.requestedQty})`; + } else { + availableBadge = `Pending (${item.requestedQty})`; + } + } + + // Render Work Order Ref + let woRefDisplay = '-'; + if (item.workOrderRef && item.workOrderRef !== '-') { + if (!showItcellRef) { + woRefDisplay = `${escapeHtml(item.workOrderRef)}`; + } else { + woRefDisplay = `${escapeHtml(item.workOrderRef)}`; + } + } else if (!showItcellRef && item.itcellRefNo) { + woRefDisplay = `${escapeHtml(item.itcellRefNo)}`; } - // Render Work Order Ref as a clickable link/action - const refLink = `${escapeHtml(item.refNo)}`; + // Render ITCell Ref No as a clickable link/action + const itcellRefLink = `${escapeHtml(item.itcellRefNo)}`; - const woBtn = `${item.workorderQty}`; - const woReqBtn = `${item.workorderReqQty}`; - const inwardBtn = `${item.inwardQty}`; - const userReqBtn = `${item.requestedQty}`; - const outwardBtn = `${item.outwardQty}`; + const woBtn = item.workorderQty > 0 + ? `${item.workorderQty}` + : `0`; - tr.innerHTML = ` + const inwardBtn = item.inwardQty > 0 + ? `${item.inwardQty}` + : `0`; + + const userReqBtn = item.requestedQty > 0 + ? `${item.requestedQty}` + : `0`; + + const outwardBtn = item.outwardQty > 0 + ? `${item.outwardQty}` + : `0`; + + let cellsHtml = ` ${index + 1} ${escapeHtml(item.printerName)} - ${refLink} + `; + if (showWoRef) { + cellsHtml += `${woRefDisplay}`; + } + if (showItcellRef) { + cellsHtml += `${itcellRefLink}`; + } + cellsHtml += ` ${woBtn} - ${woReqBtn} ${inwardBtn} ${userReqBtn} ${outwardBtn} ${availableBadge} `; + + tr.innerHTML = cellsHtml; tbody.appendChild(tr); }); } - let activeDbBreakdownRef = ''; + let activeDbBreakdownKey = ''; + let activeDbBreakdownItcell = ''; + let activeDbBreakdownWo = ''; let activeDbBreakdownPrinterName = ''; let activeDbBreakdownPid = ''; let currentDbBreakdownTab = 'summary'; - function showDashboardTonerBreakdown(printerName, refNo) { - showDashboardTonerBreakdownWithTabAndTid(printerName, refNo, 'summary', ''); + function showDashboardTonerBreakdown(printerName, refNo, key) { + showDashboardTonerBreakdownWithTabAndKey(key || refNo, 'summary', ''); } function showDashboardTonerBreakdownWithTabAndTid(printerName, refNo, tabName, tid) { - const group = dashboardGroupsCached.find(g => g.printerName === printerName && g.refNo === refNo); + showDashboardTonerBreakdownWithTabAndKey(refNo, tabName, tid); + } + + function showDashboardTonerBreakdownWithTabAndKey(groupKey, tabName, tid) { + let group = dashboardGroupsCached.find(g => g.key === groupKey); + if (!group) { + group = dashboardGroupsCached.find(g => g.itcellRefNo === groupKey || g.workOrderRef === groupKey); + } if (!group) return; - activeDbBreakdownRef = refNo; - activeDbBreakdownPrinterName = printerName; + activeDbBreakdownKey = group.key; + activeDbBreakdownItcell = group.itcellRefNo; + activeDbBreakdownWo = group.workOrderRef; + activeDbBreakdownPrinterName = group.printerName; activeDbBreakdownPid = group.pid; - document.getElementById('db-breakdown-ref').textContent = refNo; - document.getElementById('db-breakdown-printer').textContent = printerName; + const refEl = document.getElementById('db-breakdown-ref'); + if (refEl) { + if (group.workOrderRef && group.workOrderRef !== '-') { + refEl.innerHTML = `${escapeHtml(group.itcellRefNo)} (WO Ref: ${escapeHtml(group.workOrderRef)})`; + } else { + refEl.innerHTML = `${escapeHtml(group.itcellRefNo)}`; + } + } + + const printerEl = document.getElementById('db-breakdown-printer'); + if (printerEl) { + printerEl.textContent = group.printerName; + } // Populate Toner Filter dropdown const selectEl = document.getElementById('db-breakdown-toner-filter'); @@ -3788,7 +4152,8 @@ } }); - switchDbBreakdownTab(tabName); + currentDbBreakdownTab = tabName || 'summary'; + switchDbBreakdownTab(currentDbBreakdownTab); const myModal = new bootstrap.Modal(document.getElementById('dashboardTonerBreakdownModal')); myModal.show(); @@ -3805,9 +4170,11 @@ tbody.innerHTML = ''; - const refNo = activeDbBreakdownRef; + const itcellRef = activeDbBreakdownItcell; + const woRef = activeDbBreakdownWo; const printerName = activeDbBreakdownPrinterName; const pid = activeDbBreakdownPid; + const selectedTid = document.getElementById('db-breakdown-toner-filter')?.value || ''; if (tabName === 'summary') { thead.innerHTML = ` @@ -3822,9 +4189,13 @@ `; - const group = dashboardGroupsCached.find(g => g.printerName === printerName && g.refNo === refNo); - const splits = group ? Object.values(group.tonerSplits || {}) : []; - splits.sort((a, b) => a.tonerModel.localeCompare(b.tonerModel)); + const group = dashboardGroupsCached.find(g => g.key === activeDbBreakdownKey) || + dashboardGroupsCached.find(g => g.itcellRefNo === itcellRef); + let splits = group ? Object.values(group.tonerSplits || {}) : []; + if (selectedTid) { + splits = splits.filter(s => String(s.tid) === String(selectedTid)); + } + splits.sort((a, b) => (a.tonerModel || '').localeCompare(b.tonerModel || '')); if (splits.length === 0) { tbody.innerHTML = 'No toner breakdown summary available.'; @@ -3833,12 +4204,24 @@ splits.forEach((split, index) => { const tr = document.createElement('tr'); - const available = Math.max(0, split.inwardQty - split.outwardQty); - let availableBadge = `${available}`; - if (available === 0) { - availableBadge = `Out of stock`; - } else if (available <= 5) { - availableBadge = `${available} (Low)`; + let availableBadge = ''; + if (split.workorderQty > 0 || split.inwardQty > 0) { + const available = Math.max(0, split.inwardQty - split.outwardQty); + if (available === 0) { + availableBadge = `0 (Out of stock)`; + } else if (available <= 5) { + availableBadge = `${available} (Low)`; + } else { + availableBadge = `${available}`; + } + } else { + if (split.outwardQty >= split.requestedQty && split.requestedQty > 0) { + availableBadge = `Delivered (${split.outwardQty})`; + } else if (split.outwardQty > 0) { + availableBadge = `Partial (${split.outwardQty}/${split.requestedQty})`; + } else { + availableBadge = `Pending (${split.requestedQty})`; + } } tr.innerHTML = ` @@ -3857,8 +4240,8 @@ thead.innerHTML = ` # - Ref No. - File No. + ITCell Ref No. + Work Order Ref (File No) Vendor Toner Model Registered Qty @@ -3868,10 +4251,13 @@ `; - const filtered = workorderHistoryCached.filter(item => - String(item.ref_no) === String(refNo) && - String(item.pid) === String(pid) - ); + const filtered = workorderHistoryCached.filter(item => { + if (String(item.pid) !== String(pid)) return false; + if (selectedTid && String(item.tid) !== String(selectedTid)) return false; + const matchItcell = String(item.ref_no) === String(itcellRef); + const matchWo = woRef && woRef !== '-' && String(item.file_number) === String(woRef); + return matchItcell || matchWo; + }); if (filtered.length === 0) { tbody.innerHTML = 'No matching workorder records found.'; @@ -3886,8 +4272,8 @@ tr.innerHTML = ` ${index + 1} - ${escapeHtml(item.ref_no || '')} - ${escapeHtml(item.file_number || '')} + ${escapeHtml(item.ref_no || '')} + ${escapeHtml(item.file_number || '-')} ${escapeHtml(item.vendor_name || '')} ${escapeHtml(item.model_name || 'N/A')} ${item.total_qty_registered || '0'} @@ -3898,59 +4284,12 @@ tbody.appendChild(tr); }); - } else if (tabName === 'request') { - thead.innerHTML = ` - - # - Ref No. - Vendor - Toner Model - Qty Requested - Qty Inward Received - Requested Date - Email Copy - - `; - - const filtered = workorderRequestHistoryCached.filter(item => - String(item.ref_no) === String(refNo) && - String(item.pid) === String(pid) - ); - - if (filtered.length === 0) { - tbody.innerHTML = 'No matching workorder request records found.'; - return; - } - - filtered.forEach((item, index) => { - const tr = document.createElement('tr'); - const qtyReq = parseInt(item.qty_requested || 0); - const qtyInward = parseInt(item.qty_registered || 0); - - const pdfBtn = item.email_copy_upload - ? ` View` - : ` N/A`; - - const vendorName = vendorsMap[item.vendor_id] || (!isNaN(item.vendor_id) ? 'Unknown (' + item.vendor_id + ')' : (item.vendor_id || '')); - - tr.innerHTML = ` - ${index + 1} - ${escapeHtml(item.ref_no || '')} - ${escapeHtml(vendorName)} - ${escapeHtml(item.model_name || 'N/A')} - ${qtyReq} - ${qtyInward} - ${formatTimestamp(item.cdate)} - ${pdfBtn} - `; - tbody.appendChild(tr); - }); - } else if (tabName === 'inward') { thead.innerHTML = ` # - Ref No. + ITCell Ref No. + Work Order Ref (File No) Vendor Toner Model Qty Registered @@ -3960,13 +4299,16 @@ `; - const filtered = inwardHistoryCached.filter(item => - String(item.ref_no) === String(refNo) && - String(item.pid) === String(pid) - ); + const filtered = inwardHistoryCached.filter(item => { + if (String(item.pid) !== String(pid)) return false; + if (selectedTid && String(item.tid) !== String(selectedTid)) return false; + const matchItcell = String(item.ref_no) === String(itcellRef); + const matchWo = woRef && woRef !== '-' && String(item.file_number) === String(woRef); + return matchItcell || matchWo; + }); if (filtered.length === 0) { - tbody.innerHTML = 'No matching inward records found.'; + tbody.innerHTML = 'No matching inward records found.'; return; } @@ -3980,10 +4322,11 @@ tr.innerHTML = ` ${index + 1} - ${escapeHtml(item.ref_no || '')} + ${escapeHtml(item.ref_no || '')} + ${escapeHtml(item.file_number || '-')} ${escapeHtml(vendorName)} ${escapeHtml(item.model_name || 'N/A')} - ${item.qty_registered || '0'} + ${item.qty_registered || '0'} ${formatTimestamp(item.cdate)} ${escapeHtml(item.order_by || '-')} ${pdfBtn} @@ -3995,6 +4338,7 @@ thead.innerHTML = ` # + Req. No IT Cell Ref No. Department Toner Model @@ -4006,13 +4350,16 @@ `; - const filtered = userRequestsCached.filter(item => - String(item.itcell_ref_no) === String(refNo) && - String(item.pid) === String(pid) - ); + const filtered = userRequestsCached.filter(item => { + if (String(item.pid) !== String(pid)) return false; + if (selectedTid && String(item.tid) !== String(selectedTid)) return false; + const matchItcell = String(item.itcell_ref_no) === String(itcellRef); + const matchWo = woRef && woRef !== '-' && String(item.itcell_ref_no) === String(woRef); + return matchItcell || matchWo; + }); if (filtered.length === 0) { - tbody.innerHTML = 'No matching user request records found.'; + tbody.innerHTML = 'No matching user request records found.'; return; } @@ -4024,13 +4371,14 @@ tr.innerHTML = ` ${index + 1} - ${escapeHtml(item.itcell_ref_no || '')} - ${escapeHtml(item.dept_name || 'N/A')} + ${escapeHtml(item.req_no || '-')} + ${escapeHtml(item.itcell_ref_no || '')} + ${escapeHtml(item.dept_name)} ${escapeHtml(item.model_name || 'N/A')} ${item.qty_requested || '0'} ${item.qty_delivered || '0'} ${formatTimestamp(item.cdate)} - ${escapeHtml(item.requested_by || '-')} + ${escapeHtml(item.updated_by || item.requested_by || '-')} ${pdfBtn} `; tbody.appendChild(tr); @@ -4040,6 +4388,7 @@ thead.innerHTML = ` # + Req. No IT Cell Ref No. Department Toner Model @@ -4050,51 +4399,59 @@ `; - const filtered = userRequestsCached.filter(item => - String(item.itcell_ref_no) === String(refNo) && - String(item.pid) === String(pid) && - parseInt(item.qty_delivered || 0) > 0 - ); + const filtered = userRequestsCached.filter(item => { + if (String(item.pid) !== String(pid)) return false; + if (selectedTid && String(item.tid) !== String(selectedTid)) return false; + if (parseInt(item.qty_delivered || 0) <= 0) return false; + const matchItcell = String(item.itcell_ref_no) === String(itcellRef); + const matchWo = woRef && woRef !== '-' && String(item.itcell_ref_no) === String(woRef); + return matchItcell || matchWo; + }); if (filtered.length === 0) { - tbody.innerHTML = 'No matching outward register records found.'; + tbody.innerHTML = 'No matching outward register records found.'; return; } filtered.forEach((item, index) => { const tr = document.createElement('tr'); + let userReqUrl = item.final_delivered_upload ? (item.final_delivered_upload.startsWith('.') ? item.final_delivered_upload.substring(1) : item.final_delivered_upload) : ''; const pdfBtn = item.final_delivered_upload - ? ` View Receipt` + ? ` View Receipt` : ` N/A`; + let userReqPhotoUrl = item.receiver_photo ? (item.receiver_photo.startsWith('.') ? item.receiver_photo.substring(1) : item.receiver_photo) : ''; + const photoBtn = item.receiver_photo + ? ` View Photo` + : ''; tr.innerHTML = ` ${index + 1} - ${escapeHtml(item.itcell_ref_no || '')} + ${escapeHtml(item.req_no || '-')} + ${escapeHtml(item.itcell_ref_no || '')} ${escapeHtml(item.dept_name || 'N/A')} ${escapeHtml(item.model_name || 'N/A')} ${item.qty_delivered || '0'} ${formatTimestamp(item.updated_date || item.cdate)} ${escapeHtml(item.updated_by || '-')} - ${pdfBtn} + ${pdfBtn}${photoBtn} `; tbody.appendChild(tr); }); } } - // ================= REPORT VIEW API & FILTER LOGIC ================= function loadReportPage() { + if (printersCached && printersCached.length > 0) { + populateReportPrinterFilters(); + } else { + fetchPrinters(); + } + fetch(contextPath + 'gcctonermanagement/api/report') .then(res => res.json()) .then(data => { if (data.success) { - // Populate metric cards - document.getElementById('stats-inward').textContent = data.metrics.inwardStock || 0; - document.getElementById('stats-distributed').textContent = data.metrics.totalDistributed || 0; - document.getElementById('stats-stock').textContent = data.metrics.availableStock || 0; - document.getElementById('stats-workorder-registered').textContent = data.metrics.workorderRegistered || 0; - document.getElementById('stats-workorder-requested').textContent = data.metrics.workorderRequested || 0; stockSummaryCached = data.summary || []; applyReportFilters(); } @@ -4107,6 +4464,7 @@ .then(data => { userRequestsCached = data || []; renderUserRequestsTable('user-requests-table-body', userRequestsCached, true); + populateReportDepartmentFilters(); applyReportFilters(); }) .catch(err => console.error("Error fetching user requests for report:", err)); @@ -4124,33 +4482,218 @@ .then(res => res.json()) .then(data => { outwardHistoryCached = data || []; + populateReportDepartmentFilters(); applyReportFilters(); }) .catch(err => console.error("Error fetching outward history for report:", err)); + fetchDepartmentsList(); fetchWorkorderHistory(); fetchWorkorderRequestHistory(); } - function onReportTypeChange() { - const reportType = document.getElementById('report-type-select').value; - const statusWrapper = document.getElementById('report-status-filter-wrapper'); - if (statusWrapper) { - statusWrapper.style.display = (reportType === 'user_requests') ? 'block' : 'none'; - } - applyReportFilters(); - } + function populateReportFileNoFilters() { + const select = document.getElementById('report-fileno-filter'); + if (!select) return; + + const currentVal = select.value; + select.innerHTML = ''; + + const fileSet = new Set(); + if (workorderHistoryCached && workorderHistoryCached.length > 0) { + workorderHistoryCached.forEach(w => { + if (w.file_number && String(w.file_number).trim()) { + fileSet.add(String(w.file_number).trim()); + } + }); + } + + const fileNos = Array.from(fileSet); + fileNos.sort((a, b) => a.localeCompare(b)); + + fileNos.forEach(fn => { + const opt = document.createElement('option'); + opt.value = fn; + opt.textContent = fn; + select.appendChild(opt); + }); + + if (currentVal && fileNos.includes(currentVal)) { + select.value = currentVal; + } + } + + function populateReportDepartmentFilters() { + const select = document.getElementById('report-dept-filter'); + if (!select) return; + + const currentVal = select.value; + select.innerHTML = ''; + + const deptSet = new Set(); + + if (departmentsCached && departmentsCached.length > 0) { + departmentsCached.forEach(d => { + if (d.dept_name) deptSet.add(d.dept_name.trim()); + }); + } + + if (userRequestsCached && userRequestsCached.length > 0) { + userRequestsCached.forEach(r => { + if (r.dept_name) deptSet.add(r.dept_name.trim()); + else if (r.dept_id) deptSet.add(r.dept_id.trim()); + }); + } + + if (outwardHistoryCached && outwardHistoryCached.length > 0) { + outwardHistoryCached.forEach(o => { + if (o.dept_name) deptSet.add(o.dept_name.trim()); + else if (o.dept_id) deptSet.add(o.dept_id.trim()); + }); + } + + const depts = Array.from(deptSet).filter(Boolean); + depts.sort((a, b) => a.localeCompare(b)); + + depts.forEach(d => { + const opt = document.createElement('option'); + opt.value = d; + opt.textContent = d; + select.appendChild(opt); + }); + + if (currentVal && depts.includes(currentVal)) { + select.value = currentVal; + } + } + + function onReportPeriodPresetChange() { + const val = document.getElementById('report-period-preset')?.value || 'all'; + const fromWrapper = document.getElementById('report-from-date-wrapper'); + const toWrapper = document.getElementById('report-to-date-wrapper'); + if (val === 'custom') { + if (fromWrapper) fromWrapper.style.display = 'block'; + if (toWrapper) toWrapper.style.display = 'block'; + } else { + if (fromWrapper) fromWrapper.style.display = 'none'; + if (toWrapper) toWrapper.style.display = 'none'; + const fromInput = document.getElementById('report-from-date'); + const toInput = document.getElementById('report-to-date'); + if (fromInput) fromInput.value = ''; + if (toInput) toInput.value = ''; + } + applyReportFilters(1); + } + + function matchesReportPeriod(dateString) { + const presetSelect = document.getElementById('report-period-preset'); + const preset = presetSelect ? (presetSelect.value || 'all') : 'all'; + if (preset === 'all') return true; + if (!dateString) return false; + + let itemDate = null; + if (typeof dateString === 'number') { + itemDate = new Date(dateString); + } else if (typeof dateString === 'string') { + const str = dateString.trim(); + if (/^\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}/.test(str)) { + const parts = str.split(/[\/\-\s:]+/); + if (parts.length >= 3) { + const day = parseInt(parts[0], 10); + const month = parseInt(parts[1], 10) - 1; + const year = parseInt(parts[2], 10); + const hours = parts[3] ? parseInt(parts[3], 10) : 0; + const minutes = parts[4] ? parseInt(parts[4], 10) : 0; + itemDate = new Date(year, month, day, hours, minutes); + } + } else if (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(str)) { + const cleanStr = str.replace(' ', 'T'); + itemDate = new Date(cleanStr); + if (isNaN(itemDate.getTime())) { + itemDate = new Date(str); + } + } else { + itemDate = new Date(str); + } + } else if (dateString instanceof Date) { + itemDate = dateString; + } + + if (!itemDate || isNaN(itemDate.getTime())) return true; + + const now = new Date(); + const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + + if (preset === 'today') { + return itemDate >= startOfToday; + } else if (preset === 'week') { + const dayOfWeek = startOfToday.getDay(); + const startOfWeek = new Date(startOfToday); + startOfWeek.setDate(startOfToday.getDate() - dayOfWeek); + return itemDate >= startOfWeek; + } else if (preset === 'month') { + const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + return itemDate >= startOfMonth; + } else if (preset === 'last30') { + const thirtyDaysAgo = new Date(startOfToday); + thirtyDaysAgo.setDate(startOfToday.getDate() - 30); + return itemDate >= thirtyDaysAgo; + } else if (preset === 'year') { + const startOfYear = new Date(now.getFullYear(), 0, 1); + return itemDate >= startOfYear; + } else if (preset === 'custom') { + const fromVal = document.getElementById('report-from-date')?.value; + const toVal = document.getElementById('report-to-date')?.value; + + const filterStart = fromVal ? new Date(fromVal) : null; + const filterEnd = toVal ? new Date(toVal) : null; + if (filterStart) { + filterStart.setHours(0, 0, 0, 0); + } + if (filterEnd) { + filterEnd.setHours(23, 59, 59, 999); + } + + if (filterStart && itemDate < filterStart) return false; + if (filterEnd && itemDate > filterEnd) return false; + return true; + } + return true; + } + + function onReportTypeChange() { + const reportType = document.getElementById('report-type-select').value; + const statusWrapper = document.getElementById('report-status-filter-wrapper'); + const deptWrapper = document.getElementById('report-dept-filter-wrapper'); + const filenoWrapper = document.getElementById('report-fileno-filter-wrapper'); + + if (statusWrapper) { + statusWrapper.style.display = (reportType === 'user_requests') ? 'block' : 'none'; + } + if (deptWrapper) { + deptWrapper.style.display = (reportType === 'user_requests' || reportType === 'outward_history') ? 'block' : 'none'; + } + if (filenoWrapper) { + filenoWrapper.style.display = (reportType === 'workorder_register') ? 'block' : 'none'; + if (reportType === 'workorder_register') { + populateReportFileNoFilters(); + } + } + applyReportFilters(1); + } function onReportPrinterFilterChange() { - const pid = document.getElementById('report-printer-filter').value; + const pid = document.getElementById('report-printer-filter')?.value || ''; const tablePrinter = document.getElementById('table-printer-filter'); if (tablePrinter) tablePrinter.value = pid; const tonerSelect = document.getElementById('report-toner-filter'); const tableTonerSelect = document.getElementById('table-toner-filter'); - tonerSelect.innerHTML = ''; - tonerSelect.disabled = true; + if (tonerSelect) { + tonerSelect.innerHTML = ''; + tonerSelect.disabled = true; + } if (tableTonerSelect) { tableTonerSelect.innerHTML = ''; @@ -4161,11 +4704,13 @@ fetch(contextPath + 'gcctonermanagement/api/toners?pid=' + pid) .then(res => res.json()) .then(toners => { - toners.forEach(t => { - const opt = document.createElement('option'); - opt.value = t.id; - opt.textContent = t.model_name; - tonerSelect.appendChild(opt); + (toners || []).forEach(t => { + if (tonerSelect) { + const opt = document.createElement('option'); + opt.value = t.id; + opt.textContent = t.model_name; + tonerSelect.appendChild(opt); + } if (tableTonerSelect) { const opt2 = document.createElement('option'); @@ -4174,26 +4719,65 @@ tableTonerSelect.appendChild(opt2); } }); - if (toners.length > 0) { - tonerSelect.disabled = false; + if (toners && toners.length > 0) { + if (tonerSelect) tonerSelect.disabled = false; if (tableTonerSelect) tableTonerSelect.disabled = false; } - applyReportFilters(); + applyReportFilters(1); }) .catch(err => { console.error("Error loading toners filter:", err); - applyReportFilters(); + applyReportFilters(1); }); } else { - applyReportFilters(); + applyReportFilters(1); } } - function updateDynamicMetrics(selectedPrinter, selectedToner, selectedStatus, searchQuery) { - // Filter Inward stock items matching selected Printer & Toner - let filteredInward = inwardHistoryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + function onReportTonerFilterChange() { + const tid = document.getElementById('report-toner-filter')?.value || ''; + const tableToner = document.getElementById('table-toner-filter'); + if (tableToner) tableToner.value = tid; + applyReportFilters(1); + } + + function updateDynamicMetrics(selectedPrinter, selectedToner, selectedDept, selectedStatus, searchQuery, selectedFileNo) { + if (!selectedFileNo) { + selectedFileNo = (document.getElementById('report-fileno-filter')?.value || '').trim(); + } + const matchPrinter = (item) => { + if (!selectedPrinter) return true; + const pid = item.pid !== undefined && item.pid !== null ? item.pid : item.printer_id; + return pid !== undefined && pid !== null && String(pid) === String(selectedPrinter); + }; + + const matchToner = (item) => { + if (!selectedToner) return true; + const tid = item.tid !== undefined && item.tid !== null ? item.tid : item.toner_id; + return tid !== undefined && tid !== null && String(tid) === String(selectedToner); + }; + + // 1. Filter Workorders matching selected Printer, Toner, File No & Period + const filteredWorkorders = (workorderHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (selectedFileNo) { + const itemFileNo = (item.file_number || '').trim().toLowerCase(); + if (itemFileNo !== selectedFileNo.toLowerCase()) return false; + } + if (!matchesReportPeriod(item.workorder_start_date || item.cdate)) return false; + if (searchQuery) { + const searchStr = `${item.ref_no || ''} ${item.file_number || ''} ${item.vendor_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); + if (!searchStr.includes(searchQuery)) return false; + } + return true; + }); + + // 2. Filter Inward stock items matching selected Printer, Toner & Period + const filteredInward = (inwardHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (!matchesReportPeriod(item.cdate)) return false; if (searchQuery) { const vendorName = vendorsMap[item.company_name] || (!isNaN(item.company_name) ? '' : (item.company_name || '')); const searchStr = `${vendorName} ${item.printer_name || ''} ${item.model_name || ''} ${item.order_by || ''}`.toLowerCase(); @@ -4202,39 +4786,54 @@ return true; }); - // Filter User Requests items matching selected filters - let filteredRequests = userRequestsCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; - - const reqQty = parseInt(item.qty_requested || 0); - const delQty = parseInt(item.qty_delivered || 0); - let status = 'Pending'; - if (delQty >= reqQty && reqQty > 0) status = 'Delivered'; - else if (delQty > 0) status = 'Partial'; - - if (selectedStatus && status !== selectedStatus) return false; + // 3. Filter User Requests items matching selected filters, department & period + const filteredRequests = (userRequestsCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (selectedDept) { + const dName = (item.dept_name).trim().toLowerCase(); + if (dName !== selectedDept.toLowerCase()) return false; + } + if (!matchesReportPeriod(item.cdate)) return false; if (searchQuery) { - const searchStr = `${item.req_no || ''} ${item.itcell_ref_no || ''} ${item.dept_id || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); + const searchStr = `${item.req_no || ''} ${item.itcell_ref_no || ''} ${item.dept_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; } return true; }); // Calculate totals - const totalInward = filteredInward.reduce((sum, item) => sum + (parseInt(item.qty_registered) || 0), 0); - const totalRaised = filteredRequests.reduce((sum, item) => sum + (parseInt(item.qty_requested) || 0), 0); - const totalDistributed = filteredRequests.reduce((sum, item) => sum + (parseInt(item.qty_delivered) || 0), 0); - - // Available stock = total inward stock minus total distributed - const availableStock = Math.max(0, totalInward - totalDistributed); + const totalWorkorder = filteredWorkorders.reduce((sum, item) => sum + (parseInt(item.total_qty_registered || item.qty_registered || 0) || 0), 0); + const totalInward = filteredInward.reduce((sum, item) => sum + (parseInt(item.qty_registered || item.inward_qty || 0) || 0), 0); + const totalDistributed = filteredRequests.reduce((sum, item) => sum + (parseInt(item.qty_delivered || item.outward_qty || 0) || 0), 0); + + // Available stock calculation + let availableStock = 0; + if (stockSummaryCached && stockSummaryCached.length > 0 && (!selectedDept || selectedDept === '')) { + const matchedStock = stockSummaryCached.filter(item => matchPrinter(item) && matchToner(item)); + if (matchedStock.length > 0) { + availableStock = Math.max(0, matchedStock.reduce((sum, item) => sum + Math.max(0, parseInt(item.available_qty || 0)), 0)); + } else { + availableStock = Math.max(0, totalInward - totalDistributed); + } + } else { + const allDeliveredForPrinterToner = (userRequestsCached || []) + .filter(item => matchPrinter(item) && matchToner(item)) + .reduce((sum, item) => sum + (parseInt(item.qty_delivered || 0) || 0), 0); + const allInwardForPrinterToner = (inwardHistoryCached || []) + .filter(item => matchPrinter(item) && matchToner(item)) + .reduce((sum, item) => sum + (parseInt(item.qty_registered || 0) || 0), 0); + availableStock = Math.max(0, allInwardForPrinterToner - allDeliveredForPrinterToner); + } // Update DOM metrics + const woEl = document.getElementById('stats-workorder-registered'); const inwardEl = document.getElementById('stats-inward'); const distributedEl = document.getElementById('stats-distributed'); const stockEl = document.getElementById('stats-stock'); + if (woEl) woEl.textContent = totalWorkorder; if (inwardEl) inwardEl.textContent = totalInward; if (distributedEl) distributedEl.textContent = totalDistributed; if (stockEl) stockEl.textContent = availableStock; @@ -4277,10 +4876,34 @@ tableTonerSelect.disabled = true; } + const deptSelect = document.getElementById('report-dept-filter'); + if (deptSelect) deptSelect.value = ''; + + const filenoSelect = document.getElementById('report-fileno-filter'); + if (filenoSelect) filenoSelect.value = ''; + const filenoWrapper = document.getElementById('report-fileno-filter-wrapper'); + if (filenoWrapper) filenoWrapper.style.display = 'none'; + + const periodSelect = document.getElementById('report-period-preset'); + if (periodSelect) periodSelect.value = 'all'; + + const fromInput = document.getElementById('report-from-date'); + if (fromInput) fromInput.value = ''; + const toInput = document.getElementById('report-to-date'); + if (toInput) toInput.value = ''; + + const fromWrapper = document.getElementById('report-from-date-wrapper'); + if (fromWrapper) fromWrapper.style.display = 'none'; + const toWrapper = document.getElementById('report-to-date-wrapper'); + if (toWrapper) toWrapper.style.display = 'none'; + document.getElementById('report-status-filter').value = ''; const statusWrapper = document.getElementById('report-status-filter-wrapper'); if (statusWrapper) statusWrapper.style.display = 'block'; + const deptWrapper = document.getElementById('report-dept-filter-wrapper'); + if (deptWrapper) deptWrapper.style.display = 'block'; + document.getElementById('report-search-input').value = ''; const tableSearch = document.getElementById('table-quick-search'); if (tableSearch) tableSearch.value = ''; @@ -4396,11 +5019,13 @@ const reportType = document.getElementById('report-type-select').value; const selectedPrinter = document.getElementById('report-printer-filter').value; const selectedToner = document.getElementById('report-toner-filter').value; + const selectedDept = (document.getElementById('report-dept-filter')?.value || '').trim(); + const selectedFileNo = (document.getElementById('report-fileno-filter')?.value || '').trim(); const selectedStatus = document.getElementById('report-status-filter').value; const searchQuery = document.getElementById('report-search-input').value.trim().toLowerCase(); - // Calculate & update dynamic metrics based on selected filters - updateDynamicMetrics(selectedPrinter, selectedToner, selectedStatus, searchQuery); + // Calculate & update dynamic metrics based on selected filters, department & period + updateDynamicMetrics(selectedPrinter, selectedToner, selectedDept, selectedStatus, searchQuery, selectedFileNo); const titleEl = document.getElementById('report-dynamic-title'); const badgeEl = document.getElementById('report-count-badge'); @@ -4409,6 +5034,18 @@ if (!titleEl || !thead || !tbody) return; + const matchPrinter = (item) => { + if (!selectedPrinter) return true; + const pid = item.pid !== undefined && item.pid !== null ? item.pid : item.printer_id; + return pid !== undefined && pid !== null && String(pid) === String(selectedPrinter); + }; + + const matchToner = (item) => { + if (!selectedToner) return true; + const tid = item.tid !== undefined && item.tid !== null ? item.tid : item.toner_id; + return tid !== undefined && tid !== null && String(tid) === String(selectedToner); + }; + // 1. User Request Details Report if (reportType === 'user_requests') { titleEl.innerHTML = `User Request Details Report`; @@ -4427,9 +5064,14 @@ Request Doc `; - let filtered = userRequestsCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (userRequestsCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (selectedDept) { + const dName = (item.dept_name).trim().toLowerCase(); + if (dName !== selectedDept.toLowerCase()) return false; + } + if (!matchesReportPeriod(item.cdate)) return false; const reqQty = parseInt(item.qty_requested || 0); const delQty = parseInt(item.qty_delivered || 0); @@ -4440,7 +5082,7 @@ if (selectedStatus && status !== selectedStatus) return false; if (searchQuery) { - const searchStr = `${item.req_no || ''} ${item.itcell_ref_no || ''} ${item.dept_id || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); + const searchStr = `${item.req_no || ''} ${item.itcell_ref_no || ''} ${item.dept_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; } return true; @@ -4463,7 +5105,6 @@ const pagedData = filtered.slice((reportCurrentPage - 1) * REPORT_PAGE_SIZE, reportCurrentPage * REPORT_PAGE_SIZE); pagedData.forEach((item, idx) => { - //console.log(item); const tr = document.createElement('tr'); const globalIdx = (reportCurrentPage - 1) * REPORT_PAGE_SIZE + idx + 1; const createdDate = formatTimestamp(item.cdate); @@ -4486,7 +5127,7 @@ ${globalIdx} ${escapeHtml(item.req_no)} ${escapeHtml(item.itcell_ref_no || '-')} - ${escapeHtml(item.dept_id)} + ${escapeHtml(item.dept_name || '-')} ${escapeHtml(item.printer_name || '-')} ${escapeHtml(item.model_name || '-')} ${item.qty_requested} @@ -4517,9 +5158,14 @@ DC Copy `; - let filtered = workorderHistoryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (workorderHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (selectedFileNo) { + const itemFileNo = (item.file_number || '').trim().toLowerCase(); + if (itemFileNo !== selectedFileNo.toLowerCase()) return false; + } + if (!matchesReportPeriod(item.workorder_start_date || item.cdate)) return false; if (searchQuery) { const searchStr = `${item.ref_no || ''} ${item.file_number || ''} ${item.vendor_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; @@ -4581,9 +5227,10 @@ Email File `; - let filtered = workorderRequestHistoryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (workorderRequestHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (!matchesReportPeriod(item.cdate)) return false; if (searchQuery) { const vendorName = vendorsMap[item.vendor_id] || (!isNaN(item.vendor_id) ? '' : (item.vendor_id || '')); const searchStr = `${item.ref_no || ''} ${vendorName} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); @@ -4637,6 +5284,8 @@ thead.innerHTML = ` S.No + Workorder Ref + File No. Company Name Printer Model Toner Model @@ -4646,12 +5295,13 @@ DC Document `; - let filtered = inwardHistoryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (inwardHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (!matchesReportPeriod(item.cdate)) return false; if (searchQuery) { const vendorName = vendorsMap[item.company_name] || (!isNaN(item.company_name) ? '' : (item.company_name || '')); - const searchStr = `${vendorName} ${item.printer_name || ''} ${item.model_name || ''} ${item.order_by || ''}`.toLowerCase(); + const searchStr = `${item.ref_no || ''} ${item.file_number || ''} ${vendorName} ${item.printer_name || ''} ${item.model_name || ''} ${item.order_by || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; } return true; @@ -4666,7 +5316,7 @@ reportCurrentPage = Math.min(Math.max(1, reportCurrentPage), totalPages); if (filtered.length === 0) { - tbody.innerHTML = `No matching inward stock records found.`; + tbody.innerHTML = `No matching inward stock records found.`; renderReportPaginationNav(1, 0); return; } @@ -4685,6 +5335,8 @@ const vendorName = vendorsMap[item.company_name] || (!isNaN(item.company_name) ? 'Unknown Vendor (' + item.company_name + ')' : (item.company_name || '')); tr.innerHTML = ` ${globalIdx} + ${escapeHtml(item.ref_no || '-')} + ${escapeHtml(item.file_number || '-')} ${escapeHtml(vendorName)} ${escapeHtml(item.printer_name || '-')} ${escapeHtml(item.model_name || '-')} @@ -4711,9 +5363,9 @@ Available Stock `; - let filtered = stockSummaryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (stockSummaryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; if (searchQuery) { const searchStr = `${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; @@ -4740,13 +5392,14 @@ pagedData.forEach((item, idx) => { const tr = document.createElement('tr'); const globalIdx = (reportCurrentPage - 1) * REPORT_PAGE_SIZE + idx + 1; + const avail = Math.max(0, parseInt(item.available_qty || 0)); tr.innerHTML = ` ${globalIdx} ${escapeHtml(item.printer_name)} ${escapeHtml(item.model_name)} ${item.inward_qty} ${item.outward_qty} - ${item.available_qty} + ${avail} `; tbody.appendChild(tr); }); @@ -4770,11 +5423,16 @@ Action `; - let filtered = outwardHistoryCached.filter(item => { - if (selectedPrinter && String(item.pid) !== String(selectedPrinter)) return false; - if (selectedToner && String(item.tid) !== String(selectedToner)) return false; + let filtered = (outwardHistoryCached || []).filter(item => { + if (!matchPrinter(item)) return false; + if (!matchToner(item)) return false; + if (selectedDept) { + const dName = (item.dept_name).trim().toLowerCase(); + if (dName !== selectedDept.toLowerCase()) return false; + } + if (!matchesReportPeriod(item.updated_date || item.cdate)) return false; if (searchQuery) { - const searchStr = `${item.req_no || ''} ${item.dept_id || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); + const searchStr = `${item.req_no || ''} ${item.dept_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); if (!searchStr.includes(searchQuery)) return false; } return true; @@ -4811,19 +5469,29 @@ return ` ${label}`; }).join(' '); } + let photoBtn = ''; + if (item.receiver_photo) { + const photos = item.receiver_photo.split(','); + photoBtn = photos.map((p, pIdx) => { + const trimmed = p.trim(); + const url = trimmed.startsWith('.') ? trimmed.substring(1) : trimmed; + const label = photos.length > 1 ? `View Photo ${pIdx + 1}` : `View Photo`; + return ` ${label}`; + }).join(' '); + } const pdfBtn = ``; tr.innerHTML = ` ${globalIdx} ${escapeHtml(item.req_no)} - ${escapeHtml(item.dept_id)} - ${escapeHtml(item.printer_name)} - ${escapeHtml(item.model_name)} + ${escapeHtml(item.dept_name)} + ${escapeHtml(item.printer_name || '-')} + ${escapeHtml(item.model_name || '-')} ${item.qty_requested} - ${item.qty_delivered} + ${item.qty_delivered || 0} ${reqDateFormatted} - ${dateFormatted} - ${docBtn}${pdfBtn} + ${dateFormatted} + ${docBtn}${photoBtn}${pdfBtn} `; tbody.appendChild(tr); }); @@ -4980,10 +5648,10 @@ Sr. No. ${getInwardSortHeader('Workorder Ref', 'ref_no')} + ${getInwardSortHeader('File No.', 'file_number')} ${getInwardSortHeader('Company Name', 'company_name')} ${getInwardSortHeader('Printer Model', 'printer_name')} ${getInwardSortHeader('Qty Registered', 'qty_workorder_registered', 'text-center')} - ${getInwardSortHeader('Qty Requested', 'qty_workorder_requested', 'text-center')} ${getInwardSortHeader('Qty Inward', 'qty_registered', 'text-center')} ${getInwardSortHeader('Created Date', 'cdate')} ${getInwardSortHeader('Entered By', 'order_by')} @@ -4999,19 +5667,21 @@ if (!groups[key]) { groups[key] = { ref_no: cleanRef, + file_number: item.file_number || '', company_name: item.company_name, pid: item.pid, printer_name: item.printer_name || 'N/A', qty_workorder_registered: 0, - qty_workorder_requested: 0, qty_registered: 0, cdate: item.cdate, order_by: item.order_by, dc_uploads: [] }; } + if (item.file_number && !groups[key].file_number) { + groups[key].file_number = item.file_number; + } groups[key].qty_workorder_registered += parseInt(item.qty_workorder_registered || 0); - groups[key].qty_workorder_requested += parseInt(item.qty_workorder_requested || 0); groups[key].qty_registered += parseInt(item.qty_registered || 0); if (item.cdate && (!groups[key].cdate || new Date(item.cdate) > new Date(groups[key].cdate))) { @@ -5039,7 +5709,7 @@ let filtered = groupedList.filter(item => { if (!inwardSearchQuery) return true; const vendorName = vendorsMap[item.company_name] || (!isNaN(item.company_name) ? '' : (item.company_name || '')); - const searchStr = `${item.ref_no || ''} ${vendorName} ${item.printer_name || ''} ${item.order_by || ''}`.toLowerCase(); + const searchStr = `${item.ref_no || ''} ${item.file_number || ''} ${vendorName} ${item.printer_name || ''} ${item.order_by || ''}`.toLowerCase(); return searchStr.includes(inwardSearchQuery); }); @@ -5047,7 +5717,7 @@ filtered.sort((a, b) => { let valA = a[inwardSortColumn]; let valB = b[inwardSortColumn]; - if (inwardSortColumn === 'qty_registered' || inwardSortColumn === 'qty_workorder_registered' || inwardSortColumn === 'qty_workorder_requested') { + if (inwardSortColumn === 'qty_registered' || inwardSortColumn === 'qty_workorder_registered') { valA = parseInt(valA || 0); valB = parseInt(valB || 0); } else if (inwardSortColumn === 'cdate') { @@ -5099,17 +5769,13 @@ const vendorName = vendorsMap[item.company_name] || (!isNaN(item.company_name) ? 'Unknown Vendor (' + item.company_name + ')' : (item.company_name || '')); const refLink = `${escapeHtml(item.ref_no || '')}`; - const reqButton = ``; - let rowHtml = ` ${globalIdx} ${refLink} + ${escapeHtml(item.file_number || '-')} ${escapeHtml(vendorName)} ${escapeHtml(item.printer_name || '-')} ${item.qty_workorder_registered || 0} - ${reqButton} ${item.qty_registered} ${createdDate} ${escapeHtml(item.order_by || '-')} @@ -5128,6 +5794,11 @@ let userReqPageSize = 20; let userReqCurrentPage = 1; let userReqSearchQuery = ''; + let userReqNoFilter = ''; + let userReqDeptFilter = ''; + let userReqZoneFilter = ''; + let userReqPrinterFilter = ''; + let userReqMonthFilter = ''; let userReqSortColumn = ''; let userReqSortDirection = 'asc'; let activeUserReqStatusFilter = 'all'; @@ -5158,6 +5829,203 @@ renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); } + function onUserReqNoFilterChange(val) { + userReqNoFilter = (val || '').trim(); + renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); + } + + function onUserReqDeptFilterChange(val) { + userReqDeptFilter = (val || '').trim(); + renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); + } + + function onUserReqZoneFilterChange(val) { + userReqZoneFilter = (val || '').trim(); + renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); + } + + function onUserReqPrinterFilterChange(val) { + userReqPrinterFilter = (val || '').trim(); + renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); + } + + function onUserReqMonthFilterChange(val) { + userReqMonthFilter = (val || '').trim(); + renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); + } + + function getUserReqYearMonth(ts) { + if (!ts) return ''; + try { + if (typeof ts === 'string') { + const isoMatch = ts.match(/^(\d{4})-(\d{2})/); + if (isoMatch) return `${isoMatch[1]}-${isoMatch[2]}`; + const slashMatch = ts.match(/^(\d{2})\/(\d{2})\/(\d{4})/); + if (slashMatch) return `${slashMatch[3]}-${slashMatch[2]}`; + } + const d = new Date(ts); + if (!isNaN(d.getTime())) { + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, '0'); + return `${yyyy}-${mm}`; + } + } catch (e) { } + return ''; + } + + const USER_REQ_MONTH_NAMES = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + + function getUserReqYearMonthLabel(ym) { + if (!ym || !ym.includes('-')) return ym; + const parts = ym.split('-'); + const year = parts[0]; + const monthIndex = parseInt(parts[1], 10) - 1; + if (monthIndex >= 0 && monthIndex < 12) { + return `${USER_REQ_MONTH_NAMES[monthIndex]} ${year}`; + } + return ym; + } + + function populateUserReqFilters(data) { + // Populate Request No Filter Dropdown + const reqNoSelect = document.getElementById('user-req-no-filter'); + if (reqNoSelect) { + const currentReqNoVal = reqNoSelect.value; + const reqNos = new Set(); + (data || []).forEach(item => { + if (item.req_no && String(item.req_no).trim()) { + reqNos.add(String(item.req_no).trim()); + } + }); + + let reqNoOptions = ''; + Array.from(reqNos).sort((a, b) => a.localeCompare(b)).forEach(rn => { + reqNoOptions += ``; + }); + reqNoSelect.innerHTML = reqNoOptions; + if (currentReqNoVal && reqNos.has(currentReqNoVal)) { + reqNoSelect.value = currentReqNoVal; + } else if (!reqNos.has(currentReqNoVal)) { + userReqNoFilter = ''; + } + } + + // Populate Department Filter Dropdown + const deptSelect = document.getElementById('user-req-dept-filter'); + if (deptSelect) { + const currentDeptVal = deptSelect.value; + const depts = new Set(); + if (Array.isArray(departmentsCached)) { + departmentsCached.forEach(d => { + if (d && d.dept_name) depts.add(d.dept_name.trim()); + }); + } + (data || []).forEach(item => { + if (item.dept_name && String(item.dept_name).trim()) { + depts.add(String(item.dept_name).trim()); + } else if (item.dept_id && String(item.dept_id).trim()) { + depts.add(String(item.dept_id).trim()); + } + }); + + let deptOptions = ''; + Array.from(depts).sort((a, b) => a.localeCompare(b)).forEach(dept => { + deptOptions += ``; + }); + deptSelect.innerHTML = deptOptions; + if (currentDeptVal && depts.has(currentDeptVal)) { + deptSelect.value = currentDeptVal; + } else if (!depts.has(currentDeptVal)) { + userReqDeptFilter = ''; + } + } + + // Populate Zone Filter Dropdown + const zoneSelect = document.getElementById('user-req-zone-filter'); + if (zoneSelect) { + const currentZoneVal = zoneSelect.value; + const zones = new Set(); + if (Array.isArray(zonesCached)) { + zonesCached.forEach(z => { + if (z && z.zone_name) zones.add(z.zone_name.trim()); + }); + } + (data || []).forEach(item => { + if (item.zone_name && String(item.zone_name).trim() && item.zone_name !== 'N/A' && item.zone_name !== '-') { + zones.add(String(item.zone_name).trim()); + } + }); + + let zoneOptions = ''; + Array.from(zones).sort((a, b) => a.localeCompare(b)).forEach(z => { + zoneOptions += ``; + }); + zoneSelect.innerHTML = zoneOptions; + if (currentZoneVal && zones.has(currentZoneVal)) { + zoneSelect.value = currentZoneVal; + } else if (!zones.has(currentZoneVal)) { + userReqZoneFilter = ''; + } + } + + // Populate Printer Model Filter Dropdown + const printerSelect = document.getElementById('user-req-printer-filter'); + if (printerSelect) { + const currentPrinterVal = printerSelect.value; + const printersMap = new Map(); // pid -> printer_name + if (Array.isArray(printersCached)) { + printersCached.forEach(p => { + if (p && p.id) printersMap.set(String(p.id), p.printer_name || `Printer #${p.id}`); + }); + } + (data || []).forEach(item => { + if (item.pid && !printersMap.has(String(item.pid))) { + printersMap.set(String(item.pid), item.printer_name || `Printer #${item.pid}`); + } + }); + + let printerOptions = ''; + Array.from(printersMap.entries()) + .sort((a, b) => a[1].localeCompare(b[1])) + .forEach(([pid, pname]) => { + printerOptions += ``; + }); + printerSelect.innerHTML = printerOptions; + if (currentPrinterVal && printersMap.has(String(currentPrinterVal))) { + printerSelect.value = currentPrinterVal; + } else if (!printersMap.has(String(currentPrinterVal))) { + userReqPrinterFilter = ''; + } + } + + // Populate Month Filter Dropdown + const monthSelect = document.getElementById('user-req-month-filter'); + if (monthSelect) { + const currentMonthVal = monthSelect.value; + const yms = new Set(); + (data || []).forEach(item => { + const ym = getUserReqYearMonth(item.cdate); + if (ym) yms.add(ym); + }); + + const sortedYms = Array.from(yms).sort().reverse(); + let monthOptions = ''; + sortedYms.forEach(ym => { + const label = getUserReqYearMonthLabel(ym); + monthOptions += ``; + }); + monthSelect.innerHTML = monthOptions; + if (currentMonthVal && yms.has(currentMonthVal)) { + monthSelect.value = currentMonthVal; + } else if (!yms.has(currentMonthVal)) { + userReqMonthFilter = ''; + } + } + } + function toggleUserReqSort(columnKey) { if (userReqSortColumn === columnKey) { userReqSortDirection = (userReqSortDirection === 'asc') ? 'desc' : 'asc'; @@ -5183,12 +6051,13 @@ .then(res => res.json()) .then(data => { userRequestsCached = data || []; + populateUserReqFilters(userRequestsCached); renderUserRequestsTable('user-requests-table-body', userRequestsCached, true, 1); applyReportFilters(); }) .catch(err => { console.error("Error loading user requests:", err); - const errHtml = `Error loading user request details.`; + const errHtml = `Error loading user request details.`; if (document.getElementById('user-requests-table-body')) { document.getElementById('user-requests-table-body').innerHTML = errHtml; } @@ -5203,24 +6072,33 @@ // Group records by itcell_ref_no + pid const groups = {}; (data || []).forEach(item => { + //console.log(item); const cleanRef = (item.itcell_ref_no || '').trim(); const key = `${cleanRef || 'general'}_${item.pid || 0}`; if (!groups[key]) { groups[key] = { itcell_ref_no: cleanRef, - dept_id: item.dept_id, + dept_name: item.dept_name, zone_name: item.zone_name || 'N/A', pid: item.pid, printer_name: item.printer_name || 'N/A', qty_requested: 0, qty_delivered: 0, cdate: item.cdate, - user_request_uploads: [] + user_request_uploads: [], + req_nos: [] }; } groups[key].qty_requested += parseInt(item.qty_requested || 0); groups[key].qty_delivered += parseInt(item.qty_delivered || 0); + if (item.req_no && String(item.req_no).trim()) { + const cleanReqNo = String(item.req_no).trim(); + if (!groups[key].req_nos.includes(cleanReqNo)) { + groups[key].req_nos.push(cleanReqNo); + } + } + if (item.cdate && (!groups[key].cdate || new Date(item.cdate) > new Date(groups[key].cdate))) { groups[key].cdate = item.cdate; } @@ -5235,13 +6113,49 @@ }); const groupedList = Object.values(groups); - // Update status counts on the metric cards - const totalCount = groupedList.length; + // Filter by Request No, Department, Zone, Printer Model, and Month first (applies to metrics and table) + const deptMonthFiltered = groupedList.filter(item => { + if (userReqNoFilter) { + const filterVal = userReqNoFilter.trim().toLowerCase(); + const hasReqNo = (item.req_nos && item.req_nos.some(rn => rn.toLowerCase() === filterVal)) || + (item.req_no && item.req_no.toLowerCase() === filterVal); + if (!hasReqNo) { + return false; + } + } + if (userReqDeptFilter) { + const itemDept = (item.dept_name).toString().trim().toLowerCase(); + if (itemDept !== userReqDeptFilter.trim().toLowerCase()) { + return false; + } + } + if (userReqZoneFilter) { + const itemZone = (item.zone_name || '').toString().trim().toLowerCase(); + if (itemZone !== userReqZoneFilter.trim().toLowerCase()) { + return false; + } + } + if (userReqPrinterFilter) { + if (String(item.pid) !== String(userReqPrinterFilter)) { + return false; + } + } + if (userReqMonthFilter) { + const itemYm = getUserReqYearMonth(item.cdate); + if (itemYm !== userReqMonthFilter) { + return false; + } + } + return true; + }); + + // Update status counts on the metric cards based on Request No, Department & Month filter + const totalCount = deptMonthFiltered.length; let deliveredCount = 0; let partialCount = 0; let pendingCount = 0; - groupedList.forEach(item => { + deptMonthFiltered.forEach(item => { const reqQty = parseInt(item.qty_requested || 0); const delQty = parseInt(item.qty_delivered || 0); if (delQty >= reqQty && reqQty > 0) { @@ -5268,8 +6182,9 @@ thead.innerHTML = ` S.No + ${getUserReqSortHeader('Req. No', 'req_no')} ${getUserReqSortHeader('IT Cell Ref No', 'itcell_ref_no')} - ${getUserReqSortHeader('Department', 'dept_id')} + ${getUserReqSortHeader('Department', 'dept_name')} ${getUserReqSortHeader('Zone', 'zone_name')} ${getUserReqSortHeader('Printer Model', 'printer_name')} ${getUserReqSortHeader('Department Qty Requested', 'qty_requested', 'text-center')} @@ -5281,7 +6196,7 @@ `; } - let filtered = groupedList.filter(item => { + let filtered = deptMonthFiltered.filter(item => { const reqQty = parseInt(item.qty_requested || 0); const delQty = parseInt(item.qty_delivered || 0); let status = 'Pending'; @@ -5293,7 +6208,8 @@ } if (!userReqSearchQuery) return true; - const searchStr = `${item.itcell_ref_no || ''} ${item.dept_id || ''} ${item.zone_name || ''} ${item.printer_name || ''}`.toLowerCase(); + const reqNosStr = (item.req_nos || []).join(' '); + const searchStr = `${reqNosStr} ${item.req_no || ''} ${item.itcell_ref_no || ''} ${item.dept_id || ''} ${item.zone_name || ''} ${item.printer_name || ''}`.toLowerCase(); return searchStr.includes(userReqSearchQuery); }); @@ -5307,6 +6223,11 @@ } else if (userReqSortColumn === 'cdate') { valA = valA ? new Date(valA).getTime() : 0; valB = valB ? new Date(valB).getTime() : 0; + } else if (userReqSortColumn === 'req_no') { + valA = (a.req_nos && a.req_nos.length > 0) ? a.req_nos.join(', ') : (a.req_no || ''); + valB = (b.req_nos && b.req_nos.length > 0) ? b.req_nos.join(', ') : (b.req_no || ''); + valA = valA.toLowerCase(); + valB = valB.toLowerCase(); } else { valA = (valA || '').toString().toLowerCase(); valB = (valB || '').toString().toLowerCase(); @@ -5324,7 +6245,7 @@ const totalPages = Math.ceil(filtered.length / userReqPageSize) || 1; userReqCurrentPage = Math.min(Math.max(1, userReqCurrentPage), totalPages); - const colSpan = showDoc ? 11 : 10; + const colSpan = showDoc ? 12 : 11; if (filtered.length === 0) { tbody.innerHTML = `No request records found.`; renderTablePaginationNav('user-req-pagination-info', 'user-req-pagination-nav', userReqCurrentPage, userReqPageSize, 1, 0, (p) => renderUserRequestsTable(tbodyId, data, showDoc, p)); @@ -5356,15 +6277,17 @@ } const refLink = `${escapeHtml(item.itcell_ref_no || '')}`; + const reqNoDisplay = (item.req_nos && item.req_nos.length > 0) ? item.req_nos.join(', ') : (item.req_no || '-'); let rowHtml = ` ${globalIdx} + ${escapeHtml(reqNoDisplay)} ${refLink} - ${escapeHtml(item.dept_id)} + ${escapeHtml(item.dept_name)} ${escapeHtml(item.zone_name)} ${escapeHtml(item.printer_name || '-')} ${item.qty_requested} - ${item.qty_delivered || 0} + ${item.qty_delivered || 0} ${createdDate} ${statusBadge} `; @@ -5372,13 +6295,21 @@ rowHtml += `${docBtn}`; } rowHtml += ` - - `; tr.innerHTML = rowHtml; @@ -5449,8 +6380,8 @@ thead.innerHTML = ` S.No - ${getOutwardSortHeader('Req. No', 'req_no')} - ${getOutwardSortHeader('Department', 'dept_id')} + ${getOutwardSortHeader('Req. No', 'req_no')} + ${getOutwardSortHeader('Department', 'dept_name')} ${getOutwardSortHeader('Printer Model', 'printer_name')} ${getOutwardSortHeader('Toner Model', 'model_name')} ${getOutwardSortHeader('Qty Req', 'qty_requested', 'text-center')} @@ -5463,7 +6394,7 @@ let filtered = (data || []).filter(item => { if (!outwardSearchQuery) return true; - const searchStr = `${item.req_no || ''} ${item.dept_id || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); + const searchStr = `${item.req_no || ''} ${item.dept_name || ''} ${item.printer_name || ''} ${item.model_name || ''}`.toLowerCase(); return searchStr.includes(outwardSearchQuery); }); @@ -5517,19 +6448,29 @@ return ` ${label}`; }).join(' '); } + let photoBtn = ''; + if (item.receiver_photo) { + const photos = item.receiver_photo.split(','); + photoBtn = photos.map((p, pIdx) => { + const trimmed = p.trim(); + const url = trimmed.startsWith('.') ? trimmed.substring(1) : trimmed; + const label = photos.length > 1 ? `View Photo ${pIdx + 1}` : `View Photo`; + return ` ${label}`; + }).join(' '); + } const pdfBtn = ``; tr.innerHTML = ` ${globalIdx} ${escapeHtml(item.req_no)} - ${escapeHtml(item.dept_id)} + ${escapeHtml(item.dept_name)} ${escapeHtml(item.printer_name)} ${escapeHtml(item.model_name)} ${item.qty_requested} ${item.qty_delivered} ${reqDateFormatted} ${dateFormatted} - ${docBtn}${pdfBtn} + ${docBtn}${photoBtn}${pdfBtn} `; tbody.appendChild(tr); }); @@ -5559,12 +6500,12 @@ .catch(err => console.error("Error loading vendors map:", err)); } - function fetchActiveWorkorderRequests() { + function fetchActiveWorkordersForInward() { const select = document.getElementById('inward-ref-select'); if (!select) return; select.innerHTML = ''; - fetch(contextPath + 'gcctonermanagement/api/workorders/active-requests') + fetch(contextPath + 'gcctonermanagement/api/workorders/active') .then(res => res.json()) .then(data => { select.innerHTML = ''; @@ -5576,11 +6517,16 @@ }); }) .catch(err => { - console.error("Error loading active request workorders: ", err); + console.error("Error loading active workorders: ", err); select.innerHTML = ''; }); } + // Backward compatibility + function fetchActiveWorkorderRequests() { + fetchActiveWorkordersForInward(); + } + function onInwardWorkorderSelectChange(select) { const refNo = select.value; const cmpInput = document.getElementById('inward-company-name'); @@ -5591,56 +6537,55 @@ cmpInput.removeAttribute('data-vendor-id'); } if (tbody) { - tbody.innerHTML = 'Please select a Workorder Reference to view requested models.'; + tbody.innerHTML = 'Please select a Workorder Reference to view registered models.'; } inwardRequestItems = []; if (!refNo) return; - fetch(contextPath + 'gcctonermanagement/api/workorders/requests?refNo=' + encodeURIComponent(refNo)) + fetch(contextPath + 'gcctonermanagement/api/workorders/items?refNo=' + encodeURIComponent(refNo)) .then(res => res.json()) .then(data => { inwardRequestItems = data; - const vendorId = data[0] ? data[0].vendor_id : ''; - if (vendorId) { - if (cmpInput) { - cmpInput.setAttribute('data-vendor-id', vendorId); + const vendorName = data[0] ? (data[0].vendor_name || '') : ''; + const vendorId = data[0] ? (data[0].vendor_id || '') : ''; + if (cmpInput) { + if (vendorId) cmpInput.setAttribute('data-vendor-id', vendorId); + if (vendorName) cmpInput.value = vendorName; + else if (vendorId) { + loadVendorsMap(() => { + cmpInput.value = vendorsMap[vendorId] || vendorId; + }); } - loadVendorsMap(() => { - if (cmpInput) { - const resolvedName = vendorsMap[vendorId] || (!isNaN(vendorId) ? 'Unknown Vendor (' + vendorId + ')' : vendorId); - cmpInput.value = resolvedName || ''; - } - }); } if (tbody) { tbody.innerHTML = ''; - if (data.length === 0) { - tbody.innerHTML = 'No requested models found for this workorder.'; + if (!data || data.length === 0) { + tbody.innerHTML = 'No registered models found for this workorder.'; return; } data.forEach((item, idx) => { - const reqQty = parseInt(item.qty_requested || 0); + const regQty = parseInt(item.qty_registered || 0); const rcvQty = parseInt(item.qty_inward_received || 0); - const remaining = Math.max(0, reqQty - rcvQty); + const remaining = Math.max(0, regQty - rcvQty); const tr = document.createElement('tr'); tr.innerHTML = ` - ${escapeHtml(item.printer_name)} + ${escapeHtml(item.printer_name || '-')} - ${escapeHtml(item.model_name)} + ${escapeHtml(item.model_name || '-')}
Part: ${escapeHtml(item.part_code || 'N/A')}
- Req: ${reqQty} + Reg: ${regQty} Received: ${rcvQty} - Remaining: ${remaining} + Remaining: ${remaining}
@@ -5648,12 +6593,12 @@ style="border-radius: 8px;" data-pid="${item.pid}" data-tid="${item.tid}" - data-request-id="${item.id}" data-remaining="${remaining}" min="0" max="${remaining}" - value="${remaining}" - placeholder="0"> + value="${remaining > 0 ? remaining : 0}" + placeholder="0" + ${remaining <= 0 ? 'disabled' : ''}> `; tbody.appendChild(tr); @@ -5661,8 +6606,8 @@ } }) .catch(err => { - console.error("Error loading workorder requests: ", err); - showNotification('error', 'Error', 'Failed to load workorder request details.'); + console.error("Error loading workorder items: ", err); + showNotification('error', 'Error', 'Failed to load workorder details.'); }); } @@ -5866,77 +6811,321 @@ tbody.innerHTML = ''; let total = 0; - if (requestItems.length === 0) { - tbody.innerHTML = `No items added to request yet.`; - if (totalQtyInput) totalQtyInput.value = 0; + if (requestItems.length === 0) { + tbody.innerHTML = `No items added to request yet.`; + if (totalQtyInput) totalQtyInput.value = 0; + return; + } + + requestItems.forEach((item, idx) => { + total += item.qty; + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${escapeHtml(item.printer_name)} + ${escapeHtml(item.model_name)} + ${item.qty} + + + + `; + tbody.appendChild(tr); + }); + + if (totalQtyInput) totalQtyInput.value = total; + } + + function submitRequest(event) { + if (event) event.preventDefault(); + + const itcellRefNo = document.getElementById('request-itcell').value.trim(); + const deptId = document.getElementById('request-dept').value.trim(); + const zoneEl = document.getElementById('request-zone'); + const zoneId = zoneEl ? zoneEl.value.trim() : ''; + const urInput = document.getElementById('request-user-file'); + + if (!itcellRefNo || !deptId) { + showNotification('warning', 'Missing Fields', 'Please fill in all required fields.'); + return; + } + + if (requestItems.length === 0) { + showNotification('warning', 'No Items Added', 'Please add at least one printer and toner model with quantity.'); + return; + } + + if (!urInput || urInput.files.length === 0) { + showNotification('warning', 'File Required', 'Please select a User Request Letter PDF file to upload.'); + return; + } + + const formData = new FormData(); + formData.append('itcellRefNo', itcellRefNo); + formData.append('deptId', deptId); + formData.append('zoneId', zoneId); + formData.append('itemsJson', JSON.stringify(requestItems)); + formData.append('userRequestFile', urInput.files[0]); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', contextPath + 'gcctonermanagement/api/request', true); + + const csrf = getCsrfMeta(); + if (csrf.token && csrf.header) { + xhr.setRequestHeader(csrf.header, csrf.token); + } + + const progressContainer = document.getElementById('request-progress-container'); + const progressFill = document.getElementById('request-progress-fill'); + const progressPercent = document.getElementById('request-progress-percent'); + const submitBtn = document.getElementById('request-submit-btn'); + + if (progressContainer) progressContainer.style.display = 'block'; + if (submitBtn) submitBtn.disabled = true; + + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable && progressFill && progressPercent) { + const percent = Math.round((e.loaded / e.total) * 100); + progressFill.style.width = percent + '%'; + progressPercent.textContent = percent + '%'; + } + }); + + xhr.onload = function () { + if (progressContainer) progressContainer.style.display = 'none'; + if (submitBtn) submitBtn.disabled = false; + + if (xhr.status === 200) { + try { + const res = JSON.parse(xhr.responseText); + if (res.success) { + Swal.fire({ + icon: 'success', + title: 'Request Logged!', + text: res.message || 'Department request saved successfully!', + confirmButtonColor: '#0f5c78' + }).then(() => { + resetForm('request'); + requestItems = []; + renderRequestItemsTable(); + fetchUserRequests(); + switchView('request'); + }); + } else { + showNotification('error', 'Error', res.error || 'Failed to save request.'); + } + } catch (e) { + showNotification('error', 'Error', 'Failed to parse response details.'); + } + } else { + showNotification('error', 'Error', 'Server returned HTTP ' + xhr.status); + } + }; + + xhr.onerror = () => { + if (progressContainer) progressContainer.style.display = 'none'; + if (submitBtn) submitBtn.disabled = false; + showNotification('error', 'Connection Error', 'Failed to submit request.'); + }; + + xhr.send(formData); + } + + // ================= EDIT USER REQUEST LOGIC ================= + function openEditUserRequestModal(requestId) { + const item = (userRequestsCached || []).find(r => String(r.id) === String(requestId)); + if (!item) { + showNotification('warning', 'Not Found', 'Request record not found.'); + return; + } + + document.getElementById('edit-request-id').value = item.id || ''; + document.getElementById('edit-request-req-no').value = item.req_no || '-'; + document.getElementById('edit-request-itcell').value = item.itcell_ref_no || ''; + document.getElementById('edit-request-qty').value = item.qty_requested || 1; + + // Reset file input + const fileInput = document.getElementById('edit-request-file'); + if (fileInput) fileInput.value = ''; + const fileLabel = document.getElementById('edit-request-file-label'); + if (fileLabel) fileLabel.textContent = 'Upload new PDF to replace (Optional)'; + + // Show current document link + const currentDocContainer = document.getElementById('edit-request-current-doc'); + if (currentDocContainer) { + if (item.user_request_upload) { + currentDocContainer.innerHTML = ` +
+ + Current File + + View + +
+ `; + } else { + currentDocContainer.innerHTML = ` No letter uploaded`; + } + } + + // Populate departments dropdown + const deptSelect = document.getElementById('edit-request-dept'); + if (deptSelect) { + deptSelect.innerHTML = ''; + (departmentsCached || []).forEach(d => { + const opt = document.createElement('option'); + opt.value = d.dept_name || d.id; + opt.textContent = d.dept_name || `Dept #${d.id}`; + if (String(item.dept_id) === String(d.id) || String(item.dept_name) === String(d.dept_name) || String(item.dept_id) === String(d.dept_name)) { + opt.selected = true; + } + deptSelect.appendChild(opt); + }); + } + + // Populate zones dropdown + const zoneSelect = document.getElementById('edit-request-zone'); + if (zoneSelect) { + zoneSelect.innerHTML = ''; + (zonesCached || []).forEach(z => { + const opt = document.createElement('option'); + opt.value = z.zone_name || z.id; + opt.textContent = z.zone_name || `Zone #${z.id}`; + if (String(item.zone_id) === String(z.id) || String(item.zone_name) === String(z.zone_name) || String(item.zone_id) === String(z.zone_name)) { + opt.selected = true; + } + zoneSelect.appendChild(opt); + }); + } + + // Populate printers dropdown + const printerSelect = document.getElementById('edit-request-printer'); + if (printerSelect) { + printerSelect.innerHTML = ''; + (printersCached || []).forEach(p => { + const opt = document.createElement('option'); + opt.value = p.id; + opt.textContent = p.printer_name; + if (String(item.pid) === String(p.id)) { + opt.selected = true; + } + printerSelect.appendChild(opt); + }); + } + + // Load toners for the selected printer and select the current toner + loadEditRequestToners(item.tid); + + // Hide breakdown modal if open + const breakdownModalEl = document.getElementById('userRequestBreakdownModal'); + if (breakdownModalEl) { + const breakdownModal = bootstrap.Modal.getInstance(breakdownModalEl); + if (breakdownModal) breakdownModal.hide(); + } + + const editModal = new bootstrap.Modal(document.getElementById('editUserRequestModal')); + editModal.show(); + } + + function openEditUserRequestByGroup(itcellRefNo, pid) { + const items = (userRequestsCached || []).filter(r => + String(r.itcell_ref_no) === String(itcellRefNo) && + (pid === undefined || pid === null || String(r.pid) === String(pid)) + ); + if (items.length === 1) { + openEditUserRequestModal(items[0].id); + } else if (items.length > 1) { + showUserRequestBreakdownModal(itcellRefNo, pid); + } else { + showNotification('warning', 'Not Found', 'No records found to edit.'); + } + } + + function onEditRequestPrinterChange() { + loadEditRequestToners(''); + } + + function loadEditRequestToners(selectedTid) { + const printerSelect = document.getElementById('edit-request-printer'); + const tonerSelect = document.getElementById('edit-request-toner'); + if (!printerSelect || !tonerSelect) return; + + const pid = printerSelect.value; + if (!pid) { + tonerSelect.innerHTML = ''; + tonerSelect.disabled = true; return; } - requestItems.forEach((item, idx) => { - total += item.qty; - const tr = document.createElement('tr'); - tr.innerHTML = ` - ${escapeHtml(item.printer_name)} - ${escapeHtml(item.model_name)} - ${item.qty} - - - - `; - tbody.appendChild(tr); - }); + tonerSelect.disabled = false; + tonerSelect.innerHTML = ''; - if (totalQtyInput) totalQtyInput.value = total; + fetch(contextPath + `gcctonermanagement/api/toners?pid=${pid}`) + .then(res => res.json()) + .then(toners => { + tonerSelect.innerHTML = ''; + (toners || []).forEach(t => { + const opt = document.createElement('option'); + opt.value = t.id; + opt.textContent = `${t.model_name} (${t.part_code || 'N/A'})`; + if (String(t.id) === String(selectedTid)) { + opt.selected = true; + } + tonerSelect.appendChild(opt); + }); + }) + .catch(err => { + console.error("Error loading edit toners:", err); + tonerSelect.innerHTML = ''; + }); } - function submitRequest(event) { + function submitEditRequest(event) { if (event) event.preventDefault(); - const itcellRefNo = document.getElementById('request-itcell').value.trim(); - const deptId = document.getElementById('request-dept').value.trim(); - const zoneId = document.getElementById('request-zone').value.trim(); - const urInput = document.getElementById('request-user-file'); + const id = document.getElementById('edit-request-id').value; + const itcellRefNo = document.getElementById('edit-request-itcell').value.trim(); + const deptId = document.getElementById('edit-request-dept').value.trim(); + const zoneId = document.getElementById('edit-request-zone').value.trim(); + const pid = document.getElementById('edit-request-printer').value; + const tid = document.getElementById('edit-request-toner').value; + const qtyRequested = document.getElementById('edit-request-qty').value; + const fileInput = document.getElementById('edit-request-file'); - if (!itcellRefNo || !deptId || !zoneId) { + if (!id || !itcellRefNo || !deptId || !pid || !tid || !qtyRequested) { showNotification('warning', 'Missing Fields', 'Please fill in all required fields.'); return; } - if (requestItems.length === 0) { - showNotification('warning', 'No Items Added', 'Please add at least one printer and toner model with quantity.'); - return; - } - - if (!urInput || urInput.files.length === 0) { - showNotification('warning', 'File Required', 'Please select a User Request Letter PDF file to upload.'); - return; - } - const formData = new FormData(); + formData.append('id', id); formData.append('itcellRefNo', itcellRefNo); formData.append('deptId', deptId); formData.append('zoneId', zoneId); - formData.append('itemsJson', JSON.stringify(requestItems)); - formData.append('userRequestFile', urInput.files[0]); + formData.append('pid', pid); + formData.append('tid', tid); + formData.append('qtyRequested', qtyRequested); + + if (fileInput && fileInput.files.length > 0) { + formData.append('userRequestFile', fileInput.files[0]); + } + + const progressContainer = document.getElementById('edit-request-progress-container'); + const progressFill = document.getElementById('edit-request-progress-fill'); + const progressPercent = document.getElementById('edit-request-progress-percent'); + const submitBtn = document.getElementById('edit-request-submit-btn'); + + if (progressContainer) progressContainer.style.display = 'block'; + if (submitBtn) submitBtn.disabled = true; const xhr = new XMLHttpRequest(); - xhr.open('POST', contextPath + 'gcctonermanagement/api/request', true); + xhr.open('POST', contextPath + 'gcctonermanagement/api/request/update', true); const csrf = getCsrfMeta(); if (csrf.token && csrf.header) { xhr.setRequestHeader(csrf.header, csrf.token); } - const progressContainer = document.getElementById('request-progress-container'); - const progressFill = document.getElementById('request-progress-fill'); - const progressPercent = document.getElementById('request-progress-percent'); - const submitBtn = document.getElementById('request-submit-btn'); - - if (progressContainer) progressContainer.style.display = 'block'; - if (submitBtn) submitBtn.disabled = true; - xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable && progressFill && progressPercent) { const percent = Math.round((e.loaded / e.total) * 100); @@ -5953,20 +7142,24 @@ try { const res = JSON.parse(xhr.responseText); if (res.success) { + const modalEl = document.getElementById('editUserRequestModal'); + if (modalEl) { + const modal = bootstrap.Modal.getInstance(modalEl); + if (modal) modal.hide(); + } Swal.fire({ icon: 'success', - title: 'Request Logged!', - text: res.message || 'Department request saved successfully!', + title: 'Updated Successfully!', + text: res.message || 'User request details updated successfully.', confirmButtonColor: '#0f5c78' }).then(() => { - resetForm('request'); - requestItems = []; - renderRequestItemsTable(); fetchUserRequests(); - switchView('request'); + if (typeof loadDashboardPage === 'function') { + loadDashboardPage(); + } }); } else { - showNotification('error', 'Error', res.error || 'Failed to save request.'); + showNotification('error', 'Error', res.error || 'Failed to update user request.'); } } catch (e) { showNotification('error', 'Error', 'Failed to parse response details.'); @@ -5979,13 +7172,12 @@ xhr.onerror = () => { if (progressContainer) progressContainer.style.display = 'none'; if (submitBtn) submitBtn.disabled = false; - showNotification('error', 'Connection Error', 'Failed to submit request.'); + showNotification('error', 'Connection Error', 'Failed to update request.'); }; xhr.send(formData); } - // ================= WORKORDER REGISTER LOGIC ================= let workorderItems = []; @@ -6225,6 +7417,7 @@ fetch(contextPath + 'gcctonermanagement/api/departments') .then(res => res.json()) .then(data => { + departmentsCached = data || []; select.innerHTML = ''; data.forEach(d => { const opt = document.createElement('option'); @@ -6232,6 +7425,10 @@ opt.textContent = d.dept_name; select.appendChild(opt); }); + if (userRequestsCached && userRequestsCached.length > 0) { + populateUserReqFilters(userRequestsCached); + } + populateReportDepartmentFilters(); }) .catch(err => { console.error("Error loading departments list: ", err); @@ -6241,23 +7438,28 @@ function fetchZonesList() { const select = document.getElementById('request-zone'); - if (!select) return; - select.innerHTML = ''; + if (select) select.innerHTML = ''; fetch(contextPath + 'gcctonermanagement/api/zones') .then(res => res.json()) .then(data => { - select.innerHTML = ''; - data.forEach(z => { - const opt = document.createElement('option'); - opt.value = z.id; - opt.textContent = z.zone_name; - select.appendChild(opt); - }); + zonesCached = data || []; + if (select) { + select.innerHTML = ''; + data.forEach(z => { + const opt = document.createElement('option'); + opt.value = z.id; + opt.textContent = z.zone_name; + select.appendChild(opt); + }); + } + if (userRequestsCached && userRequestsCached.length > 0) { + populateUserReqFilters(userRequestsCached); + } }) .catch(err => { console.error("Error loading zones list: ", err); - select.innerHTML = ''; + if (select) select.innerHTML = ''; }); } @@ -6463,6 +7665,7 @@ } else if (qty > 0) { const remaining = parseInt(input.getAttribute('data-remaining') || 0); const modelName = input.getAttribute('data-model-name'); + if (qty > remaining) { invalidInput = true; invalidMsg = `Requested quantity for ${modelName} (${qty}) cannot exceed remaining balance (${remaining}).`; @@ -6571,7 +7774,9 @@ pendingRequestsCached.forEach(req => { const opt = document.createElement('option'); opt.value = req.id; - opt.textContent = `${req.req_no} - Dept: ${req.dept_id}`; + const stock = req.available_stock !== undefined ? parseInt(req.available_stock) : 0; + const modelInfo = (req.printer_name ? req.printer_name + ' - ' : '') + (req.model_name || ''); + opt.textContent = `${req.req_no} - Dept: ${req.dept_name} (Printer: ${req.printer_name} Stock: ${stock})`; select.appendChild(opt); }); }) @@ -6581,7 +7786,12 @@ function disableUploadArea() { const card = document.getElementById('outward-upload-card'); const input = document.getElementById('outward-final-file'); + const photoCard = document.getElementById('outward-photo-card'); + const photoInput = document.getElementById('outward-user-photo-file'); + const cameraBtn = document.getElementById('outward-camera-btn'); + const filePhotoBtn = document.getElementById('outward-file-photo-btn'); const btn = document.getElementById('outward-upload-btn'); + if (card) { card.classList.add('disabled-card'); card.style.opacity = '0.65'; @@ -6593,13 +7803,31 @@ input.value = ''; } const label = document.getElementById('outward-final-label'); - if (label) label.textContent = 'Choose PDF File'; + if (label) label.textContent = 'Choose Signed PDF'; + + if (photoCard) { + photoCard.style.opacity = '0.65'; + photoCard.style.pointerEvents = 'none'; + } + if (photoInput) { + photoInput.disabled = true; + photoInput.value = ''; + } + if (cameraBtn) cameraBtn.disabled = true; + if (filePhotoBtn) filePhotoBtn.disabled = true; + + clearOutwardPhoto(); if (btn) btn.disabled = true; } function enableUploadArea() { const card = document.getElementById('outward-upload-card'); const input = document.getElementById('outward-final-file'); + const photoCard = document.getElementById('outward-photo-card'); + const photoInput = document.getElementById('outward-user-photo-file'); + const cameraBtn = document.getElementById('outward-camera-btn'); + const filePhotoBtn = document.getElementById('outward-file-photo-btn'); + if (card) { card.classList.remove('disabled-card'); card.style.opacity = '1'; @@ -6609,6 +7837,216 @@ if (input) { input.disabled = false; } + + if (photoCard) { + photoCard.style.opacity = '1'; + photoCard.style.pointerEvents = 'auto'; + } + if (photoInput) { + photoInput.disabled = false; + } + if (cameraBtn) cameraBtn.disabled = false; + if (filePhotoBtn) filePhotoBtn.disabled = false; + } + + // Camera and Photo Capture Logic + function openOutwardCameraModal() { + const modalEl = document.getElementById('outwardCameraModal'); + if (!modalEl) return; + const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + modal.show(); + startOutwardCameraStream(); + } + + function startOutwardCameraStream() { + const video = document.getElementById('outward-camera-video'); + const loader = document.getElementById('outward-camera-loading'); + const errBox = document.getElementById('outward-camera-error'); + const errMsg = document.getElementById('outward-camera-error-msg'); + + if (errBox) errBox.classList.add('d-none'); + if (loader) loader.classList.remove('d-none'); + + stopOutwardCameraStream(); + + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + if (loader) loader.classList.add('d-none'); + if (errBox) { + errBox.classList.remove('d-none'); + if (errMsg) errMsg.textContent = 'Camera API is not supported in this browser or environment (requires HTTPS or localhost).'; + } + return; + } + + const constraints = { + video: { + facingMode: outwardCameraFacing, + width: { ideal: 1280 }, + height: { ideal: 720 } + }, + audio: false + }; + + navigator.mediaDevices.getUserMedia(constraints) + .then(stream => { + outwardCameraStream = stream; + if (video) { + video.srcObject = stream; + video.play().catch(e => console.warn('Video play error:', e)); + } + if (loader) loader.classList.add('d-none'); + }) + .catch(err => { + console.warn('Initial camera constraint failed, attempting fallback...', err); + navigator.mediaDevices.getUserMedia({ video: true, audio: false }) + .then(stream => { + outwardCameraStream = stream; + if (video) { + video.srcObject = stream; + video.play().catch(e => console.warn('Video play error:', e)); + } + if (loader) loader.classList.add('d-none'); + }) + .catch(fallbackErr => { + console.error('Camera access failed:', fallbackErr); + if (loader) loader.classList.add('d-none'); + if (errBox) { + errBox.classList.remove('d-none'); + if (errMsg) errMsg.textContent = 'Could not access camera (' + (fallbackErr.name || 'Error') + '). Please ensure camera permissions are granted or select an image file instead.'; + } + }); + }); + } + + function stopOutwardCameraStream() { + if (outwardCameraStream) { + outwardCameraStream.getTracks().forEach(track => { + try { track.stop(); } catch (e) { } + }); + outwardCameraStream = null; + } + const video = document.getElementById('outward-camera-video'); + if (video) video.srcObject = null; + } + + function closeOutwardCameraModal() { + stopOutwardCameraStream(); + const modalEl = document.getElementById('outwardCameraModal'); + if (modalEl) { + const modal = bootstrap.Modal.getInstance(modalEl); + if (modal) modal.hide(); + } + } + + function switchOutwardCameraFacing() { + outwardCameraFacing = outwardCameraFacing === 'user' ? 'environment' : 'user'; + startOutwardCameraStream(); + } + + function captureOutwardPhoto() { + const video = document.getElementById('outward-camera-video'); + const canvas = document.getElementById('outward-camera-canvas'); + if (!video || !canvas || video.videoWidth === 0) { + showNotification('warning', 'Camera Not Ready', 'Camera feed is not ready yet. Please try again in a moment.'); + return; + } + + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + + const dataUrl = canvas.toDataURL('image/jpeg', 0.85); + outwardCapturedPhotoBase64 = dataUrl; + outwardCapturedPhotoBlob = null; + + canvas.toBlob((blob) => { + if (blob) { + outwardCapturedPhotoBlob = blob; + } + }, 'image/jpeg', 0.85); + + showOutwardPhotoPreview(dataUrl); + closeOutwardCameraModal(); + checkOutwardUploadReady(); + } + + function handleOutwardPhotoSelect(input) { + if (input.files && input.files[0]) { + const file = input.files[0]; + if (!file.type.startsWith('image/')) { + showNotification('warning', 'Invalid File', 'Please select a valid image file (JPG, PNG, WEBP).'); + input.value = ''; + return; + } + outwardCapturedPhotoBlob = file; + outwardCapturedPhotoBase64 = null; + + const reader = new FileReader(); + reader.onload = function (e) { + showOutwardPhotoPreview(e.target.result); + checkOutwardUploadReady(); + }; + reader.readAsDataURL(file); + } + } + + function showOutwardPhotoPreview(src) { + const placeholder = document.getElementById('outward-photo-placeholder'); + const wrapper = document.getElementById('outward-photo-preview-wrapper'); + const previewImg = document.getElementById('outward-photo-preview'); + + if (previewImg) previewImg.src = src; + if (placeholder) placeholder.classList.add('d-none'); + if (wrapper) wrapper.classList.remove('d-none'); + } + + function clearOutwardPhoto() { + outwardCapturedPhotoBlob = null; + outwardCapturedPhotoBase64 = null; + const photoInput = document.getElementById('outward-user-photo-file'); + if (photoInput) photoInput.value = ''; + const previewImg = document.getElementById('outward-photo-preview'); + if (previewImg) previewImg.src = ''; + const placeholder = document.getElementById('outward-photo-placeholder'); + const wrapper = document.getElementById('outward-photo-preview-wrapper'); + if (placeholder) placeholder.classList.remove('d-none'); + if (wrapper) wrapper.classList.add('d-none'); + checkOutwardUploadReady(); + } + + function checkOutwardUploadReady() { + const fdInput = document.getElementById('outward-final-file'); + const uploadBtn = document.getElementById('outward-upload-btn'); + const hasPdf = Boolean(fdInput && fdInput.files && fdInput.files.length > 0); + const hasPhoto = Boolean(outwardCapturedPhotoBlob || outwardCapturedPhotoBase64); + if (uploadBtn) { + if (hasPdf && hasPhoto) { + uploadBtn.disabled = false; + } else { + uploadBtn.disabled = true; + } + } + } + + function showReceiverPhotoModal(imageUrl, reqNo) { + const modalEl = document.getElementById('receiverPhotoViewModal'); + if (!modalEl) return; + const imgEl = document.getElementById('receiver-photo-modal-img'); + const titleEl = document.getElementById('receiverPhotoViewModalLabel'); + const subtextEl = document.getElementById('receiver-photo-modal-subtext'); + const dlBtn = document.getElementById('receiver-photo-modal-download'); + + if (imgEl) imgEl.src = imageUrl; + if (titleEl) titleEl.innerHTML = ` Receiver Photo ${reqNo ? `- ${escapeHtml(reqNo)}` : ''}`; + if (subtextEl) subtextEl.textContent = reqNo ? `Request No: ${reqNo}` : ''; + if (dlBtn) { + dlBtn.href = imageUrl; + dlBtn.setAttribute('download', `Receiver_Photo_${reqNo || 'ack'}.jpg`); + } + + const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + modal.show(); } // Handle active pending selection to display details @@ -6618,12 +8056,26 @@ const printerDisp = document.getElementById('outward-printer-disp'); const tonerDisp = document.getElementById('outward-toner-disp'); const qtyReqDisp = document.getElementById('outward-qty-req-disp'); + const availStockDisp = document.getElementById('outward-avail-stock-disp'); + const qtyDeliveredInput = document.getElementById('outward-qty-delivered'); + const saveBtn = document.getElementById('outward-save-btn'); // Reset - deptDisp.value = ''; - printerDisp.value = ''; - tonerDisp.value = ''; - qtyReqDisp.value = ''; + if (deptDisp) deptDisp.value = ''; + if (printerDisp) printerDisp.value = ''; + if (tonerDisp) tonerDisp.value = ''; + if (qtyReqDisp) qtyReqDisp.value = ''; + if (availStockDisp) { + availStockDisp.value = ''; + availStockDisp.style.color = '#166534'; + availStockDisp.style.backgroundColor = '#f0fdf4'; + } + if (qtyDeliveredInput) { + qtyDeliveredInput.value = ''; + qtyDeliveredInput.max = ''; + qtyDeliveredInput.disabled = false; + } + if (saveBtn) saveBtn.disabled = false; lastSavedOutward = null; disableUploadArea(); @@ -6631,12 +8083,45 @@ const selected = pendingRequestsCached.find(r => String(r.id) === String(id)); if (selected) { - deptDisp.value = selected.dept_id || '-'; - printerDisp.value = selected.printer_name || '-'; - tonerDisp.value = selected.model_name || '-'; - qtyReqDisp.value = (selected.qty_requested || '0') + ` (Already Issued: ${selected.qty_delivered || 0})`; - document.getElementById('outward-qty-delivered').value = (selected.qty_requested - (selected.qty_delivered || 0)); - document.getElementById('outward-qty-delivered').max = (selected.qty_requested - (selected.qty_delivered || 0)); + const reqQty = parseInt(selected.qty_requested || 0); + const delQty = parseInt(selected.qty_delivered || 0); + const pendingQty = Math.max(0, reqQty - delQty); + const availStock = parseInt(selected.available_stock !== undefined ? selected.available_stock : 0); + + if (deptDisp) deptDisp.value = selected.dept_id || '-'; + if (printerDisp) printerDisp.value = selected.printer_name || '-'; + if (tonerDisp) tonerDisp.value = selected.model_name || '-'; + if (qtyReqDisp) qtyReqDisp.value = `${reqQty} (Already Issued: ${delQty}, Pending Balance: ${pendingQty})`; + + if (availStockDisp) { + availStockDisp.value = `${availStock} units available in inward stock`; + if (availStock <= 0) { + availStockDisp.value = `0 units (OUT OF STOCK - Inward required)`; + availStockDisp.style.color = '#dc2626'; + availStockDisp.style.backgroundColor = '#fef2f2'; + } else if (availStock <= 5) { + availStockDisp.style.color = '#b45309'; + availStockDisp.style.backgroundColor = '#fffbeb'; + } + } + + if (availStock <= 0) { + if (qtyDeliveredInput) { + qtyDeliveredInput.value = 0; + qtyDeliveredInput.max = 0; + qtyDeliveredInput.disabled = true; + } + if (saveBtn) saveBtn.disabled = true; + showNotification('warning', 'Out of Stock', `No inward stock available for printer "${selected.printer_name || ''}" model "${selected.model_name || ''}". Please register inward stock before issuing.`); + } else { + const maxIssuable = Math.min(pendingQty, availStock); + if (qtyDeliveredInput) { + qtyDeliveredInput.value = maxIssuable > 0 ? maxIssuable : 1; + qtyDeliveredInput.max = maxIssuable; + qtyDeliveredInput.disabled = false; + } + if (saveBtn) saveBtn.disabled = false; + } } } @@ -6694,7 +8179,7 @@ Swal.fire({ icon: 'success', title: 'Saved & Downloaded!', - text: 'Delivery details recorded successfully and acknowledgement PDF downloaded. Please upload the signed copy using the upload area below.', + text: 'Delivery details recorded successfully and acknowledgement PDF downloaded. Please upload the signed copy and receiver photo using the upload area below.', confirmButtonColor: '#0f5c78' }).then(() => { fetchOutwardHistory(); @@ -6720,7 +8205,7 @@ xhr.send(formData); } - function uploadSignedPdf() { + function uploadSignedPdfAndPhoto() { if (!lastSavedOutward) { showNotification('warning', 'Save First', 'Please save the delivery record first.'); return; @@ -6728,7 +8213,12 @@ const fdInput = document.getElementById('outward-final-file'); if (!fdInput || fdInput.files.length === 0) { - showNotification('warning', 'File Required', 'Please select a signed PDF file to upload.'); + showNotification('warning', 'Signed PDF Required', 'Please select a signed PDF file to upload.'); + return; + } + + if (!outwardCapturedPhotoBlob && !outwardCapturedPhotoBase64) { + showNotification('warning', 'Photo Required', 'Please capture or select the Receiver / User photo.'); return; } @@ -6737,6 +8227,12 @@ formData.append('qtyIssued', lastSavedOutward.qty_delivered); formData.append('finalDeliveredFile', fdInput.files[0]); + if (outwardCapturedPhotoBlob) { + formData.append('userPhotoFile', outwardCapturedPhotoBlob, 'receiver_photo.jpg'); + } else if (outwardCapturedPhotoBase64) { + formData.append('userPhotoBase64', outwardCapturedPhotoBase64); + } + const progressContainer = document.getElementById('outward-progress-container'); const progressFill = document.getElementById('outward-progress-fill'); const progressPercent = document.getElementById('outward-progress-percent'); @@ -6771,8 +8267,8 @@ if (res.success) { Swal.fire({ icon: 'success', - title: 'Uploaded!', - text: 'Signed PDF acknowledgement uploaded successfully!', + title: 'Uploaded Successfully!', + text: 'Signed PDF acknowledgement' + (outwardCapturedPhotoBlob || outwardCapturedPhotoBase64 ? ' and Receiver Photo' : '') + ' uploaded successfully!', confirmButtonColor: '#0f5c78' }).then(() => { disableUploadArea(); @@ -6780,7 +8276,7 @@ switchView('outward'); }); } else { - showNotification('error', 'Error', res.error || 'Failed to upload signed PDF.'); + showNotification('error', 'Error', res.error || 'Failed to upload outward proof.'); } } catch (e) { showNotification('error', 'Error', 'Failed to parse response details.'); @@ -6793,12 +8289,16 @@ xhr.onerror = () => { if (progressContainer) progressContainer.style.display = 'none'; if (uploadBtn) uploadBtn.disabled = false; - showNotification('error', 'Connection Error', 'Failed to upload signed PDF.'); + showNotification('error', 'Connection Error', 'Failed to upload outward files.'); }; xhr.send(formData); } + function uploadSignedPdf() { + uploadSignedPdfAndPhoto(); + } + // ================= PDF ACKNOWLEDGEMENT GENERATION LOGIC ================= function generatePdfAcknowledgementDoc(item, signatureDataUrl, callback) { const { jsPDF } = window.jspdf; @@ -6975,6 +8475,7 @@ .then(data => { workorderHistoryCached = data; renderWorkorderHistoryTable('workorder-history-table-body', data, true, 1); + populateReportFileNoFilters(); applyReportFilters(); if (typeof populateDashboardWorkOrders === 'function') populateDashboardWorkOrders(); if (typeof updateDashboardData === 'function') updateDashboardData(); @@ -6998,7 +8499,7 @@ if (thead) { thead.innerHTML = ` - # + S.No Ref No. File No. Vendor @@ -7276,12 +8777,12 @@ function showWorkorderRequestsBreakdownModal(refNo, pid, tid) { document.getElementById('breakdown-ref').textContent = refNo; const tbody = document.getElementById('breakdown-table-body'); - tbody.innerHTML = 'Loading details...'; + tbody.innerHTML = 'Loading details...'; const modal = new bootstrap.Modal(document.getElementById('workorderRequestsBreakdownModal')); modal.show(); - fetch(contextPath + 'gcctonermanagement/api/workorders/requests?refNo=' + encodeURIComponent(refNo)) + fetch(contextPath + 'gcctonermanagement/api/workorders/items?refNo=' + encodeURIComponent(refNo)) .then(res => res.json()) .then(data => { let filtered = data.filter(item => String(item.pid) === String(pid)); @@ -7290,7 +8791,7 @@ } if (filtered.length === 0) { - tbody.innerHTML = 'No request details found.'; + tbody.innerHTML = 'No model details found.'; return; } @@ -7305,31 +8806,23 @@ tbody.innerHTML = ''; filtered.forEach((item, index) => { const tr = document.createElement('tr'); - const reqQty = parseInt(item.qty_requested || 0); + const regQty = parseInt(item.qty_registered || 0); const rcvQty = parseInt(item.qty_inward_received || 0); - const remaining = Math.max(0, reqQty - rcvQty); - const reqDate = formatTimestamp(item.cdate); - - const pdfBtn = item.email_copy_upload - ? ` View` - : ` N/A`; + const remaining = Math.max(0, regQty - rcvQty); tr.innerHTML = ` ${index + 1} ${escapeHtml(item.model_name || '-')} - ${reqQty} + ${regQty} ${rcvQty} ${remaining} - ${reqDate} - ${escapeHtml(item.updated_by || '-')} - ${pdfBtn} `; tbody.appendChild(tr); }); }) .catch(err => { console.error("Error loading breakdown: ", err); - tbody.innerHTML = 'Failed to load request breakdown details.'; + tbody.innerHTML = 'Failed to load inward breakdown details.'; }); } @@ -7344,7 +8837,7 @@ } document.getElementById('user-breakdown-ref').textContent = itcellRefNo; - document.getElementById('user-breakdown-dept').textContent = filtered[0].dept_id; + document.getElementById('user-breakdown-dept').textContent = filtered[0].dept_name || filtered[0].dept_id; const zoneEl = document.getElementById('user-breakdown-zone'); if (zoneEl) { zoneEl.textContent = filtered[0].zone_name || 'N/A'; @@ -7370,8 +8863,18 @@ ? ` View` : ` N/A`; + const editBtn = ` + + `; + tr.innerHTML = ` ${index + 1} + ${escapeHtml(item.req_no || '-')} ${escapeHtml(item.printer_name || '-')} ${escapeHtml(item.model_name || '-')} ${reqQty} @@ -7379,6 +8882,7 @@ ${statusBadge} ${reqDate} ${pdfBtn} + ${editBtn} `; tbody.appendChild(tr); }); @@ -7443,14 +8947,11 @@ - + - + - - - @@ -7551,6 +9052,7 @@ + @@ -7558,6 +9060,7 @@ + @@ -7574,6 +9077,141 @@ + + + - + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/home/login.html b/src/main/resources/templates/home/login.html index a51687c..3b8562b 100644 --- a/src/main/resources/templates/home/login.html +++ b/src/main/resources/templates/home/login.html @@ -68,7 +68,7 @@ let usrName = data.usrName && data.usrName.length > 0 ? data.usrName : null; if (usrName) { sessionStorage.setItem("usrName", usrName); - console.log("Redirecting to:", contextPath + 'redirect'); + //console.log("Redirecting to:", contextPath + 'redirect'); alert("Login Success"); window.location.href = contextPath + 'redirect'; } else {
S.NoSr. No. Toner ModelQty RequestedWorkorder Registered Qty Qty Inward Received Remaining BalanceRequested DateRequested ByEmail Copy
Sr. No.Req. No Printer Model Toner Model Qty RequestedStatus Requested Date LetterAction