diff --git a/src/main/java/com/gcc_manpower/config/DataSourceConfig.java b/src/main/java/com/gcc_manpower/config/DataSourceConfig.java index 7eeb080..d4dfd9a 100644 --- a/src/main/java/com/gcc_manpower/config/DataSourceConfig.java +++ b/src/main/java/com/gcc_manpower/config/DataSourceConfig.java @@ -57,4 +57,14 @@ public class DataSourceConfig { dataSource.setPassword(dbpassword); return dataSource; } + + @Bean(name = "mysqladjustmentDataSource") + public DataSource mysqladjustmentDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://" + host + "/gcc_adjustment_management"); + dataSource.setUsername("root"); + dataSource.setPassword(dbpassword); + return dataSource; + } } diff --git a/src/main/java/com/gcc_manpower/config/SecurityConfig.java b/src/main/java/com/gcc_manpower/config/SecurityConfig.java index c60cceb..8bd9e7a 100644 --- a/src/main/java/com/gcc_manpower/config/SecurityConfig.java +++ b/src/main/java/com/gcc_manpower/config/SecurityConfig.java @@ -68,14 +68,17 @@ public class SecurityConfig { "/gccservicepaymenttracker/login", "/gccservicepaymenttracker/api/login", "/gccservicedocuments/login", - "/gccservicedocuments/api/login") + "/gccservicedocuments/api/login", + "/gccadjustmentmanagement/login", + "/gccadjustmentmanagement/api/login") .permitAll() // Allow logout endpoints .requestMatchers( "/usr-logout", "/gcctonermanagement/logout", "/gccservicepaymenttracker/logout", - "/gccservicedocuments/logout") + "/gccservicedocuments/logout", + "/gccadjustmentmanagement/logout") .permitAll() // Require authentication for all other requests .anyRequest().authenticated()) @@ -85,7 +88,8 @@ public class SecurityConfig { "/api/val-app-usr-login", "/gcctonermanagement/api/login", "/gccservicepaymenttracker/api/login", - "/gccservicedocuments/api/login")) + "/gccservicedocuments/api/login", + "/gccadjustmentmanagement/api/login")) // Session management configuration with module-aware invalid session // redirection .sessionManagement(session -> session @@ -99,6 +103,8 @@ public class SecurityConfig { response.sendRedirect(request.getContextPath() + "/gcctonermanagement/login"); } else if (requestURI.contains("/gccservicedocuments")) { response.sendRedirect(request.getContextPath() + "/gccservicedocuments/login"); + } else if (requestURI.contains("/gccadjustmentmanagement")) { + response.sendRedirect(request.getContextPath() + "/gccadjustmentmanagement/login"); } else { response.sendRedirect(request.getContextPath() + "/login"); } @@ -113,6 +119,8 @@ public class SecurityConfig { response.sendRedirect(request.getContextPath() + "/gcctonermanagement/login"); } else if (requestURI.contains("/gccservicedocuments")) { response.sendRedirect(request.getContextPath() + "/gccservicedocuments/login"); + } else if (requestURI.contains("/gccadjustmentmanagement")) { + response.sendRedirect(request.getContextPath() + "/gccadjustmentmanagement/login"); } else { response.sendRedirect(request.getContextPath() + "/login"); } diff --git a/src/main/java/com/gcc_manpower/config/WebConfig.java b/src/main/java/com/gcc_manpower/config/WebConfig.java index 9191f8f..efab472 100644 --- a/src/main/java/com/gcc_manpower/config/WebConfig.java +++ b/src/main/java/com/gcc_manpower/config/WebConfig.java @@ -21,6 +21,9 @@ public class WebConfig implements WebMvcConfigurer { @Value("${toner.upload.path:gcctonermanagementfiles}") private String tonerUploadPathConfig; + @Value("${adjustment.upload.path:gccadjustmentmanagementfiles}") + private String adjustmentUploadPathConfig; + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // Resolve absolute path (supports both relative and absolute paths) @@ -48,5 +51,11 @@ public class WebConfig implements WebMvcConfigurer { .toString(); registry.addResourceHandler("/gcctonermanagementfiles/**") .addResourceLocations(tonerUploadPath); + + // Map URL path /gccadjustmentmanagementfiles/** to the configured adjustment folder + String adjustmentUploadPath = Paths.get(adjustmentUploadPathConfig).toAbsolutePath().toUri() + .toString(); + registry.addResourceHandler("/gccadjustmentmanagementfiles/**") + .addResourceLocations(adjustmentUploadPath); } } diff --git a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java new file mode 100644 index 0000000..93141c7 --- /dev/null +++ b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/controller/AdjustmentController.java @@ -0,0 +1,307 @@ +package com.gcc_manpower.gccadjustmentmanagement.controller; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import com.gcc_manpower.gccadjustmentmanagement.service.AdjustmentService; +import com.gcc_manpower.service.AppUserService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + +@Controller +@RequestMapping("/gccadjustmentmanagement") +public class AdjustmentController { + + @Autowired + private AdjustmentService adjustmentService; + + @Autowired + private AppUserService appUserService; + + @Autowired + private AuthenticationManager authenticationManager; + + @Value("${adjustment.upload.path:gccadjustmentmanagementfiles}") + private String adjustmentUploadPathConfig; + + @GetMapping({ "", "/", "/dashboard" }) + public String viewDashboard(Model model, HttpSession session) { + String username = (String) session.getAttribute("adj_usrName"); + if (username == null) { + return "redirect:/gccadjustmentmanagement/login"; + } + + String usrRole = (String) session.getAttribute("adj_usrRole"); + boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole); + + model.addAttribute("isLoggedIn", true); + model.addAttribute("isAdmin", isAdmin); + model.addAttribute("usrName", username); + model.addAttribute("curPage", "gccadjustmentmanagement"); + + return "gccadjustmentmanagement/dashboard"; + } + + @GetMapping("/login") + public String login(HttpSession session) { + return "gccadjustmentmanagement/login"; + } + + @PostMapping("/api/login") + @ResponseBody + public Map validateLogin( + @RequestParam("usrName") String usrName, + @RequestParam("usrPass") String usrPass, + HttpSession session) { + + Map response = new HashMap<>(); + + try { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(usrName, + usrPass); + Authentication authentication = authenticationManager.authenticate(authenticationToken); + + SecurityContextHolder.getContext().setAuthentication(authentication); + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + SecurityContextHolder.getContext()); + + String role = authentication.getAuthorities().stream() + .map(auth -> auth.getAuthority().replace("ROLE_", "")) + .findFirst() + .orElse("USER"); + + session.setAttribute("adj_usrName", usrName); + session.setAttribute("adj_usrRole", role); + session.setMaxInactiveInterval(1800); // 30 minutes session timeout + response.put("result", true); + response.put("usrName", usrName); + } catch (AuthenticationException e) { + response.put("result", false); + response.put("error", e.getMessage() != null ? e.getMessage() : "Invalid username or password!"); + } + + return response; + } + + @GetMapping("/logout") + public String logout(HttpSession session, HttpServletRequest request) { + SecurityContextHolder.clearContext(); + if (session != null) { + session.removeAttribute("adj_usrName"); + session.removeAttribute("adj_usrRole"); + session.invalidate(); + } + request.getSession(true); + return "redirect:/gccadjustmentmanagement/login"; + } + + @GetMapping("/api/advance-types") + @ResponseBody + public List> getAdvanceTypes(HttpSession session) { + if (session.getAttribute("adj_usrName") == null) { + return java.util.Collections.emptyList(); + } + return adjustmentService.getAdvanceTypes(); + } + + @GetMapping("/api/fund-types") + @ResponseBody + public List> getFundTypes(HttpSession session) { + if (session.getAttribute("adj_usrName") == null) { + return java.util.Collections.emptyList(); + } + return adjustmentService.getFundTypes(); + } + + @GetMapping("/api/list") + @ResponseBody + public List> getFilesList(HttpSession session) { + if (session.getAttribute("adj_usrName") == null) { + return java.util.Collections.emptyList(); + } + return adjustmentService.getAllFiles(); + } + + @PostMapping("/api/save") + @ResponseBody + public Map saveAdjustment( + @RequestParam(value = "id", required = false) Integer id, + @RequestParam(value = "vend_name", required = false) String vendName, + @RequestParam(value = "fin_year", required = false) String finYear, + @RequestParam(value = "adv_type_id", required = false) String advTypeId, + @RequestParam(value = "fund_type_id", required = false) String fundTypeId, + @RequestParam(value = "bal_amt", required = false) Double balAmt, + @RequestParam(value = "file_remark", required = false) String fileRemark, + @RequestParam(value = "file_data_num", required = false) String fileDataNum, + @RequestParam(value = "pi_amt", required = false) Double piAmt, + @RequestParam(value = "pi_file_gen_date", required = false) String piFileGenDate, + @RequestParam(value = "fi_amt", required = false) String fiAmt, + @RequestParam(value = "fi_file_gen_date", required = false) String fiFileGenDate, + @RequestParam(value = "tc_file_gen_date", required = false) String tcFileGenDate, + @RequestParam(value = "ejv_num", required = false) String ejvNum, + @RequestParam(value = "ejv_num_file_gen_date", required = false) String ejvNumFileGenDate, + @RequestParam(value = "as_file_gen_date", required = false) String asFileGenDate, + @RequestParam(value = "as_final_file_gen_date", required = false) String asFinalFileGenDate, + @RequestParam(value = "file", required = false) MultipartFile file, + @RequestParam(value = "poFile", required = false) MultipartFile poFile, + @RequestParam(value = "dcFile", required = false) MultipartFile dcFile, + @RequestParam(value = "irFile", required = false) MultipartFile irFile, + @RequestParam(value = "srFile", required = false) MultipartFile srFile, + @RequestParam(value = "piFile", required = false) MultipartFile piFile, + @RequestParam(value = "fiFile", required = false) MultipartFile fiFile, + @RequestParam(value = "tcFile", required = false) MultipartFile tcFile, + @RequestParam(value = "rtgsFile", required = false) MultipartFile rtgsFile, + @RequestParam(value = "ejvFile", required = false) MultipartFile ejvFile, + @RequestParam(value = "asFile", required = false) MultipartFile asFile, + @RequestParam(value = "asFinalFile", required = false) MultipartFile asFinalFile, + HttpSession session) { + + Map response = new HashMap<>(); + String username = (String) session.getAttribute("adj_usrName"); + if (username == null) { + response.put("success", false); + response.put("error", "Unauthorized access. Please log in."); + return response; + } + + try { + Map existing = null; + if (id != null) { + existing = adjustmentService.getAdjustmentFileById(id); + if (existing == null) { + response.put("success", false); + response.put("error", "Record not found."); + return response; + } + } + + File uploadDir = new File(adjustmentUploadPathConfig).getAbsoluteFile(); + if (!uploadDir.exists()) { + uploadDir.mkdirs(); + } + + // Save files or keep existing + String filePath = saveFile(file, "file", uploadDir, existing != null ? (String) existing.get("file_path") : null); + String poPath = saveFile(poFile, "po", uploadDir, existing != null ? (String) existing.get("po_file_path") : null); + String dcPath = saveFile(dcFile, "dc", uploadDir, existing != null ? (String) existing.get("dc_file_path") : null); + String irPath = saveFile(irFile, "ir", uploadDir, existing != null ? (String) existing.get("ir_file_path") : null); + String srPath = saveFile(srFile, "sr", uploadDir, existing != null ? (String) existing.get("sr_filepath") : null); + String piPath = saveFile(piFile, "pi", uploadDir, existing != null ? (String) existing.get("pi_file_path") : null); + String fiPath = saveFile(fiFile, "fi", uploadDir, existing != null ? (String) existing.get("fi_file_path") : null); + String tcPath = saveFile(tcFile, "tc", uploadDir, existing != null ? (String) existing.get("tc_file_path") : null); + String rtgsPath = saveFile(rtgsFile, "rtgs", uploadDir, existing != null ? (String) existing.get("rtgs_file_path") : null); + String ejvPath = saveFile(ejvFile, "ejv", uploadDir, existing != null ? (String) existing.get("ejv_num_file_path") : null); + String asPath = saveFile(asFile, "as", uploadDir, existing != null ? (String) existing.get("as_file_path") : null); + String asFinalPath = saveFile(asFinalFile, "as_final", uploadDir, existing != null ? (String) existing.get("as_final_file_path") : null); + + // Build data map + Map data = new HashMap<>(); + if (id != null) data.put("id", id); + data.put("vend_name", vendName); + data.put("fin_year", finYear); + data.put("adv_type_id", advTypeId); + data.put("fund_type_id", fundTypeId); + data.put("bal_amt", balAmt); + data.put("file_remark", fileRemark); + data.put("file_data_num", fileDataNum); + data.put("pi_amt", piAmt); + data.put("pi_file_gen_date", piFileGenDate); + data.put("fi_amt", fiAmt); + data.put("fi_file_gen_date", fiFileGenDate); + data.put("tc_file_gen_date", tcFileGenDate); + data.put("ejv_num", ejvNum); + data.put("ejv_num_file_gen_date", ejvNumFileGenDate); + data.put("as_file_gen_date", asFileGenDate); + data.put("as_final_file_gen_date", asFinalFileGenDate); + + // Paths + data.put("file_path", filePath); + data.put("po_file_path", poPath); + data.put("dc_file_path", dcPath); + data.put("ir_file_path", irPath); + data.put("sr_filepath", srPath); + data.put("pi_file_path", piPath); + data.put("fi_file_path", fiPath); + data.put("tc_file_path", tcPath); + data.put("rtgs_file_path", rtgsPath); + data.put("ejv_num_file_path", ejvPath); + data.put("as_file_path", asPath); + data.put("as_final_file_path", asFinalPath); + + boolean saved = adjustmentService.saveAdjustmentFile(data, username); + if (saved) { + response.put("success", true); + response.put("message", "Adjustment file details saved successfully."); + } else { + response.put("success", false); + response.put("error", "Failed to save details in database."); + } + } catch (Exception e) { + response.put("success", false); + response.put("error", "Error occurred: " + e.getMessage()); + e.printStackTrace(); + } + + return response; + } + + @PostMapping("/api/delete") + @ResponseBody + public Map deleteAdjustment( + @RequestParam("id") Integer id, + @RequestParam(value = "remarks", required = false) String remarks, + HttpSession session) { + + Map response = new HashMap<>(); + String username = (String) session.getAttribute("adj_usrName"); + if (username == null) { + response.put("success", false); + response.put("error", "Unauthorized access. Please log in."); + return response; + } + + boolean deleted = adjustmentService.deleteAdjustmentFile(id, remarks != null ? remarks : "Deleted by user", username); + if (deleted) { + response.put("success", true); + response.put("message", "Record deleted successfully."); + } else { + response.put("success", false); + response.put("error", "Failed to delete record."); + } + + return response; + } + + private String saveFile(MultipartFile f, String prefix, File dir, String currentPath) throws Exception { + if (f == null || f.isEmpty()) { + return currentPath; + } + String orig = f.getOriginalFilename(); + String ext = orig != null && orig.lastIndexOf('.') > 0 ? orig.substring(orig.lastIndexOf('.')) : ".pdf"; + String filename = prefix + "_" + System.currentTimeMillis() + ext; + File dest = new File(dir, filename).getAbsoluteFile(); + f.transferTo(dest); + return "gccadjustmentmanagementfiles/" + filename; + } +} diff --git a/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java new file mode 100644 index 0000000..ebaac20 --- /dev/null +++ b/src/main/java/com/gcc_manpower/gccadjustmentmanagement/service/AdjustmentService.java @@ -0,0 +1,214 @@ +package com.gcc_manpower.gccadjustmentmanagement.service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +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; + +@Service +public class AdjustmentService { + + private JdbcTemplate jdbcTemplate; + + @Autowired + public void setDataSource(@Qualifier("mysqladjustmentDataSource") DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + public List> getAdvanceTypes() { + String sql = "SELECT id, adv_type FROM advance_type_list WHERE is_active = 1 AND is_delete = 0 ORDER BY CAST(order_by AS UNSIGNED) ASC, id ASC"; + try { + return jdbcTemplate.queryForList(sql); + } catch (Exception e) { + System.err.println("Error fetching advance types: " + e.getMessage()); + return new ArrayList<>(); + } + } + + public List> getFundTypes() { + String sql = "SELECT id, fund_type FROM fund_type_list WHERE is_active = 1 AND is_delete = 0 ORDER BY CAST(order_by AS UNSIGNED) ASC, id ASC"; + try { + return jdbcTemplate.queryForList(sql); + } catch (Exception e) { + System.err.println("Error fetching fund types: " + e.getMessage()); + return new ArrayList<>(); + } + } + + public List> getAllFiles() { + String sql = "SELECT a.*, " + + "adv.adv_type AS resolved_adv_type, " + + "f.fund_type AS resolved_fund_type " + + "FROM adjustment_file_details a " + + "LEFT JOIN advance_type_list adv ON TRIM(a.adv_type_id) REGEXP '^[0-9]+$' AND adv.id = CAST(TRIM(a.adv_type_id) AS UNSIGNED) " + + "LEFT JOIN fund_type_list f ON TRIM(a.fund_type_id) REGEXP '^[0-9]+$' AND f.id = CAST(TRIM(a.fund_type_id) AS UNSIGNED) " + + "WHERE a.is_active = 1 AND a.is_delete = 0 " + + "ORDER BY a.updated_date DESC, a.id DESC"; + try { + return jdbcTemplate.queryForList(sql); + } catch (Exception e) { + System.err.println("Error fetching adjustment files: " + e.getMessage()); + return new ArrayList<>(); + } + } + + public Map getAdjustmentFileById(int id) { + String sql = "SELECT * FROM adjustment_file_details WHERE id = ? AND is_active = 1 AND is_delete = 0"; + try { + return jdbcTemplate.queryForMap(sql, id); + } catch (Exception e) { + return null; + } + } + + public boolean saveAdjustmentFile(Map data, String username) { + java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + Object idVal = data.get("id"); + + if (idVal != null && !String.valueOf(idVal).trim().isEmpty()) { + // Update + int id = Integer.parseInt(String.valueOf(idVal).trim()); + String sql = "UPDATE adjustment_file_details SET " + + "as_file_gen_date = ?, as_file_path = ?, as_final_file_gen_date = ?, as_final_file_path = ?, " + + "bal_amt = ?, dc_file_path = ?, ejv_num = ?, ejv_num_file_gen_date = ?, ejv_num_file_path = ?, " + + "fi_amt = ?, fi_file_gen_date = ?, fi_file_path = ?, file_data_num = ?, file_path = ?, " + + "file_remark = ?, fin_year = ?, ir_file_path = ?, pi_amt = ?, pi_file_gen_date = ?, " + + "pi_file_path = ?, po_file_path = ?, rtgs_file_path = ?, sr_filepath = ?, tc_file_gen_date = ?, " + + "tc_file_path = ?, vend_name = ?, adv_type_id = ?, fund_type_id = ?, updated_date = ?, " + + "updated_by = ? WHERE id = ?"; + try { + int rows = jdbcTemplate.update(sql, + parseDate(data.get("as_file_gen_date")), + data.get("as_file_path"), + data.get("as_final_file_gen_date"), + data.get("as_final_file_path"), + parseDouble(data.get("bal_amt")), + data.get("dc_file_path"), + data.get("ejv_num"), + parseDate(data.get("ejv_num_file_gen_date")), + data.get("ejv_num_file_path"), + data.get("fi_amt"), + parseDate(data.get("fi_file_gen_date")), + data.get("fi_file_path"), + data.get("file_data_num"), + data.get("file_path"), + data.get("file_remark"), + data.get("fin_year"), + data.get("ir_file_path"), + parseDouble(data.get("pi_amt")), + parseDate(data.get("pi_file_gen_date")), + data.get("pi_file_path"), + data.get("po_file_path"), + data.get("rtgs_file_path"), + data.get("sr_filepath"), + parseDate(data.get("tc_file_gen_date")), + data.get("tc_file_path"), + data.get("vend_name"), + data.get("adv_type_id"), + data.get("fund_type_id"), + now, + username, + id + ); + return rows > 0; + } catch (Exception e) { + System.err.println("Error updating adjustment file: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } else { + // Insert + String sql = "INSERT INTO adjustment_file_details (" + + "as_file_gen_date, as_file_path, as_final_file_gen_date, as_final_file_path, " + + "bal_amt, dc_file_path, ejv_num, ejv_num_file_gen_date, ejv_num_file_path, " + + "fi_amt, fi_file_gen_date, fi_file_path, file_data_num, file_path, " + + "file_remark, fin_year, ir_file_path, pi_amt, pi_file_gen_date, " + + "pi_file_path, po_file_path, rtgs_file_path, sr_filepath, tc_file_gen_date, " + + "tc_file_path, vend_name, adv_type_id, fund_type_id, cdate, updated_date, " + + "updated_by, is_active, is_delete) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)"; + try { + int rows = jdbcTemplate.update(sql, + parseDate(data.get("as_file_gen_date")), + data.get("as_file_path"), + data.get("as_final_file_gen_date"), + data.get("as_final_file_path"), + parseDouble(data.get("bal_amt")), + data.get("dc_file_path"), + data.get("ejv_num"), + parseDate(data.get("ejv_num_file_gen_date")), + data.get("ejv_num_file_path"), + data.get("fi_amt"), + parseDate(data.get("fi_file_gen_date")), + data.get("fi_file_path"), + data.get("file_data_num"), + data.get("file_path"), + data.get("file_remark"), + data.get("fin_year"), + data.get("ir_file_path"), + parseDouble(data.get("pi_amt")), + parseDate(data.get("pi_file_gen_date")), + data.get("pi_file_path"), + data.get("po_file_path"), + data.get("rtgs_file_path"), + data.get("sr_filepath"), + parseDate(data.get("tc_file_gen_date")), + data.get("tc_file_path"), + data.get("vend_name"), + data.get("adv_type_id"), + data.get("fund_type_id"), + now, + now, + username + ); + return rows > 0; + } catch (Exception e) { + System.err.println("Error saving adjustment file: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + } + + public boolean deleteAdjustmentFile(int id, String remarks, String username) { + java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); + String sql = "UPDATE adjustment_file_details SET is_active = 0, is_delete = 1, file_remark = CONCAT(COALESCE(file_remark, ''), '\nDeleted: ', ?), updated_by = ?, updated_date = ? WHERE id = ?"; + try { + int rows = jdbcTemplate.update(sql, remarks, username, now, id); + return rows > 0; + } catch (Exception e) { + System.err.println("Error deleting adjustment file: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + private java.sql.Date parseDate(Object val) { + if (val == null || String.valueOf(val).trim().isEmpty()) { + return null; + } + try { + return java.sql.Date.valueOf(String.valueOf(val).trim()); + } catch (Exception e) { + return null; + } + } + + private Double parseDouble(Object val) { + if (val == null || String.valueOf(val).trim().isEmpty()) { + return null; + } + try { + return Double.parseDouble(String.valueOf(val).trim()); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java b/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java index a625a2a..9d8d826 100644 --- a/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java +++ b/src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java @@ -207,6 +207,7 @@ public class GccServicePaymentTrackerService { String myfollowup = (String) payload.get("myfollowup"); String orderBy = (String) payload.get("order_by"); String filePath = (String) payload.get("file_path"); + String paymentReview = (String) payload.get("payment_review"); if (idObj != null && !String.valueOf(idObj).trim().isEmpty()) { // Update @@ -219,8 +220,8 @@ public class GccServicePaymentTrackerService { String insertHistorySql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " - + "is_active, is_delete, order_by, file_path, cdate, updated_at, updated_by" - + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(insertHistorySql, old.get("id"), old.get("received_date"), @@ -241,6 +242,7 @@ public class GccServicePaymentTrackerService { old.get("is_delete"), old.get("order_by"), old.get("file_path"), + old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), userId); @@ -253,13 +255,13 @@ public class GccServicePaymentTrackerService { + "received_date = ?, vendor_name = ?, subject = ?, amount = ?, invoice_no = ?, " + "receiptno = ?, invoice_date = ?, type = ?, physical_file_no = ?, remarks = ?, " + "tat = ?, status = ?, followup = ?, myfollowup = ?, order_by = ?, file_path = ?, " - + "updated_by = ?, updated_date = ? " + + "payment_review = ?, updated_by = ?, updated_date = ? " + "WHERE id = ? AND is_active = 1 AND is_delete = 0"; try { int rows = jdbcTemplate.update(sql, receivedDate, vendorName, subject, amount, invoiceNo, receiptNo, invoiceDate, type, physicalFileNo, remarks, tat, status, followup, myfollowup, orderBy, filePath, - userId, new java.sql.Timestamp(System.currentTimeMillis()), id); + paymentReview, userId, new java.sql.Timestamp(System.currentTimeMillis()), id); return rows > 0; } catch (Exception e) { System.err.println("Error updating payment tracking detail ID " + id + ": " + e.getMessage()); @@ -271,14 +273,14 @@ public class GccServicePaymentTrackerService { + "received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, " + "followup, myfollowup, is_active, is_delete, order_by, file_path, " - + "updated_by, updated_date, cdate" - + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?, ?, ?, ?, ?)"; + + "payment_review, updated_by, updated_date, cdate" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?, ?, ?, ?, ?, ?)"; try { java.sql.Timestamp now = new java.sql.Timestamp(System.currentTimeMillis()); int rows = jdbcTemplate.update(sql, receivedDate, vendorName, subject, amount, invoiceNo, receiptNo, invoiceDate, type, physicalFileNo, remarks, tat, status, followup, myfollowup, orderBy, filePath, - userId, now, now); + paymentReview, userId, now, now); return rows > 0; } catch (Exception e) { System.err.println("Error saving payment tracking detail: " + e.getMessage()); @@ -306,8 +308,8 @@ public class GccServicePaymentTrackerService { String histSql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " - + "is_active, is_delete, order_by, file_path, cdate, updated_at, updated_by" - + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(histSql, id, old.get("received_date"), @@ -328,6 +330,7 @@ public class GccServicePaymentTrackerService { 1, // is_delete = 1 old.get("order_by"), old.get("file_path"), + old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), updatedBy); @@ -488,8 +491,8 @@ public class GccServicePaymentTrackerService { String histSql = "INSERT INTO payment_tracking_details_history (" + "detail_id, received_date, vendor_name, subject, amount, invoice_no, receiptno, " + "invoice_date, type, physical_file_no, remarks, tat, status, followup, myfollowup, " - + "is_active, is_delete, order_by, file_path, cdate, updated_at, updated_by" - + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + "is_active, is_delete, order_by, file_path, payment_review, cdate, updated_at, updated_by" + + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; jdbcTemplate.update(histSql, id, old.get("received_date"), @@ -510,6 +513,7 @@ public class GccServicePaymentTrackerService { 0, // is_delete = 0 old.get("order_by"), old.get("file_path"), + old.get("payment_review"), old.get("cdate"), new java.sql.Timestamp(System.currentTimeMillis()), updatedBy); diff --git a/src/main/java/com/gcc_manpower/service/AppUserService.java b/src/main/java/com/gcc_manpower/service/AppUserService.java index 92202e4..35e2d66 100644 --- a/src/main/java/com/gcc_manpower/service/AppUserService.java +++ b/src/main/java/com/gcc_manpower/service/AppUserService.java @@ -135,7 +135,9 @@ public class AppUserService { public List> getVendorDetailsWithCounts() { String sql = "SELECT id, vendor_name, description, total_employees, orderby, cdate, isactive, isdelete, " + - "(SELECT COUNT(*) FROM employee_details e WHERE (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) AND e.isactive = 1 AND e.isdelete = 0) AS actual_employee_count " + "(SELECT COUNT(*) FROM employee_details e LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) WHERE (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) AND (e.user_type != '3' AND (ut.usertype IS NULL OR ut.usertype != 'Standby Employees')) AND e.isactive = 1 AND e.isdelete = 0) AS actual_employee_count, " + + + "(SELECT COUNT(*) FROM employee_details e LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) WHERE (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) AND (e.user_type = '3' OR ut.usertype = 'Standby Employees') AND e.isactive = 1 AND e.isdelete = 0) AS standby_employee_count " + "FROM vendor_details v " + "WHERE v.isactive = 1 AND v.isdelete = 0 " + @@ -168,7 +170,7 @@ public class AppUserService { } public int getOverallActiveEmployeeCount() { - String sql = "SELECT COUNT(*) FROM employee_details WHERE isactive = 1 AND isdelete = 0"; + String sql = "SELECT COUNT(*) FROM employee_details e LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) WHERE (e.user_type != '3' AND (ut.usertype IS NULL OR ut.usertype != 'Standby Employees')) AND e.isactive = 1 AND e.isdelete = 0"; try { Integer count = jdbcManpowerTemplate.queryForObject(sql, Integer.class); return count != null ? count : 0; @@ -242,6 +244,7 @@ public class AppUserService { " COALESCE(u.usr_name, e.updated_by) AS updated_by_name, " + " COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + " COALESCE(ut.usertype, e.user_type) AS user_type, " + + " e.user_type AS user_type_id, " + " COALESCE(des.designation, e.designation) AS designation, " + " COALESCE(loc.location_name, e.location) AS location, " + " COALESCE(des.File_no, '-') AS work_order, " + @@ -256,7 +259,8 @@ public class AppUserService { + "LEFT JOIN app_user u ON (e.updated_by = CAST(u.usr_id AS CHAR) OR e.updated_by = u.usr_name) " + - "WHERE (e.vendor_id = ? OR e.vendor_id = ?) AND e.isactive = 1 AND e.isdelete = 0 " + + "WHERE ((e.vendor_id = ? OR e.vendor_id = ?) OR (e.user_type = '3' OR ut.usertype = 'Standby Employees')) AND e.isactive = 1 AND e.isdelete = 0 " + + "ORDER BY e.name"; try { return jdbcManpowerTemplate.queryForList(sql, vendorId != null ? vendorId : "", vendorName); @@ -282,6 +286,7 @@ public class AppUserService { " COALESCE(u.usr_name, e.updated_by) AS updated_by_name, " + " COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + " COALESCE(ut.usertype, e.user_type) AS user_type, " + + " e.user_type AS user_type_id, " + " COALESCE(des.designation, e.designation) AS designation, " + " COALESCE(loc.location_name, e.location) AS location, " + " COALESCE(des.File_no, '-') AS work_order, " + @@ -332,43 +337,52 @@ public class AppUserService { System.err.println("Error checking duplicate employee code: " + e.getMessage()); } - // Check designation limit - String designationIdOrName = String.valueOf(emp.get("designation")); - String vendorId = String.valueOf(emp.get("vendor_id")); + // Check designation limit (Skip limit validation for standby employees) + if (!isStandbyUserType(emp.get("user_type"))) { + String designationIdOrName = String.valueOf(emp.get("designation")); + String vendorId = String.valueOf(emp.get("vendor_id")); - String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " - + - "WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0"; - List> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId, designationIdOrName, - designationIdOrName); - if (rows != null && !rows.isEmpty()) { - for (Map row : rows) { - String desId = String.valueOf(row.get("id")); - String desName = (String) row.get("designation"); - String tenderStr = (String) row.get("tender_manpower_designation_wise"); + String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " + + + "WHERE vid = ? AND (CAST(id AS CHAR) = ? OR designation = ?) AND isactive = 1 AND isdelete = 0"; + List> rows = jdbcManpowerTemplate.queryForList(resolveSql, vendorId, + designationIdOrName, + designationIdOrName); + if (rows != null && !rows.isEmpty()) { + for (Map row : rows) { + String desId = String.valueOf(row.get("id")); + String desName = (String) row.get("designation"); + String tenderStr = (String) row.get("tender_manpower_designation_wise"); - // Fetch the count of active, non-deleted employees for this designation/vendor - String countSql = "SELECT COUNT(*) FROM employee_details e " + - "WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " - + - " AND (e.designation = ? OR e.designation = ?) " + - " AND e.isactive = 1 AND e.isdelete = 0"; + // Fetch the count of active, regular (non-standby) employees for this + // designation/vendor + String countSql = "SELECT COUNT(*) FROM employee_details e " + + "LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + + + "WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " + + + " AND (e.designation = ? OR e.designation = ?) " + + " AND (e.user_type != '3' AND (ut.usertype IS NULL OR ut.usertype != 'Standby Employees')) " + + + " AND e.isactive = 1 AND e.isdelete = 0"; - Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId, - desId, desName); - int currentActive = activeCount != null ? activeCount : 0; + Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, + vendorId, + desId, desName); + int currentActive = activeCount != null ? activeCount : 0; - int tenderLimit = 0; - if (tenderStr != null) { - try { - tenderLimit = Integer.parseInt(tenderStr.trim()); - } catch (NumberFormatException ignored) { + int tenderLimit = 0; + if (tenderStr != null) { + try { + tenderLimit = Integer.parseInt(tenderStr.trim()); + } catch (NumberFormatException ignored) { + } } - } - if (currentActive >= tenderLimit) { - throw new RuntimeException("Manpower limit reached for designation '" + desName - + "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive); + if (currentActive >= tenderLimit) { + throw new RuntimeException("Manpower limit reached for designation '" + desName + + "' under the selected vendor. Limit: " + tenderLimit + ", Current: " + currentActive); + } } } } @@ -424,7 +438,7 @@ public class AppUserService { String newDes = String.valueOf(emp.get("designation")); String newVendor = String.valueOf(emp.get("vendor_id")); - if (!oldDes.equals(newDes) || !oldVendor.equals(newVendor)) { + if (!isStandbyUserType(emp.get("user_type")) && (!oldDes.equals(newDes) || !oldVendor.equals(newVendor))) { // Check designation limit for new designation & vendor String resolveSql = "SELECT id, designation, tender_manpower_designation_wise FROM employee_workorder_details " + @@ -436,10 +450,15 @@ public class AppUserService { String desName = (String) row.get("designation"); String tenderStr = (String) row.get("tender_manpower_designation_wise"); + // Fetch regular active count only String countSql = "SELECT COUNT(*) FROM employee_details e " + + "LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + + "WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " + " AND (e.designation = ? OR e.designation = ?) " + + " AND (e.user_type != '3' AND (ut.usertype IS NULL OR ut.usertype != 'Standby Employees')) " + + " AND e.isactive = 1 AND e.isdelete = 0"; Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, newVendor, @@ -528,8 +547,10 @@ public class AppUserService { "FROM employee_details_update_history h " + "LEFT JOIN vendor_details v ON (h.vendor_id = CAST(v.id AS CHAR) OR h.vendor_id = v.vendor_name) " + "LEFT JOIN user_type_details ut ON (h.user_type = CAST(ut.id AS CHAR) OR h.user_type = ut.usertype) " + - "LEFT JOIN employee_workorder_details des ON (h.designation = CAST(des.id AS CHAR) OR h.designation = des.designation) " + - "LEFT JOIN employee_location_details loc ON (h.location = CAST(loc.id AS CHAR) OR h.location = loc.location_name) " + + "LEFT JOIN employee_workorder_details des ON (h.designation = CAST(des.id AS CHAR) OR h.designation = des.designation) " + + + "LEFT JOIN employee_location_details loc ON (h.location = CAST(loc.id AS CHAR) OR h.location = loc.location_name) " + + "LEFT JOIN app_user u ON (h.updated_by = CAST(u.usr_id AS CHAR) OR h.updated_by = u.usr_name) " + "WHERE h.emp_code = ? " + "ORDER BY h.updated_date DESC"; @@ -685,16 +706,37 @@ public class AppUserService { String desName = (String) row.get("designation"); String tenderStr = (String) row.get("tender_manpower_designation_wise"); - // Count active employees for this vendor and designation - String countSql = "SELECT COUNT(*) FROM employee_details e " + + // Count active regular (tender) employees (excludes standby employees) + String countRegularSql = "SELECT COUNT(*) FROM employee_details e " + + "LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + + "WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " + " AND (e.designation = ? OR e.designation = ?) " + + " AND (e.user_type != '3' AND (ut.usertype IS NULL OR ut.usertype != 'Standby Employees')) " + " AND e.isactive = 1 AND e.isdelete = 0"; - Integer activeCount = jdbcManpowerTemplate.queryForObject(countSql, Integer.class, vendorId, vendorId, + Integer regularActiveCount = jdbcManpowerTemplate.queryForObject(countRegularSql, Integer.class, + vendorId, vendorId, desId, desName); - int available = activeCount != null ? activeCount : 0; + int regularCount = regularActiveCount != null ? regularActiveCount : 0; + + // Count active standby employees + String countStandbySql = "SELECT COUNT(*) FROM employee_details e " + + "LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " + + + "WHERE (e.vendor_id = ? OR e.vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " + + + " AND (e.designation = ? OR e.designation = ?) " + + " AND (e.user_type = '3' OR ut.usertype = 'Standby Employees') " + + " AND e.isactive = 1 AND e.isdelete = 0"; + + Integer standbyActiveCount = jdbcManpowerTemplate.queryForObject(countStandbySql, Integer.class, + vendorId, vendorId, + desId, desName); + int standbyCount = standbyActiveCount != null ? standbyActiveCount : 0; + + int available = regularCount; int tender = 0; if (tenderStr != null) { @@ -703,10 +745,11 @@ public class AppUserService { } catch (NumberFormatException ignored) { } } - int vacant = tender - available; + int vacant = tender - regularCount; - String updateSql = "UPDATE employee_workorder_details SET available_manpower = ?, vacant_count = ? WHERE id = ?"; - jdbcManpowerTemplate.update(updateSql, String.valueOf(available), String.valueOf(vacant), desId); + String updateSql = "UPDATE employee_workorder_details SET available_manpower = ?, vacant_count = ?, standby_employees_count = ? WHERE id = ?"; + jdbcManpowerTemplate.update(updateSql, String.valueOf(available), String.valueOf(vacant), + String.valueOf(standbyCount), desId); } } catch (Exception e) { System.err.println("Error recalculating designation manpower for designation: " + designationIdOrName @@ -793,6 +836,7 @@ public class AppUserService { " COALESCE(u.usr_name, e.updated_by) AS updated_by_name, " + " COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + " COALESCE(ut.usertype, e.user_type) AS user_type, " + + " e.user_type AS user_type_id, " + " COALESCE(des.designation, e.designation) AS designation, " + " COALESCE(loc.location_name, e.location) AS location, " + " COALESCE(des.File_no, '-') AS work_order " + @@ -805,7 +849,7 @@ public class AppUserService { + "LEFT JOIN app_user u ON (e.updated_by = CAST(u.usr_id AS CHAR) OR e.updated_by = u.usr_name) " + - "WHERE e.vendor_id = ? OR e.vendor_id = ? " + + "WHERE e.vendor_id = ? OR e.vendor_id = ? OR e.user_type = '3' OR ut.usertype = 'Standby Employees' " + "ORDER BY e.name"; try { return jdbcManpowerTemplate.queryForList(sql, vendorId, vendorName != null ? vendorName : ""); @@ -855,6 +899,7 @@ public class AppUserService { " COALESCE(u.usr_name, e.updated_by) AS updated_by_name, " + " COALESCE(v.vendor_name, e.vendor_id) AS vendor_name, " + " COALESCE(ut.usertype, e.user_type) AS user_type, " + + " e.user_type AS user_type_id, " + " COALESCE(des.designation, e.designation) AS designation, " + " COALESCE(loc.location_name, e.location) AS location, " + " COALESCE(des.File_no, '-') AS work_order " + @@ -985,4 +1030,25 @@ public class AppUserService { return new ArrayList<>(); } } + + private boolean isStandbyUserType(Object userTypeObj) { + String userTypeStr = userTypeObj != null ? String.valueOf(userTypeObj).trim() : ""; + if ("3".equals(userTypeStr) || "Standby Employees".equalsIgnoreCase(userTypeStr)) { + return true; + } + try { + String checkStandbySql = "SELECT usertype FROM user_type_details WHERE id = ? OR usertype = ?"; + List types = jdbcManpowerTemplate.queryForList(checkStandbySql, String.class, userTypeStr, + userTypeStr); + if (types != null && !types.isEmpty()) { + for (String t : types) { + if ("Standby Employees".equalsIgnoreCase(t)) { + return true; + } + } + } + } catch (Exception ignored) { + } + return false; + } } diff --git a/src/main/resources/static/assets/css/home-styles.css b/src/main/resources/static/assets/css/home-styles.css index 994e335..89edef6 100644 --- a/src/main/resources/static/assets/css/home-styles.css +++ b/src/main/resources/static/assets/css/home-styles.css @@ -823,4 +823,32 @@ justify-content: center; margin-top: 5px; } +} + +/* Standby Employee Count Interactive Badge */ +.emp-count-badge-standby { + background: linear-gradient(135deg, #f39c12, #e67e22); + color: #ffffff; + font-weight: 800; + font-size: 1.05rem; + padding: 8px 18px; + border-radius: 30px; + cursor: pointer; + border: none; + box-shadow: 0 4px 10px rgba(230, 126, 34, 0.25); + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.emp-count-badge-standby:hover { + transform: scale(1.08); + box-shadow: 0 6px 15px rgba(230, 126, 34, 0.4); + background: linear-gradient(135deg, #e67e22, #f39c12); + color: #ffffff; +} + +.emp-count-badge-standby:active { + transform: scale(0.96); } \ No newline at end of file diff --git a/src/main/resources/templates/fragments/adjustment.html b/src/main/resources/templates/fragments/adjustment.html new file mode 100644 index 0000000..79da189 --- /dev/null +++ b/src/main/resources/templates/fragments/adjustment.html @@ -0,0 +1,22 @@ + + + + + + + +
+
+ Greater Chennai Corporation Logo +
+

