Browse Source

added updated by in all view options and added history

master
yoga-agoy 3 months ago
parent
commit
a0123a8f43
  1. 3
      src/main/java/com/gcc_manpower/config/DataSourceConfig.java
  2. 119
      src/main/java/com/gcc_manpower/controller/HomeController.java
  3. 49
      src/main/java/com/gcc_manpower/gccservicepaymenttracker/controller/GccServicePaymentTrackerController.java
  4. 60
      src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java
  5. 155
      src/main/java/com/gcc_manpower/service/AppUserService.java
  6. 207
      src/main/resources/templates/gccservicepaymenttracker/dashboard.html
  7. 210
      src/main/resources/templates/home/home.html
  8. 75
      src/main/resources/templates/home/view-login-details.html

3
src/main/java/com/gcc_manpower/config/DataSourceConfig.java

@ -14,8 +14,7 @@ public class DataSourceConfig {
private static String dbpassword = "root";
// AWS
// private static String host =
// "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
// private static String host = "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306";
// private static String dbpassword = "gcc-facial-password";
@Bean(name = "mysqlManpowerDataSource")

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

@ -15,6 +15,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@Controller
@RequestMapping("/")
@ -23,6 +26,9 @@ public class HomeController {
@Autowired
private AppUserService appUserService;
@Value("${upload.path:gcc_manpower_uploads}")
private String uploadPathConfig;
private void addSummaryStats(Model model, List<Map<String, Object>> vendorDetails) {
int totalVendors = vendorDetails.size();
int totalTenderCount = appUserService.getOverallTenderCount();
@ -247,7 +253,7 @@ public class HomeController {
}
try {
boolean success = appUserService.addEmployee(payload);
boolean success = appUserService.addEmployee(payload, usrName);
if (success) {
response.put("success", true);
response.put("message", "Employee registered successfully!");
@ -279,7 +285,7 @@ public class HomeController {
}
try {
boolean success = appUserService.updateEmployee(payload);
boolean success = appUserService.updateEmployee(payload, usrName);
if (success) {
response.put("success", true);
response.put("message", "Employee updated successfully!");
@ -297,7 +303,18 @@ public class HomeController {
@PostMapping("/api/vendor/add-work-order")
@ResponseBody
public Map<String, Object> addVendorWorkOrder(
@RequestBody Map<String, Object> payload,
@RequestParam("vendorType") String vendorType,
@RequestParam(value = "vendorId", required = false) String vendorIdStr,
@RequestParam(value = "vendorName", required = false) String vendorName,
@RequestParam("description") String description,
@RequestParam("contractorName") String contractorName,
@RequestParam("fileNo") String fileNo,
@RequestParam("subject") String subject,
@RequestParam("issueDate") String issueDate,
@RequestParam("endDate") String endDate,
@RequestParam("totalEmployees") String totalEmployees,
@RequestParam(value = "orderby", required = false) Integer orderby,
@RequestParam(value = "file", required = false) MultipartFile file,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
@ -311,25 +328,6 @@ public class HomeController {
}
try {
String vendorType = (String) payload.get("vendorType");
String vendorIdStr = (String) payload.get("vendorId");
String vendorName = (String) payload.get("vendorName");
String description = (String) payload.get("description");
String contractorName = (String) payload.get("contractorName");
String fileNo = (String) payload.get("fileNo");
String subject = (String) payload.get("subject");
String issueDate = (String) payload.get("issueDate");
String endDate = (String) payload.get("endDate");
String totalEmployees = (String) payload.get("totalEmployees");
Integer orderby = null;
if (payload.get("orderby") != null) {
try {
orderby = ((Number) payload.get("orderby")).intValue();
} catch (Exception ignored) {
}
}
// Convert to uppercase for Contractor Name and File No
if (contractorName != null) {
contractorName = contractorName.toUpperCase();
@ -378,6 +376,35 @@ public class HomeController {
}
}
// Save upload file (mandatory validation)
if (file == null || file.isEmpty()) {
response.put("success", false);
response.put("error", "Work order PDF file is required.");
return response;
}
File uploadDir = new File(uploadPathConfig).getAbsoluteFile();
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String originalFilename = file.getOriginalFilename();
String nameWithoutExt = "";
String ext = ".pdf";
if (originalFilename != null && originalFilename.contains(".")) {
int dotIdx = originalFilename.lastIndexOf(".");
nameWithoutExt = originalFilename.substring(0, dotIdx);
ext = originalFilename.substring(dotIdx);
} else {
nameWithoutExt = originalFilename != null ? originalFilename : "file";
}
nameWithoutExt = nameWithoutExt.replaceAll("[^a-zA-Z0-9-_]", "_");
String savedFilename = nameWithoutExt + "_" + System.currentTimeMillis() + ext;
File destFile = new File(uploadDir, savedFilename).getAbsoluteFile();
file.transferTo(destFile);
String fileWebUrl = "/gcc_manpower_uploads/" + savedFilename;
// Insert into vendor_details_work_order
boolean success = appUserService.addVendorWorkOrder(
String.valueOf(finalVendorId),
@ -388,7 +415,9 @@ public class HomeController {
issueDate,
endDate,
totalEmployees,
orderby);
orderby,
fileWebUrl,
usrName);
if (success) {
// If it's an existing vendor, add total_employees to vendor_details
@ -444,7 +473,7 @@ public class HomeController {
return response;
}
boolean success = appUserService.deleteEmployee(empCode);
boolean success = appUserService.deleteEmployee(empCode, usrName);
if (success) {
response.put("success", true);
response.put("message", "Employee deleted successfully!");
@ -499,6 +528,46 @@ public class HomeController {
return response;
}
@PostMapping("/api/vendor/delete-work-order")
@ResponseBody
public Map<String, Object> deleteWorkOrder(
@RequestBody Map<String, Object> payload,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String usrName = (String) session.getAttribute("usrName");
String usrRole = (String) session.getAttribute("usrRole");
boolean isAdmin = "ADMIN".equalsIgnoreCase(usrRole);
if (usrName == null || !isAdmin) {
response.put("success", false);
response.put("error", "Unauthorized! Admin access required.");
return response;
}
try {
Object idObj = payload.get("workOrderId");
if (idObj == null) {
response.put("success", false);
response.put("error", "Work order ID is required.");
return response;
}
String workOrderId = String.valueOf(idObj);
boolean success = appUserService.deleteWorkOrder(workOrderId, usrName);
if (success) {
response.put("success", true);
response.put("message", "Work order deleted successfully!");
} else {
response.put("success", false);
response.put("error", "Failed to delete work order or record not found.");
}
} catch (Exception e) {
response.put("success", false);
response.put("error", "Error: " + e.getMessage());
}
return response;
}
@GetMapping("/api/vendor-employees-all")
@ResponseBody
public List<Map<String, Object>> getVendorEmployeesAll(
@ -530,7 +599,7 @@ public class HomeController {
return response;
}
boolean success = appUserService.restoreEmployee(empCode);
boolean success = appUserService.restoreEmployee(empCode, usrName);
if (success) {
response.put("success", true);
response.put("message", "Employee restored successfully!");

49
src/main/java/com/gcc_manpower/gccservicepaymenttracker/controller/GccServicePaymentTrackerController.java

@ -124,14 +124,20 @@ public class GccServicePaymentTrackerController {
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String username = (String) session.getAttribute("usrName");
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
String usrRole = (String) session.getAttribute("usrRole");
String usrRole = (String) session.getAttribute("pay_usrRole");
if (usrRole == null) {
usrRole = (String) session.getAttribute("usrRole");
}
if (!"ADMIN".equalsIgnoreCase(usrRole)) {
response.put("success", false);
response.put("error", "Access denied! Only admins can save or update records.");
@ -139,7 +145,7 @@ public class GccServicePaymentTrackerController {
}
try {
boolean saved = paymentTrackerService.saveOrUpdateDetail(payload);
boolean saved = paymentTrackerService.saveOrUpdateDetail(payload, username);
response.put("success", saved);
if (!saved) {
response.put("error", "Failed to save details to database.");
@ -159,14 +165,20 @@ public class GccServicePaymentTrackerController {
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String username = (String) session.getAttribute("usrName");
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
String usrRole = (String) session.getAttribute("usrRole");
String usrRole = (String) session.getAttribute("pay_usrRole");
if (usrRole == null) {
usrRole = (String) session.getAttribute("usrRole");
}
if (!"ADMIN".equalsIgnoreCase(usrRole)) {
response.put("success", false);
response.put("error", "Access denied! Only admins can delete records.");
@ -193,14 +205,20 @@ public class GccServicePaymentTrackerController {
@RequestParam("file") MultipartFile file,
HttpSession session) {
Map<String, Object> response = new HashMap<>();
String username = (String) session.getAttribute("usrName");
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
response.put("success", false);
response.put("error", "Unauthorized access! Please log in.");
return response;
}
String usrRole = (String) session.getAttribute("usrRole");
String usrRole = (String) session.getAttribute("pay_usrRole");
if (usrRole == null) {
usrRole = (String) session.getAttribute("usrRole");
}
if (!"ADMIN".equalsIgnoreCase(usrRole)) {
response.put("success", false);
response.put("error", "Access denied! Only admins can upload files.");
@ -261,4 +279,21 @@ public class GccServicePaymentTrackerController {
public List<Map<String, Object>> getStatusList() {
return paymentTrackerService.getStatusList();
}
@GetMapping("/api/history")
@ResponseBody
public ResponseEntity<List<Map<String, Object>>> getHistory(
@RequestParam("id") int id,
HttpSession session) {
String username = (String) session.getAttribute("pay_usrName");
if (username == null) {
username = (String) session.getAttribute("usrName");
}
if (username == null) {
return ResponseEntity.status(401).body(null);
}
return ResponseEntity.ok()
.cacheControl(CacheControl.noCache().mustRevalidate())
.body(paymentTrackerService.getHistory(id));
}
}

60
src/main/java/com/gcc_manpower/gccservicepaymenttracker/service/GccServicePaymentTrackerService.java

@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.gcc_manpower.service.AppUserService;
import javax.sql.DataSource;
import jakarta.annotation.PostConstruct;
import java.util.ArrayList;
@ -16,6 +17,9 @@ public class GccServicePaymentTrackerService {
private JdbcTemplate jdbcTemplate;
@Autowired
private AppUserService appUserService;
@Autowired
public void setDataSource(@Qualifier("mysqlPaymentTrackingServiceDataSource") DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
@ -74,11 +78,13 @@ public class GccServicePaymentTrackerService {
"SELECT p.*, "
+ "t.type_name as resolved_type, "
+ "v.vendor_name as resolved_vendor, "
+ "s.status_name as resolved_status "
+ "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details p "
+ "LEFT JOIN type_list t ON p.type REGEXP '^[0-9]+$' AND t.id = p.type "
+ "LEFT JOIN vendors_list v ON p.vendor_name REGEXP '^[0-9]+$' AND v.id = p.vendor_name "
+ "LEFT JOIN status_list s ON p.status REGEXP '^[0-9]+$' AND s.id = p.status "
+ "LEFT JOIN gcc_manpower.app_user u ON (p.updated_by = CAST(u.usr_id AS CHAR) OR p.updated_by = u.usr_name) "
+ "WHERE p.is_active = 1 AND p.is_delete = 0");
List<Object> params = new ArrayList<>();
@ -159,7 +165,12 @@ public class GccServicePaymentTrackerService {
return null;
}
public boolean saveOrUpdateDetail(Map<String, Object> payload) {
public boolean saveOrUpdateDetail(Map<String, Object> payload, String updatedByUsername) {
String userId = appUserService.getUserIdByName(updatedByUsername);
if (userId == null) {
userId = updatedByUsername;
}
Object idObj = payload.get("id");
java.sql.Date receivedDate = parseDate(payload.get("received_date"));
String vendorName = (String) payload.get("vendor_name");
@ -189,8 +200,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"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ "is_active, is_delete, order_by, file_path, cdate, updated_at, updated_by"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.update(insertHistorySql,
old.get("id"),
old.get("received_date"),
@ -210,7 +221,10 @@ public class GccServicePaymentTrackerService {
old.get("is_active"),
old.get("is_delete"),
old.get("order_by"),
old.get("file_path"));
old.get("file_path"),
old.get("cdate"),
new java.sql.Timestamp(System.currentTimeMillis()),
userId);
}
} catch (Exception e) {
System.err.println("Error saving history for payment tracking detail ID " + id + ": " + e.getMessage());
@ -219,12 +233,14 @@ public class GccServicePaymentTrackerService {
String sql = "UPDATE payment_tracking_details SET "
+ "received_date = ?, vendor_name = ?, subject = ?, amount = ?, invoice_no = ?, "
+ "receiptno = ?, invoice_date = ?, type = ?, physical_file_no = ?, remarks = ?, "
+ "tat = ?, status = ?, followup = ?, myfollowup = ?, order_by = ?, file_path = ? "
+ "tat = ?, status = ?, followup = ?, myfollowup = ?, order_by = ?, file_path = ?, "
+ "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, id);
tat, status, followup, myfollowup, orderBy, filePath,
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());
@ -235,12 +251,15 @@ public class GccServicePaymentTrackerService {
String sql = "INSERT INTO payment_tracking_details ("
+ "received_date, vendor_name, subject, amount, invoice_no, receiptno, "
+ "invoice_date, type, physical_file_no, remarks, tat, status, "
+ "followup, myfollowup, is_active, is_delete, order_by, file_path"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?, ?)";
+ "followup, myfollowup, is_active, is_delete, order_by, file_path, "
+ "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);
tat, status, followup, myfollowup, orderBy, filePath,
userId, now, now);
return rows > 0;
} catch (Exception e) {
System.err.println("Error saving payment tracking detail: " + e.getMessage());
@ -344,4 +363,25 @@ public class GccServicePaymentTrackerService {
return new ArrayList<>();
}
}
public List<Map<String, Object>> getHistory(int detailId) {
String sql = "SELECT h.*, "
+ "t.type_name as resolved_type, "
+ "v.vendor_name as resolved_vendor, "
+ "s.status_name as resolved_status, "
+ "u.usr_name as updated_by_name "
+ "FROM payment_tracking_details_history h "
+ "LEFT JOIN type_list t ON h.type REGEXP '^[0-9]+$' AND t.id = h.type "
+ "LEFT JOIN vendors_list v ON h.vendor_name REGEXP '^[0-9]+$' AND v.id = h.vendor_name "
+ "LEFT JOIN status_list s ON h.status REGEXP '^[0-9]+$' AND s.id = h.status "
+ "LEFT JOIN gcc_manpower.app_user u ON (h.updated_by = CAST(u.usr_id AS CHAR) OR h.updated_by = u.usr_name) "
+ "WHERE h.detail_id = ? "
+ "ORDER BY h.updated_at DESC";
try {
return jdbcTemplate.queryForList(sql, detailId);
} catch (Exception e) {
System.err.println("Error fetching payment tracking history for detail ID " + detailId + ": " + e.getMessage());
return new ArrayList<>();
}
}
}

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

@ -25,6 +25,18 @@ public class AppUserService {
this.jdbcManpowerTemplate = new JdbcTemplate(manpowerDataSource);
}
@PostConstruct
public void init() {
try {
jdbcManpowerTemplate
.execute("ALTER TABLE app_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci");
System.out.println(
"Collation mismatch auto-resolved: app_user converted to utf8mb4_0900_ai_ci successfully.");
} catch (Exception e) {
System.err.println("Note: Could not alter app_user collation automatically: " + e.getMessage());
}
}
public Map<String, Object> authenticate(String username, String password) {
Map<String, Object> result = new HashMap<>();
@ -106,8 +118,11 @@ public class AppUserService {
" COALESCE(des.File_no, '-') AS File_no, " +
" COALESCE(des.work_order_issue_date, '-') AS work_order_issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS work_order_end_date, " +
" des.file_path " +
" des.file_path, " +
" des.updated_at, " +
" COALESCE(u.usr_name, des.updated_by) AS updated_by_name " +
"FROM employee_workorder_details des " +
"LEFT JOIN app_user u ON (des.updated_by = CAST(u.usr_id AS CHAR) OR des.updated_by = u.usr_name) " +
"WHERE des.vid = ? AND des.isactive = 1 AND des.isdelete = 0 " +
"ORDER BY des.orderby, des.designation";
try {
@ -140,6 +155,7 @@ public class AppUserService {
return new ArrayList<>();
}
}
public int getOverallTenderCount() {
String sql = "SELECT SUM(total_employees) FROM vendor_details WHERE isactive = 1 AND isdelete = 0";
try {
@ -179,7 +195,7 @@ public class AppUserService {
}
public List<Map<String, Object>> getActiveDesignations() {
String sql = "SELECT id, designation, File_no FROM employee_workorder_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, designation";
String sql = "SELECT id, designation, File_no, file_path FROM employee_workorder_details WHERE isactive = 1 AND isdelete = 0 ORDER BY orderby, designation";
try {
return jdbcManpowerTemplate.queryForList(sql);
} catch (Exception e) {
@ -222,6 +238,8 @@ public class AppUserService {
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" e.updated_at, " +
" 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, " +
" COALESCE(des.designation, e.designation) AS designation, " +
@ -236,6 +254,8 @@ public class AppUserService {
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"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 " +
"ORDER BY e.name";
try {
@ -258,13 +278,15 @@ public class AppUserService {
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" e.updated_at, " +
" 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, " +
" COALESCE(des.designation, e.designation) AS designation, " +
" COALESCE(loc.location_name, e.location) AS location, " +
" COALESCE(des.File_no, '-') AS work_order, " +
" COALESCE(des.work_order_issue_date, '-') AS issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS end_date " +
" COALESCE(des.work_order_issue_date, '-') AS work_order_issue_date, " +
" COALESCE(des.work_order_end_date, '-') AS work_order_end_date " +
"FROM employee_details e " +
"LEFT JOIN vendor_details v ON (e.vendor_id = CAST(v.id AS CHAR) OR e.vendor_id = v.vendor_name) " +
"LEFT JOIN user_type_details ut ON (e.user_type = CAST(ut.id AS CHAR) OR e.user_type = ut.usertype) " +
@ -272,6 +294,7 @@ public class AppUserService {
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"LEFT JOIN app_user u ON (e.updated_by = CAST(u.usr_id AS CHAR) OR e.updated_by = u.usr_name) " +
"WHERE e.isactive = 1 AND e.isdelete = 0 " +
"ORDER BY e.name";
try {
@ -298,7 +321,7 @@ public class AppUserService {
return null;
}
public boolean addEmployee(Map<String, Object> emp) {
public boolean addEmployee(Map<String, Object> emp, String updatedByUsername) {
String checkSql = "SELECT COUNT(*) FROM employee_details WHERE emp_code = ? AND isdelete = 0";
try {
Integer count = jdbcManpowerTemplate.queryForObject(checkSql, Integer.class, emp.get("emp_code"));
@ -350,9 +373,9 @@ public class AppUserService {
}
}
String sql = "INSERT INTO employee_details (emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, cdate, isactive, isdelete) "
String sql = "INSERT INTO employee_details (emp_code, name, email, mobile, user_type, designation, location, vendor_id, joined_date, cdate, updated_at, updated_by, isactive, isdelete) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)";
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), ?, 1, 0)";
try {
int updateRows = jdbcManpowerTemplate.update(sql,
emp.get("emp_code"),
@ -363,7 +386,8 @@ public class AppUserService {
emp.get("designation"),
emp.get("location"),
emp.get("vendor_id"),
emp.get("joined_date"));
emp.get("joined_date"),
getUserIdByName(updatedByUsername));
if (updateRows > 0) {
recalculateDesignationManpower(String.valueOf(emp.get("designation")),
String.valueOf(emp.get("vendor_id")));
@ -376,9 +400,9 @@ public class AppUserService {
}
}
public boolean updateEmployee(Map<String, Object> emp) {
public boolean updateEmployee(Map<String, Object> emp, String updatedByUsername) {
String empCode = (String) emp.get("emp_code");
String selectSql = "SELECT emp_code, name, email, mobile, user_type, designation, location, vendor_id, isactive, isdelete "
String selectSql = "SELECT emp_code, name, email, mobile, user_type, designation, location, vendor_id, isactive, isdelete, updated_by, cdate "
+
"FROM employee_details WHERE emp_code = ? AND isdelete = 0";
List<Map<String, Object>> oldList;
@ -438,9 +462,9 @@ public class AppUserService {
}
}
String historySql = "INSERT INTO employee_details_update_history (emp_code, name, email, mobile, user_type, designation, location, vendor_id, updated_date, isactive, isdelete) "
String historySql = "INSERT INTO employee_details_update_history (emp_code, name, email, mobile, user_type, designation, location, vendor_id, updated_date, isactive, isdelete, updated_by, cdate) "
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?)";
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, ?, ?, ?)";
try {
jdbcManpowerTemplate.update(historySql,
old.get("emp_code"),
@ -452,13 +476,15 @@ public class AppUserService {
old.get("location"),
old.get("vendor_id"),
old.get("isactive"),
old.get("isdelete"));
old.get("isdelete"),
old.get("updated_by"),
old.get("cdate"));
} catch (Exception e) {
System.err.println("Error inserting history record: " + e.getMessage());
return false;
}
String updateSql = "UPDATE employee_details SET name = ?, email = ?, mobile = ?, user_type = ?, designation = ?, location = ?, vendor_id = ?, joined_date = ? "
String updateSql = "UPDATE employee_details SET name = ?, email = ?, mobile = ?, user_type = ?, designation = ?, location = ?, vendor_id = ?, joined_date = ?, updated_at = NOW(), updated_by = ? "
+
"WHERE emp_code = ? AND isdelete = 0";
try {
@ -471,6 +497,7 @@ public class AppUserService {
emp.get("location"),
emp.get("vendor_id"),
emp.get("joined_date"),
getUserIdByName(updatedByUsername),
empCode);
if (rows > 0) {
recalculateDesignationManpower(String.valueOf(old.get("designation")),
@ -500,8 +527,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) "
+
"WHERE h.emp_code = ? " +
"ORDER BY h.updated_date DESC";
try {
@ -566,7 +595,8 @@ public class AppUserService {
}
public boolean addVendorWorkOrder(String vendorId, String description, String contractorName, String fileNo,
String subject, String issueDate, String endDate, String totalEmployees, Integer orderby) {
String subject, String issueDate, String endDate, String totalEmployees, Integer orderby, String filePath,
String updatedByUsername) {
if (orderby == null) {
try {
Integer maxOrder = jdbcManpowerTemplate
@ -576,9 +606,9 @@ public class AppUserService {
orderby = 1;
}
}
String sql = "INSERT INTO employee_workorder_details (designation, vid, tender_manpower_designation_wise, available_manpower, vacant_count, contract_name, File_no, subject, work_order_issue_date, work_order_end_date, orderby, cdate, isactive, isdelete) "
String sql = "INSERT INTO employee_workorder_details (designation, vid, tender_manpower_designation_wise, available_manpower, vacant_count, contract_name, File_no, subject, work_order_issue_date, work_order_end_date, orderby, cdate, updated_at, updated_by, isactive, isdelete, file_path) "
+
"VALUES (?, ?, ?, '0', ?, ?, ?, ?, ?, ?, ?, NOW(), 1, 0)";
"VALUES (?, ?, ?, '0', ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), ?, 1, 0, ?)";
try {
int rows = jdbcManpowerTemplate.update(sql,
subject, // designation
@ -590,7 +620,9 @@ public class AppUserService {
subject, // subject
issueDate, // work_order_issue_date
endDate, // work_order_end_date
orderby // orderby
orderby, // orderby
getUserIdByName(updatedByUsername), // updated_by
filePath // file_path
);
if (rows > 0) {
// Recalculate designation manpower (handles available_manpower and
@ -682,7 +714,7 @@ public class AppUserService {
}
}
public boolean deleteEmployee(String empCode) {
public boolean deleteEmployee(String empCode, String updatedByUsername) {
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ? AND isdelete = 0";
try {
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode);
@ -693,8 +725,8 @@ public class AppUserService {
String designation = String.valueOf(emp.get("designation"));
String vendorId = String.valueOf(emp.get("vendor_id"));
String updateSql = "UPDATE employee_details SET isactive = 0, isdelete = 1 WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, empCode);
String updateSql = "UPDATE employee_details SET isactive = 0, isdelete = 1, updated_at = NOW(), updated_by = ? WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, getUserIdByName(updatedByUsername), empCode);
if (rows > 0) {
recalculateDesignationManpower(designation, vendorId);
return true;
@ -757,6 +789,8 @@ public class AppUserService {
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" e.updated_at, " +
" 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, " +
" COALESCE(des.designation, e.designation) AS designation, " +
@ -769,6 +803,8 @@ public class AppUserService {
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"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 = ? " +
"ORDER BY e.name";
try {
@ -779,7 +815,7 @@ public class AppUserService {
}
}
public boolean restoreEmployee(String empCode) {
public boolean restoreEmployee(String empCode, String updatedByUsername) {
String selectSql = "SELECT designation, vendor_id FROM employee_details WHERE emp_code = ?";
try {
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, empCode);
@ -790,8 +826,8 @@ public class AppUserService {
String designation = String.valueOf(emp.get("designation"));
String vendorId = String.valueOf(emp.get("vendor_id"));
String updateSql = "UPDATE employee_details SET isactive = 1, isdelete = 0 WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, empCode);
String updateSql = "UPDATE employee_details SET isactive = 1, isdelete = 0, updated_at = NOW(), updated_by = ? WHERE emp_code = ?";
int rows = jdbcManpowerTemplate.update(updateSql, getUserIdByName(updatedByUsername), empCode);
if (rows > 0) {
recalculateDesignationManpower(designation, vendorId);
return true;
@ -814,6 +850,8 @@ public class AppUserService {
" e.isdelete, " +
" e.joined_date, " +
" e.cdate, " +
" e.updated_at, " +
" 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, " +
" COALESCE(des.designation, e.designation) AS designation, " +
@ -826,6 +864,8 @@ public class AppUserService {
+
"LEFT JOIN employee_location_details loc ON (e.location = CAST(loc.id AS CHAR) OR e.location = loc.location_name) "
+
"LEFT JOIN app_user u ON (e.updated_by = CAST(u.usr_id AS CHAR) OR e.updated_by = u.usr_name) "
+
"ORDER BY e.name";
try {
return jdbcManpowerTemplate.queryForList(sql);
@ -834,4 +874,67 @@ public class AppUserService {
return new ArrayList<>();
}
}
public String getUserIdByName(String username) {
if (username == null || username.trim().isEmpty())
return null;
try {
Integer id = jdbcManpowerTemplate.queryForObject(
"SELECT usr_id FROM app_user WHERE usr_name = ?", Integer.class, username);
return id != null ? String.valueOf(id) : null;
} catch (Exception e) {
System.err.println("Error resolving user ID for name " + username + ": " + e.getMessage());
return null;
}
}
public boolean deleteWorkOrder(String workOrderId, String updatedByUsername) {
try {
// 1. Get the details of the work order before deleting
String selectSql = "SELECT vid, tender_manpower_designation_wise, designation FROM employee_workorder_details WHERE id = ? AND isdelete = 0";
List<Map<String, Object>> list = jdbcManpowerTemplate.queryForList(selectSql, workOrderId);
if (list == null || list.isEmpty()) {
return false;
}
Map<String, Object> wo = list.get(0);
String vendorId = String.valueOf(wo.get("vid"));
String tenderStr = (String) wo.get("tender_manpower_designation_wise");
String designationName = (String) wo.get("designation");
String userId = getUserIdByName(updatedByUsername);
// 2. Soft-delete the work order in employee_workorder_details, reset counts and
// record updater
String deleteSql = "UPDATE employee_workorder_details SET isactive = 0, isdelete = 1, available_manpower = '0', vacant_count = tender_manpower_designation_wise, updated_at = NOW(), updated_by = ? WHERE id = ?";
int rows = jdbcManpowerTemplate.update(deleteSql, userId, workOrderId);
if (rows > 0) {
// 3. Subtract its tender count from vendor's total_employees in vendor_details
int tenderCount = 0;
if (tenderStr != null) {
try {
tenderCount = Integer.parseInt(tenderStr.trim());
} catch (NumberFormatException ignored) {
}
}
if (tenderCount > 0) {
updateVendorTotalEmployees(vendorId, -tenderCount);
}
// 4. Soft-delete employees under this designation/work order for this vendor
// and record updater
String updateEmployeesSql = "UPDATE employee_details SET isactive = 0, isdelete = 1, updated_at = NOW(), updated_by = ? "
+
"WHERE (vendor_id = ? OR vendor_id = (SELECT vendor_name FROM vendor_details WHERE id = ?)) " +
" AND (designation = ? OR designation = ?)";
jdbcManpowerTemplate.update(updateEmployeesSql, userId, vendorId, vendorId, workOrderId,
designationName);
return true;
}
} catch (Exception e) {
System.err.println("Error deleting work order: " + e.getMessage());
}
return false;
}
}

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

@ -1270,8 +1270,9 @@
<th>Age of Bill (Days)</th>
<th>Status (Pending / Completed)</th>
<th>Remarks</th>
<th>Updated on</th>
<th class="text-center" style="width: 100px;">Actions</th>
<th>Last Updated At</th>
<th>Last Updated By</th>
<th class="text-center" style="width: 120px;">Actions</th>
</tr>
</thead>
<tbody id="tracking-tbody">
@ -1482,6 +1483,50 @@
</div>
</div>
<!-- History Modal -->
<div class="modal fade" id="historyModal" tabindex="-1" aria-labelledby="historyModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-custom-header" style="background-color: #2c3e50;">
<h5 class="modal-custom-title" id="historyModalLabel">
<i class="fa-solid fa-clock-rotate-left"></i> Payment Record 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">
<div class="table-responsive" style="max-height: 400px; border: 1px solid #eef2f6;">
<table class="table table-hover modal-table grid-table" id="history-table">
<thead>
<tr>
<th style="width: 50px;" class="text-center">S.No</th>
<th>Received Date</th>
<th>Invoice Date</th>
<th>Subject</th>
<th>Type</th>
<th>Vendor Name</th>
<th>Amount</th>
<th>Status</th>
<th>Remarks</th>
<th>Last Updated At</th>
<th>Last Updated By</th>
<th class="text-center" style="width: 80px;">Document</th>
</tr>
</thead>
<tbody id="history-tbody">
<!-- Populated dynamically -->
</tbody>
</table>
<div id="no-history-found" class="text-center text-muted py-5" style="display: none;">
<i class="fa-solid fa-history fs-1 mb-3 text-secondary" style="opacity: 0.5;"></i>
<p class="fw-bold mb-0">No update history found for this record.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap & FontAwesome bundles -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
@ -1493,6 +1538,7 @@
let activeCategory = 'all';
let allRecords = [];
let recordModalObj = null;
let historyModalObj = null;
let currentPage = 1;
let pageSize = 20;
@ -1511,6 +1557,7 @@
document.addEventListener('DOMContentLoaded', async () => {
recordModalObj = new bootstrap.Modal(document.getElementById('recordModal'));
historyModalObj = new bootstrap.Modal(document.getElementById('historyModal'));
// initDatePickers();
await loadTypesAndVendors();
loadCounts();
@ -1972,13 +2019,15 @@
const formatDateTime = dateTimeStr => {
if (!dateTimeStr) return '-';
try {
const tParts = dateTimeStr.split('T');
const datePart = tParts[0];
const timePart = tParts.length > 1 ? tParts[1].split('.')[0] : '';
const dateParts = datePart.split('-');
if (dateParts.length === 3) {
const formattedDate = `${dateParts[2]}/${dateParts[1]}/${dateParts[0]}`;
return timePart ? `${formattedDate} ${timePart}` : formattedDate;
const d = new Date(dateTimeStr);
if (!isNaN(d.getTime())) {
const dd = String(d.getDate()).padStart(2, '0');
const MM = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const HH = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${dd}/${MM}/${yyyy} ${HH}:${mm}:${ss}`;
}
} catch (e) {
console.error(e);
@ -2041,6 +2090,7 @@
<td><span class="status-badge status-${dispStatus.toLowerCase().replace(/\s+/g, '-')}">${text(dispStatus)}</span></td>
<td><small class="text-muted">${text(row.remarks)}</small></td>
<td>${formatDateTime(row.updated_date)}</td>
<td class="fw-semibold text-secondary">${text(row.updated_by_name || '-')}</td>
`;
// Actions cell
@ -2064,6 +2114,15 @@
tdActions.appendChild(btnViewPdf);
}
// History Button (Available to all logged in users)
const btnHistory = document.createElement('button');
btnHistory.className = 'btn-grid-action btn-outline-secondary me-1';
btnHistory.innerHTML = '<i class="fa-solid fa-clock-rotate-left"></i>';
btnHistory.style.border = '1px solid #cbd5e1';
btnHistory.title = 'View Update History';
btnHistory.onclick = () => showHistoryModal(row.id);
tdActions.appendChild(btnHistory);
if (isAdmin) {
const btnEdit = document.createElement('button');
btnEdit.className = 'btn-grid-action btn-grid-edit me-1';
@ -2893,6 +2952,136 @@
showLoader(false);
});
}
// Show update history for a payment tracking record
function showHistoryModal(id) {
const tbody = document.getElementById('history-tbody');
const noHistoryFound = document.getElementById('no-history-found');
tbody.innerHTML = '';
noHistoryFound.style.display = 'none';
historyModalObj.show();
// Local formatting helpers for history records
const text = val => (val === null || val === undefined || String(val).trim() === '') ? '-' : val;
const formatDate = dateStr => {
if (!dateStr) return '-';
const parts = dateStr.split('-');
if (parts.length === 3) {
return `${parts[2]}/${parts[1]}/${parts[0]}`;
}
return dateStr;
};
const formatDateTime = dateTimeStr => {
if (!dateTimeStr) return '-';
try {
const d = new Date(dateTimeStr);
if (!isNaN(d.getTime())) {
const dd = String(d.getDate()).padStart(2, '0');
const MM = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const HH = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${dd}/${MM}/${yyyy} ${HH}:${mm}:${ss}`;
}
} catch (e) {
console.error(e);
}
return dateTimeStr;
};
fetch(contextPath + 'gccservicepaymenttracker/api/history?id=' + id)
.then(response => response.json())
.then(historyList => {
if (!historyList || historyList.length === 0) {
noHistoryFound.style.display = 'block';
return;
}
historyList.forEach((item, index) => {
const tr = document.createElement('tr');
const dispStatus = String(item.resolved_status || getStatusName(item.status) || '').trim();
// Document button if file path is available
let docBtnHtml = '-';
if (item.file_path) {
let pdfUrl = item.file_path;
if (contextPath && contextPath.endsWith('/') && pdfUrl.startsWith('/')) {
pdfUrl = contextPath + pdfUrl.substring(1);
} else {
pdfUrl = contextPath + pdfUrl;
}
docBtnHtml = `<a href="${pdfUrl}" target="_blank" class="btn-grid-action btn-grid-pdf"><i class="fa-solid fa-file-pdf"></i></a>`;
}
// Resolve type badge
let typeHtml = '';
if (item.type) {
const cleanType = String(item.type).toLowerCase().trim();
let label = cleanType;
let badgeClass = 'type-sd';
if (cleanType === 'asd' || cleanType.includes('asd') || cleanType.includes('additional security')) {
label = 'ASD';
badgeClass = 'type-asd';
} else if (cleanType.includes('agreement')) {
label = 'Agreement';
badgeClass = 'type-agreement';
} else if (cleanType === 'sd' || (cleanType.includes('security') && !cleanType.includes('additional') && !cleanType.includes('asd'))) {
label = 'Security Deposit';
badgeClass = 'type-sd';
} else if (cleanType.includes('invoice') || cleanType.includes('bill')) {
label = 'Tax Invoice';
badgeClass = 'type-tax-invoice';
} else if (cleanType === 'emd' || cleanType.includes('earnest')) {
label = 'EMD';
badgeClass = 'type-emd';
} else if (cleanType.includes('guarantee') || cleanType === 'bg') {
label = 'Bank Guarantee';
badgeClass = 'type-bank-guarantee';
} else if (cleanType.includes('chellan') || cleanType.includes('challan') || cleanType === 'dc') {
label = 'Delivery Chellan';
badgeClass = 'type-delivery-chellan';
} else {
const typeObj = activeTypes.find(t => String(t.id) === cleanType);
if (typeObj) {
label = typeObj.type_name;
}
badgeClass = 'status-default';
}
typeHtml = `<span class="type-badge ${badgeClass}">${label}</span>`;
} else {
typeHtml = '-';
}
// Resolve vendor name
let resolvedVendor = item.resolved_vendor || item.vendor_name || '-';
tr.innerHTML = `
<td class="text-center fw-semibold text-secondary">${index + 1}</td>
<td>${formatDate(item.received_date)}</td>
<td>${formatDate(item.invoice_date)}</td>
<td>${text(item.subject)}</td>
<td>${typeHtml}</td>
<td class="fw-semibold text-secondary">${text(resolvedVendor)}</td>
<td class="fw-semibold text-dark">&#8377; ${text(item.amount)}</td>
<td><span class="status-badge status-${dispStatus.toLowerCase().replace(/\s+/g, '-')}">${text(dispStatus)}</span></td>
<td><small class="text-muted">${text(item.remarks)}</small></td>
<td>${formatDateTime(item.updated_at)}</td>
<td class="fw-semibold text-secondary">${text(item.updated_by_name || '-')}</td>
<td class="text-center">${docBtnHtml}</td>
`;
tbody.appendChild(tr);
});
})
.catch(err => {
console.error("Fetch History Error:", err);
tbody.innerHTML = '<tr><td colspan="12" class="text-center text-danger py-3"><i class="fa-solid fa-triangle-exclamation"></i> Failed to load history records.</td></tr>';
});
}
</script>
</body>

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

@ -346,6 +346,13 @@
oninput="filterEmployees()">
</div>
<div class="d-flex align-items-center gap-2 w-100 w-md-auto justify-content-md-end">
<!-- Work Order PDF Link -->
<a id="modal-workorder-file-link" href="#" target="_blank"
class="btn btn-danger text-nowrap align-items-center justify-content-center gap-2"
style="height: 44px; border-radius: 8px; font-weight: 600; display: none; padding: 0 15px; background-color: #c0392b; border-color: #c0392b; color: white;">
<i class="fa-solid fa-file-pdf"></i> Work Order File
</a>
<select id="modal-designation-filter" class="form-select form-select-sm"
style="height: 44px; border-radius: 8px; border: 1px solid #cbd5e1; padding: 0 35px 0 15px; font-weight: 600; min-width: 200px;"
onchange="filterEmployees()">
@ -369,7 +376,8 @@
<th>Mobile</th>
<th>User Type</th>
<th>Designation</th>
<th>Created Date</th>
<th class="text-center" style="width: 150px;">Last Updated On</th>
<th class="text-center" style="width: 130px;">Last Updated By</th>
<th>Location</th>
<th>Vendor Name</th>
<th th:if="${isAdmin}" style="width: 80px;" class="text-center">Actions</th>
@ -800,12 +808,14 @@
<th>Designation</th>
<th>Location</th>
<th>Vendor Name</th>
<th class="text-center" style="width: 150px;">Last Updated On</th>
<th class="text-center" style="width: 130px;">Last Updated By</th>
<th style="width: 100px;" class="text-center">Action</th>
</tr>
</thead>
<tbody id="update-modal-employee-tbody">
<tr>
<td colspan="10" class="text-center py-4 text-secondary">
<td colspan="12" class="text-center py-4 text-secondary">
<i class="fa-solid fa-users fs-3 mb-1"></i>
<p class="mb-0 fw-semibold">Please select a Vendor and Work Order to list
employees, or search directly.</p>
@ -1065,7 +1075,14 @@
placeholder="Number of employees" required style="border-radius: 8px;">
</div>
<!-- Order By -->
<!-- Upload PDF -->
<div class="col-md-6">
<label for="add-vo-file" class="form-label fw-semibold text-secondary">Upload Work Order
PDF <span class="text-danger">*</span></label>
<input type="file" class="form-control w-100" id="add-vo-file" accept=".pdf"
style="border-radius: 8px;" required>
</div>
<!-- <div class="col-md-6">
<label for="add-vo-orderby" class="form-label fw-semibold text-secondary">Display Order
(Orderby)</label>
@ -1953,6 +1970,24 @@
}
noWoEl.style.display = "none";
// Dynamically handle the Action column header based on admin access
const tableHeadRow = document.querySelector("#modal-wo-table thead tr");
let actionHeader = document.getElementById("wo-action-header");
if (isAdmin) {
if (!actionHeader) {
const th = document.createElement("th");
th.id = "wo-action-header";
th.className = "text-center";
th.style.width = "100px";
th.textContent = "Action";
tableHeadRow.appendChild(th);
}
} else {
if (actionHeader) {
actionHeader.remove();
}
}
let totalTender = 0;
let totalOnroll = 0;
@ -1991,22 +2026,41 @@
detailCellHtml = `<span class="fw-bold text-dark">${escapeHtml(detailText)}</span>`;
}
let actionCellHtml = "";
if (isAdmin) {
actionCellHtml = `
<td class="text-center">
<button class="btn btn-sm btn-danger py-1 px-2" style="border-radius: 6px; font-size: 0.8rem;" onclick="deleteWorkOrder('${des.id}', '${escapeHtml(vendorName).replace(/'/g, "\\'")}')">
<i class="fa-solid fa-trash-can"></i> Delete
</button>
</td>
`;
}
tr.innerHTML = `
<td class="text-center fw-bold text-secondary">${index + 1}</td>
<td>${detailCellHtml}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(String(tenderVal))}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(String(onrollVal))}</td>
${actionCellHtml}
`;
tbody.appendChild(tr);
});
const totalTr = document.createElement("tr");
totalTr.className = "modal-table-total-row";
let actionTotalPlaceholder = "";
if (isAdmin) {
actionTotalPlaceholder = `<td></td>`;
}
totalTr.innerHTML = `
<td class="text-center"></td>
<td class="fw-bold text-end" style="color: #0f5c78; font-size: 0.95rem; letter-spacing: 0.5px;">TOTAL</td>
<td class="text-center fw-bold text-primary" style="font-size: 0.95rem;">${totalTender}</td>
<td class="text-center fw-bold text-primary" style="font-size: 0.95rem;">${totalOnroll}</td>
${actionTotalPlaceholder}
`;
tbody.appendChild(totalTr);
@ -2014,6 +2068,57 @@
modal.show();
}
function deleteWorkOrder(workOrderId, vendorName) {
Swal.fire({
title: 'Are you sure?',
text: `Do you want to delete this work order? All employees registered under this designation/work order will also be soft-deleted, and the vendor's total employees count will be updated.`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
showLoadingOverlay("Deleting work order...");
fetch(contextPath + 'api/vendor/delete-work-order', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ workOrderId: workOrderId })
})
.then(res => res.json())
.then(data => {
hideLoadingOverlay();
if (data.success) {
Swal.fire(
'Deleted!',
'Work order has been deleted successfully.',
'success'
).then(() => {
window.location.reload();
});
} else {
Swal.fire(
'Error!',
data.error || 'Failed to delete work order.',
'error'
);
}
})
.catch(err => {
hideLoadingOverlay();
console.error("Error deleting work order:", err);
Swal.fire(
'Error!',
'Network or server error occurred.',
'error'
);
});
}
});
}
// Ensure body has class 'modal-open' and is scrollable if a parent modal is still open when child modal closes
document.addEventListener('hidden.bs.modal', function () {
if (document.querySelectorAll('.modal.show').length > 0) {
@ -2100,7 +2205,7 @@
const tbody = document.getElementById("modal-employee-tbody");
tbody.innerHTML = `
<tr>
<td colspan="12" class="text-center py-5">
<td colspan="13" class="text-center py-5">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
@ -2127,7 +2232,7 @@
console.error("Error fetching employees:", err);
tbody.innerHTML = `
<tr>
<td colspan="12" class="text-center py-5 text-danger fw-bold">
<td colspan="13" class="text-center py-5 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i>
<p>Failed to connect to server and fetch database records. Please try again.</p>
</td>
@ -2155,7 +2260,7 @@
const tbody = document.getElementById("modal-employee-tbody");
tbody.innerHTML = `
<tr>
<td colspan="12" class="text-center py-5">
<td colspan="13" class="text-center py-5">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
@ -2180,7 +2285,7 @@
console.error("Error fetching all employees:", err);
tbody.innerHTML = `
<tr>
<td colspan="12" class="text-center py-5 text-danger fw-bold">
<td colspan="13" class="text-center py-5 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i>
<p>Failed to connect to server and fetch database records. Please try again.</p>
</td>
@ -2223,7 +2328,8 @@
<td>${escapeHtml(emp.mobile || '-')}</td>
<td><span class="badge bg-secondary">${escapeHtml(emp.user_type || '-')}</span></td>
<td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td>
<td class="fw-semibold text-primary">${escapeHtml(formatDateTime(emp.cdate))}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(formatDateTime(emp.updated_at || emp.cdate))}</td>
<td class="text-center fw-semibold text-secondary">${escapeHtml(emp.updated_by_name || '-')}</td>
<td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td>
<td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td>
${actionTd}
@ -2276,6 +2382,52 @@
}
renderEmployeeTable(filteredVendorEmployees);
updateWorkOrderFileLink();
}
function updateWorkOrderFileLink() {
const selectEl = document.getElementById("modal-designation-filter");
const fileLink = document.getElementById("modal-workorder-file-link");
if (!selectEl || !fileLink) return;
const selectedVal = selectEl.value.trim().toLowerCase();
if (selectedVal === "") {
fileLink.classList.add("d-none");
fileLink.href = "#";
return;
}
let filePath = null;
if (currentVendorName === "All Active Employees") {
// Find matching designation in allDesignations
const matchedDes = allDesignations.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal);
filePath = matchedDes ? matchedDes.file_path : null;
} else {
// Find matching designation details for current vendor
const vendor = vendorDetailsData.find(v => v.vendor_name === currentVendorName);
if (vendor && vendor.designationDetails) {
const matchedDes = vendor.designationDetails.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal);
filePath = matchedDes ? matchedDes.file_path : null;
}
}
if (filePath) {
filePath = filePath.trim();
}
if (filePath && filePath !== '-' && filePath !== '') {
if (!filePath.toLowerCase().endsWith('.pdf')) {
filePath += '.pdf';
}
const normalizedPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
fileLink.href = contextPath + normalizedPath;
fileLink.classList.remove("d-none");
fileLink.classList.add("d-inline-flex");
return;
}
fileLink.classList.add("d-none");
fileLink.href = "#";
}
// Premium CSV Exporter
@ -2711,6 +2863,7 @@
const totalEmployees = document.getElementById("add-vo-total-employees").value.trim();
const orderbyInput = document.getElementById("add-vo-orderby");
const orderbyVal = orderbyInput ? orderbyInput.value.trim() : "";
const fileInput = document.getElementById("add-vo-file");
const form = document.getElementById("addVendorWorkOrderForm");
const prevAlert = form.querySelector(".alert");
@ -2728,28 +2881,29 @@
return;
}
const payload = {
vendorType: mode,
vendorId: vendorId,
vendorName: vendorName,
contractorName: contractorName,
fileNo: fileNo,
subject: subject,
description: description,
issueDate: issueDate,
endDate: endDate,
totalEmployees: totalEmployees,
orderby: orderbyVal ? parseInt(orderbyVal) : null
};
const formData = new FormData();
formData.append("vendorType", mode);
formData.append("vendorId", vendorId);
formData.append("vendorName", vendorName);
formData.append("contractorName", contractorName);
formData.append("fileNo", fileNo);
formData.append("subject", subject);
formData.append("description", description);
formData.append("issueDate", issueDate);
formData.append("endDate", endDate);
formData.append("totalEmployees", totalEmployees);
if (orderbyVal) {
formData.append("orderby", orderbyVal);
}
if (fileInput && fileInput.files.length > 0) {
formData.append("file", fileInput.files[0]);
}
showLoadingOverlay("Saving work order details...");
fetch(contextPath + 'api/vendor/add-work-order', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
body: formData
})
.then(res => res.json())
.then(data => {
@ -3356,7 +3510,7 @@
const tbody = document.getElementById("update-modal-employee-tbody");
tbody.innerHTML = `
<tr>
<td colspan="10" class="text-center py-4">
<td colspan="12" class="text-center py-4">
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
<span class="visually-hidden">Loading...</span>
</div>
@ -3384,7 +3538,7 @@
console.error("Error fetching employees for update modal:", err);
tbody.innerHTML = `
<tr>
<td colspan="10" class="text-center py-3 text-danger fw-bold">
<td colspan="12" class="text-center py-3 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-3 mb-1"></i>
<p class="mb-0">Failed to fetch employee list from server.</p>
</td>
@ -3461,6 +3615,8 @@
<td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td>
<td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td>
<td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(formatDateTime(emp.updated_at || emp.cdate))}</td>
<td class="text-center fw-semibold text-secondary">${escapeHtml(emp.updated_by_name || '-')}</td>
<td class="text-center">${actionButton}</td>
`;
tbody.appendChild(tr);

75
src/main/resources/templates/home/view-login-details.html

@ -585,6 +585,11 @@
oninput="filterEmployees()">
</div>
<div class="d-flex align-items-center gap-2 w-100 w-md-auto justify-content-md-end">
<!-- Work Order PDF Link -->
<a id="modal-workorder-file-link" href="#" target="_blank" class="btn btn-danger text-nowrap align-items-center justify-content-center gap-2" style="height: 44px; border-radius: 8px; font-weight: 600; display: none; padding: 0 15px; background-color: #c0392b; border-color: #c0392b; color: white;">
<i class="fa-solid fa-file-pdf"></i> Work Order File
</a>
<select id="modal-designation-filter" class="form-select form-select-sm"
style="height: 44px; border-radius: 8px; border: 1px solid #cbd5e1; padding: 0 35px 0 15px; font-weight: 600; min-width: 200px;"
onchange="filterEmployees()">
@ -608,6 +613,7 @@
<th>Mobile</th>
<th>User Type</th>
<th>Designation</th>
<th class="text-center" style="width: 150px;">Last Updated On</th>
<th>Location</th>
<th>Vendor Name</th>
</tr>
@ -694,7 +700,7 @@
const tbody = document.getElementById("modal-employee-tbody");
tbody.innerHTML = `
<tr>
<td colspan="9" class="text-center py-5">
<td colspan="10" class="text-center py-5">
<div class="spinner-border text-primary" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
@ -721,7 +727,7 @@
console.error("Error fetching employees:", err);
tbody.innerHTML = `
<tr>
<td colspan="9" class="text-center py-5 text-danger fw-bold">
<td colspan="10" class="text-center py-5 text-danger fw-bold">
<i class="fa-solid fa-circle-exclamation fs-1 mb-2"></i>
<p>Failed to connect to server and fetch database records. Please try again.</p>
</td>
@ -753,6 +759,7 @@
<td>${escapeHtml(emp.mobile || '-')}</td>
<td><span class="badge bg-secondary">${escapeHtml(emp.user_type || '-')}</span></td>
<td class="fw-semibold">${escapeHtml(emp.designation || '-')}</td>
<td class="text-center fw-semibold text-primary">${escapeHtml(formatDateTime(emp.updated_at || emp.cdate))}</td>
<td><span class="badge bg-light text-dark border">${escapeHtml(emp.location || '-')}</span></td>
<td class="fw-semibold text-secondary">${escapeHtml(emp.vendor_name || '-')}</td>
`;
@ -788,6 +795,52 @@
}
renderEmployeeTable(filteredVendorEmployees);
updateWorkOrderFileLink();
}
function updateWorkOrderFileLink() {
const selectEl = document.getElementById("modal-designation-filter");
const fileLink = document.getElementById("modal-workorder-file-link");
if (!selectEl || !fileLink) return;
const selectedVal = selectEl.value.trim().toLowerCase();
if (selectedVal === "") {
fileLink.classList.add("d-none");
fileLink.href = "#";
return;
}
let filePath = null;
if (currentVendorName === "All Active Employees") {
// Find matching designation in allDesignations
const matchedDes = allDesignations.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal);
filePath = matchedDes ? matchedDes.file_path : null;
} else {
// Find matching designation details for current vendor
const vendor = vendorDetailsData.find(v => v.vendor_name === currentVendorName);
if (vendor && vendor.designationDetails) {
const matchedDes = vendor.designationDetails.find(d => d.designation && d.designation.trim().toLowerCase() === selectedVal);
filePath = matchedDes ? matchedDes.file_path : null;
}
}
if (filePath) {
filePath = filePath.trim();
}
if (filePath && filePath !== '-' && filePath !== '') {
if (!filePath.toLowerCase().endsWith('.pdf')) {
filePath += '.pdf';
}
const normalizedPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
fileLink.href = contextPath + normalizedPath;
fileLink.classList.remove("d-none");
fileLink.classList.add("d-inline-flex");
return;
}
fileLink.classList.add("d-none");
fileLink.href = "#";
}
// Premium CSV Exporter
@ -838,6 +891,24 @@
.replace(/'/g, "&#039;");
}
// Helper function to format timestamp to dd/mm/yyyy HH:MM:ss
function formatDateTime(dateTimeStr) {
if (!dateTimeStr || dateTimeStr === '-' || dateTimeStr.trim() === '') return '-';
dateTimeStr = dateTimeStr.trim();
let dateObj = new Date(dateTimeStr.replace('T', ' '));
if (isNaN(dateObj.getTime())) {
dateObj = new Date(dateTimeStr);
}
if (isNaN(dateObj.getTime())) return dateTimeStr;
const day = String(dateObj.getDate()).padStart(2, '0');
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const year = dateObj.getFullYear();
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
const seconds = String(dateObj.getSeconds()).padStart(2, '0');
return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`;
}
// Dynamic calculation when page loads
document.addEventListener("DOMContentLoaded", () => {
// Update the date badge dynamically to current date format

Loading…
Cancel
Save