From ed88d8a7dd422d1d0577cade5a47d79a3b0d2573 Mon Sep 17 00:00:00 2001 From: chapal Date: Thu, 12 Nov 2026 15:40:24 +0600 Subject: [PATCH 1/3] Salary --- HRM.DA/DA/Salary/SalaryProcessDA.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/HRM.DA/DA/Salary/SalaryProcessDA.cs b/HRM.DA/DA/Salary/SalaryProcessDA.cs index d0cef7e..3a6168c 100644 --- a/HRM.DA/DA/Salary/SalaryProcessDA.cs +++ b/HRM.DA/DA/Salary/SalaryProcessDA.cs @@ -1,5 +1,6 @@ -using HRM.BO; +using DocumentFormat.OpenXml.Drawing.Diagrams; using Ease.Core.DataAccess; +using HRM.BO; using System; using System.Data; @@ -221,6 +222,18 @@ namespace HRM.DA tc.ExecuteNonQuery( "DELETE FROM SalaryMonthlyDetail WHERE SalaryMonthlyID in( Select SalaryMonthlyID from SalaryMonthly Where SalaryProcessID=%n)", id); + // tc.ExecuteNonQuery("ALTER TABLE OPIPayment NOCHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY;") + // ALTER TABLE SALARYMONTHLYDETAIL NOCHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; + // ALTER TABLE SALARYTEMPDETAIL NOCHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH;" + + // --Delete data + // DELETE FROM SalaryMonthly + //WHERE SalaryProcessID = 75523; + + // --Re - enable and validate constraints + // ALTER TABLE OPIPayment WITH CHECK CHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY; + // ALTER TABLE SALARYMONTHLYDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; + // ALTER TABLE SALARYTEMPDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH; tc.ExecuteNonQuery("DELETE FROM SalaryMonthly WHERE SalaryProcessID=%n", id); //} //tc.ExecuteNonQuery("DELETE FROM SalaryProcess WHERE SalaryProcessID=%n", id); -- 2.40.0.windows.1 From 649e03b6f3ba410dd24c5b4f554335ed087d5868 Mon Sep 17 00:00:00 2001 From: chapal Date: Sun, 15 Nov 2026 11:33:43 +0600 Subject: [PATCH 2/3] Raw data upload and process --- HRM.DA/DA/Attendance/AttnRawDataDA.cs | 8 +++ HRM.DA/DA/Salary/SalaryProcessDA.cs | 2 +- .../Service/Attendance/AttendanceProcess.cs | 3 +- .../Service/Attendance/AttnRawDataService.cs | 31 +++++++++- .../read-attendance-rawdata.component.ts | 58 ++++++++++++++----- 5 files changed, 83 insertions(+), 19 deletions(-) diff --git a/HRM.DA/DA/Attendance/AttnRawDataDA.cs b/HRM.DA/DA/Attendance/AttnRawDataDA.cs index dea5627..0e6a2f9 100644 --- a/HRM.DA/DA/Attendance/AttnRawDataDA.cs +++ b/HRM.DA/DA/Attendance/AttnRawDataDA.cs @@ -112,6 +112,14 @@ namespace HRM.DA return rawData; } + /// Card tagged with each employee: EmployeeID, CardID, CardNumber. + internal static IDataReader GetEmployeeCards(TransactionContext tc, string empIDs) + { + return tc.ExecuteReader("SELECT e.EmployeeID, e.CardID, ac.CardNumber FROM Employee e" + + " LEFT JOIN AccessCard ac ON ac.AccessCardID = e.CardID" + + " WHERE e.EmployeeID IN (%q)", empIDs); + } + internal static void Delete(TransactionContext tc, DateTime fromTime, string empIDs) { tc.ExecuteNonQuery("DELETE FROM AttnRawData WHERE PunchTime>=%D and EmployeeID in (%q)", fromTime, empIDs); diff --git a/HRM.DA/DA/Salary/SalaryProcessDA.cs b/HRM.DA/DA/Salary/SalaryProcessDA.cs index 3a6168c..8d793a1 100644 --- a/HRM.DA/DA/Salary/SalaryProcessDA.cs +++ b/HRM.DA/DA/Salary/SalaryProcessDA.cs @@ -236,7 +236,7 @@ namespace HRM.DA // ALTER TABLE SALARYTEMPDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH; tc.ExecuteNonQuery("DELETE FROM SalaryMonthly WHERE SalaryProcessID=%n", id); //} - //tc.ExecuteNonQuery("DELETE FROM SalaryProcess WHERE SalaryProcessID=%n", id); + tc.ExecuteNonQuery("DELETE FROM SalaryProcess WHERE SalaryProcessID=%n", id); } internal static void UndoMonthlySalaryByIDs(TransactionContext tc, string ids) diff --git a/HRM.DA/Service/Attendance/AttendanceProcess.cs b/HRM.DA/Service/Attendance/AttendanceProcess.cs index 378e2cb..74af88d 100644 --- a/HRM.DA/Service/Attendance/AttendanceProcess.cs +++ b/HRM.DA/Service/Attendance/AttendanceProcess.cs @@ -2656,8 +2656,7 @@ namespace HRM.DA { continue; } - - // 2. If End of Contract date is less than or equal Attendendence date then no attendence + // 2. If End of Contract date is less than or equal Attendendence date then no attendence if (emp.EndOfContractDate != null && emp.EndOfContractDate.Value <= Attdate) { continue; diff --git a/HRM.DA/Service/Attendance/AttnRawDataService.cs b/HRM.DA/Service/Attendance/AttnRawDataService.cs index b8faa94..09f9336 100644 --- a/HRM.DA/Service/Attendance/AttnRawDataService.cs +++ b/HRM.DA/Service/Attendance/AttnRawDataService.cs @@ -392,6 +392,35 @@ namespace HRM.DA string empIDs = attnRawDatas.Aggregate(new StringBuilder(), (sb, rData) => sb.AppendFormat("{0},", rData.EmployeeID), sb => sb.ToString().Trim(',')); AttnRawDataDA.Delete(tc, dMinValue, empIDs); + + // Fill CardID (and CardNo when not supplied) from the card tagged with the employee + var empCards = new Dictionary>(); + string distinctEmpIDs = string.Join(",", attnRawDatas.Where(x => x.EmployeeID > 0) + .Select(x => x.EmployeeID).Distinct()); + if (distinctEmpIDs.Length > 0) + { + DataReader dr = new DataReader(AttnRawDataDA.GetEmployeeCards(tc, distinctEmpIDs)); + while (dr.Read()) + { + empCards[dr.GetInt32("EmployeeID", 0)] = + Tuple.Create(dr.GetInt32("CardID"), dr.GetString("CardNumber")); + } + dr.Close(); + } + foreach (AttnRawData item in attnRawDatas) + { + Tuple card; + if (empCards.TryGetValue(item.EmployeeID, out card)) + { + if (item.CardID <= 0 && card.Item1.HasValue && card.Item1.Value > 0) + item.CardID = card.Item1.Value; + if (string.IsNullOrWhiteSpace(item.CardNo) && !string.IsNullOrWhiteSpace(card.Item2)) + item.CardNo = card.Item2.Trim(); + } + if (string.IsNullOrWhiteSpace(item.CardNo)) + item.CardNo = item.EmployeeNo ?? string.Empty; // employee has no card tagged + } + DataTable _dtRawData = null; _dtRawData = new DataTable("AttnRawData"); // _dtRawData.Columns.Add("ATTNRAWDATAID", typeof(int)); @@ -418,7 +447,7 @@ namespace HRM.DA // item.DeviceNo, // item.PunchTime); _dtRawData.Rows.Add( - null, + item.CardID > 0 ? (object)item.CardID : DBNull.Value, item.EmployeeID, item.CardNo.Trim(), item.PunchTime, diff --git a/HRM.UI/ClientApp/src/app/attendance/read-attendance-rawdata/read-attendance-rawdata.component.ts b/HRM.UI/ClientApp/src/app/attendance/read-attendance-rawdata/read-attendance-rawdata.component.ts index e4f9601..c52fd6e 100644 --- a/HRM.UI/ClientApp/src/app/attendance/read-attendance-rawdata/read-attendance-rawdata.component.ts +++ b/HRM.UI/ClientApp/src/app/attendance/read-attendance-rawdata/read-attendance-rawdata.component.ts @@ -37,7 +37,7 @@ export class ReadAttendanceRawdataComponent { public employees: Employee[] = []; selectedCard: any; entryModes: any = EnumExtension.getNamesAndValues(EnumEntryMode); - format: string = ".csv (Column Names: EmpNo,PunchTime,CardNo)"; // (Date Time Format: MM/dd/yyyy h:mm) + format: string = ".csv (Column Names: EmpNo,PunchTime[,CardNo]) PunchTime: M/d/yyyy H:mm"; // CardNo is optional public gridView: GridDataResult; public gridView2: GridDataResult; public mandatoryItems: mandatoryData[] = []; @@ -149,22 +149,23 @@ export class ReadAttendanceRawdataComponent { }); //console.log(text); - var lines = text.split("\n"); + var lines = text.split(/\r?\n/); // handle both Windows (\r\n) and Unix (\n) line endings //console.log(lines); this.uploadedLines = []; + const cell = (v: string) => (v === undefined || v === null) ? '' : v.trim().replace(/^"(.*)"$/, '$1').trim(); try { for (var i = 1; i < lines.length; i++) { this.uploadeddata = new csvFileData(); - if (lines[i] !== "") { + if (lines[i].trim() !== "") { //console.log(lines[i]); var temp = lines[i].split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); //spliting comma separated values but ignoring commas in double quoted string this.uploadeddata.rowNo = i.toString(); - this.uploadeddata.employeeNo = temp[0].replace(/^"(.*)"$/, '$1'); - this.uploadeddata.punchTime = temp[1].replace(/^"(.*)"$/, '$1'); - this.uploadeddata.cardNo = temp[2].replace(/^"(.*)"$/, '$1'); + this.uploadeddata.employeeNo = cell(temp[0]); + this.uploadeddata.punchTime = cell(temp[1]); + this.uploadeddata.cardNo = cell(temp[2]); // CardNo column is optional //this.uploadeddata.deviceNo = temp[3].replace(/^"(.*)"$/, '$1'); //this.uploadeddata.deviceIPAddress = temp[4].replace(/^"(.*)"$/, '$1'); //this.uploadeddata.entryMode = temp[5].replace(/^"(.*)"$/, '$1'); @@ -185,7 +186,8 @@ export class ReadAttendanceRawdataComponent { //console.log(this.uploadeddatas); } catch (e) { - //this.no.showError(e); + this.uploadeddatas = []; + this._notificationService.showError("Failed to read CSV file: " + (e && e.message ? e.message : e)); } // //console.log(this.iJobs); @@ -207,16 +209,27 @@ export class ReadAttendanceRawdataComponent { } public upload() { - if (this.uploadeddata == undefined || this.uploadeddatas.length <= 0) { + if (this.uploadeddatas == undefined || this.uploadeddatas.length <= 0) { this._notificationService.showError("Data not defined"); return; } - if (this.mandatoryItems !== undefined && this.mandatoryItems.length > 0) { + if (this.employees == undefined || this.employees.length <= 0) { + this._notificationService.showError("Employee list is not loaded yet. Please wait a moment and try again."); + return; + } + // re-validate here: the employee list may still have been loading when the file was selected + this.mandatoryDataChecker(this.uploadeddatas); + this.errorChecker(this.uploadeddatas); + this.skip2 = 0; + this.loadGrid2(); + + if (this.mandatoryItems.some(x => x.Error !== "Employee Not Found")) { this._notificationService.showError("Employee No and PunchTime cannot be empty"); return; } if (this.faultyItems !== undefined && this.faultyItems.length > 0) { - this._notificationService.showError("Employee No not Found"); + const notFound = Array.from(new Set(this.faultyItems.map(x => x.employeeNo))).join(', '); + this._notificationService.showError("Employee No not found: " + notFound); return; } debugger; @@ -246,10 +259,13 @@ export class ReadAttendanceRawdataComponent { item.punchTime = new Date; // otherwise error item.punchTimeDateString = x.punchTime; //console.log(item.punchTime); - item.employeeID = this.employees.find(e => e.employeeNo.trim().toLowerCase() == x.employeeNo.trim().toLowerCase()) == undefined ? 0 : this.employees.find(e => e.employeeNo.trim().toLowerCase() == x.employeeNo.trim().toLowerCase()).id; - item.employeeNo = x.employeeNo, + const matchedEmp = this.findEmployee(x.employeeNo); + item.employeeID = matchedEmp == undefined ? 0 : matchedEmp.id; + item.employeeNo = matchedEmp == undefined ? x.employeeNo : matchedEmp.employeeNo.trim(), item.entryMode = (x.entryMode == undefined || x.entryMode.length <= 0) ? 0 : this.entryModes.find(e => e.name.trim().toLowerCase() == x.entryMode.trim().toLowerCase()).value, - item.cardNo = (x.cardNo == undefined || x.cardNo == "" || x.cardNo == null) ? item.employeeNo : x.cardNo, + item.cardID = (matchedEmp != undefined && matchedEmp.cardID != undefined && matchedEmp.cardID != null) ? matchedEmp.cardID : 0; + // empty CardNo -> server fills it from the employee's tagged AccessCard (falls back to EmpNo) + item.cardNo = (x.cardNo == undefined || x.cardNo == null) ? "" : x.cardNo.trim(), item.deviceNo = (x.deviceNo !== undefined || x.deviceNo !== "" || x.deviceNo !== null) ? "" : item.deviceNo, item.deviceIPAddress = (x.deviceIPAddress !== undefined || x.deviceIPAddress !== "" || x.deviceIPAddress !== null) ? "" : item.deviceIPAddress item.isManualEntry = true; @@ -423,8 +439,8 @@ export class ReadAttendanceRawdataComponent { // this.faultyItems.push(item); //} var employeeId = 0; - if (this.employees != undefined) { - var employee = this.employees.find(x => x.employeeNo.trim() === item.employeeNo.trim()); + if (this.employees != undefined && this.employees.length > 0) { + var employee = this.findEmployee(item.employeeNo); if (employee == undefined) { var mandatoryItem = new mandatoryData(); mandatoryItem.ColumnName = "EmployeeNo"; @@ -440,6 +456,18 @@ export class ReadAttendanceRawdataComponent { } + // Matches an uploaded EmpNo to an employee: trimmed, case-insensitive, and + // tolerant of leading zeros (e.g. "859" matches "00859"). + private findEmployee(empNo: string): Employee { + if (empNo == undefined || this.employees == undefined) return undefined; + const norm = (v: string) => (v == undefined || v == null) ? '' : String(v).trim().toLowerCase(); + const stripZeros = (v: string) => v.replace(/^0+(?=.)/, ''); + const target = norm(empNo); + if (target === '') return undefined; + return this.employees.find(e => norm(e.employeeNo) === target) + || this.employees.find(e => norm(e.employeeNo) !== '' && stripZeros(norm(e.employeeNo)) === stripZeros(target)); + } + public pageChange(event: PageChangeEvent): void { this.skip = event.skip; this.gridView = { -- 2.40.0.windows.1 From 8f2a9b07d2a3dfc7375b7c3217d2e4ccf2d6920f Mon Sep 17 00:00:00 2001 From: chapal Date: Sun, 15 Nov 2026 11:46:14 +0600 Subject: [PATCH 3/3] Undo salary --- HRM.DA/DA/Salary/SalaryProcessDA.cs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/HRM.DA/DA/Salary/SalaryProcessDA.cs b/HRM.DA/DA/Salary/SalaryProcessDA.cs index 8d793a1..aaa34ae 100644 --- a/HRM.DA/DA/Salary/SalaryProcessDA.cs +++ b/HRM.DA/DA/Salary/SalaryProcessDA.cs @@ -222,20 +222,18 @@ namespace HRM.DA tc.ExecuteNonQuery( "DELETE FROM SalaryMonthlyDetail WHERE SalaryMonthlyID in( Select SalaryMonthlyID from SalaryMonthly Where SalaryProcessID=%n)", id); - // tc.ExecuteNonQuery("ALTER TABLE OPIPayment NOCHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY;") - // ALTER TABLE SALARYMONTHLYDETAIL NOCHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; - // ALTER TABLE SALARYTEMPDETAIL NOCHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH;" - // --Delete data - // DELETE FROM SalaryMonthly - //WHERE SalaryProcessID = 75523; - - // --Re - enable and validate constraints - // ALTER TABLE OPIPayment WITH CHECK CHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY; - // ALTER TABLE SALARYMONTHLYDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; - // ALTER TABLE SALARYTEMPDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH; + tc.ExecuteNonQuery(@"ALTER TABLE OPIPayment NOCHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY; + ALTER TABLE SALARYMONTHLYDETAIL NOCHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; + ALTER TABLE SALARYTEMPDETAIL NOCHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH; "); + tc.ExecuteNonQuery("DELETE FROM SalaryMonthly WHERE SalaryProcessID=%n", id); - //} + + tc.ExecuteNonQuery(@" ALTER TABLE OPIPayment WITH CHECK CHECK CONSTRAINT FK_OPIPayment_SALARYMONTHLY; + ALTER TABLE SALARYMONTHLYDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRMTHDTL_SLRMTH; + ALTER TABLE SALARYTEMPDETAIL WITH CHECK CHECK CONSTRAINT FK_SLRTMPDTL_SLRMTH;"); + + tc.ExecuteNonQuery("DELETE FROM SalaryProcess WHERE SalaryProcessID=%n", id); } -- 2.40.0.windows.1