Browse Source

new changes in manpower and toner management and payment tracker api and UI changes

master
yoganandhank 3 months ago
parent
commit
b7451652b0
  1. 10
      src/main/java/com/gcc_manpower/controller/HomeController.java
  2. 268
      src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java
  3. 380
      src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java
  4. 18
      src/main/java/com/gcc_manpower/service/AppUserService.java
  5. 5
      src/main/resources/templates/fragments/navbar.html
  6. 218
      src/main/resources/templates/gccservicepaymenttracker/dashboard.html
  7. 30
      src/main/resources/templates/gccservicepaymenttracker/details.html
  8. 3640
      src/main/resources/templates/gcctonermanagement/upload.html
  9. 126
      src/main/resources/templates/home/home.html

10
src/main/java/com/gcc_manpower/controller/HomeController.java

@ -653,6 +653,16 @@ public class HomeController {
return appUserService.getDeletedWorkOrders(); return appUserService.getDeletedWorkOrders();
} }
@GetMapping("/api/employee/recent-updates")
@ResponseBody
public List<Map<String, Object>> getRecentEmployeeUpdates(HttpSession session) {
String usrName = (String) session.getAttribute("usrName");
if (usrName == null) {
return new ArrayList<>();
}
return appUserService.getRecentEmployeeUpdates();
}
@PostMapping("/usr-logout") @PostMapping("/usr-logout")
public String logout(HttpSession session) { public String logout(HttpSession session) {
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();

268
src/main/java/com/gcc_manpower/gcctonermanagement/controller/TonerController.java

@ -1,5 +1,6 @@
package com.gcc_manpower.gcctonermanagement.controller; package com.gcc_manpower.gcctonermanagement.controller;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -16,6 +17,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File; import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.gcc_manpower.gcctonermanagement.service.TonerService; import com.gcc_manpower.gcctonermanagement.service.TonerService;
import com.gcc_manpower.service.AppUserService; import com.gcc_manpower.service.AppUserService;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
@ -137,14 +140,25 @@ public class TonerController {
return tonerService.getActiveTonersByPrinter(pid); return tonerService.getActiveTonersByPrinter(pid);
} }
@GetMapping("/api/departments")
@ResponseBody
public List<Map<String, Object>> getDepartments(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return java.util.Collections.emptyList();
}
return tonerService.getActiveDepartments();
}
@PostMapping("/api/inward") @PostMapping("/api/inward")
@ResponseBody @ResponseBody
public Map<String, Object> uploadInward( public Map<String, Object> uploadInward(
@RequestParam("refNo") String refNo,
@RequestParam("companyName") String companyName, @RequestParam("companyName") String companyName,
@RequestParam("pid") int pid, @RequestParam("pid") int pid,
@RequestParam("tid") int tid, @RequestParam("tid") int tid,
@RequestParam("qty") String qty, @RequestParam("qty") String qty,
@RequestParam(value = "dcFile", required = false) MultipartFile dcFile, @RequestParam(value = "dcFile", required = false) MultipartFile dcFile,
@RequestParam(value = "requestId", required = false) Integer requestId,
HttpSession session) { HttpSession session) {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
@ -155,7 +169,17 @@ public class TonerController {
return response; return response;
} }
if (dcFile != null && !dcFile.isEmpty() && !isPdfFile(dcFile)) {
String updatedBy = appUserService.getUserIdByName(username);
if (updatedBy == null)
updatedBy = username;
if (dcFile == null || dcFile.isEmpty()) {
response.put("success", false);
response.put("error", "Delivery Challan (DC) PDF file is required.");
return response;
}
if (!isPdfFile(dcFile)) {
response.put("success", false); response.put("success", false);
response.put("error", "Delivery Challan file must be a PDF."); response.put("error", "Delivery Challan file must be a PDF.");
return response; return response;
@ -170,7 +194,7 @@ public class TonerController {
String dcPath = saveUploadedFile(dcFile, "dc", uploadDir); String dcPath = saveUploadedFile(dcFile, "dc", uploadDir);
boolean saved = tonerService.saveInwardRegister( boolean saved = tonerService.saveInwardRegister(
companyName.trim(), pid, tid, qty.trim(), dcPath, username);
refNo.trim(), companyName.trim(), pid, tid, qty.trim(), dcPath, updatedBy, requestId);
if (saved) { if (saved) {
response.put("success", true); response.put("success", true);
@ -207,12 +231,38 @@ public class TonerController {
return response; return response;
} }
if (userRequestFile != null && !userRequestFile.isEmpty() && !isPdfFile(userRequestFile)) {
String updatedBy = appUserService.getUserIdByName(username);
if (updatedBy == null)
updatedBy = username;
if (userRequestFile == null || userRequestFile.isEmpty()) {
response.put("success", false);
response.put("error", "User Request Letter PDF file is required.");
return response;
}
if (!isPdfFile(userRequestFile)) {
response.put("success", false); response.put("success", false);
response.put("error", "User Request file must be a PDF."); response.put("error", "User Request file must be a PDF.");
return response; return response;
} }
int qtyReq;
try {
qtyReq = Integer.parseInt(qtyRequested.trim());
} catch (NumberFormatException e) {
response.put("success", false);
response.put("error", "Quantity requested must be a valid number.");
return response;
}
int availStock = tonerService.getTonerAvailableStock(tid);
if (qtyReq > availStock) {
response.put("success", false);
response.put("error", "Requested quantity (" + qtyReq + ") exceeds available stock (" + availStock + ").");
return response;
}
try { try {
File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile(); File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile();
if (!uploadDir.exists()) { if (!uploadDir.exists()) {
@ -222,7 +272,7 @@ public class TonerController {
String urPath = saveUploadedFile(userRequestFile, "user_request", uploadDir); String urPath = saveUploadedFile(userRequestFile, "user_request", uploadDir);
String reqNo = tonerService.saveUserRequest( String reqNo = tonerService.saveUserRequest(
itcellRefNo.trim(), deptId.trim(), pid, tid, qtyRequested.trim(), urPath, username);
itcellRefNo.trim(), deptId.trim(), pid, tid, qtyRequested.trim(), urPath, updatedBy);
if (reqNo != null) { if (reqNo != null) {
response.put("success", true); response.put("success", true);
@ -257,6 +307,10 @@ public class TonerController {
return response; return response;
} }
String updatedBy = appUserService.getUserIdByName(username);
if (updatedBy == null)
updatedBy = username;
if (finalDeliveredFile != null && !finalDeliveredFile.isEmpty() && !isPdfFile(finalDeliveredFile)) { if (finalDeliveredFile != null && !finalDeliveredFile.isEmpty() && !isPdfFile(finalDeliveredFile)) {
response.put("success", false); response.put("success", false);
response.put("error", "Final Delivered file must be a PDF."); response.put("error", "Final Delivered file must be a PDF.");
@ -272,7 +326,7 @@ public class TonerController {
String fdPath = saveUploadedFile(finalDeliveredFile, "final_delivered", uploadDir); String fdPath = saveUploadedFile(finalDeliveredFile, "final_delivered", uploadDir);
boolean saved = tonerService.saveOutwardRegister( boolean saved = tonerService.saveOutwardRegister(
id, qtyIssued.trim(), fdPath, username);
id, qtyIssued.trim(), fdPath, updatedBy);
if (saved) { if (saved) {
response.put("success", true); response.put("success", true);
@ -290,6 +344,88 @@ public class TonerController {
return response; return response;
} }
@PostMapping("/api/workorder")
@ResponseBody
public Map<String, Object> saveWorkorder(
@RequestParam("fileNumber") String fileNumber,
@RequestParam("vendorName") String vendorName,
@RequestParam("startDate") String startDate,
@RequestParam("endDate") String endDate,
@RequestParam("totalQty") String totalQty,
@RequestParam("itemsJson") String itemsJson,
@RequestParam("dcFile") MultipartFile dcFile,
HttpSession session) {
Map<String, Object> 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 (dcFile == null || dcFile.isEmpty()) {
response.put("success", false);
response.put("error", "Delivery Challan (DC) PDF upload file is required.");
return response;
}
if (!isPdfFile(dcFile)) {
response.put("success", false);
response.put("error", "Delivery Challan file must be a PDF.");
return response;
}
// Auto-generate refNo: ITCell-YYYY/MM/DD/XXXX
java.time.LocalDate today = java.time.LocalDate.now();
int randomNum = (int) (Math.random() * 9000) + 1000; // 4-digit random number
String refNo = String.format("ITCell-%d/%02d/%02d/%d", today.getYear(), today.getMonthValue(),
today.getDayOfMonth(), randomNum);
try {
ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> items = mapper.readValue(itemsJson,
new TypeReference<List<Map<String, Object>>>() {
});
if (items == null || items.isEmpty()) {
response.put("success", false);
response.put("error", "Please add at least one printer/toner model quantity entry.");
return response;
}
File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile();
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String dcPath = saveUploadedFile(dcFile, "workorder_dc", uploadDir);
boolean saved = tonerService.saveWorkorder(
refNo, fileNumber.trim(), vendorName.trim(),
startDate, endDate, dcPath, totalQty.trim(), items, updatedBy);
if (saved) {
response.put("success", true);
response.put("refNo", refNo);
response.put("message", "Workorder registered successfully.");
} else {
deleteFileIfExists(dcPath, uploadDir);
response.put("success", false);
response.put("error", "Failed to save workorder details in the database.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "An error occurred: " + e.getMessage());
e.printStackTrace();
}
return response;
}
@GetMapping("/api/report") @GetMapping("/api/report")
@ResponseBody @ResponseBody
public Map<String, Object> getReportData(HttpSession session) { public Map<String, Object> getReportData(HttpSession session) {
@ -359,7 +495,7 @@ public class TonerController {
File destFile = new File(uploadDir, savedFilename).getAbsoluteFile(); File destFile = new File(uploadDir, savedFilename).getAbsoluteFile();
file.transferTo(destFile); file.transferTo(destFile);
return "/gcctonermanagementfiles/" + savedFilename;
return "gcctonermanagementfiles/" + savedFilename;
} }
private void deleteFileIfExists(String webPath, File uploadDir) { private void deleteFileIfExists(String webPath, File uploadDir) {
@ -375,4 +511,124 @@ public class TonerController {
System.err.println("Error deleting file: " + e.getMessage()); System.err.println("Error deleting file: " + e.getMessage());
} }
} }
@GetMapping("/api/workorders/active")
@ResponseBody
public List<String> getActiveWorkorderRefNos(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return new ArrayList<>();
}
return tonerService.getActiveWorkorderRefNos();
}
@PostMapping("/api/workorder-request")
@ResponseBody
public Map<String, Object> uploadWorkorderModelRequest(
@RequestParam("refNo") String refNo,
@RequestParam("companyName") String companyName,
@RequestParam("pid") String pid,
@RequestParam("tid") String tid,
@RequestParam("qtyRequested") String qtyRequested,
@RequestParam("emailCopyFile") MultipartFile emailCopyFile,
HttpSession session) {
Map<String, Object> 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 (emailCopyFile == null || emailCopyFile.isEmpty()) {
response.put("success", false);
response.put("error", "Email copy PDF upload file is required.");
return response;
}
if (!isPdfFile(emailCopyFile)) {
response.put("success", false);
response.put("error", "Email copy file must be a PDF.");
return response;
}
try {
File uploadDir = new File(tonerUploadPathConfig).getAbsoluteFile();
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String ecPath = saveUploadedFile(emailCopyFile, "workorder_request_email", uploadDir);
boolean saved = tonerService.saveWorkorderModelRequest(
refNo.trim(), companyName.trim(), pid.trim(), tid.trim(), qtyRequested.trim(), ecPath, updatedBy);
if (saved) {
response.put("success", true);
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.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "An error occurred: " + e.getMessage());
e.printStackTrace();
}
return response;
}
@GetMapping("/api/workorders/items")
@ResponseBody
public List<Map<String, Object>> 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<String> getActiveWorkorderRequestRefNos(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return new ArrayList<>();
}
return tonerService.getActiveWorkorderRequestRefNos();
}
@GetMapping("/api/workorders/requests")
@ResponseBody
public List<Map<String, Object>> getWorkorderRequests(
@RequestParam("refNo") String refNo,
HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return new ArrayList<>();
}
return tonerService.getWorkorderRequests(refNo);
}
@GetMapping("/api/workorder/history")
@ResponseBody
public List<Map<String, Object>> getWorkorderRegisterHistory(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return new ArrayList<>();
}
return tonerService.getWorkorderRegisterHistory();
}
@GetMapping("/api/workorder-request/history")
@ResponseBody
public List<Map<String, Object>> getWorkorderModelRequestHistory(HttpSession session) {
if (session.getAttribute("toner_usrName") == null) {
return new ArrayList<>();
}
return tonerService.getWorkorderModelRequestHistory();
}
} }

380
src/main/java/com/gcc_manpower/gcctonermanagement/service/TonerService.java

@ -6,11 +6,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
public class TonerService { public class TonerService {
@ -22,50 +24,6 @@ public class TonerService {
this.jdbcTemplate = new JdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
} }
@jakarta.annotation.PostConstruct
public void initTables() {
try {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS stock_register_details (" +
"id INT AUTO_INCREMENT PRIMARY KEY, " +
"company_name VARCHAR(30), " +
"pid VARCHAR(20), " +
"tid VARCHAR(30), " +
"qty_registered VARCHAR(30), " +
"dc_upload VARCHAR(250), " +
"cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
"updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"order_by VARCHAR(45), " +
"is_active TINYINT DEFAULT 1, " +
"is_delete TINYINT DEFAULT 0" +
")");
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS department_requested_details (" +
"id INT AUTO_INCREMENT PRIMARY KEY, " +
"req_no VARCHAR(70), " +
"company_name VARCHAR(30), " +
"itcell_ref_no VARCHAR(45), " +
"dept_id VARCHAR(30), " +
"pid VARCHAR(20), " +
"tid VARCHAR(30), " +
"qty_registered VARCHAR(30), " +
"qty_requested VARCHAR(30), " +
"qty_delivered VARCHAR(30), " +
"dc_upload VARCHAR(250), " +
"user_request_upload VARCHAR(250), " +
"final_delivered_upload VARCHAR(250), " +
"cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
"updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"updated_by VARCHAR(45), " +
"order_by VARCHAR(45), " +
"is_active TINYINT DEFAULT 1, " +
"is_delete TINYINT DEFAULT 0" +
")");
} catch (Exception e) {
System.err.println("Error initializing tables: " + e.getMessage());
e.printStackTrace();
}
}
public List<Map<String, Object>> getActivePrinters() { public List<Map<String, Object>> getActivePrinters() {
String sql = "SELECT id, printer_name FROM printer_model_list where is_active = 1 AND is_delete = 0 "; String sql = "SELECT id, printer_name FROM printer_model_list where is_active = 1 AND is_delete = 0 ";
try { try {
@ -77,7 +35,12 @@ public class TonerService {
} }
public List<Map<String, Object>> getActiveTonersByPrinter(int pid) { public List<Map<String, Object>> getActiveTonersByPrinter(int pid) {
String sql = "SELECT id, model_name FROM toner_model_list WHERE pid = ? AND is_active = 1 AND is_delete = 0 ";
String sql = "SELECT t.id, t.model_name, t.part_code, " +
"(COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM 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 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 { try {
return jdbcTemplate.queryForList(sql, pid); return jdbcTemplate.queryForList(sql, pid);
} catch (Exception e) { } catch (Exception e) {
@ -96,13 +59,32 @@ public class TonerService {
} }
} }
public boolean saveInwardRegister(String companyName, int pid, int tid, String qty, String dcUpload, String username) {
public int getTonerAvailableStock(int tid) {
String sql = "SELECT " +
"(COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM 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 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);
} catch (Exception e) {
System.err.println("Error fetching toner available stock for id " + tid + ": " + e.getMessage());
return 0;
}
}
public boolean saveInwardRegister(String refNo, String companyName, int pid, int tid, String qty, String dcUpload,
String username, Integer requestId) {
String sql = "INSERT INTO stock_register_details " + String sql = "INSERT INTO stock_register_details " +
"(company_name, pid, tid, qty_registered, dc_upload, cdate, updated_date, order_by, is_active, is_delete) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 0)";
"(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()); java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis());
try { try {
int rows = jdbcTemplate.update(sql, companyName, String.valueOf(pid), String.valueOf(tid), qty, dcUpload, now, now, username);
int rows = jdbcTemplate.update(sql, refNo, companyName, String.valueOf(pid), String.valueOf(tid), qty,
dcUpload,
now, now, username, requestId);
return rows > 0; return rows > 0;
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error saving inward register: " + e.getMessage()); System.err.println("Error saving inward register: " + e.getMessage());
@ -121,7 +103,8 @@ public class TonerService {
} }
} }
public String saveUserRequest(String itCellRefNo, String deptId, int pid, int tid, String qtyRequested, String userRequestUpload, String username) {
public String saveUserRequest(String itCellRefNo, String deptId, int pid, int tid, String qtyRequested,
String userRequestUpload, String username) {
String tonerModel = getTonerModelName(tid); String tonerModel = getTonerModelName(tid);
String cleanModel = tonerModel.trim().replaceAll("\\s+", "-").replaceAll("[^a-zA-Z0-9-]", ""); String cleanModel = tonerModel.trim().replaceAll("\\s+", "-").replaceAll("[^a-zA-Z0-9-]", "");
int maxId = getMaxRequestId(); int maxId = getMaxRequestId();
@ -130,11 +113,13 @@ public class TonerService {
String reqNo = String.format("%s-%d-%04d", cleanModel, year, seqNum); String reqNo = String.format("%s-%d-%04d", cleanModel, year, seqNum);
String sql = "INSERT INTO department_requested_details " + String sql = "INSERT INTO department_requested_details " +
"(req_no, itcell_ref_no, dept_id, pid, tid, qty_requested, qty_delivered, user_request_upload, cdate, updated_date, updated_by, is_active, is_delete) " +
"(req_no, itcell_ref_no, dept_id, pid, tid, qty_requested, qty_delivered, user_request_upload, cdate, updated_date, updated_by, is_active, is_delete) "
+
"VALUES (?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?, 1, 0)"; "VALUES (?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?, 1, 0)";
java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis());
try { try {
int rows = jdbcTemplate.update(sql, reqNo, itCellRefNo, deptId, String.valueOf(pid), String.valueOf(tid), qtyRequested, userRequestUpload, now, now, username);
int rows = jdbcTemplate.update(sql, reqNo, itCellRefNo, deptId, String.valueOf(pid), String.valueOf(tid),
qtyRequested, userRequestUpload, now, now, username);
return rows > 0 ? reqNo : null; return rows > 0 ? reqNo : null;
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error saving user request: " + e.getMessage()); System.err.println("Error saving user request: " + e.getMessage());
@ -144,11 +129,13 @@ public class TonerService {
} }
public List<Map<String, Object>> getPendingRequests() { public List<Map<String, Object>> getPendingRequests() {
String sql = "SELECT r.id, r.req_no, r.dept_id, r.pid, r.tid, r.qty_requested, r.qty_delivered, " +
String sql = "SELECT r.id, r.req_no, COALESCE(d.dept_name, r.dept_id) AS dept_id, r.pid, r.tid, r.qty_requested, r.qty_delivered, "
+
"p.printer_name, t.model_name " + "p.printer_name, t.model_name " +
"FROM department_requested_details r " + "FROM department_requested_details r " +
"LEFT JOIN printer_model_list p ON r.pid = p.id " + "LEFT JOIN printer_model_list p ON r.pid = p.id " +
"LEFT JOIN toner_model_list t ON r.tid = t.id " + "LEFT JOIN toner_model_list t ON r.tid = t.id " +
"LEFT JOIN department_list d ON CAST(r.dept_id AS SIGNED) = d.id " +
"WHERE r.is_active = 1 AND r.is_delete = 0 " + "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)) " + "AND (r.qty_delivered IS NULL OR CAST(r.qty_delivered AS SIGNED) < CAST(r.qty_requested AS SIGNED)) " +
"ORDER BY r.id DESC"; "ORDER BY r.id DESC";
@ -161,17 +148,29 @@ public class TonerService {
} }
public boolean saveOutwardRegister(int id, String qtyIssued, String finalDeliveredUpload, String username) { public boolean saveOutwardRegister(int id, String qtyIssued, String finalDeliveredUpload, String username) {
String sql = "UPDATE department_requested_details " +
"SET qty_delivered = ?, final_delivered_upload = ?, updated_date = ?, updated_by = ? " +
"WHERE id = ?";
String sql;
int rows;
java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis());
try { try {
int rows = jdbcTemplate.update(sql, qtyIssued, finalDeliveredUpload, now, username, id);
if (finalDeliveredUpload != null) {
sql = "UPDATE 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, "
+
"updated_date = ?, updated_by = ? " +
"WHERE id = ?";
rows = jdbcTemplate.update(sql, finalDeliveredUpload, finalDeliveredUpload, now, username, id);
} else {
sql = "UPDATE department_requested_details " +
"SET qty_delivered = CAST(COALESCE(qty_delivered, 0) AS SIGNED) + CAST(? AS SIGNED), " +
"updated_date = ?, updated_by = ? " +
"WHERE id = ?";
rows = jdbcTemplate.update(sql, qtyIssued, now, username, id);
}
return rows > 0; return rows > 0;
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error updating outward register: " + e.getMessage()); System.err.println("Error updating outward register: " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
return false;
throw new RuntimeException("Rollback saveOutwardRegister due to: " + e.getMessage());
} }
} }
@ -181,29 +180,41 @@ public class TonerService {
String sqlRaised = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM department_requested_details WHERE is_active = 1 AND is_delete = 0"; String sqlRaised = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM department_requested_details WHERE is_active = 1 AND is_delete = 0";
String sqlDistributed = "SELECT COALESCE(SUM(CAST(qty_delivered AS SIGNED)), 0) FROM department_requested_details WHERE is_active = 1 AND is_delete = 0"; String sqlDistributed = "SELECT COALESCE(SUM(CAST(qty_delivered AS SIGNED)), 0) FROM department_requested_details WHERE is_active = 1 AND is_delete = 0";
String sqlInward = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM stock_register_details WHERE is_active = 1 AND is_delete = 0"; String sqlInward = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM stock_register_details WHERE is_active = 1 AND is_delete = 0";
String sqlWorkorderRegistered = "SELECT COALESCE(SUM(CAST(qty_registered AS SIGNED)), 0) FROM workorder_printermodel_qty WHERE is_active = 1 AND is_delete = 0";
String sqlWorkorderRequested = "SELECT COALESCE(SUM(CAST(qty_requested AS SIGNED)), 0) FROM workorder_model_request_details WHERE is_active = 1 AND is_delete = 0";
try { try {
int totalRaised = jdbcTemplate.queryForObject(sqlRaised, Integer.class); int totalRaised = jdbcTemplate.queryForObject(sqlRaised, Integer.class);
int totalDistributed = jdbcTemplate.queryForObject(sqlDistributed, Integer.class); int totalDistributed = jdbcTemplate.queryForObject(sqlDistributed, Integer.class);
int inwardStock = jdbcTemplate.queryForObject(sqlInward, Integer.class); int inwardStock = jdbcTemplate.queryForObject(sqlInward, Integer.class);
int availableStock = inwardStock - totalDistributed; int availableStock = inwardStock - totalDistributed;
int workorderRegistered = jdbcTemplate.queryForObject(sqlWorkorderRegistered, Integer.class);
int workorderRequested = jdbcTemplate.queryForObject(sqlWorkorderRequested, Integer.class);
metrics.put("totalRaised", totalRaised); metrics.put("totalRaised", totalRaised);
metrics.put("totalDistributed", totalDistributed); metrics.put("totalDistributed", totalDistributed);
metrics.put("inwardStock", inwardStock);
metrics.put("availableStock", availableStock); metrics.put("availableStock", availableStock);
metrics.put("workorderRegistered", workorderRegistered);
metrics.put("workorderRequested", workorderRequested);
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error computing dashboard metrics: " + e.getMessage()); System.err.println("Error computing dashboard metrics: " + e.getMessage());
metrics.put("totalRaised", 0); metrics.put("totalRaised", 0);
metrics.put("totalDistributed", 0); metrics.put("totalDistributed", 0);
metrics.put("inwardStock", 0);
metrics.put("availableStock", 0); metrics.put("availableStock", 0);
metrics.put("workorderRegistered", 0);
metrics.put("workorderRequested", 0);
} }
return metrics; return metrics;
} }
public List<Map<String, Object>> getInventorySummary() { public List<Map<String, Object>> getInventorySummary() {
String safeSql = "SELECT p.printer_name, t.model_name, " +
"COALESCE((SELECT SUM(CAST(sr.qty_registered AS SIGNED)) FROM 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 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 " +
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 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 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 " + "FROM toner_model_list t " +
"JOIN printer_model_list p ON t.pid = p.id " + "JOIN printer_model_list p ON t.pid = p.id " +
"WHERE t.is_active = 1 AND t.is_delete = 0 " + "WHERE t.is_active = 1 AND t.is_delete = 0 " +
@ -225,13 +236,19 @@ public class TonerService {
} }
public List<Map<String, Object>> getInwardHistory() { public List<Map<String, Object>> getInwardHistory() {
String sql = "SELECT s.id, s.company_name, s.qty_registered, s.dc_upload, s.cdate, s.order_by, " +
"p.printer_name, t.model_name " +
"FROM stock_register_details s " +
"LEFT JOIN 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 " +
"WHERE s.is_active = 1 AND s.is_delete = 0 " +
"ORDER BY s.id DESC";
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 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_requested AS SIGNED)) FROM workorder_model_request_details mr WHERE mr.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), 0) AS qty_workorder_requested "
+ "FROM stock_register_details s "
+ "LEFT JOIN 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 gcc_manpower.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 { try {
return jdbcTemplate.queryForList(sql); return jdbcTemplate.queryForList(sql);
} catch (Exception e) { } catch (Exception e) {
@ -241,14 +258,16 @@ public class TonerService {
} }
public List<Map<String, Object>> getAllUserRequests() { public List<Map<String, Object>> getAllUserRequests() {
String sql = "SELECT r.id, r.req_no, r.itcell_ref_no, r.dept_id, r.qty_requested, r.qty_delivered, " +
"r.user_request_upload, r.cdate, r.updated_date, r.updated_by, " +
"p.printer_name, t.model_name " +
"FROM department_requested_details r " +
"LEFT JOIN 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 " +
"WHERE r.is_active = 1 AND r.is_delete = 0 " +
"ORDER BY r.id DESC";
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, 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 department_requested_details r "
+ "LEFT JOIN 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 gcc_manpower.app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id "
+ "LEFT JOIN department_list d ON CAST(r.dept_id AS SIGNED) = d.id "
+ "WHERE r.is_active = 1 AND r.is_delete = 0 "
+ "ORDER BY r.id DESC";
try { try {
return jdbcTemplate.queryForList(sql); return jdbcTemplate.queryForList(sql);
} catch (Exception e) { } catch (Exception e) {
@ -258,20 +277,223 @@ public class TonerService {
} }
public List<Map<String, Object>> getOutwardHistory() { public List<Map<String, Object>> getOutwardHistory() {
String sql = "SELECT r.id, r.req_no, r.itcell_ref_no, r.dept_id, r.qty_requested, r.qty_delivered, r.final_delivered_upload, r.user_request_upload, r.cdate, r.updated_date, " +
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, 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 " + "p.printer_name, t.model_name " +
"FROM department_requested_details r " + "FROM department_requested_details r " +
"LEFT JOIN printer_model_list p ON CAST(r.pid AS SIGNED) = p.id " + "LEFT JOIN 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_model_list t ON CAST(r.tid AS SIGNED) = t.id " +
"LEFT JOIN department_list d ON CAST(r.dept_id AS SIGNED) = d.id " +
"WHERE r.is_active = 1 AND r.is_delete = 0 " + "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 " + "AND r.qty_delivered IS NOT NULL AND CAST(r.qty_delivered AS SIGNED) > 0 " +
"ORDER BY r.updated_date DESC"; "ORDER BY r.updated_date DESC";
try { try {
return jdbcTemplate.queryForList(sql); return jdbcTemplate.queryForList(sql);
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error fetching outward history: " + e.getMessage());
return new ArrayList<>(); return new ArrayList<>();
} }
} }
}
public boolean saveWorkorder(String refNo, String fileNumber, String vendorName,
String startDate, String endDate, String dcUpload,
String totalQty, List<Map<String, Object>> items, String username) {
java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis());
String sqlMain = "INSERT INTO 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) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)";
String firstPid = null;
String firstTid = null;
if (items != null && !items.isEmpty()) {
Map<String, Object> firstItem = items.get(0);
if (firstItem.get("pid") != null)
firstPid = String.valueOf(firstItem.get("pid"));
if (firstItem.get("tid") != null)
firstTid = String.valueOf(firstItem.get("tid"));
}
try {
int mainRows = jdbcTemplate.update(sqlMain,
refNo, fileNumber, vendorName, startDate, endDate,
firstPid, firstTid, totalQty, dcUpload, now, now, username);
if (mainRows <= 0) {
return false;
}
String sqlSub = "INSERT INTO workorder_printermodel_qty " +
"(ref_no, pid, tid, qty_registered, cdate, updated_date, updated_by, is_active, is_delete) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, 1, 0)";
for (Map<String, Object> item : items) {
String pid = String.valueOf(item.get("pid"));
String tid = String.valueOf(item.get("tid"));
String qty = String.valueOf(item.get("qty"));
jdbcTemplate.update(sqlSub, refNo, pid, tid, qty, now, now, username);
}
return true;
} catch (Exception e) {
System.err.println("Error saving workorder: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("Rollback saveWorkorder due to: " + e.getMessage());
}
}
@PostConstruct
public void initDatabase() {
try {
jdbcTemplate.execute("ALTER TABLE stock_register_details ADD COLUMN request_id INT DEFAULT NULL");
} catch (Exception e) {
// Ignore
}
try {
jdbcTemplate.execute("ALTER TABLE workorder_register_details DROP COLUMN order_by");
} catch (Exception e) {
// Ignore
}
try {
jdbcTemplate
.execute("ALTER TABLE workorder_printermodel_qty ADD COLUMN updated_by VARCHAR(255) DEFAULT NULL");
} catch (Exception e) {
// Ignore
}
try {
jdbcTemplate.execute("ALTER TABLE workorder_printermodel_qty DROP COLUMN order_by");
} catch (Exception e) {
// Ignore
}
}
public List<String> getActiveWorkorderRefNos() {
String sql = "SELECT ref_no FROM workorder_register_details WHERE is_active = 1 AND 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 boolean saveWorkorderModelRequest(String refNo, String companyName, String pid, String tid,
String qtyRequested, String emailCopyUpload, String username) {
String sql = "INSERT INTO workorder_model_request_details " +
"(ref_no, company_name, pid, tid, qty_requested, email_copy_upload, cdate, updated_date, updated_by, is_active, is_delete) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)";
java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis());
try {
int rows = jdbcTemplate.update(sql, refNo, companyName, pid, tid, qtyRequested, emailCopyUpload, now, now,
username);
return rows > 0;
} catch (Exception e) {
System.err.println("Error saving workorder model request: " + e.getMessage());
e.printStackTrace();
return false;
}
}
public List<Map<String, Object>> 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_requested AS SIGNED)) FROM workorder_model_request_details 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 workorder_printermodel_qty w " +
"LEFT JOIN workorder_register_details r ON w.ref_no = r.ref_no " +
"LEFT JOIN gccpayment_processing_tracker.vendors_list vl ON CAST(r.vendor_name AS SIGNED) = vl.id " +
"LEFT JOIN 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<String> getActiveWorkorderRequestRefNos() {
String sql = "SELECT ref_no FROM workorder_model_request_details WHERE is_active = 1 AND 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<Map<String, Object>> getWorkorderRequests(String refNo) {
String sql = "SELECT r.id, r.pid, r.tid, r.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 stock_register_details sr " +
"WHERE ((sr.request_id = r.id) OR (sr.request_id IS NULL AND sr.ref_no = r.ref_no AND sr.pid = r.pid AND sr.tid = r.tid)) "
+ "AND sr.is_active = 1 AND sr.is_delete = 0), 0) AS qty_inward_received "
+ "FROM workorder_model_request_details r "
+ "LEFT JOIN 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 gcc_manpower.app_user u ON CAST(r.updated_by AS SIGNED) = u.usr_id "
+ "WHERE r.ref_no = ? 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<Map<String, Object>> 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 workorder_printermodel_qty w " +
"LEFT JOIN workorder_register_details r ON w.ref_no = r.ref_no " +
"LEFT JOIN gccpayment_processing_tracker.vendors_list vl ON CAST(r.vendor_name AS SIGNED) = vl.id " +
"LEFT JOIN 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 gcc_manpower.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);
} catch (Exception e) {
System.err.println("Error fetching workorder register history: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getWorkorderModelRequestHistory() {
String sql = "SELECT MAX(mr.id) AS id, mr.ref_no, mr.pid, mr.tid, mr.company_name AS vendor_id, "
+ "SUM(CAST(mr.qty_requested AS SIGNED)) AS qty_requested, MAX(mr.email_copy_upload) AS email_copy_upload, "
+ "MAX(mr.cdate) AS cdate, COALESCE(MAX(u.usr_name), MAX(mr.updated_by)) AS order_by, p.printer_name, t.model_name, "
+ "COALESCE((SELECT w.qty_registered FROM workorder_printermodel_qty w WHERE w.ref_no = mr.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 workorder_model_request_details mr "
+ "LEFT JOIN 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 gcc_manpower.app_user u ON CAST(mr.updated_by AS SIGNED) = u.usr_id "
+ "WHERE mr.is_active = 1 AND mr.is_delete = 0 "
+ "GROUP BY mr.ref_no, mr.pid, mr.tid, mr.company_name, p.printer_name, t.model_name "
+ "ORDER BY MAX(mr.id) DESC";
try {
return jdbcTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching workorder model request history: " + e.getMessage());
return new ArrayList<>();
}
}
public List<Map<String, Object>> getActiveDepartments() {
String sql = "SELECT id, dept_name FROM department_list WHERE is_active = 1 AND 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<>();
}
}
}

18
src/main/java/com/gcc_manpower/service/AppUserService.java

@ -967,4 +967,22 @@ public class AppUserService {
return new ArrayList<>(); return new ArrayList<>();
} }
} }
public List<Map<String, Object>> getRecentEmployeeUpdates() {
String sql = "SELECT e.emp_code, e.name, e.updated_at, e.isactive, e.isdelete, e.cdate, " +
"COALESCE(u.usr_name, e.updated_by) AS updated_by_name, " +
"COALESCE(ew.designation, e.designation) AS designation_name, " +
"COALESCE(v.vendor_name, e.vendor_id) AS vendor_name " +
"FROM employee_details e " +
"LEFT JOIN gcc_manpower.app_user u ON CAST(e.updated_by AS SIGNED) = u.usr_id " +
"LEFT JOIN employee_workorder_details ew ON CAST(e.designation AS SIGNED) = ew.id " +
"LEFT JOIN vendor_details v ON CAST(e.vendor_id AS SIGNED) = v.id " +
"ORDER BY e.updated_at DESC LIMIT 20";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
System.err.println("Error fetching recent employee updates: " + e.getMessage());
return new ArrayList<>();
}
}
} }

5
src/main/resources/templates/fragments/navbar.html

@ -75,6 +75,11 @@
<!--Navigation item on the right--> <!--Navigation item on the right-->
<th:block th:if="${session.usrName != null}"> <th:block th:if="${session.usrName != null}">
<ul class="navbar-nav ms-auto align-items-center flex-row gap-3"> <ul class="navbar-nav ms-auto align-items-center flex-row gap-3">
<li class="nav-item me-2" id="manpower-update-badge" style="display: none;" th:if="${curPage == 'home'}">
<a href="#" onclick="openManpowerUpdatesModal(); return false;" class="btn btn-sm btn-outline-info text-dark fw-semibold" style="border-radius: 20px; font-size: 0.85rem; padding: 4px 12px; background: rgba(13, 202, 240, 0.08); border-color: rgba(13, 202, 240, 0.4); display: inline-flex; align-items: center; gap: 5px;">
<i class="fa-solid fa-clock-rotate-left text-info"></i> Last Updated: <span id="manpower-update-time">-</span>
</a>
</li>
<li class="nav-item text-secondary fw-semibold" style="font-size: 0.9rem;"> <li class="nav-item text-secondary fw-semibold" style="font-size: 0.9rem;">
<i class="fa-solid fa-user me-1 text-primary"></i> <i class="fa-solid fa-user me-1 text-primary"></i>
<span th:text="${session.usrName} + ' (' + ${session.usrRole} + ')'">admin (ADMIN)</span> <span th:text="${session.usrName} + ' (' + ${session.usrRole} + ')'">admin (ADMIN)</span>

218
src/main/resources/templates/gccservicepaymenttracker/dashboard.html

@ -1036,6 +1036,15 @@
<div class="main-container"> <div class="main-container">
<!-- As On Date Display Bar (Above Total No of Bills card) -->
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<div class="as-on-date-badge p-2 px-3 rounded-pill bg-white shadow-sm d-inline-flex align-items-center gap-2"
style="border: 1px solid #e2e8f0; font-size: 0.88rem; color: #475569; font-weight: 600;">
<i class="fa-regular fa-calendar-check text-primary fs-6"></i>
<span>As on Date: <strong id="as-on-date-val" style="color: #09303d;"></strong></span>
</div>
</div>
<!-- Category Grid (3 Cards) --> <!-- Category Grid (3 Cards) -->
<div class="category-grid"> <div class="category-grid">
@ -1902,10 +1911,10 @@
if (name.includes('awaiting') || name.includes('details')) { if (name.includes('awaiting') || name.includes('details')) {
return 'awaiting details'; return 'awaiting details';
} }
if (name.includes('dept') || name.includes('uac') || name.includes('office') || name.includes('fm unit')) {
if (name.includes('dept') || name.includes('uac') || name.includes('fm unit')) {
return 'pending with dept'; return 'pending with dept';
} }
if (name.includes('pending') || name.includes('initiated')) {
if (name.includes('pending') || name.includes('initiated') || name.includes('eoffice') || name.includes('e-office')) {
return 'pending'; return 'pending';
} }
if (name.includes('no action') || name.includes('action required') || name.includes('duplicate')) { if (name.includes('no action') || name.includes('action required') || name.includes('duplicate')) {
@ -1914,6 +1923,18 @@
return 'default'; return 'default';
} }
function getStatusGroup(statusName) {
if (!statusName) return 'others';
const name = String(statusName).toLowerCase().trim();
if (name.includes('completed') || name.includes('approved') || name.includes('paid')) {
return 'completed';
}
if (name.includes('initiated') || name.includes('eoffice') || name.includes('e-office')) {
return 'it_cell';
}
return 'others';
}
const statusColors = { const statusColors = {
'completed': { bg: '#f0fdf4', text: '#166534', badgeClass: 'text-success-dark' }, 'completed': { bg: '#f0fdf4', text: '#166534', badgeClass: 'text-success-dark' },
'awaiting details': { bg: '#fffbeb', text: '#92400e', badgeClass: 'text-warning-dark' }, 'awaiting details': { bg: '#fffbeb', text: '#92400e', badgeClass: 'text-warning-dark' },
@ -1937,11 +1958,6 @@
const filterTypeSelect = document.getElementById('filter-type'); const filterTypeSelect = document.getElementById('filter-type');
if (filterTypeSelect) { if (filterTypeSelect) {
filterTypeSelect.innerHTML = '<option value="all">- All Types -</option>'; filterTypeSelect.innerHTML = '<option value="all">- All Types -</option>';
const optNotUpdated = document.createElement('option');
optNotUpdated.value = 'status_not_updated';
optNotUpdated.textContent = 'Status Not Updated';
filterTypeSelect.appendChild(optNotUpdated);
activeTypes.forEach(t => { activeTypes.forEach(t => {
const categoryCode = getCategoryCode(t.type_name); const categoryCode = getCategoryCode(t.type_name);
const opt = document.createElement('option'); const opt = document.createElement('option');
@ -2037,35 +2053,40 @@
vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`; vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`;
}); });
// Custom Pending column header - with toggle icon
const toggleIcon = showVendorAgeBreakdown ? 'fa-angles-left' : 'fa-angles-right';
const titleText = showVendorAgeBreakdown ? 'Click to hide days-wise count breakdown' : 'Click to show days-wise count breakdown';
// Pending files column header with click toggle icon
const toggleIcon = showPendingSubBreakdown ? 'fa-angles-left' : 'fa-angles-right';
const titleText = showPendingSubBreakdown ? 'Click to hide status breakdown' : 'Click to show Pending with IT Cell & Pending with Others breakdown';
const subDNone = showPendingSubBreakdown ? '' : 'd-none';
vendorHeadHtml += ` vendorHeadHtml += `
<th class="cursor-pointer" style="background-color: #fef2f2; color: #991b1b; cursor: pointer;" onclick="toggleVendorAgeCols()" title="${titleText}">
<th class="cursor-pointer" style="background-color: #fef2f2; color: #991b1b; cursor: pointer;" onclick="togglePendingSubBreakdown()" title="${titleText}">
Pending files <i class="fa-solid ${toggleIcon} ms-1 text-muted" style="transition: transform 0.2s;"></i> Pending files <i class="fa-solid ${toggleIcon} ms-1 text-muted" style="transition: transform 0.2s;"></i>
</th> </th>
`; `;
nonCompletedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name);
vendorHeadHtml += `<th style="background-color: ${style.bg}; color: ${style.text};">${escapeHtml(s.status_name)}</th>`;
});
// Sub-breakdown headers under Pending (Shown when showPendingSubBreakdown is true)
vendorHeadHtml += `
<th class="pending-sub-col ${subDNone}" style="background-color: #e0f2fe; color: #0369a1;">Pending with IT Cell</th>
<th class="pending-sub-col ${subDNone}" style="background-color: #fff7ed; color: #c2410c;">Pending with Others</th>
`;
const ageDNone = showVendorAgeBreakdown ? '' : 'd-none';
// Days-wise count headers - ALWAYS VISIBLE BY DEFAULT
vendorHeadHtml += ` vendorHeadHtml += `
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">1 to 15 Days</th>
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">16 to 30 Days</th>
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">31 to 60 Days</th>
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">61 to 90 Days</th>
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">91 to 120 Days</th>
<th class="age-col ${ageDNone}" style="background-color: #fafaf9;">Over 120 Days</th>
<th class="age-col" style="background-color: #fafaf9;">1 to 15 Days</th>
<th class="age-col" style="background-color: #fafaf9;">16 to 30 Days</th>
<th class="age-col" style="background-color: #fafaf9;">31 to 60 Days</th>
<th class="age-col" style="background-color: #fafaf9;">61 to 90 Days</th>
<th class="age-col" style="background-color: #fafaf9;">91 to 120 Days</th>
<th class="age-col" style="background-color: #fafaf9;">Over 120 Days</th>
`; `;
vendorBreakdownTheadTr.innerHTML = vendorHeadHtml; vendorBreakdownTheadTr.innerHTML = vendorHeadHtml;
} }
} }
function toggleVendorAgeCols() {
showVendorAgeBreakdown = !showVendorAgeBreakdown;
let showPendingSubBreakdown = false;
function togglePendingSubBreakdown() {
showPendingSubBreakdown = !showPendingSubBreakdown;
populateDropdowns(); populateDropdowns();
renderVendorBreakdown(); renderVendorBreakdown();
} }
@ -2236,6 +2257,7 @@
allRecords = data; allRecords = data;
currentPage = 1; currentPage = 1;
clearClickFilter(null); clearClickFilter(null);
updateAsOnDateDisplay();
calculateSummaries(); calculateSummaries();
renderTable(); renderTable();
updateLatestUpdateBadge(); updateLatestUpdateBadge();
@ -3024,9 +3046,27 @@
} }
} }
function updateAsOnDateDisplay() {
const now = new Date();
const dd = String(now.getDate()).padStart(2, '0');
const mm = String(now.getMonth() + 1).padStart(2, '0');
const yyyy = now.getFullYear();
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
const monthName = monthNames[now.getMonth()];
const formatted = `${dd}/${mm}/${yyyy} (${monthName} ${yyyy})`;
const el = document.getElementById('as-on-date-val');
if (el) el.textContent = formatted;
}
// --- NEW METRICS & INTERACTIVE CLICK FILTER LOGIC --- // --- NEW METRICS & INTERACTIVE CLICK FILTER LOGIC ---
function calculateSummaries() { function calculateSummaries() {
let total = allRecords.length;
let rawTotal = allRecords.length;
const now = new Date();
const isFirstDayOfMonth = (now.getDate() === 1);
const firstDayOfCurrentMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
// Dynamic Status Counts Map // Dynamic Status Counts Map
const statusCounts = {}; const statusCounts = {};
@ -3042,6 +3082,9 @@
let age_over_120 = 0; let age_over_120 = 0;
const vendorMap = {}; const vendorMap = {};
let completedPrevMonthsCount = 0;
let completedCurrentMonthCount = 0;
let allCompletedCount = 0;
allRecords.forEach(row => { allRecords.forEach(row => {
const statusName = String(row.resolved_status || getStatusName(row.status) || '').trim(); const statusName = String(row.resolved_status || getStatusName(row.status) || '').trim();
@ -3058,11 +3101,28 @@
const age = parseInt(calculateAgeOfBill(row.received_date), 10); const age = parseInt(calculateAgeOfBill(row.received_date), 10);
const vendorName = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim(); const vendorName = String(row.resolved_vendor || row.vendor_name || 'Unknown').trim();
if (isCompleted) {
allCompletedCount++;
const compDateStr = row.updated_date || row.received_date || row.cdate;
let compDate = compDateStr ? new Date(compDateStr) : null;
if (compDate && !isNaN(compDate.getTime()) && compDate < firstDayOfCurrentMonth) {
completedPrevMonthsCount++;
} else {
completedCurrentMonthCount++;
}
if (!isFirstDayOfMonth && statusId !== null) {
statusCounts[statusId] = (statusCounts[statusId] || 0) + 1;
} else if (isFirstDayOfMonth && statusId !== null) {
if (compDate && compDate >= firstDayOfCurrentMonth) {
statusCounts[statusId] = (statusCounts[statusId] || 0) + 1;
}
}
} else {
if (statusId !== null) { if (statusId !== null) {
statusCounts[statusId] = (statusCounts[statusId] || 0) + 1; statusCounts[statusId] = (statusCounts[statusId] || 0) + 1;
} }
if (!isCompleted) {
if (age >= 1 && age <= 15) age_1_15++; if (age >= 1 && age <= 15) age_1_15++;
else if (age >= 16 && age <= 30) age_16_30++; else if (age >= 16 && age <= 30) age_16_30++;
else if (age >= 31 && age <= 60) age_31_60++; else if (age >= 31 && age <= 60) age_31_60++;
@ -3076,6 +3136,8 @@
vendorName: vendorName, vendorName: vendorName,
total: 0, total: 0,
statusCounts: {}, // Map status ID to count statusCounts: {}, // Map status ID to count
itCellCount: 0,
othersCount: 0,
age_1_15: 0, age_1_15: 0,
age_16_30: 0, age_16_30: 0,
age_31_60: 0, age_31_60: 0,
@ -3095,19 +3157,37 @@
} }
const vData = vendorMap[vendorName]; const vData = vendorMap[vendorName];
if (isCompleted) {
const compDateStr = row.updated_date || row.received_date || row.cdate;
let compDate = compDateStr ? new Date(compDateStr) : null;
const isPrevMonthComp = compDate && !isNaN(compDate.getTime()) && compDate < firstDayOfCurrentMonth;
if (isFirstDayOfMonth && isPrevMonthComp) {
// On 1st of month: exclude previous month completed from current month's vendor total
} else {
vData.total++; vData.total++;
if (statusId !== null) { if (statusId !== null) {
vData.statusCounts[statusId] = (vData.statusCounts[statusId] || 0) + 1; vData.statusCounts[statusId] = (vData.statusCounts[statusId] || 0) + 1;
} }
if (isCompleted) {
if (age >= 1 && age <= 15) vData.comp_age_1_15++; if (age >= 1 && age <= 15) vData.comp_age_1_15++;
else if (age >= 16 && age <= 30) vData.comp_age_16_30++; else if (age >= 16 && age <= 30) vData.comp_age_16_30++;
else if (age >= 31 && age <= 60) vData.comp_age_31_60++; else if (age >= 31 && age <= 60) vData.comp_age_31_60++;
else if (age >= 61 && age <= 90) vData.comp_age_61_90++; else if (age >= 61 && age <= 90) vData.comp_age_61_90++;
else if (age >= 91 && age <= 120) vData.comp_age_91_120++; else if (age >= 91 && age <= 120) vData.comp_age_91_120++;
else if (age > 120) vData.comp_age_over_120++; else if (age > 120) vData.comp_age_over_120++;
}
} else { } else {
vData.total++;
if (statusId !== null) {
vData.statusCounts[statusId] = (vData.statusCounts[statusId] || 0) + 1;
}
const group = getStatusGroup(statusName);
if (group === 'it_cell') {
vData.itCellCount++;
} else {
vData.othersCount++;
}
if (age >= 1 && age <= 15) vData.age_1_15++; if (age >= 1 && age <= 15) vData.age_1_15++;
else if (age >= 16 && age <= 30) vData.age_16_30++; else if (age >= 16 && age <= 30) vData.age_16_30++;
else if (age >= 31 && age <= 60) vData.age_31_60++; else if (age >= 31 && age <= 60) vData.age_31_60++;
@ -3116,13 +3196,18 @@
else if (age > 120) vData.age_over_120++; else if (age > 120) vData.age_over_120++;
} }
}); });
let completedTotal = 0;
const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed');
completedStatuses.forEach(s => {
completedTotal += (statusCounts[s.id] || 0);
});
const pendingTotal = total - completedTotal;
// Apply 1st day of month logic only on day 1
let total, completedTotal, pendingTotal;
if (isFirstDayOfMonth) {
total = rawTotal - completedPrevMonthsCount;
completedTotal = completedCurrentMonthCount;
pendingTotal = total - completedTotal;
} else {
total = rawTotal;
completedTotal = allCompletedCount;
pendingTotal = total - completedTotal;
}
// Set top dashboard cards counts dynamically // Set top dashboard cards counts dynamically
const cardTotalEl = document.getElementById('count-total'); const cardTotalEl = document.getElementById('count-total');
@ -3259,9 +3344,10 @@
const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed'); const completedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) === 'completed');
const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed'); const nonCompletedStatuses = activeStatus.filter(s => getStatusKey(s.status_name) !== 'completed');
const subDNone = showPendingSubBreakdown ? '' : 'd-none';
if (filtered.length === 0) { if (filtered.length === 0) {
const colSpan = 2 + activeStatus.length + 1 + 6;
const colSpan = 2 + completedStatuses.length + 1 + (showPendingSubBreakdown ? (2 + nonCompletedStatuses.length) : 0) + 6;
tbody.innerHTML = `<tr><td colspan="${colSpan}" class="text-secondary py-3">No matching vendors found</td></tr>`; tbody.innerHTML = `<tr><td colspan="${colSpan}" class="text-secondary py-3">No matching vendors found</td></tr>`;
return; return;
} }
@ -3272,6 +3358,8 @@
activeStatus.forEach(s => { activeStatus.forEach(s => {
sumStatuses[s.id] = 0; sumStatuses[s.id] = 0;
}); });
let sumItCell = 0;
let sumOthers = 0;
let sumAge1_15 = 0; let sumAge1_15 = 0;
let sumAge16_30 = 0; let sumAge16_30 = 0;
let sumAge31_60 = 0; let sumAge31_60 = 0;
@ -3284,6 +3372,8 @@
activeStatus.forEach(s => { activeStatus.forEach(s => {
sumStatuses[s.id] += (v.statusCounts[s.id] || 0); sumStatuses[s.id] += (v.statusCounts[s.id] || 0);
}); });
sumItCell += (v.itCellCount || 0);
sumOthers += (v.othersCount || 0);
sumAge1_15 += v.age_1_15; sumAge1_15 += v.age_1_15;
sumAge16_30 += v.age_16_30; sumAge16_30 += v.age_16_30;
sumAge31_60 += v.age_31_60; sumAge31_60 += v.age_31_60;
@ -3310,23 +3400,22 @@
// 2. Custom "Pending" column // 2. Custom "Pending" column
const pendingCount = v.total - totalCompleted; const pendingCount = v.total - totalCompleted;
trHtml += `<td class="cursor-pointer text-danger-dark" onclick="openModalForCount('vendor_pending_custom', null, 'Vendor: ' + this.getAttribute('data-vendor') + ' (Pending)', this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${pendingCount}</td>`;
trHtml += `<td class="cursor-pointer text-danger-dark fw-bold" onclick="openModalForCount('vendor_pending_custom', null, 'Vendor: ' + this.getAttribute('data-vendor') + ' (Pending)', this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${pendingCount}</td>`;
// 3. Non-completed statuses
nonCompletedStatuses.forEach(s => {
const count = v.statusCounts[s.id] || 0;
const style = getStatusStyle(s.status_name);
trHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('vendor_status', '${s.id}', 'Vendor: ' + this.getAttribute('data-vendor') + ' (${escapeHtml(s.status_name)})', this.getAttribute('data-vendor'), '${s.id}')" data-vendor="${escapeHtml(v.vendorName)}">${count}</td>`;
});
// 3. Sub-breakdown: Pending with IT Cell & Pending with Others
trHtml += `
<td class="cursor-pointer pending-sub-col ${subDNone} text-primary fw-semibold" onclick="openModalForCount('vendor_it_cell', null, 'Vendor: ' + this.getAttribute('data-vendor') + ' (Pending with IT Cell)', this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${v.itCellCount || 0}</td>
<td class="cursor-pointer pending-sub-col ${subDNone} text-warning-dark fw-semibold" onclick="openModalForCount('vendor_others', null, 'Vendor: ' + this.getAttribute('data-vendor') + ' (Pending with Others)', this.getAttribute('data-vendor'))" data-vendor="${escapeHtml(v.vendorName)}">${v.othersCount || 0}</td>
`;
const ageDNone = showVendorAgeBreakdown ? '' : 'd-none';
// 4. Days-wise columns (ALWAYS VISIBLE)
trHtml += ` trHtml += `
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [1, 15], 'Vendor: ' + this.getAttribute('data-vendor') + ' (1-15 Days)', this.getAttribute('data-vendor'), 'age_1_15')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_1_15}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [16, 30], 'Vendor: ' + this.getAttribute('data-vendor') + ' (16-30 Days)', this.getAttribute('data-vendor'), 'age_16_30')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_16_30}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [31, 60], 'Vendor: ' + this.getAttribute('data-vendor') + ' (31-60 Days)', this.getAttribute('data-vendor'), 'age_31_60')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_31_60}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [61, 90], 'Vendor: ' + this.getAttribute('data-vendor') + ' (61-90 Days)', this.getAttribute('data-vendor'), 'age_61_90')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_61_90}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [91, 120], 'Vendor: ' + this.getAttribute('data-vendor') + ' (91-120 Days)', this.getAttribute('data-vendor'), 'age_91_120')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_91_120}</td>
<td class="cursor-pointer text-danger age-col ${ageDNone}" onclick="openModalForCount('vendor_age', [121, null], 'Vendor: ' + this.getAttribute('data-vendor') + ' (Over 120 Days)', this.getAttribute('data-vendor'), 'age_over_120')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_over_120}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('vendor_age', [1, 15], 'Vendor: ' + this.getAttribute('data-vendor') + ' (1-15 Days)', this.getAttribute('data-vendor'), 'age_1_15')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_1_15}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('vendor_age', [16, 30], 'Vendor: ' + this.getAttribute('data-vendor') + ' (16-30 Days)', this.getAttribute('data-vendor'), 'age_16_30')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_16_30}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('vendor_age', [31, 60], 'Vendor: ' + this.getAttribute('data-vendor') + ' (31-60 Days)', this.getAttribute('data-vendor'), 'age_31_60')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_31_60}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('vendor_age', [61, 90], 'Vendor: ' + this.getAttribute('data-vendor') + ' (61-90 Days)', this.getAttribute('data-vendor'), 'age_61_90')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_61_90}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('vendor_age', [91, 120], 'Vendor: ' + this.getAttribute('data-vendor') + ' (91-120 Days)', this.getAttribute('data-vendor'), 'age_91_120')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_91_120}</td>
<td class="cursor-pointer text-danger age-col" onclick="openModalForCount('vendor_age', [121, null], 'Vendor: ' + this.getAttribute('data-vendor') + ' (Over 120 Days)', this.getAttribute('data-vendor'), 'age_over_120')" data-vendor="${escapeHtml(v.vendorName)}">${v.age_over_120}</td>
`; `;
tr.innerHTML = trHtml; tr.innerHTML = trHtml;
@ -3353,22 +3442,22 @@
// 2. Custom "Pending" column // 2. Custom "Pending" column
const sumPending = sumTotal - sumCompleted; const sumPending = sumTotal - sumCompleted;
totalTrHtml += `<td class="cursor-pointer text-danger-dark" onclick="openModalForCount('pending_custom', null, 'Pending Files')">${sumPending}</td>`;
totalTrHtml += `<td class="cursor-pointer text-danger-dark fw-bold" onclick="openModalForCount('pending_custom', null, 'Pending Files')">${sumPending}</td>`;
// 3. Non-completed statuses
nonCompletedStatuses.forEach(s => {
const style = getStatusStyle(s.status_name);
totalTrHtml += `<td class="cursor-pointer ${style.badgeClass}" onclick="openModalForCount('status', '${s.id}', 'Status: ' + this.getAttribute('data-status-name'))" data-status-name="${escapeHtml(s.status_name)}">${sumStatuses[s.id]}</td>`;
});
// 3. Sub-breakdown: Pending with IT Cell & Pending with Others
totalTrHtml += `
<td class="cursor-pointer pending-sub-col ${subDNone} text-primary fw-bold" onclick="openModalForCount('it_cell_custom', null, 'Pending with IT Cell')">${sumItCell}</td>
<td class="cursor-pointer pending-sub-col ${subDNone} text-warning-dark fw-bold" onclick="openModalForCount('others_custom', null, 'Pending with Others')">${sumOthers}</td>
`;
const ageDNone = showVendorAgeBreakdown ? '' : 'd-none';
// 4. Days-wise columns (ALWAYS VISIBLE)
totalTrHtml += ` totalTrHtml += `
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('age', [1, 15], 'Age: 1-15 Days')">${sumAge1_15}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('age', [16, 30], 'Age: 16-30 Days')">${sumAge16_30}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('age', [31, 60], 'Age: 31-60 Days')">${sumAge31_60}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('age', [61, 90], 'Age: 61-90 Days')">${sumAge61_90}</td>
<td class="cursor-pointer age-col ${ageDNone}" onclick="openModalForCount('age', [91, 120], 'Age: 91-120 Days')">${sumAge91_120}</td>
<td class="cursor-pointer text-danger age-col ${ageDNone}" onclick="openModalForCount('age', [121, null], 'Age: Over 120 Days')">${sumAgeOver120}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('age', [1, 15], 'Age: 1-15 Days')">${sumAge1_15}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('age', [16, 30], 'Age: 16-30 Days')">${sumAge16_30}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('age', [31, 60], 'Age: 31-60 Days')">${sumAge31_60}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('age', [61, 90], 'Age: 61-90 Days')">${sumAge61_90}</td>
<td class="cursor-pointer age-col" onclick="openModalForCount('age', [91, 120], 'Age: 91-120 Days')">${sumAge91_120}</td>
<td class="cursor-pointer text-danger age-col" onclick="openModalForCount('age', [121, null], 'Age: Over 120 Days')">${sumAgeOver120}</td>
`; `;
totalTr.innerHTML = totalTrHtml; totalTr.innerHTML = totalTrHtml;
tbody.appendChild(totalTr); tbody.appendChild(totalTr);
@ -3500,11 +3589,6 @@
if (modalTypeSelect) { if (modalTypeSelect) {
modalTypeSelect.innerHTML = '<option value="all">- All Types -</option>'; modalTypeSelect.innerHTML = '<option value="all">- All Types -</option>';
const optNotUpdated = document.createElement('option');
optNotUpdated.value = 'status_not_updated';
optNotUpdated.textContent = 'Status Not Updated';
modalTypeSelect.appendChild(optNotUpdated);
activeTypes.forEach(t => { activeTypes.forEach(t => {
const opt = document.createElement('option'); const opt = document.createElement('option');
opt.value = getCategoryCode(t.type_name); opt.value = getCategoryCode(t.type_name);

30
src/main/resources/templates/gccservicepaymenttracker/details.html

@ -975,7 +975,25 @@
} }
} }
function getStatusGroup(statusName) {
if (!statusName) return 'others';
const name = String(statusName).toLowerCase().trim();
if (name.includes('completed') || name.includes('approved') || name.includes('paid')) {
return 'completed';
}
if (name.includes('initiated') || name.includes('eoffice') || name.includes('e-office')) {
return 'it_cell';
}
return 'others';
}
function evaluateClickFilter() { function evaluateClickFilter() {
const urlParams = new URLSearchParams(window.location.search);
const urlClickType = urlParams.get('clickType');
const urlClickValue = urlParams.get('clickValue');
const urlVendor = urlParams.get('vendor');
const urlCol = urlParams.get('col');
let parsedClickValue = urlClickValue; let parsedClickValue = urlClickValue;
if (urlClickType === 'age' || urlClickType === 'vendor_age' || urlClickType === 'completed_age' || urlClickType === 'vendor_completed_age') { if (urlClickType === 'age' || urlClickType === 'vendor_age' || urlClickType === 'completed_age' || urlClickType === 'vendor_completed_age') {
if (urlClickValue) { if (urlClickValue) {
@ -1024,6 +1042,18 @@
if (isCompleted) return false; if (isCompleted) return false;
} }
if (urlClickType === 'vendor_it_cell' || urlClickType === 'it_cell_custom') {
if (urlClickType === 'vendor_it_cell' && rowVendor !== cleanStr(urlVendor)) return false;
const group = getStatusGroup(rowStatusName);
if (group !== 'it_cell') return false;
}
if (urlClickType === 'vendor_others' || urlClickType === 'others_custom') {
if (urlClickType === 'vendor_others' && rowVendor !== cleanStr(urlVendor)) return false;
const group = getStatusGroup(rowStatusName);
if (group !== 'others' || isCompleted) return false;
}
if (urlClickType === 'status') { if (urlClickType === 'status') {
const targetVal = cleanStr(parsedClickValue); const targetVal = cleanStr(parsedClickValue);
const matchedTarget = activeStatus.find(s => cleanStr(s.id) === targetVal || cleanStr(s.status_name) === targetVal); const matchedTarget = activeStatus.find(s => cleanStr(s.id) === targetVal || cleanStr(s.status_name) === targetVal);

3640
src/main/resources/templates/gcctonermanagement/upload.html
File diff suppressed because it is too large
View File

126
src/main/resources/templates/home/home.html

@ -1430,8 +1430,6 @@
</th> </th>
<th style="width: 140px; background-color: #eef7fa; color: #0f5c78; font-weight: 700;" <th style="width: 140px; background-color: #eef7fa; color: #0f5c78; font-weight: 700;"
class="text-center">Quota Required</th> class="text-center">Quota Required</th>
<th style="width: 120px; background-color: #eef7fa; color: #0f5c78; font-weight: 700;"
class="text-center">Slot</th>
<th style="background-color: #eef7fa; color: #0f5c78; font-weight: 700;">Current <th style="background-color: #eef7fa; color: #0f5c78; font-weight: 700;">Current
Employee (Active)</th> Employee (Active)</th>
<th style="background-color: #eef7fa; color: #0f5c78; font-weight: 700;">Previous <th style="background-color: #eef7fa; color: #0f5c78; font-weight: 700;">Previous
@ -1442,7 +1440,7 @@
</thead> </thead>
<tbody id="position-history-tbody"> <tbody id="position-history-tbody">
<tr> <tr>
<td colspan="7" class="text-center py-5 text-secondary">
<td colspan="6" class="text-center py-5 text-secondary">
<i class="fa-solid fa-circle-info fs-2 mb-2 text-primary"></i> <i class="fa-solid fa-circle-info fs-2 mb-2 text-primary"></i>
<p class="mb-0 fw-semibold">Please select a Vendor and a Work Order to display <p class="mb-0 fw-semibold">Please select a Vendor and a Work Order to display
position history.</p> position history.</p>
@ -1524,6 +1522,55 @@
</div> </div>
</div> </div>
<!-- Manpower Recent Updates Modal -->
<div class="modal fade" id="manpowerRecentUpdatesModal" tabindex="-1"
aria-labelledby="manpowerRecentUpdatesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content"
style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);">
<div class="modal-header text-white"
style="background-color: #0f5c78; border-top-left-radius: 14px; border-top-right-radius: 14px; padding: 1.2rem 1.5rem;">
<h5 class="modal-title fw-bold text-white m-0" id="manpowerRecentUpdatesModalLabel"
style="display: inline-flex; align-items: center; gap: 8px;">
<i class="fa-solid fa-clock-rotate-left"></i> Manpower Update History
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<div class="table-responsive"
style="max-height: 400px; overflow-y: auto; border: 1px solid #eef2f6; border-radius: 8px;">
<table class="table table-hover align-middle mb-0" id="manpower-updates-table">
<thead style="position: sticky; top: 0; z-index: 5; background-color: #f8fafc;">
<tr class="table-light">
<th class="text-center fw-bold"
style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">S.No</th>
<th fw-bold style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">Employee
</th>
<th fw-bold style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">Designation
& Vendor</th>
<th class="text-center fw-bold"
style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">Action</th>
<th fw-bold style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">Updated At
</th>
<th fw-bold style="color: #0f5c78; font-size: 0.9rem; font-weight: 700;">Updated By
</th>
</tr>
</thead>
<tbody id="manpower-updates-tbody">
<tr>
<td colspan="6" class="text-center py-4 text-muted">
<i class="fa-solid fa-spinner fa-spin me-2"></i>Loading updates...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Include the quick-view-modal for product --> <!-- Include the quick-view-modal for product -->
<th:block th:replace="~{modals/login-modal :: login-modal}"></th:block> <th:block th:replace="~{modals/login-modal :: login-modal}"></th:block>
@ -3571,6 +3618,7 @@
// Handle action parameters on load (e.g. ?action=addEmployee) // Handle action parameters on load (e.g. ?action=addEmployee)
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
fetchRecentManpowerUpdates();
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const action = params.get('action'); const action = params.get('action');
if (action === 'addEmployee') { if (action === 'addEmployee') {
@ -4917,7 +4965,7 @@
console.log("Vendor or Work Order not selected. Rendering prompt."); console.log("Vendor or Work Order not selected. Rendering prompt.");
tbody.innerHTML = ` tbody.innerHTML = `
<tr> <tr>
<td colspan="7" class="text-center py-5 text-secondary">
<td colspan="6" class="text-center py-5 text-secondary">
<i class="fa-solid fa-circle-info fs-2 mb-2 text-primary"></i> <i class="fa-solid fa-circle-info fs-2 mb-2 text-primary"></i>
<p class="mb-0 fw-semibold">Please select a Vendor and a Work Order to display position history.</p> <p class="mb-0 fw-semibold">Please select a Vendor and a Work Order to display position history.</p>
</td> </td>
@ -5045,7 +5093,6 @@
${sNoTd} ${sNoTd}
${designationTd} ${designationTd}
${requiredTd} ${requiredTd}
<td class="text-center fw-semibold align-middle text-secondary">Slot ${slot + 1}</td>
<td class="align-middle">${currentEmpTd}</td> <td class="align-middle">${currentEmpTd}</td>
<td class="align-middle">${historyTd}</td> <td class="align-middle">${historyTd}</td>
<td class="text-center align-middle">${statusTd}</td> <td class="text-center align-middle">${statusTd}</td>
@ -5115,6 +5162,75 @@
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
} }
let recentUpdatesCached = [];
function fetchRecentManpowerUpdates() {
fetch(contextPath + 'api/employee/recent-updates')
.then(res => res.json())
.then(data => {
recentUpdatesCached = data || [];
if (recentUpdatesCached.length > 0) {
const latest = recentUpdatesCached[0];
if (latest && latest.updated_at) {
const formatted = formatDateTime(latest.updated_at);
const badge = document.getElementById('manpower-update-badge');
const timeSpan = document.getElementById('manpower-update-time');
if (timeSpan) timeSpan.textContent = formatted;
if (badge) badge.style.display = 'block';
}
}
})
.catch(err => console.error("Error loading recent updates:", err));
}
function openManpowerUpdatesModal() {
const tbody = document.getElementById('manpower-updates-tbody');
if (!tbody) return;
tbody.innerHTML = '';
if (recentUpdatesCached.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted">No update records found.</td></tr>';
} else {
recentUpdatesCached.forEach((item, idx) => {
const tr = document.createElement('tr');
// Determine Action Badge
let actionBadge = '';
const isDel = parseInt(item.isdelete) === 1 || item.isdelete === true;
const cTime = new Date(item.cdate).getTime();
const uTime = new Date(item.updated_at).getTime();
const isAdd = Math.abs(cTime - uTime) < 5000 && !isDel;
if (isDel) {
actionBadge = '<span class="badge bg-danger"><i class="fa-solid fa-user-minus me-1"></i> Deleted</span>';
} else if (isAdd) {
actionBadge = '<span class="badge bg-success"><i class="fa-solid fa-user-plus me-1"></i> Added</span>';
} else {
actionBadge = '<span class="badge bg-primary"><i class="fa-solid fa-user-pen me-1"></i> Updated</span>';
}
tr.innerHTML = `
<td class="text-center fw-semibold text-secondary">${idx + 1}</td>
<td>
<strong class="text-dark">${escapeHtml(item.name || '')}</strong><br>
<small class="text-muted font-monospace">${escapeHtml(item.emp_code || '')}</small>
</td>
<td>
<span class="fw-semibold text-secondary" style="font-size: 0.9rem;">${escapeHtml(item.designation_name || '-')}</span><br>
<small class="text-muted">${escapeHtml(item.vendor_name || '-')}</small>
</td>
<td class="text-center">${actionBadge}</td>
<td><small class="fw-semibold text-dark">${formatDateTime(item.updated_at)}</small></td>
<td><span class="text-secondary fw-semibold">${escapeHtml(item.updated_by_name || '-')}</span></td>
`;
tbody.appendChild(tr);
});
}
const modal = new bootstrap.Modal(document.getElementById('manpowerRecentUpdatesModal'));
modal.show();
}
</script> </script>
<script th:src="@{/assets/js/home-script.js}" type="text/javascript" defer></script> <script th:src="@{/assets/js/home-script.js}" type="text/javascript" defer></script>

Loading…
Cancel
Save