GREATER

+

CHENNAI

+

CORPORATION

+

பெருநகர சென்னை மாநகராட்சி

+
+
+
+

GCC Adjustment Management

+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/gccadjustmentmanagement/dashboard.html b/src/main/resources/templates/gccadjustmentmanagement/dashboard.html new file mode 100644 index 0000000..2430001 --- /dev/null +++ b/src/main/resources/templates/gccadjustmentmanagement/dashboard.html @@ -0,0 +1,1661 @@ + + + + + + + + Adjustment File Management System + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+ Greater Chennai Corporation Logo +
+

GREATER

+

CHENNAI

+

CORPORATION

+

பெருநகர சென்னை மாநகராட்சி

+
+
+
+

Adjustment File Management System

+
+
+ + + Guest + + + Logout + +
+
+ + +
+ +
+ + + + +
+
+ +
+
+ +
+ + +
+
+
+ Adjustment Summary Counts +
+
+ +
+
+
+ + +
+
+ +
+ +
+ + +

New + File Registration

+ +
+ + + + +
+ + + + +
+ + +
+ + +
+ + +
+ + +
+ +
+
+ Choose File +
+ +
+ +
+
+
+ + +
+ + +
+ + +
+ +
+
+ Choose File +
+ +
+ +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + +
+ + +
+ + +
+ + +
+
+ +
+ +
+
+
+
+ + +
+
+
+
+ Existing + Adjustment Files +
+
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + +
IDVendor NameFin YearFile NumAdvance TypeFund TypeBal AmountPI AmountFI AmountEJV NumberLast UpdatedActions
+
+
+
+ + +
+
+
+
+ Elcot OC Files Registry +
+
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
IDVendorFin YearFile NumOriginal FilePO FileDC FileInward FileStock FileTemp BillRTGS FileEJV FileFinal AS File
+
+
+
+ +
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/gccadjustmentmanagement/login.html b/src/main/resources/templates/gccadjustmentmanagement/login.html new file mode 100644 index 0000000..c962ca0 --- /dev/null +++ b/src/main/resources/templates/gccadjustmentmanagement/login.html @@ -0,0 +1,89 @@ + + + + + + + GCC Adjustment Management + + + + + + + + + + + + + + +
+ +

