diff --git a/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql deleted file mode 100644 index bca30538..00000000 --- a/docs/migrations/fix_GetProfileEmployeeSalaryLevel_calendar_arithmetic.sql +++ /dev/null @@ -1,140 +0,0 @@ --- ==================================================================== --- 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 deleted file mode 100644 index fa53b467..00000000 --- a/docs/migrations/fix_GetProfileEmployeeSalaryPosition_calendar_arithmetic.sql +++ /dev/null @@ -1,137 +0,0 @@ --- ==================================================================== --- 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 07d85ee8..e3585c9f 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 - DATEDIFF(r.commandDateAffect, + TIMESTAMPDIFF(DAY, 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) % 12 MONTH) - ) + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), + r.commandDateAffect) + 1 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, - DATEDIFF(_date, + TIMESTAMPDIFF(DAY, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) - ), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), + _date) + 1, 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 0ce8bbb5..ca811a23 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 - DATEDIFF(r.commandDateAffect, + TIMESTAMPDIFF(DAY, 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) % 12 MONTH) - ) + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), + r.commandDateAffect) + 1 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, - DATEDIFF(_date, + TIMESTAMPDIFF(DAY, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) - ), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), + _date) + 1, 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 aed2e9e7..a546ad97 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) % 12 MONTH), - r.commandDateAffect) + INTERVAL TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) MONTH), + r.commandDateAffect) + 1 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, - DATEDIFF(_date, + TIMESTAMPDIFF(DAY, DATE_ADD( DATE_ADD(MAX(commandDateAffect), INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), - INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12 MONTH) - ), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), + _date) + 1, NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,NULL,NULL,NULL,NULL FROM resultWithDiff; diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 19a9f5e4..277ac081 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -23,21 +23,12 @@ import { ProfileEmployee } from "../entities/ProfileEmployee"; 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, - 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 { calculateTenure } 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 { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgRevision } from "../entities/OrgRevision"; @@ -55,303 +46,138 @@ 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; - - 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 { + for await (const x of profile) { + // Use leave date if available and valid, otherwise use current date let _currentDate = baseCurrentDate; - if (profile.isLeave && profile.leaveDate) { - _currentDate = Extension.toDateOnlyString(profile.leaveDate); + if (x.isLeave && x.leaveDate) { + _currentDate = Extension.toDateOnlyString(x.leaveDate); } - const position = await AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [ - profile.id, + x.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; - const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ + days_diff: curr.days_diff, positionName: _position[index]?.positionName, - 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 === profile.position); - - if (currentTenure) { - const normalized = normalizeDurationSumSimple( - currentTenure.year, - currentTenure.month, - currentTenure.day, + 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 }, ); - return { - profileId: profile.id, - positionName: currentTenure.positionName, - days_diff: null, - Years: normalized.years, - Months: normalized.months, - Days: normalized.days, - }; - } - return null; - } catch (error) { - return 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); } + 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 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 { + 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 (profile.isLeave && profile.leaveDate) { - _currentDate = Extension.toDateOnlyString(profile.leaveDate); + if (x?.isLeave && x.leaveDate) { + _currentDate = Extension.toDateOnlyString(x.leaveDate); } - const position = await AppDataSource.query("CALL GetProfileEmployeeSalaryPosition(?, ?)", [ - profile.id, + x.id, _currentDate, ]); const _position = position.length > 0 ? position[0] : []; - const mapPosition = _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ + days_diff: curr.days_diff, positionName: _position[index]?.positionName, - 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 === profile.position); - - if (currentTenure) { - const normalized = normalizeDurationSumSimple( - currentTenure.year, - currentTenure.month, - currentTenure.day, + 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 }, ); - return { - profileEmployeeId: profile.id, - positionName: currentTenure.positionName, - days_diff: null, - Years: normalized.years, - Months: normalized.months, - Days: normalized.days, - }; - } - return null; - } catch (error) { - return 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); } + 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; - - 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 { + for await (const x of profile) { + // Use leave date if available and valid, otherwise use current date let _currentDate = baseCurrentDate; - if (profile.isLeave && profile.leaveDate) { - _currentDate = Extension.toDateOnlyString(profile.leaveDate); + if (x?.isLeave && x.leaveDate) { + _currentDate = Extension.toDateOnlyString(x.leaveDate); } - const positionLevel = await AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [ - profile.id, + x.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; - const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -359,24 +185,13 @@ 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 .filter( (curr: any) => - curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && - curr.positionType === (profile.posType?.posTypeName ?? null), + curr.positionLevel == (x.posLevel?.posLevelName ?? null) && + curr.positionType == (x.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -384,118 +199,45 @@ 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, - year: 0, - month: 0, - day: 0, - }, + { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - - const normalized = normalizeDurationSumSimple( - calDayDiff.year, - calDayDiff.month, - calDayDiff.day, - ); - - return { - profileId: profile.id, + const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const mapData: any = { + profileId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: profile.posLevel == null ? 0 : normalized.years, - Months: profile.posLevel == null ? 0 : normalized.months, - Days: profile.posLevel == null ? 0 : normalized.days, + Years: x.posLevel == null ? 0 : year.toFixed(4), + Months: x.posLevel == null ? 0 : month.toFixed(4), + Days: x.posLevel == null ? 0 : day.toFixed(4), }; - } catch (error) { - return null; + data.push(mapData); } + 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; - - 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 { + for await (const x of profile) { + // Use leave date if available and valid, otherwise use current date let _currentDate = baseCurrentDate; - if (profile.isLeave && profile.leaveDate) { - _currentDate = Extension.toDateOnlyString(profile.leaveDate); + if (x?.isLeave && x.leaveDate) { + _currentDate = Extension.toDateOnlyString(x.leaveDate); } - const positionLevel = await AppDataSource.query("CALL GetProfileEmployeeSalaryLevel(?, ?)", [ - profile.id, + x.id, _currentDate, ]); const _positionLevel = positionLevel.length > 0 ? positionLevel[0] : []; - const mapPositionLevel = _positionLevel.length > 1 ? _positionLevel.slice(1).map((curr: any, index: number) => ({ @@ -503,24 +245,13 @@ 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 .filter( (curr: any) => - curr.positionLevel === (profile.posLevel?.posLevelName ?? null) && - curr.positionType === (profile.posType?.posTypeName ?? null), + curr.positionLevel == (x.posLevel?.posLevelName ?? null) && + curr.positionType == (x.posType?.posTypeName ?? null), ) .reduce( (acc: any, curr: any) => { @@ -528,161 +259,97 @@ 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, - year: 0, - month: 0, - day: 0, - }, + { days_diff: 0, positionType: null, positionLevel: null, positionCee: null }, ); - - const normalized = normalizeDurationSumSimple( - calDayDiff.year, - calDayDiff.month, - calDayDiff.day, - ); - - return { - profileEmployeeId: profile.id, + const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const mapData: any = { + profileEmployeeId: x.id, positionType: calDayDiff.positionType, positionLevel: calDayDiff.positionLevel, positionCee: calDayDiff.positionCee, days_diff: calDayDiff.days_diff, - Years: profile.posLevel == null ? 0 : normalized.years, - Months: profile.posLevel == null ? 0 : normalized.months, - Days: profile.posLevel == null ? 0 : normalized.days, + Years: x.posLevel == null ? 0 : year.toFixed(4), + Months: x.posLevel == null ? 0 : month.toFixed(4), + Days: x.posLevel == null ? 0 : day.toFixed(4), }; - } catch (error) { - return null; + data.push(mapData); } + 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; - - 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 { + for await (const x of profile) { + // Use leave date if available and valid, otherwise use current date let _currentDate = baseCurrentDate; - if (profile.isLeave && profile.leaveDate) { - _currentDate = Extension.toDateOnlyString(profile.leaveDate); + 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 positionExecutive = await AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [ - profile.id, + x.id, _currentDate, ]); const _position = positionExecutive.length > 0 ? positionExecutive[0] : []; - const mapPosition = _position.length > 1 ? _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; const calDayDiff = mapPosition - .filter((curr: any) => curr.positionExecutive === profile.posExecutive) + .filter((curr: any) => _posExecutiveName && curr.positionExecutive == _posExecutiveName) .reduce( (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, year: 0, month: 0, day: 0 }, + { days_diff: 0, positionExecutive: null }, ); - - const normalized = normalizeDurationSumSimple( - calDayDiff.year, - calDayDiff.month, - calDayDiff.day, - ); - - return { - profileId: profile.id, + const { year, month, day } = calculateTenure(calDayDiff.days_diff); + const mapData: any = { + profileId: x.id, positionExecutiveName: calDayDiff.positionExecutive, days_diff: calDayDiff.days_diff, - Years: normalized.years, - Months: normalized.months, - Days: normalized.days, + Years: year.toFixed(4), + Months: month.toFixed(4), + Days: day.toFixed(4), }; - } catch (error) { - return null; + data.push(mapData); } + await this.positionExecutiveOfficerRepo.save(data); + return new HttpSuccess(); } @Get("Registry") @@ -929,12 +596,8 @@ 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, })) @@ -950,21 +613,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, @@ -980,12 +641,8 @@ 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 @@ -1007,21 +664,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, @@ -1038,12 +693,8 @@ 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, })) @@ -1059,21 +710,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, @@ -1117,12 +766,8 @@ 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, })) @@ -1138,21 +783,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, @@ -1169,12 +812,8 @@ 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 @@ -1196,21 +835,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, @@ -1227,12 +864,8 @@ 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, })) @@ -1248,21 +881,19 @@ 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); - existing.year = normalized.years; - existing.month = normalized.months; - existing.day = normalized.days; + // 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; + } return acc; }, diff --git a/src/controllers/ProfileSalaryEmployeeController.ts b/src/controllers/ProfileSalaryEmployeeController.ts index 5f2ea997..44b93a5d 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 { normalizeDurationSumSimple } from "../utils/tenure"; +import { calculateTenure } from "../utils/tenure"; import { Command } from "../entities/Command"; import { OrgRoot } from "../entities/OrgRoot"; import Extension from "../interfaces/extension"; @@ -161,14 +161,6 @@ 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, })) : []; @@ -179,25 +171,15 @@ 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, - year: curr.year, - month: curr.month, - day: curr.day, - }; + existing = { name: curr.name, days: curr.days }; acc.push(existing); } - // 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; + const { year, month, day } = calculateTenure(existing.days); + existing.year = year; + existing.month = month; + existing.day = day; return acc; }, @@ -213,14 +195,6 @@ 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()}` @@ -234,25 +208,15 @@ 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, - year: curr.year, - month: curr.month, - day: curr.day, - }; + existing = { name: curr.name, days: curr.days }; acc.push(existing); } - // 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; + const { year, month, day } = calculateTenure(existing.days); + existing.year = year; + existing.month = month; + existing.day = day; return acc; }, @@ -290,14 +254,6 @@ 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, })) : []; @@ -308,25 +264,15 @@ 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, - year: curr.year, - month: curr.month, - day: curr.day, - }; + existing = { name: curr.name, days: curr.days }; acc.push(existing); } - // 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; + const { year, month, day } = calculateTenure(existing.days); + existing.year = year; + existing.month = month; + existing.day = day; return acc; }, @@ -342,14 +288,6 @@ 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()}` @@ -363,25 +301,15 @@ 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, - year: curr.year, - month: curr.month, - day: curr.day, - }; + existing = { name: curr.name, days: curr.days }; acc.push(existing); } - // 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; + const { year, month, day } = calculateTenure(existing.days); + existing.year = year; + existing.month = month; + existing.day = day; return acc; }, diff --git a/src/entities/TenureLevelEmployee.ts b/src/entities/TenureLevelEmployee.ts index 5654e306..36ae0176 100644 --- a/src/entities/TenureLevelEmployee.ts +++ b/src/entities/TenureLevelEmployee.ts @@ -74,7 +74,7 @@ export class TenureLevelEmployee extends EntityBase { positionLevel: string; } -export class CreateTenureLevelEmployee { +export class CreateTenureLevelOfficer { profileEmployeeId: string; positionCee: string | null; days_diff: number | null; diff --git a/src/utils/tenure.ts b/src/utils/tenure.ts index 1c97dff1..dbdedbb3 100644 --- a/src/utils/tenure.ts +++ b/src/utils/tenure.ts @@ -1,37 +1,18 @@ /** - * 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 } + * คำนวณอายุงานจากจำนวนวันรวม + * ใช้สูตรเดียวกับ Stored Procedure GetProfileSalaryPosition + * @param totalDays จำนวนวันรวม + * @returns { year, month, day } ปี เดือน วัน */ -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 +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 - let totalMonths = months; - let totalDays = days; + const year = Math.floor(totalDays / 365.2524); + const month = Math.floor((totalDays / 30.4375) % 12); + const day = Math.floor(totalDays % 30.4375); - // 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) }; + return { year, month, day }; }