From f1c546ba8fc13951883dc5ddd19e12dc8d098355 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 17:37:43 +0700 Subject: [PATCH 1/4] fix store procedure --- ...mployeeSalaryLevel_calendar_arithmetic.sql | 140 ++++++++++++++++++ ...oyeeSalaryPosition_calendar_arithmetic.sql | 137 +++++++++++++++++ ...ileSalaryExecutive_calendar_arithmetic.sql | 12 +- ...ProfileSalaryLevel_calendar_arithmetic.sql | 12 +- ...fileSalaryPosition_calendar_arithmetic.sql | 10 +- 5 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql create mode 100644 docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql diff --git a/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql new file mode 100644 index 00000000..bca30538 --- /dev/null +++ b/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql @@ -0,0 +1,140 @@ +-- ==================================================================== +-- Fix GetProfileEmployeeSalaryLevel to use calendar arithmetic +-- This changes from fixed formulas to actual calendar arithmetic, +-- matching calculateGovAge and GetProfileSalaryLevel behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileEmployeeSalaryLevel`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileEmployeeSalaryLevel`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * + FROM profileSalary + WHERE profileEmployeeId = personId + AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') +), +work_session AS ( + SELECT *, + COALESCE( + SUM(CASE WHEN commandCode IN (12,15,16) THEN 1 ELSE 0 END) + OVER (ORDER BY commandDateAffect, commandDateSign + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), + 0) AS sessionId + FROM ordered +), +session_end AS ( + SELECT sessionId, MAX(commandDateAffect) AS sessionEndDate + FROM work_session + GROUP BY sessionId +), +level_change AS ( + SELECT *, + CASE + WHEN LAG(positionCee) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionCee + AND LAG(positionType) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionType + AND LAG(positionLevel) OVER (ORDER BY commandDateAffect, commandDateSign) <=> positionLevel + AND LAG(sessionId) OVER (ORDER BY commandDateAffect, commandDateSign) = sessionId + THEN 0 + ELSE 1 + END AS isNewLevel + FROM work_session +), +level_group AS ( + SELECT *, + SUM(isNewLevel) OVER (ORDER BY commandDateAffect, commandDateSign) AS levelGroup + FROM level_change +), +first_rows AS ( + SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY levelGroup ORDER BY commandDateAffect, commandDateSign) AS rnLevel + FROM level_group + ) t WHERE rnLevel = 1 +), +rows_with_duration AS ( + SELECT + fr.*, + CASE + WHEN LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) IS NULL + THEN NULL + WHEN LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) <> fr.sessionId + THEN TIMESTAMPDIFF(DAY, fr.commandDateAffect, se.sessionEndDate) + 1 + ELSE + TIMESTAMPDIFF(DAY, fr.commandDateAffect, + LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign)) + END AS duration_days + FROM first_rows fr + LEFT JOIN session_end se ON se.sessionId = fr.sessionId +), +resultWithDiff AS ( + SELECT + *, + LAG(duration_days) OVER (ORDER BY commandDateAffect, commandDateSign) AS days_diff + FROM rows_with_duration +) +SELECT + r.commandDateAffect, + r.positionType, + r.positionLevel, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + DATEDIFF(r.commandDateAffect, + DATE_ADD( + DATE_ADD(LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), + INTERVAL TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 MONTH) + ) + ELSE 0 + END AS Days, + r.posNo, + r.positionExecutive, + r.orgRoot, + r.orgChild1, + r.orgChild2, + r.orgChild3, + r.orgChild4, + r.commandCode, + r.commandName, + r.commandNo, + r.commandYear, + r.remark +FROM resultWithDiff r + +UNION ALL + +SELECT + _date, NULL, NULL, NULL, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + DATEDIFF(_date, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL +FROM resultWithDiff; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql new file mode 100644 index 00000000..fa53b467 --- /dev/null +++ b/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql @@ -0,0 +1,137 @@ +-- ==================================================================== +-- Fix GetProfileEmployeeSalaryPosition to use calendar arithmetic +-- This changes from fixed formulas to actual calendar arithmetic, +-- matching calculateGovAge and GetProfileSalaryPosition behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileEmployeeSalaryPosition`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileEmployeeSalaryPosition`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * FROM profileSalary WHERE profileEmployeeId = personId AND commandCode IN ('0','1','2','3','4','8','9','10','11','12','13','14','15','16','20') +), +work_session AS ( + SELECT *, + COALESCE( + SUM(CASE WHEN commandCode IN (12,15,16) THEN 1 ELSE 0 END) + OVER (ORDER BY commandDateAffect, commandDateSign + ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), + 0) AS sessionId + FROM ordered +), +session_end AS ( + SELECT sessionId, MAX(commandDateAffect) AS sessionEndDate + FROM work_session + GROUP BY sessionId +), +position_change AS ( + SELECT *, + CASE + WHEN LAG(positionName) OVER (ORDER BY commandDateAffect, commandDateSign) = positionName + AND LAG(sessionId) OVER (ORDER BY commandDateAffect, commandDateSign) = sessionId + THEN 0 + ELSE 1 + END AS isNewPosition + FROM work_session +), +position_group AS ( + SELECT *, + SUM(isNewPosition) OVER (ORDER BY commandDateAffect, commandDateSign) AS posGroup + FROM position_change +), +first_rows AS ( + SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY posGroup ORDER BY commandDateAffect, commandDateSign) AS rnPos + FROM position_group + ) t WHERE rnPos = 1 +), +rows_with_duration AS ( + SELECT + fr.*, + LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) AS nextSessionId, + CASE + WHEN LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) IS NULL + THEN NULL + WHEN LEAD(fr.sessionId) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign) <> fr.sessionId + THEN TIMESTAMPDIFF(DAY, fr.commandDateAffect, se.sessionEndDate) + 1 + ELSE + TIMESTAMPDIFF(DAY, fr.commandDateAffect, + LEAD(fr.commandDateAffect) OVER (ORDER BY fr.commandDateAffect, fr.commandDateSign)) + END AS duration_days + FROM first_rows fr + LEFT JOIN session_end se ON se.sessionId = fr.sessionId +), +resultWithDiff AS ( + SELECT + *, + LAG(duration_days) OVER (ORDER BY commandDateAffect, commandDateSign) AS days_diff + FROM rows_with_duration +) +SELECT + r.commandDateAffect, + r.positionName, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(DAY, + DATE_ADD( + DATE_ADD(LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), + INTERVAL TIMESTAMPDIFF(YEAR, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, LAG(commandDateAffect) OVER (ORDER BY commandDateAffect, commandDateSign), r.commandDateAffect) % 12 MONTH), + r.commandDateAffect) + ELSE 0 + END AS Days, + r.posNo, + r.positionExecutive, + r.positionType, + r.positionLevel, + r.orgRoot, + r.orgChild1, + r.orgChild2, + r.orgChild3, + r.orgChild4, + r.commandCode, + r.commandName, + r.commandNo, + r.commandYear, + r.remark +FROM resultWithDiff r + +UNION ALL + +SELECT + _date, NULL, NULL, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + DATEDIFF(_date, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL,NULL +FROM resultWithDiff; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql index e3585c9f..07d85ee8 100644 --- a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql @@ -90,12 +90,12 @@ SELECT END AS Months, CASE WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN - TIMESTAMPDIFF(DAY, + DATEDIFF(r.commandDateAffect, DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH) + ) ELSE 0 END AS Days, r.posNo, @@ -121,12 +121,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql index ca811a23..0ce8bbb5 100644 --- a/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql @@ -94,12 +94,12 @@ SELECT END AS Months, CASE WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN - TIMESTAMPDIFF(DAY, + DATEDIFF(r.commandDateAffect, DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH) + ) ELSE 0 END AS Days, r.posNo, @@ -123,12 +123,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql index a546ad97..aed2e9e7 100644 --- a/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql +++ b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql @@ -96,8 +96,8 @@ SELECT DATE_ADD( DATE_ADD(LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), INTERVAL TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), - r.commandDateAffect) + 1 + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 MONTH), + r.commandDateAffect) ELSE 0 END AS Days, r.posNo, @@ -124,12 +124,12 @@ SELECT TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, - TIMESTAMPDIFF(DAY, + DATEDIFF(_date, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), - _date) + 1, + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) + ), NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL,NULL FROM resultWithDiff; From d093953fbe44c7c774f674d85d2526e44370da0b Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 20:56:20 +0700 Subject: [PATCH 2/4] =?UTF-8?q?=E0=B9=81=E0=B8=81=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=82=E0=B8=81=E0=B8=B2=E0=B8=A3=E0=B8=84=E0=B8=B3=E0=B8=99?= =?UTF-8?q?=E0=B8=A7=E0=B8=99=E0=B8=A3=E0=B8=B0=E0=B8=A2=E0=B8=B0=E0=B9=80?= =?UTF-8?q?=E0=B8=A7=E0=B8=A5=E0=B8=B2=E0=B8=84=E0=B8=A3=E0=B8=AD=E0=B8=87?= =?UTF-8?q?=E0=B8=95=E0=B8=B3=E0=B9=81=E0=B8=AB=E0=B8=99=E0=B9=88=E0=B8=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/ProfileSalaryController.ts | 211 +++++++++--------- .../ProfileSalaryEmployeeController.ts | 114 ++++++++-- src/utils/tenure.ts | 45 ++-- 3 files changed, 227 insertions(+), 143 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 277ac081..ce477490 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -23,7 +23,7 @@ import { ProfileEmployee } from "../entities/ProfileEmployee"; import { In, IsNull, LessThan, MoreThan, Not } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { calculateTenure } from "../utils/tenure"; +import { normalizeDurationSumSimple } from "../utils/tenure"; import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; @@ -78,33 +78,28 @@ export class ProfileSalaryController extends Controller { _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + // Filter for current position and use SP's calculated values (calendar arithmetic) const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + // Use stored procedure's calculated values (calendar arithmetic) + year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const calDayDiff = mapPosition - .filter((curr: any) => curr.positionName == x.position) - .reduce( - (acc: any, curr: any) => { - acc.days_diff += Number(curr.days_diff) || 0; - acc.positionName = curr.positionName; - return acc; - }, - { days_diff: 0, positionName: null }, - ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileId: x.id, - positionName: calDayDiff.positionName, - days_diff: calDayDiff.days_diff, - Years: year, - Months: month, - Days: day, - }; - data.push(mapData); + const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + if (currentTenure) { + const mapData: any = { + profileId: x.id, + positionName: currentTenure.positionName, + year: currentTenure.year, + month: currentTenure.month, + day: currentTenure.day, + }; + data.push(mapData); + } } await this.positionOfficerRepo.save(data); @@ -128,33 +123,28 @@ export class ProfileSalaryController extends Controller { _currentDate, ]); const _position = position.length > 0 ? position[0] : []; + // Filter for current position and use SP's calculated values (calendar arithmetic) const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ - days_diff: curr.days_diff, positionName: _position[index]?.positionName, + // Use stored procedure's calculated values (calendar arithmetic) + year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const calDayDiff = mapPosition - .filter((curr: any) => curr.positionName == x.position) - .reduce( - (acc: any, curr: any) => { - acc.days_diff += Number(curr.days_diff) || 0; - acc.positionName = curr.positionName; - return acc; - }, - { days_diff: 0, positionName: null }, - ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); - const mapData: any = { - profileEmployeeId: x.id, - positionName: calDayDiff.positionName, - days_diff: calDayDiff.days_diff, - Years: year, - Months: month, - Days: day, - }; - data.push(mapData); + const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + if (currentTenure) { + const mapData: any = { + profileEmployeeId: x.id, + positionName: currentTenure.positionName, + year: currentTenure.year, + month: currentTenure.month, + day: currentTenure.day, + }; + data.push(mapData); + } } await this.positionEmployeeRepo.save(data); @@ -203,16 +193,17 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : year.toFixed(4), - Months: x.posLevel == null ? 0 : month.toFixed(4), - Days: x.posLevel == null ? 0 : day.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), + Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), + Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), }; data.push(mapData); } @@ -263,16 +254,17 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileEmployeeId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : year.toFixed(4), - Months: x.posLevel == null ? 0 : month.toFixed(4), - Days: x.posLevel == null ? 0 : day.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), + Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), + Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), }; data.push(mapData); } @@ -337,14 +329,15 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionExecutive: null }, ); - const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const years = calDayDiff.days_diff / 365; + const normalized = normalizeDurationSumSimple(years, 0, 0); const mapData: any = { profileId: x.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: year.toFixed(4), - Months: month.toFixed(4), - Days: day.toFixed(4), + Years: normalized.years.toFixed(4), + Months: normalized.months.toFixed(4), + Days: normalized.days.toFixed(4), }; data.push(mapData); } @@ -617,15 +610,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -668,15 +661,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -714,15 +707,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -787,15 +780,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -839,15 +832,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -885,15 +878,15 @@ export class ProfileSalaryController extends Controller { acc.push(existing); } - // Normalize the summed values (convert excess days to months, months to years) - while (existing.day >= 30) { - existing.month += Math.floor(existing.day / 30); - existing.day = existing.day % 30; - } - while (existing.month >= 12) { - existing.year += Math.floor(existing.month / 12); - existing.month = existing.month % 12; - } + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple( + existing.year, + existing.month, + existing.day + ); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, diff --git a/src/controllers/ProfileSalaryEmployeeController.ts b/src/controllers/ProfileSalaryEmployeeController.ts index 44b93a5d..5f2ea997 100644 --- a/src/controllers/ProfileSalaryEmployeeController.ts +++ b/src/controllers/ProfileSalaryEmployeeController.ts @@ -27,7 +27,7 @@ import { Profile } from "../entities/Profile"; import { In, LessThan, IsNull, MoreThan } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; -import { calculateTenure } from "../utils/tenure"; +import { normalizeDurationSumSimple } from "../utils/tenure"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import Extension from "../interfaces/extension"; @@ -161,6 +161,14 @@ export class ProfileSalaryEmployeeController extends Controller { _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) : []; @@ -171,15 +179,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -195,6 +213,14 @@ export class ProfileSalaryEmployeeController extends Controller { _posLevel.length > 1 ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee ? `ระดับ ${_posLevel[index]?.positionCee.trim()}` @@ -208,15 +234,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -254,6 +290,14 @@ export class ProfileSalaryEmployeeController extends Controller { _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) : []; @@ -264,15 +308,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, @@ -288,6 +342,14 @@ export class ProfileSalaryEmployeeController extends Controller { _posLevel.length > 1 ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, + // Use stored procedure's calculated values (calendar arithmetic) + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee ? `ระดับ ${_posLevel[index]?.positionCee.trim()}` @@ -301,15 +363,25 @@ export class ProfileSalaryEmployeeController extends Controller { if (existing) { existing.days += curr.days; + existing.year += curr.year; + existing.month += curr.month; + existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } - const { year, month, day } = calculateTenure(existing.days); - existing.year = year; - existing.month = month; - existing.day = day; + // Normalize the summed values using calendar arithmetic + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); + existing.year = normalized.years; + existing.month = normalized.months; + existing.day = normalized.days; return acc; }, diff --git a/src/utils/tenure.ts b/src/utils/tenure.ts index dbdedbb3..1c97dff1 100644 --- a/src/utils/tenure.ts +++ b/src/utils/tenure.ts @@ -1,18 +1,37 @@ /** - * คำนวณอายุงานจากจำนวนวันรวม - * ใช้สูตรเดียวกับ Stored Procedure GetProfileSalaryPosition - * @param totalDays จำนวนวันรวม - * @returns { year, month, day } ปี เดือน วัน + * Normalize a duration sum using calendar arithmetic + * Converts excess days to months using average month length (30.4375 days) + * and excess months to years. Matches the logic used in stored procedures. + * + * @param years Total years from sum + * @param months Total months from sum + * @param days Total days from sum + * @returns Normalized { years, months, days } */ -export function calculateTenure(totalDays: number) { - // Match stored procedure formula: - // days_diff / 365.2524 AS Years - // (days_diff / 30.4375) % 12 AS Months - // days_diff % 30.4375 AS Days +export function normalizeDurationSumSimple( + years: number, + months: number, + days: number, +): { years: number; months: number; days: number } { + const DAYS_PER_MONTH = 30.4375; // Average days per month in Gregorian calendar - const year = Math.floor(totalDays / 365.2524); - const month = Math.floor((totalDays / 30.4375) % 12); - const day = Math.floor(totalDays % 30.4375); + let totalMonths = months; + let totalDays = days; - return { year, month, day }; + // Convert excess days to months + if (totalDays >= DAYS_PER_MONTH) { + const additionalMonths = Math.floor(totalDays / DAYS_PER_MONTH); + totalMonths += additionalMonths; + totalDays = totalDays - additionalMonths * DAYS_PER_MONTH; + } + + // Convert excess months to years + let totalYears = years; + if (totalMonths >= 12) { + const additionalYears = Math.floor(totalMonths / 12); + totalYears += additionalYears; + totalMonths = totalMonths % 12; + } + + return { years: totalYears, months: Math.floor(totalMonths), days: Math.floor(totalDays) }; } From 5ea111a3c5b1b0a5d5a0b1c1b02a57b20b5079ac Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 21:51:23 +0700 Subject: [PATCH 3/4] fixed cron job --- src/controllers/ProfileSalaryController.ts | 280 +++++++++++++++------ 1 file changed, 202 insertions(+), 78 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index ce477490..cd8e2948 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -84,19 +84,31 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, - day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); const mapData: any = { profileId: x.id, positionName: currentTenure.positionName, - year: currentTenure.year, - month: currentTenure.month, - day: currentTenure.day, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -129,19 +141,31 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, - day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); if (currentTenure) { + const normalized = normalizeDurationSumSimple( + currentTenure.year, + currentTenure.month, + currentTenure.day, + ); const mapData: any = { profileEmployeeId: x.id, positionName: currentTenure.positionName, - year: currentTenure.year, - month: currentTenure.month, - day: currentTenure.day, + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -175,6 +199,16 @@ export class ProfileSalaryController extends Controller { positionType: _positionLevel[index]?.positionType, positionLevel: _positionLevel[index]?.positionLevel, positionCee: _positionLevel[index]?.positionCee, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; const calDayDiff = mapPositionLevel @@ -189,21 +223,35 @@ export class ProfileSalaryController extends Controller { acc.positionType = curr.positionType; acc.positionLevel = curr.positionLevel; acc.positionCee = curr.positionCee; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + year: 0, + month: 0, + day: 0, + }, ); - const years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), - Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), - Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years, + Months: x.posLevel == null ? 0 : normalized.months, + Days: x.posLevel == null ? 0 : normalized.days, }; data.push(mapData); } @@ -236,6 +284,16 @@ export class ProfileSalaryController extends Controller { positionType: _positionLevel[index]?.positionType, positionLevel: _positionLevel[index]?.positionLevel, positionCee: _positionLevel[index]?.positionCee, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; const calDayDiff = mapPositionLevel @@ -250,21 +308,35 @@ export class ProfileSalaryController extends Controller { acc.positionType = curr.positionType; acc.positionLevel = curr.positionLevel; acc.positionCee = curr.positionCee; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, + { + days_diff: 0, + positionType: null, + positionLevel: null, + positionCee: null, + year: 0, + month: 0, + day: 0, + }, ); - const years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileEmployeeId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years.toFixed(4), - Months: x.posLevel == null ? 0 : normalized.months.toFixed(4), - Days: x.posLevel == null ? 0 : normalized.days.toFixed(4), + Years: x.posLevel == null ? 0 : normalized.years, + Months: x.posLevel == null ? 0 : normalized.months, + Days: x.posLevel == null ? 0 : normalized.days, }; data.push(mapData); } @@ -316,6 +388,16 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ days_diff: curr.days_diff, positionExecutive: _position[index]?.positionExecutive, + year: + curr.Years !== null && curr.Years !== undefined + ? Math.floor(Number(curr.Years)) + : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, + day: + curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; const _posExecutiveName = position?.posExecutive?.posExecutiveName; @@ -325,19 +407,25 @@ export class ProfileSalaryController extends Controller { (acc: any, curr: any) => { acc.days_diff += Number(curr.days_diff) || 0; acc.positionExecutive = curr.positionExecutive; + acc.year += curr.year; + acc.month += curr.month; + acc.day += curr.day; return acc; }, - { days_diff: 0, positionExecutive: null }, + { days_diff: 0, positionExecutive: null, year: 0, month: 0, day: 0 }, ); - const years = calDayDiff.days_diff / 365; - const normalized = normalizeDurationSumSimple(years, 0, 0); + const normalized = normalizeDurationSumSimple( + calDayDiff.year, + calDayDiff.month, + calDayDiff.day, + ); const mapData: any = { profileId: x.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: normalized.years.toFixed(4), - Months: normalized.months.toFixed(4), - Days: normalized.days.toFixed(4), + Years: normalized.years, + Months: normalized.months, + Days: normalized.days, }; data.push(mapData); } @@ -589,8 +677,12 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) @@ -606,16 +698,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -634,8 +728,12 @@ export class ProfileSalaryController extends Controller { ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee @@ -657,16 +755,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -686,8 +786,12 @@ export class ProfileSalaryController extends Controller { ? _posExecutive.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _posExecutive[index]?.positionExecutive, })) @@ -703,16 +807,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -759,8 +865,12 @@ export class ProfileSalaryController extends Controller { ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _position[index]?.positionName, })) @@ -776,16 +886,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -805,8 +917,12 @@ export class ProfileSalaryController extends Controller { ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: !_posLevel[index]?.positionType && _posLevel[index]?.positionCee @@ -828,16 +944,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; @@ -857,8 +975,12 @@ export class ProfileSalaryController extends Controller { ? _posExecutive.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, // Use stored procedure's calculated values (calendar arithmetic) - year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, - month: curr.Months !== null && curr.Months !== undefined ? Math.floor(Number(curr.Months)) : 0, + year: + curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) : 0, + month: + curr.Months !== null && curr.Months !== undefined + ? Math.floor(Number(curr.Months)) + : 0, day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _posExecutive[index]?.positionExecutive, })) @@ -874,16 +996,18 @@ export class ProfileSalaryController extends Controller { existing.month += curr.month; existing.day += curr.day; } else { - existing = { name: curr.name, days: curr.days, year: curr.year, month: curr.month, day: curr.day }; + existing = { + name: curr.name, + days: curr.days, + year: curr.year, + month: curr.month, + day: curr.day, + }; acc.push(existing); } // Normalize the summed values using calendar arithmetic - const normalized = normalizeDurationSumSimple( - existing.year, - existing.month, - existing.day - ); + const normalized = normalizeDurationSumSimple(existing.year, existing.month, existing.day); existing.year = normalized.years; existing.month = normalized.months; existing.day = normalized.days; From 6c5356ca4688b899737fa3233ad4608b7e1a5845 Mon Sep 17 00:00:00 2001 From: waruneeauy Date: Mon, 18 May 2026 23:25:09 +0700 Subject: [PATCH 4/4] fixed tenure --- src/controllers/ProfileSalaryController.ts | 504 +++++++++++++++------ src/entities/TenureLevelEmployee.ts | 2 +- 2 files changed, 379 insertions(+), 127 deletions(-) diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index cd8e2948..19a9f5e4 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -24,11 +24,20 @@ import { In, IsNull, LessThan, MoreThan, Not } from "typeorm"; import permission from "../interfaces/permission"; import { setLogDataDiff } from "../interfaces/utils"; import { normalizeDurationSumSimple } from "../utils/tenure"; -import { TenurePositionOfficer } from "../entities/TenurePositionOfficer"; -import { TenureLevelOfficer } from "../entities/TenureLevelOfficer"; -import { TenurePositionEmployee } from "../entities/TenurePositionEmployee"; -import { TenureLevelEmployee } from "../entities/TenureLevelEmployee"; -import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer"; +import { + TenurePositionOfficer, + CreateTenurePositionOfficer, +} from "../entities/TenurePositionOfficer"; +import { TenureLevelOfficer, CreateTenureLevelOfficer } from "../entities/TenureLevelOfficer"; +import { + TenurePositionEmployee, + CreateTenurePositionEmployee, +} from "../entities/TenurePositionEmployee"; +import { TenureLevelEmployee, CreateTenureLevelEmployee } from "../entities/TenureLevelEmployee"; +import { + TenurePositionExecutiveOfficer, + CreateTenurePositionExecutiveOfficer, +} from "../entities/TenurePositionExecutiveOfficer"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgRevision } from "../entities/OrgRevision"; @@ -46,44 +55,84 @@ export class ProfileSalaryController extends Controller { private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee); private salaryRepo = AppDataSource.getRepository(ProfileSalary); private salaryHistoryRepo = AppDataSource.getRepository(ProfileSalaryHistory); - private positionOfficerRepo = AppDataSource.getRepository(TenurePositionOfficer); - private positionEmployeeRepo = AppDataSource.getRepository(TenurePositionEmployee); - private levelOfficerRepo = AppDataSource.getRepository(TenureLevelOfficer); - private levelEmployeeRepo = AppDataSource.getRepository(TenureLevelEmployee); - private positionExecutiveOfficerRepo = AppDataSource.getRepository( - TenurePositionExecutiveOfficer, - ); private commandRepository = AppDataSource.getRepository(Command); private orgRootRepository = AppDataSource.getRepository(OrgRoot); - private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); - private positionRepo = AppDataSource.getRepository(Position); private registryRepo = AppDataSource.getRepository(Registry); private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee); @Get("TenurePositionOfficer") public async cronjobTenurePositionOfficer() { - let data: any = []; - await this.positionOfficerRepo.clear(); - const profile = await this.profileRepo.find(); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileRepo.find({ + select: ["id", "position", "isLeave", "leaveDate"], + where: { position: Not(IsNull()) }, + }); + + const BATCH_SIZE = 100; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenurePositionOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionOfficer, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure position officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenurePositionOfficer( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const position = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; - // Filter for current position and use SP's calculated values (calendar arithmetic) + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, - // Use stored procedure's calculated values (calendar arithmetic) year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) @@ -96,51 +145,102 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + if (currentTenure) { const normalized = normalizeDurationSumSimple( currentTenure.year, currentTenure.month, currentTenure.day, ); - const mapData: any = { - profileId: x.id, + return { + profileId: profile.id, positionName: currentTenure.positionName, + days_diff: null, Years: normalized.years, Months: normalized.months, Days: normalized.days, }; - data.push(mapData); } + return null; + } catch (error) { + return null; } - await this.positionOfficerRepo.save(data); - - return new HttpSuccess(); } @Get("TenurePositionEmployee") public async cronjobTenurePositionEmployee() { - let data: any = []; - await this.positionEmployeeRepo.clear(); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - const profile = await this.profileEmployeeRepo.find(); - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileEmployeeRepo.find({ + select: ["id", "position", "isLeave", "leaveDate"], + where: { position: Not(IsNull()) }, + }); + + const BATCH_SIZE = 100; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionEmployee[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenurePositionEmployee(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionEmployee, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionEmployee(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionEmployee, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure position employee สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenurePositionEmployee( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const position = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; - // Filter for current position and use SP's calculated values (calendar arithmetic) + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ positionName: _position[index]?.positionName, - // Use stored procedure's calculated values (calendar arithmetic) year: curr.Years !== null && curr.Years !== undefined ? Math.floor(Number(curr.Years)) @@ -153,45 +253,105 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const currentTenure = mapPosition.find((curr: any) => curr.positionName == x.position); + + const currentTenure = mapPosition.find((curr: any) => curr.positionName === profile.position); + if (currentTenure) { const normalized = normalizeDurationSumSimple( currentTenure.year, currentTenure.month, currentTenure.day, ); - const mapData: any = { - profileEmployeeId: x.id, + return { + profileEmployeeId: profile.id, positionName: currentTenure.positionName, + days_diff: null, Years: normalized.years, Months: normalized.months, Days: normalized.days, }; - data.push(mapData); } + return null; + } catch (error) { + return null; } - await this.positionEmployeeRepo.save(data); - - return new HttpSuccess(); } @Get("TenureLevelOfficer") public async cronjobTenureLevelOfficer() { - let data: any = []; - await this.levelOfficerRepo.clear(); - const profile = await this.profileRepo.find({ relations: ["posLevel", "posType"] }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileRepo.find({ + relations: ["posLevel", "posType"], + select: ["id", "isLeave", "leaveDate", "posLevel", "posType"], + where: { + posLevel: Not(IsNull()), + posType: Not(IsNull()), + }, + }); + + const BATCH_SIZE = 100; + let successCount = 0; + let failCount = 0; + const allData: CreateTenureLevelOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureLevelOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenureLevelOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenureLevelOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenureLevelOfficer, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure level officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureLevelOfficer( + profile: Pick & { + posLevel?: { posLevelName?: string } | null; + posType?: { posTypeName?: string } | null; + }, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionLevel = await AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; + const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -211,11 +371,12 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; + const calDayDiff = mapPositionLevel .filter( (curr: any) => - curr.positionLevel == (x.posLevel?.posLevelName ?? null) && - curr.positionType == (x.posType?.posTypeName ?? null), + curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && + curr.positionType === (profile.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -238,45 +399,103 @@ export class ProfileSalaryController extends Controller { day: 0, }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileId: x.id, + + return { + profileId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years, - Months: x.posLevel == null ? 0 : normalized.months, - Days: x.posLevel == null ? 0 : normalized.days, + Years: profile.posLevel == null ? 0 : normalized.years, + Months: profile.posLevel == null ? 0 : normalized.months, + Days: profile.posLevel == null ? 0 : normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.levelOfficerRepo.save(data); - - return new HttpSuccess(); } @Get("TenureLevelEmployee") public async cronjobTenureLevelEmployee() { - let data: any = []; - await this.levelEmployeeRepo.clear(); - const profile = await this.profileEmployeeRepo.find({ relations: ["posLevel", "posType"] }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); + + const profiles = await this.profileEmployeeRepo.find({ + relations: ["posLevel", "posType"], + select: ["id", "isLeave", "leaveDate", "posLevel", "posType"], + where: { + posLevel: Not(IsNull()), + posType: Not(IsNull()), + }, + }); + + const BATCH_SIZE = 100; + let successCount = 0; + let failCount = 0; + const allData: CreateTenureLevelEmployee[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureLevelEmployee(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } + }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenureLevelEmployee, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenureLevelEmployee(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenureLevelEmployee, entities); } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure level employee สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureLevelEmployee( + profile: Pick & { + posLevel?: { posLevelName?: string } | null; + posType?: { posTypeName?: string } | null; + }, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionLevel = await AppDataSource.query("CALL GetProfileEmployeeSalaryLevel(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; + const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -296,11 +515,12 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; + const calDayDiff = mapPositionLevel .filter( (curr: any) => - curr.positionLevel == (x.posLevel?.posLevelName ?? null) && - curr.positionType == (x.posType?.posTypeName ?? null), + curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && + curr.positionType === (profile.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -323,66 +543,97 @@ export class ProfileSalaryController extends Controller { day: 0, }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileEmployeeId: x.id, + + return { + profileEmployeeId: profile.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: x.posLevel == null ? 0 : normalized.years, - Months: x.posLevel == null ? 0 : normalized.months, - Days: x.posLevel == null ? 0 : normalized.days, + Years: profile.posLevel == null ? 0 : normalized.years, + Months: profile.posLevel == null ? 0 : normalized.months, + Days: profile.posLevel == null ? 0 : normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.levelEmployeeRepo.save(data); - - return new HttpSuccess(); } @Get("TenurePositionExecutiveOfficer") public async cronjobTenureExecutivePositionOfficer() { - let data: any = []; - await this.positionExecutiveOfficerRepo.clear(); - const profile = await this.profileRepo.find(); - const orgRevision = await this.orgRevisionRepository.findOne({ - select: ["id"], - where: { - orgRevisionIsDraft: false, - orgRevisionIsCurrent: true, - }, - }); const CURRENT_DATE = await AppDataSource.query("SELECT CURRENT_DATE() as today"); const baseCurrentDate = CURRENT_DATE[0].today; - for await (const x of profile) { - // Use leave date if available and valid, otherwise use current date - let _currentDate = baseCurrentDate; - if (x?.isLeave && x.leaveDate) { - _currentDate = Extension.toDateOnlyString(x.leaveDate); - } - const position = await this.positionRepo.findOne({ - where: { - positionIsSelected: true, - posMaster: { - orgRevisionId: orgRevision?.id, - current_holderId: x.id, - }, - }, - order: { createdAt: "DESC" }, - relations: { - posExecutive: true, - }, + + const profiles = await this.profileRepo.find({ + select: ["id", "posExecutive", "isLeave", "leaveDate"], + where: { posExecutive: Not(IsNull()) }, + }); + + const BATCH_SIZE = 100; + let successCount = 0; + let failCount = 0; + const allData: CreateTenurePositionExecutiveOfficer[] = []; + + for (let i = 0; i < profiles.length; i += BATCH_SIZE) { + const batch = profiles.slice(i, Math.min(i + BATCH_SIZE, profiles.length)); + const results = await Promise.allSettled( + batch.map((profile) => + this.processSingleProfileForTenureExecutivePositionOfficer(profile, baseCurrentDate), + ), + ); + + results.forEach((result) => { + if (result.status === "fulfilled" && result.value) { + allData.push(result.value); + successCount++; + } else { + failCount++; + } }); + } + + await AppDataSource.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(TenurePositionExecutiveOfficer, {}); + if (allData.length > 0) { + const entities = allData.map((data) => { + const entity = new TenurePositionExecutiveOfficer(); + Object.assign(entity, data); + return entity; + }); + await transactionalEntityManager.save(TenurePositionExecutiveOfficer, entities); + } + }); + + return new HttpSuccess({ + message: `อัปเดต tenure executive position officer สำเร็จ`, + total: profiles.length, + success: successCount, + failed: failCount, + }); + } + + private async processSingleProfileForTenureExecutivePositionOfficer( + profile: Pick, + baseCurrentDate: string, + ): Promise { + try { + let _currentDate = baseCurrentDate; + if (profile.isLeave && profile.leaveDate) { + _currentDate = Extension.toDateOnlyString(profile.leaveDate); + } + const positionExecutive = await AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [ - x.id, + profile.id, _currentDate, ]); const _position = positionExecutive.length > 0 ? positionExecutive[0] : []; + const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ @@ -400,9 +651,9 @@ export class ProfileSalaryController extends Controller { curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, })) : []; - const _posExecutiveName = position?.posExecutive?.posExecutiveName; + const calDayDiff = mapPosition - .filter((curr: any) => _posExecutiveName && curr.positionExecutive == _posExecutiveName) + .filter((curr: any) => curr.positionExecutive === profile.posExecutive) .reduce( (acc: any, curr: any) => { acc.days_diff += Number(curr.days_diff) || 0; @@ -414,23 +665,24 @@ export class ProfileSalaryController extends Controller { }, { days_diff: 0, positionExecutive: null, year: 0, month: 0, day: 0 }, ); + const normalized = normalizeDurationSumSimple( calDayDiff.year, calDayDiff.month, calDayDiff.day, ); - const mapData: any = { - profileId: x.id, + + return { + profileId: profile.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, Years: normalized.years, Months: normalized.months, Days: normalized.days, }; - data.push(mapData); + } catch (error) { + return null; } - await this.positionExecutiveOfficerRepo.save(data); - return new HttpSuccess(); } @Get("Registry") diff --git a/src/entities/TenureLevelEmployee.ts b/src/entities/TenureLevelEmployee.ts index 36ae0176..5654e306 100644 --- a/src/entities/TenureLevelEmployee.ts +++ b/src/entities/TenureLevelEmployee.ts @@ -74,7 +74,7 @@ export class TenureLevelEmployee extends EntityBase { positionLevel: string; } -export class CreateTenureLevelOfficer { +export class CreateTenureLevelEmployee { profileEmployeeId: string; positionCee: string | null; days_diff: number | null;