GREATER CHENNAI CORPORATION

+

பெருநகர சென்னை மாநகராட்சி

+
+

GCC Adjustment Management

+
+

Login

+ +
+
+ +
+
+
+ +
+
+ +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/gccservicepaymenttracker/dashboard.html b/src/main/resources/templates/gccservicepaymenttracker/dashboard.html index 7b7b592..f172030 100644 --- a/src/main/resources/templates/gccservicepaymenttracker/dashboard.html +++ b/src/main/resources/templates/gccservicepaymenttracker/dashboard.html @@ -1355,6 +1355,7 @@ Age of Bill (Days) Status Remarks + Payment Review Last Updated At @@ -1555,6 +1556,14 @@ placeholder="Additional remarks..."> + +
+ + +
+
@@ -1595,6 +1604,7 @@ Amount Status Remarks + Payment Review Last Updated At Last Updated By Document @@ -1774,6 +1784,7 @@ Age of Bill (Days) Status Deletion Remarks + Payment Review Deleted Date Deleted By Actions @@ -1803,6 +1814,20 @@ let activeCategory = 'all'; let allRecords = []; + + function getLocalDateString(dateTimeStr) { + if (!dateTimeStr) return null; + try { + const d = new Date(dateTimeStr); + if (isNaN(d.getTime())) return null; + const dd = String(d.getDate()).padStart(2, '0'); + const MM = String(d.getMonth() + 1).padStart(2, '0'); + const yyyy = d.getFullYear(); + return `${yyyy}-${MM}-${dd}`; + } catch (e) { + return null; + } + } let currentFilteredRecords = []; let recordModalObj = null; let historyModalObj = null; @@ -2510,6 +2535,7 @@ ${calculateAgeOfBill(row.received_date)} ${text(dispStatus)} ${text(row.remarks)} + ${text(row.payment_review)} ${formatDateTime(row.updated_date)} ${text(row.updated_by_name || '-')} `; @@ -2855,6 +2881,7 @@ const orderbyEl = document.getElementById('form-orderby'); if (orderbyEl) orderbyEl.value = row.order_by || ''; document.getElementById('form-remarks').value = row.remarks || ''; + document.getElementById('form-payment-review').value = row.payment_review || ''; // Set vendor select (using ID or legacy fallback/custom) const select = document.getElementById('form-vendor-select'); @@ -2940,6 +2967,7 @@ const orderbyEl = document.getElementById('form-orderby'); const order_by = orderbyEl ? orderbyEl.value : (originalRecord ? originalRecord.order_by : ''); const remarks = document.getElementById('form-remarks').value; + const payment_review = document.getElementById('form-payment-review').value; // Resolve vendor const vendorSelect = document.getElementById('form-vendor-select').value; @@ -2987,7 +3015,7 @@ const payload = { id, received_date, type, vendor_name: vendorName, subject, amount, invoice_no, receiptno, invoice_date, physical_file_no, tat, status, followup, - myfollowup, order_by, remarks, file_path: filePath + myfollowup, order_by, remarks, payment_review, file_path: filePath }; const csrfHeaders = getCsrfHeaders(); @@ -3930,7 +3958,7 @@ } if (clickType === 'latest_updates') { - if (row.updated_date !== clickValue) return false; + if (getLocalDateString(row.updated_date) !== clickValue) return false; } return true; @@ -4151,6 +4179,7 @@ ${calculateAgeOfBill(row.received_date)} ${text(dispStatus)} ${text(row.remarks)} + ${text(row.payment_review)} ${formatDateTime(row.updated_date)} ${text(row.updated_by_name)} `; @@ -4328,6 +4357,7 @@ Age of Bill (Days) Status Remarks + Payment Review Last Updated At Last Updated By @@ -4413,6 +4443,7 @@ ${calculateAgeOfBill(row.received_date)} ${text(dispStatus)} ${text(row.remarks)} + ${text(row.payment_review)} ${formatDateTime(row.updated_date)} ${text(row.updated_by_name || '-')} `; @@ -4454,10 +4485,12 @@ return; } - // Count how many files share this exact latest update date/time stamp + const latestDateOnly = getLocalDateString(maxDateStr); + + // Count how many files share this exact latest update date stamp let count = 0; allRecords.forEach(r => { - if (r.updated_date === maxDateStr) { + if (r.updated_date && getLocalDateString(r.updated_date) === latestDateOnly) { count++; } }); @@ -4481,7 +4514,7 @@ if (timeEl) timeEl.textContent = formatDateTimeHelper(maxDateStr); badge.style.setProperty('display', 'flex', 'important'); - badge.setAttribute('data-latest-date', maxDateStr); + badge.setAttribute('data-latest-date', latestDateOnly); } function openLatestUpdatesModal() { @@ -4490,7 +4523,16 @@ const latestDate = badge.getAttribute('data-latest-date'); if (!latestDate) return; - openModalForCount('latest_updates', latestDate, 'Latest Updates'); + let dateDisplay = '-'; + try { + const d = new Date(latestDate); + if (!isNaN(d.getTime())) { + const pad = n => String(n).padStart(2, '0'); + dateDisplay = `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()}`; + } + } catch (e) {} + + openModalForCount('latest_updates', latestDate, `Latest Updates (${dateDisplay})`); } function escapeHtml(str) { @@ -4683,6 +4725,7 @@ ₹ ${text(item.amount)} ${text(dispStatus)} ${text(item.remarks)} + ${text(item.payment_review)} ${formatDateTime(item.updated_at)} ${text(item.updated_by_name || '-')} ${docBtnHtml} @@ -4826,6 +4869,7 @@ ${calculateAgeOfBill(row.received_date)} ${text(dispStatus)} ${text(row.remarks)} + ${text(row.payment_review)} ${formatDateTime(row.updated_date)} ${text(row.updated_by_name)} `; diff --git a/src/main/resources/templates/gccservicepaymenttracker/details.html b/src/main/resources/templates/gccservicepaymenttracker/details.html index 3da284f..b0b3404 100644 --- a/src/main/resources/templates/gccservicepaymenttracker/details.html +++ b/src/main/resources/templates/gccservicepaymenttracker/details.html @@ -446,6 +446,7 @@ style="max-width: 90px;">STATUS REMARKS + PAYMENT REVIEW LAST
UPDATED AT @@ -586,6 +587,12 @@
+
+ + +
@@ -626,6 +633,7 @@ AMOUNT STATUS REMARKS + PAYMENT REVIEW LAST
UPDATED AT LAST UPDATED
BY ACTIONS @@ -707,6 +715,7 @@ AGE OF
BILL
(DAYS) STATUS DELETION
REMARKS + PAYMENT REVIEW DELETED
DATE DELETED
BY ACTIONS @@ -735,6 +744,20 @@ // Data Caches let allRecords = []; + + function getLocalDateString(dateTimeStr) { + if (!dateTimeStr) return null; + try { + const d = new Date(dateTimeStr); + if (isNaN(d.getTime())) return null; + const dd = String(d.getDate()).padStart(2, '0'); + const MM = String(d.getMonth() + 1).padStart(2, '0'); + const yyyy = d.getFullYear(); + return `${yyyy}-${MM}-${dd}`; + } catch (e) { + return null; + } + } let clickFilteredRecords = []; let filteredRecords = []; let activeTypes = []; @@ -1115,7 +1138,7 @@ } if (urlClickType === 'latest_updates') { - if (cleanStr(row.updated_date) !== cleanStr(parsedClickValue)) return false; + if (getLocalDateString(row.updated_date) !== parsedClickValue) return false; } return true; @@ -1497,6 +1520,7 @@ ${ageDays} ${statusBadge} ${escapeHtml(text(row.remarks))} + ${escapeHtml(text(row.payment_review))} ${updatedSplit} ${escapeHtml(text(row.updated_by_name || row.updated_by))} `; @@ -1693,6 +1717,8 @@ const remEl = document.getElementById('form-remarks'); if (remEl) remEl.value = row.remarks || ''; + const revEl = document.getElementById('form-payment-review'); + if (revEl) revEl.value = row.payment_review || ''; // Set Vendor Select const select = document.getElementById('form-vendor-select'); @@ -1786,6 +1812,7 @@ const physical_file_no = document.getElementById('form-physical-file-no')?.value; const status = document.getElementById('form-status')?.value; const remarks = document.getElementById('form-remarks')?.value; + const payment_review = document.getElementById('form-payment-review')?.value; let originalRecord = id ? allRecords.find(r => String(r.id) === String(id)) : null; @@ -1826,7 +1853,7 @@ const payload = { id, received_date, type, vendor_name: vendorName, subject, amount, invoice_no, - receiptno, invoice_date, physical_file_no, status, remarks, file_path: filePath + receiptno, invoice_date, physical_file_no, status, remarks, payment_review, file_path: filePath }; const csrfHeaders = getCsrfHeaders(); @@ -1908,6 +1935,7 @@ ₹ ${text(item.amount)} ${escapeHtml(dispStatus)} ${text(item.remarks)} + ${text(item.payment_review)} ${formatDateTimeSplit(item.updated_at)} ${text(item.updated_by_name || '-')} ${docBtnHtml} @@ -2028,6 +2056,7 @@ ${calculateAgeOfBill(row.received_date)} ${escapeHtml(dispStatus)} ${text(row.remarks)} + ${text(row.payment_review)} ${formatDateTimeSplit(row.deleted_at)} ${text(row.deleted_by)} `; @@ -2228,6 +2257,7 @@ "AGE OF BILL (DAYS)": ageDays, "STATUS": statusName, "REMARKS": row.remarks || '-', + "PAYMENT REVIEW": row.payment_review || '-', "LAST UPDATED AT": updatedDateTime, "LAST UPDATED BY": row.updated_by_name || row.updated_by || '-' }; @@ -2250,6 +2280,7 @@ { wch: 18 }, // AGE OF BILL { wch: 25 }, // STATUS { wch: 35 }, // REMARKS + { wch: 35 }, // PAYMENT REVIEW { wch: 20 }, // LAST UPDATED AT { wch: 18 } // LAST UPDATED BY ]; diff --git a/src/main/resources/templates/home/home.html b/src/main/resources/templates/home/home.html index 916c227..07a560a 100644 --- a/src/main/resources/templates/home/home.html +++ b/src/main/resources/templates/home/home.html @@ -323,6 +323,18 @@ 0
+
+ + Standby Count: + + +
@@ -338,8 +350,8 @@ style="border-radius: 14px; border: none; box-shadow: 0 10px 40px rgba(0,0,0,0.2);"> + + + + + +