diff --git a/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql new file mode 100644 index 00000000..e3585c9f --- /dev/null +++ b/docs/migrations/fix_GetProfileSalaryExecutive_calendar_arithmetic.sql @@ -0,0 +1,136 @@ +-- ==================================================================== +-- Fix GetProfileSalaryExecutive to use calendar arithmetic +-- This changes the years/months/days calculation from fixed formulas +-- to actual calendar arithmetic, matching calculateGovAge behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileSalaryExecutive`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileSalaryExecutive`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * FROM profileSalary WHERE profileId = 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 +), +executive_change AS ( + SELECT *, + CASE + WHEN LAG(positionExecutive) OVER (ORDER BY commandDateAffect, commandDateSign) = positionExecutive + AND LAG(sessionId) OVER (ORDER BY commandDateAffect, commandDateSign) = sessionId + THEN 0 + ELSE 1 + END AS isNewExecutive + FROM work_session +), +executive_group AS ( + SELECT *, + SUM(isNewExecutive) OVER (ORDER BY commandDateAffect, commandDateSign) AS execGroup + FROM executive_change +), +first_rows AS ( + SELECT * FROM ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY execGroup ORDER BY commandDateAffect, commandDateSign) AS rnExec + FROM executive_group + ) t WHERE rnExec = 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.positionExecutive, + r.days_diff, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + 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) MONTH), + r.commandDateAffect) + 1 + ELSE 0 + END AS Days, + r.posNo, + r.positionType, + r.positionLevel, + r.positionCee, + 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, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + TIMESTAMPDIFF(DAY, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + 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; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql new file mode 100644 index 00000000..ca811a23 --- /dev/null +++ b/docs/migrations/fix_GetProfileSalaryLevel_calendar_arithmetic.sql @@ -0,0 +1,138 @@ +-- ==================================================================== +-- Fix GetProfileSalaryLevel to use calendar arithmetic +-- This changes the years/months/days calculation from fixed formulas +-- to actual calendar arithmetic, matching calculateGovAge behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileSalaryLevel`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileSalaryLevel`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * FROM profileSalary WHERE profileId = 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(positionLevel) OVER (ORDER BY commandDateAffect, commandDateSign) = positionLevel + AND LAG(positionType) OVER (ORDER BY commandDateAffect, commandDateSign) = positionType + AND LAG(positionCee) OVER (ORDER BY commandDateAffect, commandDateSign) = positionCee + 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.*, + 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.positionType, + r.positionLevel, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + 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) MONTH), + r.commandDateAffect) + 1 + 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, + TIMESTAMPDIFF(DAY, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + INTERVAL TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) MONTH), + _date) + 1, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + NULL,NULL,NULL,NULL +FROM resultWithDiff; + +END$$ + +DELIMITER ; diff --git a/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql new file mode 100644 index 00000000..a546ad97 --- /dev/null +++ b/docs/migrations/fix_GetProfileSalaryPosition_calendar_arithmetic.sql @@ -0,0 +1,144 @@ +-- ==================================================================== +-- Fix GetProfileSalaryPosition to use calendar arithmetic +-- This changes the years/months/days calculation from fixed formulas +-- to actual calendar arithmetic, matching calculateGovAge behavior +-- ==================================================================== + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `GetProfileSalaryPosition`$$ + +CREATE DEFINER=`root`@`%` PROCEDURE `GetProfileSalaryPosition`( + IN personId VARCHAR(36), + IN _date DATE +) +BEGIN +WITH ordered AS ( + SELECT * FROM profileSalary WHERE profileId = 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 +) +-- ✅ NEW: Use calendar arithmetic for years/months/days calculation +SELECT + r.commandDateAffect, + r.positionName, + r.positionCee, + r.days_diff, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(YEAR, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) + ELSE 0 + END AS Years, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + TIMESTAMPDIFF(MONTH, LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign), r.commandDateAffect) % 12 + ELSE 0 + END AS Months, + CASE + WHEN LAG(r.commandDateAffect) OVER (ORDER BY r.commandDateAffect, r.commandDateSign) IS NOT NULL THEN + 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) MONTH), + r.commandDateAffect) + 1 + 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 + +-- ✅ NEW: Use calendar arithmetic for the final row too +SELECT + _date, NULL, NULL, + TIMESTAMPDIFF(DAY, MAX(commandDateAffect), _date) + 1, + TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date), + TIMESTAMPDIFF(MONTH, MAX(commandDateAffect), _date) % 12, + TIMESTAMPDIFF(DAY, + DATE_ADD( + DATE_ADD(MAX(commandDateAffect), + INTERVAL TIMESTAMPDIFF(YEAR, MAX(commandDateAffect), _date) YEAR), + 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; + +END$$ + +DELIMITER ; + +-- ==================================================================== +-- Verification query (optional) +-- ==================================================================== +-- CALL GetProfileSalaryPosition('your-profile-id', '2024-06-14'); diff --git a/src/controllers/ProfileSalaryController.ts b/src/controllers/ProfileSalaryController.ts index 814f5e89..277ac081 100644 --- a/src/controllers/ProfileSalaryController.ts +++ b/src/controllers/ProfileSalaryController.ts @@ -595,6 +595,10 @@ export class ProfileSalaryController 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, })) : []; @@ -605,15 +609,23 @@ export class ProfileSalaryController 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 (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; }, @@ -628,6 +640,10 @@ export class ProfileSalaryController 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()}` @@ -644,15 +660,23 @@ export class ProfileSalaryController 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 (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; }, @@ -668,6 +692,10 @@ export class ProfileSalaryController extends Controller { _posExecutive.length > 1 ? _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, + day: curr.Days !== null && curr.Days !== undefined ? Math.floor(Number(curr.Days)) : 0, name: _posExecutive[index]?.positionExecutive, })) : []; @@ -678,15 +706,23 @@ export class ProfileSalaryController 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 (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; }, @@ -729,9 +765,10 @@ export class ProfileSalaryController extends Controller { _position.length > 1 ? _position.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, - // year: curr.Years ? Math.floor(Number(curr.Years)) : 0, - // month: curr.Months ? Math.floor(Number(curr.Months)) : 0, - // day: curr.Days ? Math.floor(Number(curr.Days)) : 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, })) : []; @@ -742,15 +779,23 @@ export class ProfileSalaryController 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 (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; }, @@ -766,9 +811,10 @@ export class ProfileSalaryController extends Controller { _posLevel.length > 1 ? _posLevel.slice(1).map((curr: any, index: number) => ({ days: curr.days_diff ? Number(curr.days_diff) : 0, - // year: curr.Years ? Math.floor(Number(curr.Years)) : 0, - // month: curr.Months ? Math.floor(Number(curr.Months)) : 0, - // day: curr.Days ? Math.floor(Number(curr.Days)) : 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()}` @@ -785,15 +831,23 @@ export class ProfileSalaryController 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 (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; }, @@ -808,10 +862,11 @@ export class ProfileSalaryController extends Controller { const mapPosExecutive = _posExecutive.length > 1 ? _posExecutive.slice(1).map((curr: any, index: number) => ({ - // year: curr.Years ? Math.floor(Number(curr.Years)) : 0, - // month: curr.Months ? Math.floor(Number(curr.Months)) : 0, - // day: curr.Days ? Math.floor(Number(curr.Days)) : 0, 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: _posExecutive[index]?.positionExecutive, })) : []; @@ -822,15 +877,23 @@ export class ProfileSalaryController 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 (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/utils/tenure.ts b/src/utils/tenure.ts index 577d314b..dbdedbb3 100644 --- a/src/utils/tenure.ts +++ b/src/utils/tenure.ts @@ -1,23 +1,18 @@ /** * คำนวณอายุงานจากจำนวนวันรวม + * ใช้สูตรเดียวกับ Stored Procedure GetProfileSalaryPosition * @param totalDays จำนวนวันรวม * @returns { year, month, day } ปี เดือน วัน */ export function calculateTenure(totalDays: number) { - // 1. แปลงเป็น year เต็ม + // Match stored procedure formula: + // days_diff / 365.2524 AS Years + // (days_diff / 30.4375) % 12 AS Months + // days_diff % 30.4375 AS Days + const year = Math.floor(totalDays / 365.2524); - - // 2. วันที่เหลือหลังหัก year ออก - const remainAfterYear = totalDays - year * 365.2524; - - // 3. แปลงเป็น month เต็ม - const month = Math.floor(remainAfterYear / 30.4375); - - // 4. วันที่เหลือหลังหัก month ออก - const remainAfterMonth = remainAfterYear - month * 30.4375; - - // 5. ปัดลง เฉพาะวัน - const day = Math.floor(remainAfterMonth); + const month = Math.floor((totalDays / 30.4375) % 12); + const day = Math.floor(totalDays % 30.4375); return { year, month, day }; }