Compare commits

..

No commits in common. "dev" and "v1.1.90" have entirely different histories.
dev ... v1.1.90

55 changed files with 2404 additions and 7468 deletions

View file

@ -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 ;

View file

@ -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 ;

View file

@ -1,136 +0,0 @@
-- ====================================================================
-- 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') AND positionExecutive <> ''
),
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
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) % 12 MONTH)
)
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,
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 ;

View file

@ -1,138 +0,0 @@
-- ====================================================================
-- 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
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) % 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 ;

View file

@ -1,144 +0,0 @@
-- ====================================================================
-- 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) % 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
-- ✅ 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,
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 ;
-- ====================================================================
-- Verification query (optional)
-- ====================================================================
-- CALL GetProfileSalaryPosition('your-profile-id', '2024-06-14');

View file

@ -20,12 +20,6 @@ import { In } from "typeorm";
import { RequestWithUser } from "../middlewares/user";
import { ApiName } from "../entities/ApiName";
import { ApiHistory } from "../entities/ApiHistory";
import { OrgRoot } from "../entities/OrgRoot";
import { OrgChild1 } from "../entities/OrgChild1";
import { OrgChild2 } from "../entities/OrgChild2";
import { OrgChild3 } from "../entities/OrgChild3";
import { OrgChild4 } from "../entities/OrgChild4";
import { OrgRevision } from "../entities/OrgRevision";
const jwt = require("jsonwebtoken");
@Route("api/v1/org/apiKey")
@ -39,12 +33,6 @@ export class ApiKeyController extends Controller {
private apiKeyRepository = AppDataSource.getRepository(ApiKey);
private apiNameRepository = AppDataSource.getRepository(ApiName);
private apiHistoryRepository = AppDataSource.getRepository(ApiHistory);
private orgRootRepository = AppDataSource.getRepository(OrgRoot);
private orgChild1Repository = AppDataSource.getRepository(OrgChild1);
private orgChild2Repository = AppDataSource.getRepository(OrgChild2);
private orgChild3Repository = AppDataSource.getRepository(OrgChild3);
private orgChild4Repository = AppDataSource.getRepository(OrgChild4);
private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
/**
* API JWT token
@ -163,9 +151,6 @@ export class ApiKeyController extends Controller {
relations: ["apiNames", "apiHistorys"],
order: { createdAt: "DESC", apiNames: { createdAt: "DESC" } },
});
const orgNames = await this.buildOrgNameBatch(apiKey);
const data = apiKey.map((_data) => ({
id: _data.id,
createdAt: _data.createdAt,
@ -178,7 +163,6 @@ export class ApiKeyController extends Controller {
dnaChild2Id: _data.dnaChild2Id,
dnaChild3Id: _data.dnaChild3Id,
dnaChild4Id: _data.dnaChild4Id,
orgName: orgNames.get(_data.id),
apiNames: _data.apiNames.map((x) => ({
id: x.id,
name: x.name,
@ -190,139 +174,10 @@ export class ApiKeyController extends Controller {
return new HttpSuccess(data);
}
private async buildOrgNameBatch(apiKeys: ApiKey[]): Promise<Map<string, string | null>> {
const currentRevision = await this.orgRevisionRepository.findOne({
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
});
if (!currentRevision) {
return new Map(apiKeys.map((k) => [k.id, null]));
}
const currentRevisionId = currentRevision.id;
const rootIds = [...new Set(apiKeys.map((k) => k.dnaRootId).filter(Boolean))];
const child1Ids = [...new Set(apiKeys.map((k) => k.dnaChild1Id).filter(Boolean))];
const child2Ids = [...new Set(apiKeys.map((k) => k.dnaChild2Id).filter(Boolean))];
const child3Ids = [...new Set(apiKeys.map((k) => k.dnaChild3Id).filter(Boolean))];
const child4Ids = [...new Set(apiKeys.map((k) => k.dnaChild4Id).filter(Boolean))];
const [roots, child1s, child2s, child3s, child4s] = await Promise.all([
rootIds.length > 0
? this.orgRootRepository.find({
where: [
{ id: In(rootIds), orgRevisionId: currentRevisionId },
{ ancestorDNA: In(rootIds), orgRevisionId: currentRevisionId },
],
select: ["id", "ancestorDNA", "orgRootName"],
})
: [],
child1Ids.length > 0
? this.orgChild1Repository.find({
where: [
{ id: In(child1Ids), orgRevisionId: currentRevisionId },
{ ancestorDNA: In(child1Ids), orgRevisionId: currentRevisionId },
],
select: ["id", "ancestorDNA", "orgChild1Name"],
})
: [],
child2Ids.length > 0
? this.orgChild2Repository.find({
where: [
{ id: In(child2Ids), orgRevisionId: currentRevisionId },
{ ancestorDNA: In(child2Ids), orgRevisionId: currentRevisionId },
],
select: ["id", "ancestorDNA", "orgChild2Name"],
})
: [],
child3Ids.length > 0
? this.orgChild3Repository.find({
where: [
{ id: In(child3Ids), orgRevisionId: currentRevisionId },
{ ancestorDNA: In(child3Ids), orgRevisionId: currentRevisionId },
],
select: ["id", "ancestorDNA", "orgChild3Name"],
})
: [],
child4Ids.length > 0
? this.orgChild4Repository.find({
where: [
{ id: In(child4Ids), orgRevisionId: currentRevisionId },
{ ancestorDNA: In(child4Ids), orgRevisionId: currentRevisionId },
],
select: ["id", "ancestorDNA", "orgChild4Name"],
})
: [],
]);
const rootMap = new Map(
roots.map((r) => [r.id, { name: r.orgRootName, ancestorDNA: r.ancestorDNA }]),
);
const child1Map = new Map(
child1s.map((c) => [c.id, { name: c.orgChild1Name, ancestorDNA: c.ancestorDNA }]),
);
const child2Map = new Map(
child2s.map((c) => [c.id, { name: c.orgChild2Name, ancestorDNA: c.ancestorDNA }]),
);
const child3Map = new Map(
child3s.map((c) => [c.id, { name: c.orgChild3Name, ancestorDNA: c.ancestorDNA }]),
);
const child4Map = new Map(
child4s.map((c) => [c.id, { name: c.orgChild4Name, ancestorDNA: c.ancestorDNA }]),
);
const result = new Map<string, string | null>();
for (const apiKey of apiKeys) {
if (apiKey.accessType === "ALL") {
result.set(apiKey.id, null);
continue;
}
const parts: string[] = [];
const getOrgName = (
dnaId: string,
orgMap: Map<string, { name: string; ancestorDNA: string }>,
): string | null => {
const byId = orgMap.get(dnaId);
if (byId) return byId.name;
for (const [, value] of orgMap) {
if (value.ancestorDNA === dnaId) return value.name;
}
return null;
};
if (apiKey.dnaChild4Id) {
const name = getOrgName(apiKey.dnaChild4Id, child4Map);
if (name) parts.push(name);
}
if (apiKey.dnaChild3Id) {
const name = getOrgName(apiKey.dnaChild3Id, child3Map);
if (name) parts.push(name);
}
if (apiKey.dnaChild2Id) {
const name = getOrgName(apiKey.dnaChild2Id, child2Map);
if (name) parts.push(name);
}
if (apiKey.dnaChild1Id) {
const name = getOrgName(apiKey.dnaChild1Id, child1Map);
if (name) parts.push(name);
}
if (apiKey.dnaRootId) {
const name = getOrgName(apiKey.dnaRootId, rootMap);
if (name) parts.push(name);
}
result.set(apiKey.id, parts.length > 0 ? parts.join(" ") : null);
}
return result;
}
/**
* API Api Name
* API Api Key
*
* @summary Api Name (ADMIN)
* @summary Api Key (ADMIN)
*
*/
@Get("name")

View file

@ -106,10 +106,10 @@ export class ApiManageController extends Controller {
code: "organization",
name: "ข้อมูลโครงสร้าง",
},
// {
// code: "position",
// name: "ข้อมูลอัตรากำลัง",
// },
{
code: "position",
name: "ข้อมูลอัตรากำลัง",
},
];
// รายการเอนทิตีทั้งหมด
@ -273,240 +273,59 @@ export class ApiManageController extends Controller {
description: "ข้อมูลส่วนราชการ ระดับที่ 4",
system: ["organization"],
},
// {
// name: "PosMaster",
// repository: this.posMasterRepository,
// description: "ข้อมูลอัตรากำลัง",
// isMain: true,
// system: ["position"],
// },
// {
// name: "Position",
// repository: this.positionRepository,
// description: "ข้อมูลตำแหน่ง",
// system: ["position"],
// },
// {
// name: "OrgRoot",
// repository: this.orgRootRepository,
// description: "ข้อมูลหน่วยงาน",
// system: ["position"],
// },
// {
// name: "OrgChild1",
// repository: this.orgChild1Repository,
// description: "ข้อมูลส่วนราชการ ระดับที่ 1",
// system: ["position"],
// },
// {
// name: "OrgChild2",
// repository: this.orgChild2Repository,
// description: "ข้อมูลส่วนราชการ ระดับที่ 2",
// system: ["position"],
// },
// {
// name: "OrgChild3",
// repository: this.orgChild3Repository,
// description: "ข้อมูลส่วนราชการ ระดับที่ 3",
// system: ["position"],
// },
// {
// name: "OrgChild4",
// repository: this.orgChild4Repository,
// description: "ข้อมูลส่วนราชการ ระดับที่ 4",
// system: ["position"],
// },
// {
// name: "Profile",
// repository: this.profileRepository,
// description: "ข้อมูลคนครอง",
// system: ["position"],
// },
{
name: "PosMaster",
repository: this.posMasterRepository,
description: "ข้อมูลอัตรากำลัง",
isMain: true,
system: ["position"],
},
{
name: "Position",
repository: this.positionRepository,
description: "ข้อมูลตำแหน่ง",
system: ["position"],
},
{
name: "OrgRoot",
repository: this.orgRootRepository,
description: "ข้อมูลหน่วยงาน",
system: ["position"],
},
{
name: "OrgChild1",
repository: this.orgChild1Repository,
description: "ข้อมูลส่วนราชการ ระดับที่ 1",
system: ["position"],
},
{
name: "OrgChild2",
repository: this.orgChild2Repository,
description: "ข้อมูลส่วนราชการ ระดับที่ 2",
system: ["position"],
},
{
name: "OrgChild3",
repository: this.orgChild3Repository,
description: "ข้อมูลส่วนราชการ ระดับที่ 3",
system: ["position"],
},
{
name: "OrgChild4",
repository: this.orgChild4Repository,
description: "ข้อมูลส่วนราชการ ระดับที่ 4",
system: ["position"],
},
{
name: "Profile",
repository: this.profileRepository,
description: "ข้อมูลคนครอง",
system: ["position"],
},
];
private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น
private readonly EXCLUDED_COLUMNS = [
"createdUserId",
"lastUpdateUserId",
"createdAt",
"createdFullName",
"lastUpdateFullName",
"avatarName",
"profileId",
"prefixId",
"profileEmployeeId",
"documentId",
"orgRevisionId",
"posMasterId",
"orgRootId",
"orgChild1Id",
"orgChild2Id",
"orgChild3Id",
"orgChild4Id",
"keycloak",
"commandId",
"prefixMain",
"authRoleId",
"next_holderId",
"current_holderId",
"ancestorDNA",
"leaveCommandId",
"posLevelId",
"posTypeId",
"posExecutiveId",
"registrationProvinceId",
"registrationDistrictId",
"registrationSubDistrictId",
"currentProvinceId",
"currentDistrictId",
"currentSubDistrictId",
"isDelete",
"keycloak",
"statusCheckEdit",
"privacyCheckin",
"privacyUser",
"privacyMgt",
"dutyTimeId",
"dutyTimeEffectiveDate",
"profileId",
"profileEmployeeId",
"orgRevisionId",
"rank",
"isUpload",
"isDeleted",
"isEntry",
"prefixId",
"leaveId",
"leaveTypeId",
"isDeputy",
"isCommission",
]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Profile entity
private readonly PROFILE_FIELD_REPLACEMENTS: Record<
string,
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
> = {
posLevelId: {
propertyName: "posLevelName",
type: "string",
comment: "ระดับตำแหน่ง",
joinTable: "PosLevel",
joinField: "posLevelName",
},
posTypeId: {
propertyName: "posTypeName",
type: "string",
comment: "ประเภทตำแหน่ง",
joinTable: "PosType",
joinField: "posTypeName",
},
registrationProvinceId: {
propertyName: "registrationProvinceName",
type: "string",
comment: "จังหวัดตามทะเบียนบ้าน",
joinTable: "Province",
joinField: "name",
},
registrationDistrictId: {
propertyName: "registrationDistrictName",
type: "string",
comment: "เขตตามทะเบียนบ้าน",
joinTable: "District",
joinField: "name",
},
registrationSubDistrictId: {
propertyName: "registrationSubDistrictName",
type: "string",
comment: "แขวงตามทะเบียนบ้าน",
joinTable: "SubDistrict",
joinField: "name",
},
currentProvinceId: {
propertyName: "currentProvinceName",
type: "string",
comment: "จังหวัดตามปัจจุบัน",
joinTable: "Province",
joinField: "name",
},
currentDistrictId: {
propertyName: "currentDistrictName",
type: "string",
comment: "เขตตามปัจจุบัน",
joinTable: "District",
joinField: "name",
},
currentSubDistrictId: {
propertyName: "currentSubDistrictName",
type: "string",
comment: "แขวงตามปัจจุบัน",
joinTable: "SubDistrict",
joinField: "name",
},
};
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ Position entity
private readonly POSITION_FIELD_REPLACEMENTS: Record<
string,
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
> = {
posTypeId: {
propertyName: "posTypeName",
type: "string",
comment: "ประเภทตำแหน่ง",
joinTable: "PosType",
joinField: "posTypeName",
},
posLevelId: {
propertyName: "posLevelName",
type: "string",
comment: "ระดับตำแหน่ง",
joinTable: "PosLevel",
joinField: "posLevelName",
},
posExecutiveId: {
propertyName: "posExecutiveName",
type: "string",
comment: "ตำแหน่งทางการบริหาร",
joinTable: "PosExecutive",
joinField: "posExecutiveName",
},
};
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileEmployee entity
private readonly PROFILEEMPLOYEE_FIELD_REPLACEMENTS: Record<
string,
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
> = {
posLevelId: {
propertyName: "posLevelName",
type: "string",
comment: "ระดับชั้นงาน",
joinTable: "EmployeePosLevel",
joinField: "posLevelName",
},
posTypeId: {
propertyName: "posTypeName",
type: "string",
comment: "กลุ่มงาน",
joinTable: "EmployeePosType",
joinField: "posTypeName",
},
};
// การแทนที่ฟิลด์ ID ด้วยฟิลด์ Name สำหรับ ProfileLeave entity
private readonly PROFILELEAVE_FIELD_REPLACEMENTS: Record<
string,
{ propertyName: string; type: string; comment: string; joinTable: string; joinField: string }
> = {
leaveTypeId: {
propertyName: "leaveTypeName",
type: "string",
comment: "ประเภทการลา",
joinTable: "LeaveType",
joinField: "name",
},
};
private readonly EXCLUDED_COLUMNS = ["createdUserId", "lastUpdateUserId"]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์
private validateSuperAdminRole(user: any): void {
if (!user.role.includes("SUPER_ADMIN")) {
@ -545,8 +364,11 @@ export class ApiManageController extends Controller {
const result = this.entities
.filter((entity) => entity.system.includes(system))
.map(({ name, repository, description, isMain }) => {
let columns = repository.metadata.columns
.map(({ name, repository, description, isMain }) => ({
tb: name,
description,
isMain: isMain || false,
propertys: repository.metadata.columns
.filter(
(column: any) =>
!column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName),
@ -556,115 +378,9 @@ export class ApiManageController extends Controller {
type: typeof column.type === "string" ? column.type : "string",
comment: column.comment,
key: column.propertyName,
})),
}));
// Special handling for Profile entity - replace ID fields with name fields
if (name === "Profile") {
const replacementKeys = Object.keys(this.PROFILE_FIELD_REPLACEMENTS);
// Remove ID fields that should be replaced
columns = columns.filter(
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
);
// Add the corresponding name fields
const nameFields = replacementKeys.map((key) => ({
propertyName: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName,
type: "string",
comment: this.PROFILE_FIELD_REPLACEMENTS[key].comment,
key: this.PROFILE_FIELD_REPLACEMENTS[key].propertyName,
}));
columns = [...columns, ...nameFields];
}
// Special handling for Position entity - replace ID fields with name fields
if (name === "Position") {
const replacementKeys = Object.keys(this.POSITION_FIELD_REPLACEMENTS);
// Remove ID fields that should be replaced
columns = columns.filter(
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
);
// Add the corresponding name fields
const nameFields = replacementKeys.map((key) => ({
propertyName: this.POSITION_FIELD_REPLACEMENTS[key].propertyName,
type: "string",
comment: this.POSITION_FIELD_REPLACEMENTS[key].comment,
key: this.POSITION_FIELD_REPLACEMENTS[key].propertyName,
}));
columns = [...columns, ...nameFields];
}
// Special handling for ProfileEmployee entity - replace ID fields with name fields
if (name === "ProfileEmployee") {
const replacementKeys = Object.keys(this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS);
// Remove ID fields that should be replaced
columns = columns.filter(
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
);
// Add the corresponding name fields
const nameFields = replacementKeys.map((key) => ({
propertyName: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].propertyName,
type: "string",
comment: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].comment,
key: this.PROFILEEMPLOYEE_FIELD_REPLACEMENTS[key].propertyName,
}));
columns = [...columns, ...nameFields];
}
// Special handling for ProfileLeave entity - replace ID fields with name fields
if (name === "ProfileLeave") {
const replacementKeys = Object.keys(this.PROFILELEAVE_FIELD_REPLACEMENTS);
// Remove ID fields that should be replaced
columns = columns.filter(
(col: { propertyName: string }) => !replacementKeys.includes(col.propertyName),
);
// Add the corresponding name fields
const nameFields = replacementKeys.map((key) => ({
propertyName: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].propertyName,
type: "string",
comment: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].comment,
key: this.PROFILELEAVE_FIELD_REPLACEMENTS[key].propertyName,
}));
columns = [...columns, ...nameFields];
}
// Special handling for PosMaster entity - add Profile fields for holder information
if (name === "PosMaster") {
// Add Profile fields that are accessible via current_holder relation
const profileFields = ["prefix", "rank", "firstName", "lastName", "citizenId"];
const profileRepository = AppDataSource.getRepository(Profile);
const profileColumns = profileRepository.metadata.columns
.filter(
(column: any) => !column.isPrimary && profileFields.includes(column.propertyName),
)
.map((column: any) => ({
propertyName: `Profile.${column.propertyName}`,
type: typeof column.type === "string" ? column.type : "string",
comment: column.comment,
key: `Profile.${column.propertyName}`,
}));
columns = [...columns, ...profileColumns];
}
return {
tb: name,
description,
isMain: isMain || false,
propertys: columns,
};
});
return new HttpSuccess(result);
} catch (error) {
throw new HttpError(

File diff suppressed because it is too large Load diff

View file

@ -123,25 +123,18 @@ export class AuthRoleController extends Controller {
// เช็คว่าถ้ามีค่า current_holderId ให้ลบ key สิทธิ์ใน redis
if (posMaster.current_holderId) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
redisClient.del("role_" + posMaster.current_holderId, (err: Error) => {
if (err) console.error("Redis delete role error:", err);
redisClient.del("role_" + posMaster.current_holderId, (err: Error, response: Response) => {
if (err) throw err;
});
redisClient.del("menu_" + posMaster.current_holderId, (err: Error) => {
if (err) console.error("Redis delete menu error:", err);
redisClient.del("menu_" + posMaster.current_holderId, (err: Error, response: Response) => {
if (err) throw err;
});
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
return new HttpSuccess();
@ -267,33 +260,13 @@ export class AuthRoleController extends Controller {
return newAttr;
});
const before = structuredClone(record);
await Promise.all([
this.authRoleRepo.save(record, { data: req }),
setLogDataDiff(req, { before, after: record }),
...newAttrs.map((attr) => this.authRoleAttrRepo.save(attr)),
]);
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(AuthRole, record);
await Promise.all(
newAttrs.map((attr) => queryRunner.manager.save(AuthRoleAttr, attr))
);
await queryRunner.commitTransaction();
setLogDataDiff(req, { before, after: record });
} catch (error) {
await queryRunner.rollbackTransaction();
console.error("Error saving auth role:", error);
throw new HttpError(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาดในการบันทึกข้อมูลบทบาท กรุณาลองใหม่ในภายหลัง"
);
} finally {
await queryRunner.release();
}
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -301,11 +274,6 @@ export class AuthRoleController extends Controller {
await redisClient.flushdb(function (err: any, succeeded: any) {
console.log(succeeded); // will be true if successfull
});
} finally {
if (redisClient) {
redisClient.quit();
}
}
return new HttpSuccess();
}

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ import {
Path,
Request,
Response,
Get,
Get
} from "tsoa";
import { LessThan, MoreThan } from "typeorm";
import { AppDataSource } from "../database/data-source";
@ -37,7 +37,9 @@ export class CommandOperatorController extends Controller {
* @param commandId
*/
@Get("{commandId}")
async getCommandOperatorByCommandId(@Path() commandId: string) {
async getCommandOperatorByCommandId(
@Path() commandId: string
) {
const command = await this.commandRepo.findOne({
where: { id: commandId },
select: { id: true },
@ -59,7 +61,10 @@ export class CommandOperatorController extends Controller {
* @param operatorId
*/
@Get("swap/{direction}/{operatorId}")
async swapCommandOperator(@Path() direction: string, @Path() operatorId: string) {
async swapCommandOperator(
@Path() direction: string,
@Path() operatorId: string,
) {
const source = await this.commandOperatorRepo.findOne({
where: { id: operatorId },
});
@ -101,7 +106,10 @@ export class CommandOperatorController extends Controller {
source.orderNo = dest.orderNo;
dest.orderNo = temp;
await Promise.all([this.commandOperatorRepo.save(source), this.commandOperatorRepo.save(dest)]);
await Promise.all([
this.commandOperatorRepo.save(source),
this.commandOperatorRepo.save(dest),
]);
return new HttpSuccess();
}
@ -133,7 +141,9 @@ export class CommandOperatorController extends Controller {
const nextOrderNo = (lastOrderNo?.orderNo ?? 1) + 1;
const now = new Date();
const operator = Object.assign(new CommandOperator(), {
const operator = Object.assign(
new CommandOperator(),
{
...body,
commandId: command.id,
orderNo: nextOrderNo,
@ -143,7 +153,8 @@ export class CommandOperatorController extends Controller {
lastUpdateUserId: request.user.sub,
lastUpdateFullName: request.user.name,
lastUpdatedAt: now,
});
}
);
await this.commandOperatorRepo.save(operator);
return new HttpSuccess();
}
@ -155,7 +166,10 @@ export class CommandOperatorController extends Controller {
* @param operatorId
*/
@Delete("{commandId}/{operatorId}")
public async deleteCommandOperator(@Path() commandId: string, @Path() operatorId: string) {
public async deleteCommandOperator(
@Path() commandId: string,
@Path() operatorId: string,
) {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
@ -201,9 +215,10 @@ export class CommandOperatorController extends Controller {
return new HttpSuccess(true);
} catch (error) {
await queryRunner.rollbackTransaction();
console.error("Delete command operator error:", error);
throw error;
} finally {
await queryRunner.release();
}
}
}

View file

@ -1,576 +0,0 @@
import {
Controller,
Post,
Put,
Patch,
Delete,
Route,
Security,
Tags,
Body,
Path,
Request,
Response,
Get,
Query,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpStatus from "../interfaces/http-status";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { Command } from "../entities/Command";
import { Brackets, LessThan, MoreThan, Double, In, Between, IsNull, Not, Any } from "typeorm";
import { CommandType } from "../entities/CommandType";
import { Profile, CreateProfileAllFields } from "../entities/Profile";
import { RequestWithUser, RequestWithUserWebService } from "../middlewares/user";
import { OrgRevision } from "../entities/OrgRevision";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import { PosMaster } from "../entities/PosMaster";
import permission from "../interfaces/permission";
import { viewCurrentTenureOfficer } from "../entities/view/viewCurrentTenureOfficer";
import { CommandController } from "./CommandController";
import Extension from "../interfaces/extension";
import { viewRegistryOfficer } from "../entities/view/viewRegistryOfficer";
import { viewRegistryEmployee } from "../entities/view/viewRegistryEmployee";
import { Registry } from "../entities/Registry";
import { RegistryEmployee } from "../entities/RegistryEmployee";
import { TenurePositionOfficer } from "../entities/TenurePositionOfficer";
import { PosMasterAssign, PosMasterAssignDTO } from "../entities/PosMasterAssign";
import { PermissionProfile } from "../entities/PermissionProfile";
import { OrgRoot } from "../entities/OrgRoot";
import { MetaWorkflow } from "../entities/MetaWorkflow";
import { MetaState } from "../entities/MetaState";
import { MetaStateOperator } from "../entities/MetaStateOperator";
import { Workflow } from "../entities/Workflow";
import { State } from "../entities/State";
import { StateOperator } from "../entities/StateOperator";
import { StateOperatorUser } from "../entities/StateOperatorUser";
import {
commandTypePath,
calculateGovAge,
calculateAge,
calculateRetireDate,
calculateRetireLaw,
removeProfileInOrganize,
setLogDataDiff,
} from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
import { PostRetireToExprofile } from "./ExRetirementController"
import { Position } from "../entities/Position";
import { PosLevel } from "../entities/PosLevel";
import { TenureLevelOfficer } from "../entities/TenureLevelOfficer";
import { TenurePositionEmployee } from "../entities/TenurePositionEmployee";
import { TenureLevelEmployee } from "../entities/TenureLevelEmployee";
import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer";
@Route("api/v1/org/DevTest")
@Tags("DevTest")
@Security("bearerAuth")
@Response(
HttpStatusCode.INTERNAL_SERVER_ERROR,
"เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
)
export class DevTestController extends Controller {
private commandRepository = AppDataSource.getRepository(Command);
private commandTypeRepository = AppDataSource.getRepository(CommandType);
private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
private orgRootRepo = AppDataSource.getRepository(OrgRoot);
private posMasterRepo = AppDataSource.getRepository(PosMaster);
private profileRepo = AppDataSource.getRepository(Profile);
private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee);
private registryRepo = AppDataSource.getRepository(Registry);
private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee);
private posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign);
private permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile);
private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
private metaWorkflowRepo = AppDataSource.getRepository(MetaWorkflow);
private metaStateRepo = AppDataSource.getRepository(MetaState);
private metaStateOperatorRepo = AppDataSource.getRepository(MetaStateOperator);
private workflowRepo = AppDataSource.getRepository(Workflow);
private stateRepo = AppDataSource.getRepository(State);
private stateOperatorRepo = AppDataSource.getRepository(StateOperator);
private stateOperatorUserRepo = AppDataSource.getRepository(StateOperatorUser);
private positionRepository = AppDataSource.getRepository(Position);
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,
);
@Patch("tick-officer-registry")
public async calculateOfficerPosition(
@Request() req: RequestWithUser,
@Body()
body: {
profileIds: string[];
},
) {
console.log("1.")
/**
* ===============================
* PREPARE DATA
* ===============================
*/
const profile = await this.profileRepo.find({
where: { id: In(body.profileIds) },
relations: {
posLevel: true,
posType: true,
},
});
if (!profile.length) return;
const [{ today }] = await AppDataSource.query(
"SELECT CURRENT_DATE() as today",
);
const orgRevision = await this.orgRevisionRepo.findOne({
select: ["id"],
where: {
orgRevisionIsDraft: false,
orgRevisionIsCurrent: true,
},
});
/**
* ===============================
* TRANSACTION
* ===============================
*/
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
console.log("2.")
try {
/**
* ===============================
* RESULT BUFFERS (SAVE ARRAY)
* ===============================
*/
const positionOfficerBulk: any[] = [];
const levelOfficerBulk: any[] = [];
const executiveOfficerBulk: any[] = [];
console.log("3.")
/**
* ===============================
* MAIN LOOP (SINGLE LOOP)
* ===============================
*/
for (const x of profile) {
const currentDate =
x.isLeave && x.leaveDate
? Extension.toDateOnlyString(x.leaveDate)
: today;
/**
* ====================================
* PARALLEL STORED PROCEDURES
* ====================================
*/
const [
positionResult,
levelResult,
executiveResult,
] = await Promise.all([
AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [
x.id,
currentDate,
]),
AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [
x.id,
currentDate,
]),
AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [
x.id,
currentDate,
]),
]);
console.log("4.",x.id)
/**
* ====================================
* POSITION
* ====================================
*/
const posRows = positionResult?.[0] ?? [];
const posMap =
posRows.length > 1
? posRows.slice(1).map((r: any, i: number) => ({
days_diff: Number(r.days_diff) || 0,
positionName: posRows[i]?.positionName,
}))
: [];
const posCal = posMap
.filter((p:any) => p.positionName === x.position)
.reduce(
(a:any, c:any) => ({
days_diff: a.days_diff + c.days_diff,
positionName: c.positionName,
}),
{ days_diff: 0, positionName: null },
);
positionOfficerBulk.push({
profileId: x.id,
positionName: posCal.positionName,
days_diff: posCal.days_diff,
Years: Math.floor(posCal.days_diff / 365.2524),
Months: Math.floor((posCal.days_diff / 30.4375) % 12),
Days: Math.floor(posCal.days_diff % 30.4375),
});
console.log("5.",x.id)
/**
* ====================================
* 2 POSITION LEVEL
* ====================================
*/
const lvlRows = levelResult?.[0] ?? [];
const lvlMap =
lvlRows.length > 1
? lvlRows.slice(1).map((r: any, i: number) => ({
days_diff: Number(r.days_diff) || 0,
positionType: lvlRows[i]?.positionType,
positionLevel: lvlRows[i]?.positionLevel,
positionCee: lvlRows[i]?.positionCee,
}))
: [];
const lvlCal = lvlMap
.filter(
(l:any) =>
l.positionLevel === x.posLevel?.posLevelName &&
l.positionType === x.posType?.posTypeName,
)
.reduce(
(a:any, c:any) => ({
days_diff: a.days_diff + c.days_diff,
positionType: c.positionType,
positionLevel: c.positionLevel,
positionCee: c.positionCee,
}),
{
days_diff: 0,
positionType: null,
positionLevel: null,
positionCee: null,
},
);
levelOfficerBulk.push({
profileId: x.id,
positionType: lvlCal.positionType,
positionLevel: lvlCal.positionLevel,
positionCee: lvlCal.positionCee,
days_diff: lvlCal.days_diff,
Years: x.posLevel ? (lvlCal.days_diff / 365.2524).toFixed(4) : 0,
Months: x.posLevel ? ((lvlCal.days_diff / 30.4375) % 12).toFixed(4) : 0,
Days: x.posLevel ? (lvlCal.days_diff % 30.4375).toFixed(4) : 0,
});
console.log("6.",x.id)
/**
* ====================================
* 3 POSITION EXECUTIVE
* ====================================
*/
const exeRows = executiveResult?.[0] ?? [];
const exeMap =
exeRows.length > 1
? exeRows.slice(1).map((r: any, i: number) => ({
days_diff: Number(r.days_diff) || 0,
positionExecutive: exeRows[i]?.positionExecutive,
}))
: [];
const position = await this.positionRepository.findOne({
where: {
positionIsSelected: true,
posMaster: {
orgRevisionId: orgRevision?.id,
current_holderId: x.id,
},
},
order: { createdAt: "DESC" },
relations: {
posExecutive: true,
},
});
const exeName = position?.posExecutive?.posExecutiveName;
const exeCal = exeMap
.filter((e:any) => exeName && e.positionExecutive === exeName)
.reduce(
(a:any, c:any) => ({
days_diff: a.days_diff + c.days_diff,
positionExecutive: c.positionExecutive,
}),
{ days_diff: 0, positionExecutive: null },
);
executiveOfficerBulk.push({
profileId: x.id,
positionExecutiveName: exeCal.positionExecutive,
days_diff: exeCal.days_diff,
Years: (exeCal.days_diff / 365.2524).toFixed(4),
Months: ((exeCal.days_diff / 30.4375) % 12).toFixed(4),
Days: (exeCal.days_diff % 30.4375).toFixed(4),
});
}
console.log("7.")
/**
* ===============================
* CLEAR ALL DATA AND SAVE ARRAY (BULK)
* ===============================
*/
await queryRunner.manager
.createQueryBuilder()
.delete()
.from(this.positionOfficerRepo.target)
.execute();
await queryRunner.manager
.createQueryBuilder()
.delete()
.from(this.levelOfficerRepo.target)
.execute();
await queryRunner.manager
.createQueryBuilder()
.delete()
.from(this.positionExecutiveOfficerRepo.target)
.execute();
console.log("8.")
await queryRunner.manager.save(this.positionOfficerRepo.target, positionOfficerBulk);
await queryRunner.manager.save(this.levelOfficerRepo.target, levelOfficerBulk);
await queryRunner.manager.save(this.positionExecutiveOfficerRepo.target,executiveOfficerBulk);
console.log("9.")
/**
* ===============================
* REGISTRY OFFICER (SYNC VIEW)
* ===============================
*/
const allRegis = await queryRunner.manager
.getRepository(viewRegistryOfficer)
.createQueryBuilder("registryOfficer")
.where("registryOfficer.profileId IN (:...profileIds)", {
profileIds: new Set(profile.map((p) => p.id))
})
.getMany();
const mapRegistryData = allRegis.map((x) => ({
...x,
isProbation: Boolean(x.isProbation),
isLeave: Boolean(x.isLeave),
isRetirement: Boolean(x.isRetirement),
Educations: x.Educations ? JSON.stringify(x.Educations) : "",
}));
console.log("10.")
await queryRunner.manager
.createQueryBuilder()
.delete()
.from(this.registryRepo.target)
.execute();
if (mapRegistryData.length > 0) {
await queryRunner.manager.save(this.registryRepo.target, mapRegistryData);
}
console.log("11.")
/**
* ===============================
* COMMIT
* ===============================
*/
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
@Post("getDNA")
public async GetData(
@Request() req: RequestWithUser
){
let _data: any = {
root: null,
child1: null,
child2: null,
child3: null,
child4: null,
};
_data = await new permission().PermissionOrgList(req, "COMMAND");
return new HttpSuccess(_data);
}
@Post("calculateGovAge")
public async calculateGovAge(
@Request() req: RequestWithUser,
@Body()
body: {
profileId: string;
},
){
return new HttpSuccess(await calculateGovAge(body.profileId, "OFFICER"));
}
/**
* @summary Test Job 2
*/
@Post("cronjobCommand")
async CronjobCommand() {
const commandController = new CommandController();
await commandController.cronjobCommand();
}
/**
* @summary payload & Endpoint
*/
@Put("path-excec/{id}")
async Bright(
@Path() id: string,
@Request() request: RequestWithUser,
) {
const command = await this.commandRepository.findOne({
where: { id: id },
relations: ["commandType", "commandRecives", "commandSends", "commandSends.commandSendCCs"],
});
if (!command) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
}
const path = commandTypePath(command.commandType.code);
return new HttpSuccess({
path: path + "/excecute",
refIds: command.commandRecives
.filter((x) => x.refId != null)
.map((x) => ({
refId: x.refId,
commandNo: command.commandNo,
commandYear: command.commandYear,
commandId: command.id,
remark: command.positionDetail,
amount: x.amount,
amountSpecial: x.amountSpecial,
positionSalaryAmount: x.positionSalaryAmount,
mouthSalaryAmount: x.mouthSalaryAmount,
commandCode: command.commandType.commandCode,
commandName: command.commandType.name,
commandDateAffect: command.commandExcecuteDate,
commandDateSign: command.commandAffectDate,
})),
});
}
/**
* API tab4
* @summary API tab4
* @param {string} id Id
* @param {string} profileId profileId
*/
@Get("tab4/attachment/{id}/{profileId}")
async GetByIdTab4Attachment(
@Path() id: string,
@Path() profileId: string,
@Request() request: RequestWithUser
) {
await new permission().PermissionGet(request, "COMMAND");
let profile: Profile | ProfileEmployee | null = null;
profile = await this.profileRepo.findOne({ where: { id: profileId } });
if (!profile) {
profile = await this.profileEmpRepo.findOne({ where: { id: profileId } });
if (!profile)
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคลากรนี้");
}
const command = await this.commandRepository.findOne({
where: { id },
relations: ["commandType", "commandRecives"],
});
if (!command) {
throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
}
let _command: any = [];
const path = commandTypePath(command.commandType.code);
if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ");
await new CallAPI()
.PostData(request, path + "/attachment", {
refIds: command.commandRecives
.filter((x) =>
x.refId != null &&
x.profileId != null && x.profileId == profileId
)
.map((x) => ({
refId: x.refId,
Sequence: x.order,
CitizenId: x.citizenId,
Prefix: x.prefix,
FirstName: x.firstName,
LastName: x.lastName,
Amount: x.amount,
PositionSalaryAmount: x.positionSalaryAmount,
MouthSalaryAmount: x.mouthSalaryAmount,
RemarkHorizontal: x.remarkHorizontal,
RemarkVertical: x.remarkVertical,
CommandYear: command.commandYear,
CommandExcecuteDate: command.commandExcecuteDate,
})),
})
.then(async (res) => {
_command = res;
})
.catch(() => {});
let issue =
command.isBangkok == "OFFICE"
? "สำนักปลัดกรุงเทพมหานคร"
: command.isBangkok == "BANGKOK"
? "กรุงเทพมหานคร"
: null;
if (issue == null) {
const orgRevisionActive = await this.orgRevisionRepo.findOne({
where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
relations: ["posMasters", "posMasters.orgRoot"],
});
if (orgRevisionActive != null) {
const profile = await this.profileRepo.findOne({
where: {
keycloak: command.createdUserId.toString(),
},
});
if (profile != null) {
issue =
orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0]
?.orgRoot?.orgRootName || null;
}
}
}
if (issue == null) issue = "...................................";
return new HttpSuccess({
template: command.commandType.fileAttachment,
reportName: "xlsx-report",
data: {
data: _command,
issuerOrganizationName: issue,
commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo),
commandYear:
command.commandYear == null
? ""
: Extension.ToThaiNumber(Extension.ToThaiYear(command.commandYear).toString()),
commandExcecuteDate:
command.commandExcecuteDate == null
? ""
: Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)),
},
});
}
}

View file

@ -1058,11 +1058,11 @@ export class EmployeePositionController extends Controller {
let checkChildConditions: any = {};
let keywordAsInt: any;
let searchShortName = "1=1";
let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`;
let _data = await new permission().PermissionOrgList(request, "SYS_ORG_EMP");
if (body.type === 0) {
typeCondition = {
@ -1072,7 +1072,7 @@ export class EmployeePositionController extends Controller {
checkChildConditions = {
orgChild1Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 1) {
@ -1083,7 +1083,7 @@ export class EmployeePositionController extends Controller {
checkChildConditions = {
orgChild2Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 2) {
@ -1094,7 +1094,7 @@ export class EmployeePositionController extends Controller {
checkChildConditions = {
orgChild3Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 3) {
@ -1105,14 +1105,14 @@ export class EmployeePositionController extends Controller {
checkChildConditions = {
orgChild4Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 4) {
typeCondition = {
orgChild4Id: body.id,
};
searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
let findPosition: any;
let masterId = new Array();
@ -1140,8 +1140,10 @@ export class EmployeePositionController extends Controller {
select: ["posMasterId"],
});
masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId));
const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/);
keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null;
keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10);
if (isNaN(keywordAsInt)) {
keywordAsInt = "P@ssw0rd!z";
}
masterId = [...new Set(masterId)];
}
@ -1156,7 +1158,7 @@ export class EmployeePositionController extends Controller {
...(body.keyword &&
(masterId.length > 0
? { id: In(masterId) }
: /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })),
: { posMasterNo: Like(`%${body.keyword}%`) })),
},
];

View file

@ -777,11 +777,11 @@ export class EmployeeTempPositionController extends Controller {
let checkChildConditions: any = {};
let keywordAsInt: any;
let searchShortName = "1=1";
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName,' ',posMaster.posMasterNo)`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName,' ',posMaster.posMasterNo)`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName,' ',posMaster.posMasterNo)`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName,' ',posMaster.posMasterNo)`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName,' ',posMaster.posMasterNo)`;
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`;
let _data = await new permission().PermissionOrgList(request, "SYS_ORG_TEMP");
if (body.type === 0) {
typeCondition = {
@ -791,7 +791,7 @@ export class EmployeeTempPositionController extends Controller {
checkChildConditions = {
orgChild1Id: IsNull(),
};
searchShortName = `CONCAT(orgRoot.orgRootShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 1) {
@ -802,7 +802,7 @@ export class EmployeeTempPositionController extends Controller {
checkChildConditions = {
orgChild2Id: IsNull(),
};
searchShortName = `CONCAT(orgChild1.orgChild1ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 2) {
@ -813,7 +813,7 @@ export class EmployeeTempPositionController extends Controller {
checkChildConditions = {
orgChild3Id: IsNull(),
};
searchShortName = `CONCAT(orgChild2.orgChild2ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 3) {
@ -824,14 +824,14 @@ export class EmployeeTempPositionController extends Controller {
checkChildConditions = {
orgChild4Id: IsNull(),
};
searchShortName = `CONCAT(orgChild3.orgChild3ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 4) {
typeCondition = {
orgChild4Id: body.id,
};
searchShortName = `CONCAT(orgChild4.orgChild4ShortName,' ',posMaster.posMasterNo) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
let findPosition: any;
let masterId = new Array();
@ -859,8 +859,10 @@ export class EmployeeTempPositionController extends Controller {
select: ["posMasterTempId"],
});
masterId = masterId.concat(findPosition.map((position: any) => position.posMasterTempId));
const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/);
keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null;
keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10);
if (isNaN(keywordAsInt)) {
keywordAsInt = "P@ssw0rd!z";
}
masterId = [...new Set(masterId)];
}
@ -875,7 +877,7 @@ export class EmployeeTempPositionController extends Controller {
...(body.keyword &&
(masterId.length > 0
? { id: In(masterId) }
: /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })),
: { posMasterNo: Like(`%${body.keyword}%`) })),
},
];
let query = AppDataSource.getRepository(EmployeeTempPosMaster)

View file

@ -214,7 +214,6 @@ export class OrganizationController extends Controller {
await sendToQueueOrgDraft(msg);
return new HttpSuccess("Draft is being created... Processing in the background.");
} catch (error: any) {
console.error("Error creating draft organization:", error);
throw error;
}
}
@ -2530,7 +2529,6 @@ export class OrganizationController extends Controller {
await sendToQueueOrg(msg);
return new HttpSuccess();
} catch (error: any) {
console.error("Error publishing draft organization:", error);
throw error;
}
}
@ -5809,7 +5807,6 @@ export class OrganizationController extends Controller {
.leftJoin("orgRoot.posMasters", "posMasters")
.leftJoin("posMasters.current_holder", "current_holder")
.orderBy("orgRoot.orgRootOrder", "ASC")
.addOrderBy("posMasters.posMasterOrder", "ASC")
.getMany();
const orgRootIds = orgRootData.map((orgRoot) => orgRoot.id) || null;
@ -5850,7 +5847,6 @@ export class OrganizationController extends Controller {
.leftJoin("orgChild1.posMasters", "posMasters")
.leftJoin("posMasters.current_holder", "current_holder")
.orderBy("orgChild1.orgChild1Order", "ASC")
.addOrderBy("posMasters.posMasterOrder", "ASC")
.getMany()
: [];
@ -5892,7 +5888,6 @@ export class OrganizationController extends Controller {
.leftJoin("orgChild2.posMasters", "posMasters")
.leftJoin("posMasters.current_holder", "current_holder")
.orderBy("orgChild2.orgChild2Order", "ASC")
.addOrderBy("posMasters.posMasterOrder", "ASC")
.getMany()
: [];
@ -5934,7 +5929,6 @@ export class OrganizationController extends Controller {
.leftJoin("orgChild3.posMasters", "posMasters")
.leftJoin("posMasters.current_holder", "current_holder")
.orderBy("orgChild3.orgChild3Order", "ASC")
.addOrderBy("posMasters.posMasterOrder", "ASC")
.getMany()
: [];
@ -5971,7 +5965,6 @@ export class OrganizationController extends Controller {
.leftJoin("orgChild4.posMasters", "posMasters")
.leftJoin("posMasters.current_holder", "current_holder")
.orderBy("orgChild4.orgChild4Order", "ASC")
.addOrderBy("posMasters.posMasterOrder", "ASC")
.getMany()
: [];

File diff suppressed because it is too large Load diff

View file

@ -37,9 +37,7 @@ export class PermissionController extends Controller {
@Get("")
public async getPermission(@Request() request: RequestWithUser) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -93,18 +91,11 @@ export class PermissionController extends Controller {
orgRevisionId: orgRevision?.id,
},
});
}
// ตรวจสอบว่ามีสิทธิ์อย่างน้อยหนึ่งอย่าง (posMaster หรือ acting position)
if (!posMaster && !actingData.isAct) {
if (!posMaster) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์");
}
let getDetail: any = null;
let roleAttrData: any[] = [];
if (posMaster) {
getDetail = await this.authRoleRepo.findOne({
}
const getDetail = await this.authRoleRepo.findOne({
select: ["id", "roleName", "roleDescription"],
where: { id: posMaster.authRoleId },
});
@ -113,7 +104,7 @@ export class PermissionController extends Controller {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}
roleAttrData = await this.authRoleAttrRepo.find({
const roleAttrData = await this.authRoleAttrRepo.find({
select: [
"authSysId",
"parentNode",
@ -127,14 +118,6 @@ export class PermissionController extends Controller {
],
where: { authRoleId: getDetail.id },
});
} else {
// ถ้าไม่มี posMaster แต่มี acting: สร้าง getDetail เปล่าๆ
getDetail = {
id: null,
roleName: "Acting",
roleDescription: "สิทธิ์จากตำแหน่งรักษาการ",
};
}
// ถ้า User มีตำแหน่งรักษาการ ให้รวมสิทธิ์
if (actingData.isAct && actingData.posMasterActs.length > 0) {
@ -272,11 +255,6 @@ export class PermissionController extends Controller {
redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply));
}
return new HttpSuccess(reply);
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
@Get("menu")
@ -288,9 +266,7 @@ export class PermissionController extends Controller {
orgRevisionIsCurrent: true,
},
});
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -338,22 +314,16 @@ export class PermissionController extends Controller {
orgRevisionId: orgRevision?.id,
},
});
}
// ตรวจสอบว่ามีสิทธิ์อย่างน้อยหนึ่งอย่าง (posMaster หรือ acting position)
if (!posMaster && !actingData.isAct) {
if (!posMaster) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์");
}
}
let authRole: any = null;
let roleAttrData: any[] = [];
if (posMaster) {
if (!posMaster.authRoleId) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์");
}
authRole = await this.authRoleRepo.findOne({
const authRole = await this.authRoleRepo.findOne({
select: ["id"],
where: { id: posMaster.authRoleId },
});
@ -363,11 +333,10 @@ export class PermissionController extends Controller {
}
// ดึง roleAttrData ของ user ปกติ
roleAttrData = await this.authRoleAttrRepo.find({
let roleAttrData = await this.authRoleAttrRepo.find({
select: ["authSysId", "parentNode"],
where: { authRoleId: authRole.id, attrIsList: true },
});
}
// ถ้ามี acting positions ให้รวมสิทธิ์
if (actingData.isAct && actingData.posMasterActs.length > 0) {
@ -447,11 +416,6 @@ export class PermissionController extends Controller {
}
return new HttpSuccess(reply);
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
/**
@ -686,9 +650,7 @@ export class PermissionController extends Controller {
@Path() system: string,
@Path() action: string,
) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -781,11 +743,6 @@ export class PermissionController extends Controller {
}
return new HttpSuccess(reply);
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
@Get("user/{system}/{action}/{id}")
@ -802,9 +759,7 @@ export class PermissionController extends Controller {
orgRevisionIsCurrent: true,
},
});
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -889,17 +844,10 @@ export class PermissionController extends Controller {
}
return new HttpSuccess(reply);
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
public async getPermissionFunc(@Request() request: RequestWithUser) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -953,18 +901,12 @@ export class PermissionController extends Controller {
orgRevisionId: orgRevision?.id,
},
});
}
// ตรวจสอบว่ามีสิทธิ์อย่างน้อยหนึ่งอย่าง (posMaster หรือ acting position)
if (!posMaster && !actingData.isAct) {
if (!posMaster) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลสิทธิ์");
}
}
let getDetail: any = null;
let roleAttrData: any[] = [];
if (posMaster) {
getDetail = await this.authRoleRepo.findOne({
const getDetail = await this.authRoleRepo.findOne({
select: ["id", "roleName", "roleDescription"],
where: { id: posMaster.authRoleId },
});
@ -972,7 +914,7 @@ export class PermissionController extends Controller {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
}
roleAttrData = await this.authRoleAttrRepo.find({
const roleAttrData = await this.authRoleAttrRepo.find({
select: [
"authSysId",
"parentNode",
@ -986,14 +928,6 @@ export class PermissionController extends Controller {
],
where: { authRoleId: getDetail.id },
});
} else {
// ถ้าไม่มี posMaster แต่มี acting: สร้าง getDetail เปล่าๆ
getDetail = {
id: null,
roleName: "Acting",
roleDescription: "สิทธิ์จากตำแหน่งรักษาการ",
};
}
// ถ้ามี acting positions ให้รวมสิทธิ์
if (actingData.isAct && actingData.posMasterActs.length > 0) {
@ -1120,11 +1054,6 @@ export class PermissionController extends Controller {
redisClient.setex("role_" + profile.id, 86400, JSON.stringify(reply));
}
return reply;
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
public async Permission(req: RequestWithUser, system: string, action: string) {
@ -1150,9 +1079,7 @@ export class PermissionController extends Controller {
}
public async listAuthSysOrgFunc(request: RequestWithUser, system: string, action: string) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -1224,11 +1151,6 @@ export class PermissionController extends Controller {
redisClient.setex("posMaster_" + profile.id, 86400, JSON.stringify(reply));
}
return reply;
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
// Helper method: ดึง org scope จากตำแหน่งปกติ
@ -1408,9 +1330,7 @@ export class PermissionController extends Controller {
@Get("checkOrg/{keycloakId}")
public async checkOrg(@Path() keycloakId: string) {
let redisClient;
try {
redisClient = await this.redis.createClient({
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
@ -1492,10 +1412,5 @@ export class PermissionController extends Controller {
// }
return new HttpSuccess(reply);
} finally {
if (redisClient) {
redisClient.quit();
}
}
}
}

View file

@ -296,7 +296,6 @@ export class PosMasterActController extends Controller {
where: {
id: id,
},
relations: ["posMasterChild", "posMasterChild.current_holder"],
});
try {
result = await this.posMasterActRepository.delete({ id: id });
@ -321,22 +320,6 @@ export class PosMasterActController extends Controller {
await this.posMasterActRepository.save(p);
});
}
// ลบ Redis cache ของคนที่เป็น acting
if (posMasterAct != null && posMasterAct.posMasterChild?.current_holderId) {
const profileId = posMasterAct.posMasterChild.current_holderId;
const redisClient = await this.redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
const delAsync = promisify(redisClient.del).bind(redisClient);
await delAsync("role_" + profileId);
await delAsync("menu_" + profileId);
redisClient.quit();
}
return new HttpSuccess();
}

View file

@ -38,7 +38,7 @@ import { EmployeePosLevel } from "../entities/EmployeePosLevel";
import { AuthRole } from "../entities/AuthRole";
import { RequestWithUser } from "../middlewares/user";
import permission from "../interfaces/permission";
import { resolveNodeLevel, setLogDataDiff, logPositionIsSelectedChange } from "../interfaces/utils";
import { resolveNodeLevel, setLogDataDiff } from "../interfaces/utils";
import { getPosMasterNo, getOrgFullName } from "../utils/org-formatting";
import { PosMasterAssign } from "../entities/PosMasterAssign";
import { Assign } from "../entities/Assign";
@ -1427,17 +1427,7 @@ export class PositionController extends Controller {
requestBody.positions.map(async (x: any) => {
const match = posMaster.positions.find((p: any) => p.id == x.id);
if (match) {
const oldValue = match.positionIsSelected;
const newValue = x.positionIsSelected ?? false;
logPositionIsSelectedChange(match.id, oldValue, newValue, {
posMasterId: posMaster.id,
userId: request.user.sub,
endpoint: "updateMaster",
action: "update_position",
});
match.positionIsSelected = newValue;
match.positionIsSelected = x.positionIsSelected ?? false;
match.orderNo = x.orderNo ?? null;
return match;
} else {
@ -1688,11 +1678,11 @@ export class PositionController extends Controller {
let checkChildConditions: any = {};
let keywordAsInt: any;
let searchShortName = "1=1";
let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix)`;
if (body.type != null && body.id != null) {
if (body.type === 0) {
typeCondition = {
@ -1702,7 +1692,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild1Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 1) {
@ -1713,7 +1703,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild2Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 2) {
@ -1724,7 +1714,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild3Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 3) {
@ -1735,14 +1725,14 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild4Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`;
} else {
}
} else if (body.type === 4) {
typeCondition = {
orgChild4Id: body.id,
};
searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNoPrefix,posMaster.posMasterNo,posMaster.posMasterNoSuffix) like '%${body.keyword}%'`;
}
} else {
body.isAll = true;
@ -1787,8 +1777,10 @@ export class PositionController extends Controller {
select: ["posMasterId"],
});
masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId));
const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/);
keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null;
keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10);
if (isNaN(keywordAsInt)) {
keywordAsInt = "P@ssw0rd!z";
}
masterId = [...new Set(masterId)];
//serch name สิทธิ์
@ -1821,7 +1813,7 @@ export class PositionController extends Controller {
...(body.keyword &&
(masterId.length > 0
? { id: In(masterId) }
: /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })),
: { posMasterNo: Like(`%${body.keyword}%`) })),
},
];
let [posMaster, total] = await AppDataSource.getRepository(PosMaster)
@ -2162,11 +2154,11 @@ export class PositionController extends Controller {
let checkChildConditions: any = {};
let keywordAsInt: any;
let searchShortName = "1=1";
let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo)`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo)`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo)`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo)`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo)`;
let _data = await new permission().PermissionOrgList(request, "SYS_ORG");
if (body.type === 0) {
typeCondition = {
@ -2176,7 +2168,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild1Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgRoot.orgRootShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
} else if (body.type === 1) {
typeCondition = {
@ -2186,7 +2178,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild2Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
} else if (body.type === 2) {
typeCondition = {
@ -2196,7 +2188,7 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild3Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
} else if (body.type === 3) {
typeCondition = {
@ -2206,13 +2198,13 @@ export class PositionController extends Controller {
checkChildConditions = {
orgChild4Id: IsNull(),
};
searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
} else if (body.type === 4) {
typeCondition = {
orgChild4Id: body.id,
};
searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",posMaster.posMasterNo) like '%${body.keyword}%'`;
}
let findPosition: any;
let masterId = new Array();
@ -2249,8 +2241,10 @@ export class PositionController extends Controller {
select: ["posMasterId"],
});
masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId));
const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/);
keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null;
keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10);
if (isNaN(keywordAsInt)) {
keywordAsInt = "P@ssw0rd!z";
}
masterId = [...new Set(masterId)];
}
@ -2277,7 +2271,7 @@ export class PositionController extends Controller {
...(body.keyword &&
(masterId.length > 0
? { id: In(masterId) }
: /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })),
: { posMasterNo: Like(`%${body.keyword}%`) })),
},
];
@ -2766,19 +2760,7 @@ export class PositionController extends Controller {
id: data.id,
posMasterOrder: requestBody.sortId.indexOf(data.id) + 1,
}));
// Bulk update using CASE WHEN instead of save() per row
const caseClauses_0 = sortData_0
.map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`)
.join(" ");
const ids_0 = sortData_0.map((d) => `'${d.id}'`).join(",");
await this.posMasterRepository
.createQueryBuilder()
.update(PosMaster)
.set({
posMasterOrder: () => `CASE id ${caseClauses_0} END`,
})
.where(`id IN (${ids_0})`)
.execute();
await this.posMasterRepository.save(sortData_0, { data: request });
setLogDataDiff(request, { before, after: sortData_0 });
break;
}
@ -2807,19 +2789,7 @@ export class PositionController extends Controller {
id: data.id,
posMasterOrder: requestBody.sortId.indexOf(data.id) + 1,
}));
// Bulk update using CASE WHEN instead of save() per row
const caseClauses_1 = sortData_1
.map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`)
.join(" ");
const ids_1 = sortData_1.map((d) => `'${d.id}'`).join(",");
await this.posMasterRepository
.createQueryBuilder()
.update(PosMaster)
.set({
posMasterOrder: () => `CASE id ${caseClauses_1} END`,
})
.where(`id IN (${ids_1})`)
.execute();
await this.posMasterRepository.save(sortData_1, { data: request });
setLogDataDiff(request, { before, after: sortData_1 });
break;
}
@ -2848,19 +2818,7 @@ export class PositionController extends Controller {
id: data.id,
posMasterOrder: requestBody.sortId.indexOf(data.id) + 1,
}));
// Bulk update using CASE WHEN instead of save() per row
const caseClauses_2 = sortData_2
.map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`)
.join(" ");
const ids_2 = sortData_2.map((d) => `'${d.id}'`).join(",");
await this.posMasterRepository
.createQueryBuilder()
.update(PosMaster)
.set({
posMasterOrder: () => `CASE id ${caseClauses_2} END`,
})
.where(`id IN (${ids_2})`)
.execute();
await this.posMasterRepository.save(sortData_2, { data: request });
setLogDataDiff(request, { before, after: sortData_2 });
break;
}
@ -2889,19 +2847,7 @@ export class PositionController extends Controller {
id: data.id,
posMasterOrder: requestBody.sortId.indexOf(data.id) + 1,
}));
// Bulk update using CASE WHEN instead of save() per row
const caseClauses_3 = sortData_3
.map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`)
.join(" ");
const ids_3 = sortData_3.map((d) => `'${d.id}'`).join(",");
await this.posMasterRepository
.createQueryBuilder()
.update(PosMaster)
.set({
posMasterOrder: () => `CASE id ${caseClauses_3} END`,
})
.where(`id IN (${ids_3})`)
.execute();
await this.posMasterRepository.save(sortData_3, { data: request });
setLogDataDiff(request, { before, after: sortData_3 });
break;
}
@ -2930,19 +2876,7 @@ export class PositionController extends Controller {
id: data.id,
posMasterOrder: requestBody.sortId.indexOf(data.id) + 1,
}));
// Bulk update using CASE WHEN instead of save() per row
const caseClauses_4 = sortData_4
.map((d) => `WHEN '${d.id}' THEN ${d.posMasterOrder}`)
.join(" ");
const ids_4 = sortData_4.map((d) => `'${d.id}'`).join(",");
await this.posMasterRepository
.createQueryBuilder()
.update(PosMaster)
.set({
posMasterOrder: () => `CASE id ${caseClauses_4} END`,
})
.where(`id IN (${ids_4})`)
.execute();
await this.posMasterRepository.save(sortData_4, { data: request });
setLogDataDiff(request, { before, after: sortData_4 });
break;
}
@ -4040,18 +3974,7 @@ export class PositionController extends Controller {
statusReport: "PENDING",
});
console.log(
`[positionIsSelected-DEBUG] Deleting holder, resetting ALL positions to false (posMasterId: ${id}, userId: ${request.user.sub}, endpoint: deleteHolder)`
);
dataMaster.positions.forEach(async (position) => {
logPositionIsSelectedChange(position.id, position.positionIsSelected, false, {
posMasterId: id,
userId: request.user.sub,
endpoint: "deleteHolder",
action: "delete_holder_reset_positions",
});
await this.positionRepository.update(position.id, {
positionIsSelected: false,
});
@ -5351,11 +5274,11 @@ export class PositionController extends Controller {
let checkChildConditions: any = {};
let keywordAsInt: any;
let searchShortName = "1=1";
let searchShortName0 = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName1 = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName2 = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName3 = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName4 = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,''))`;
let searchShortName0 = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let searchShortName1 = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let searchShortName2 = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let searchShortName3 = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let searchShortName4 = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, ""))`;
let _data = await new permission().PermissionOrgList(request, "SYS_POS_CONDITION");
const orgDna = await new permission().checkDna(request, request.user.sub);
let level: any = resolveNodeLevel(orgDna);
@ -5397,7 +5320,7 @@ export class PositionController extends Controller {
// checkChildConditions = {
// orgChild1Id: IsNull(),
// };
// searchShortName = `CONCAT_WS(' ',orgRoot.orgRootShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
// searchShortName = `CONCAT(orgRoot.orgRootShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
// } else {
// }
} else if (body.type === 1) {
@ -5408,7 +5331,7 @@ export class PositionController extends Controller {
// checkChildConditions = {
// orgChild2Id: IsNull(),
// };
// searchShortName = `CONCAT_WS(' ',orgChild1.orgChild1ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
// searchShortName = `CONCAT(orgChild1.orgChild1ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
// } else {
// }
} else if (body.type === 2) {
@ -5419,7 +5342,7 @@ export class PositionController extends Controller {
// checkChildConditions = {
// orgChild3Id: IsNull(),
// };
// searchShortName = `CONCAT_WS(' ',orgChild2.orgChild2ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
// searchShortName = `CONCAT(orgChild2.orgChild2ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
// } else {
// }
} else if (body.type === 3) {
@ -5430,14 +5353,14 @@ export class PositionController extends Controller {
// checkChildConditions = {
// orgChild4Id: IsNull(),
// };
// searchShortName = `CONCAT_WS(' ',orgChild3.orgChild3ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
// searchShortName = `CONCAT(orgChild3.orgChild3ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
// } else {
// }
} else if (body.type === 4) {
typeCondition = {
...(cannotViewChild4PosMaster ? { orgChild4Id: null } : { orgChild4Id: body.id }),
};
searchShortName = `CONCAT_WS(' ',orgChild4.orgChild4ShortName,NULLIF(posMaster.posMasterNoPrefix,''),posMaster.posMasterNo,NULLIF(posMaster.posMasterNoSuffix,'')) like '%${body.keyword}%'`;
searchShortName = `CONCAT(orgChild4.orgChild4ShortName," ",COALESCE(posMaster.posMasterNoPrefix, ""),posMaster.posMasterNo,COALESCE(posMaster.posMasterNoSuffix, "")) like '%${body.keyword}%'`;
}
let findPosition: any;
let masterId = new Array();
@ -5474,8 +5397,10 @@ export class PositionController extends Controller {
select: ["posMasterId"],
});
masterId = masterId.concat(findPosition.map((position: any) => position.posMasterId));
const numericMatch = body.keyword == null ? null : body.keyword.match(/\d+/);
keywordAsInt = numericMatch ? parseInt(numericMatch[0], 10) : null;
keywordAsInt = body.keyword == null ? null : parseInt(body.keyword, 10);
if (isNaN(keywordAsInt)) {
keywordAsInt = "P@ssw0rd!z";
}
masterId = [...new Set(masterId)];
}
@ -5502,7 +5427,7 @@ export class PositionController extends Controller {
...(body.keyword &&
(masterId.length > 0
? { id: In(masterId) }
: /^\d+$/.test(body.keyword) ? { posMasterNo: keywordAsInt } : { posMasterNo: Like(`%${body.keyword}%`) })),
: { posMasterNo: Like(`%${body.keyword}%`) })),
...(!body.isAll && { isCondition: true }),
},
];

View file

@ -25,7 +25,6 @@ import {
} from "../entities/ProfileChangeName";
import { updateName } from "../keycloak";
import permission from "../interfaces/permission";
import { updateHolderProfileHistory } from "../services/PositionService";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile/changeName")
@Tags("ProfileChangeName")
@ -128,9 +127,6 @@ export class ProfileChangeNameController extends Controller {
}
}
// บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่)
await updateHolderProfileHistory(profile.id, req);
return new HttpSuccess(data.id);
}

View file

@ -24,7 +24,6 @@ import {
} from "../entities/ProfileChangeName";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import permission from "../interfaces/permission";
import { updateHolderProfileHistory } from "../services/PositionService";
import { updateName } from "../keycloak";
import { setLogDataDiff } from "../interfaces/utils";
@Route("api/v1/org/profile-employee/changeName")
@ -134,9 +133,6 @@ export class ProfileChangeNameEmployeeController extends Controller {
}
}
// บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่)
await updateHolderProfileHistory(profile.id, req, "EMPLOYEE");
return new HttpSuccess(data.id);
}

View file

@ -93,7 +93,6 @@ import { CreatePosMasterHistoryOfficer, getTopDegrees, getPosMasterPositions } f
import { ProfileLeaveService } from "../services/ProfileLeaveService";
// import { PostRetireToExprofile } from "./ExRetirementController";
import { getPosNumCodeSit } from "../services/CommandService";
import { updateHolderProfileHistory } from "../services/PositionService";
@Route("api/v1/org/profile")
@Tags("Profile")
@Security("bearerAuth")
@ -5775,26 +5774,29 @@ export class ProfileController extends Controller {
}
if (body.citizenId) {
Extension.CheckCitizen(body.citizenId);
const citizenIdDigits = body.citizenId.toString().split("").map(Number);
const cal =
citizenIdDigits[0] * 13 +
citizenIdDigits[1] * 12 +
citizenIdDigits[2] * 11 +
citizenIdDigits[3] * 10 +
citizenIdDigits[4] * 9 +
citizenIdDigits[5] * 8 +
citizenIdDigits[6] * 7 +
citizenIdDigits[7] * 6 +
citizenIdDigits[8] * 5 +
citizenIdDigits[9] * 4 +
citizenIdDigits[10] * 3 +
citizenIdDigits[11] * 2;
const calStp2 = cal % 11;
const chkDigit = (11 - calStp2) % 10;
if (citizenIdDigits[12] !== chkDigit) {
throw new HttpError(HttpStatus.NOT_FOUND, "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง");
}
}
const record = await this.profileRepo.findOneBy({ id });
const before = structuredClone(record);
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
const historyCount = await this.profileHistoryRepo.count({
where: { profileId: id },
});
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
if (historyCount === 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileHistory(), {
...before,
birthDateOld: before?.birthDate,
profileId: id,
id: undefined,
}),
);
}
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
@ -5831,9 +5833,6 @@ export class ProfileController extends Controller {
}
}
// บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่)
await updateHolderProfileHistory(record.id, request);
return new HttpSuccess();
}
@ -6027,11 +6026,11 @@ export class ProfileController extends Controller {
} else if (searchField == "posNo") {
queryLike = `
CASE
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo)
ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo)
END LIKE :keyword
`;
}
@ -6301,7 +6300,7 @@ export class ProfileController extends Controller {
@Query() sortBy: string = "profile.dateLeave",
@Query() sort: "ASC" | "DESC" = "ASC",
) {
let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_OFFICER");
let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER");
const { data, total } = await this.profileLeaveService.getLeaveOfficer(request, {
page,
@ -6617,11 +6616,11 @@ export class ProfileController extends Controller {
} else if (searchField == "posNo") {
queryLike = `
CASE
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo)
ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo)
END LIKE :keyword
`;
}
@ -6804,19 +6803,18 @@ export class ProfileController extends Controller {
.filter(Boolean)
.join("\n");
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const shortName = !holder
? null
: holder.orgChild4 != null
? `${holder.orgChild4.orgChild4ShortName} ${numPart}`
? `${holder.orgChild4.orgChild4ShortName} ${holder.posMasterNo}`
: holder.orgChild3 != null
? `${holder.orgChild3.orgChild3ShortName} ${numPart}`
? `${holder.orgChild3.orgChild3ShortName} ${holder.posMasterNo}`
: holder.orgChild2 != null
? `${holder.orgChild2.orgChild2ShortName} ${numPart}`
? `${holder.orgChild2.orgChild2ShortName} ${holder.posMasterNo}`
: holder.orgChild1 != null
? `${holder.orgChild1.orgChild1ShortName} ${numPart}`
? `${holder.orgChild1.orgChild1ShortName} ${holder.posMasterNo}`
: holder.orgRoot != null
? `${holder.orgRoot.orgRootShortName} ${numPart}`
? `${holder.orgRoot.orgRootShortName} ${holder.posMasterNo}`
: null;
return {
@ -7011,11 +7009,11 @@ export class ProfileController extends Controller {
} else if (searchField == "posNo") {
queryLike = `
CASE
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo)
ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo)
END LIKE :keyword
`;
}
@ -7192,7 +7190,7 @@ export class ProfileController extends Controller {
.filter(Boolean)
.join("\n");
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const numPart = holder ? `${holder.posMasterNoPrefix ?? ''}${holder.posMasterNo ?? ''}${holder.posMasterNoSuffix ?? ''}` : '';
const shortName = !holder
? null
@ -7951,38 +7949,40 @@ export class ProfileController extends Controller {
privacyUser: profile.privacyUser,
privacyMgt: profile.privacyMgt,
isDeputy: root?.isDeputy ?? false,
// root?.orgRootShortName && posMaster?.posMasterNo
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
// : "",
};
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
if (_profile.child4Id != null) {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeDnaId = _profile.child4DnaId;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeDnaId = _profile.child3DnaId;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeDnaId = _profile.child2DnaId;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeDnaId = _profile.child1DnaId;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeDnaId = _profile.rootDnaId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
_profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`;
}
return new HttpSuccess(_profile);
}
@ -8122,39 +8122,41 @@ export class ProfileController extends Controller {
privacyUser: profile.privacyUser,
privacyMgt: profile.privacyMgt,
isDeputy: root?.isDeputy ?? false,
// root?.orgRootShortName && posMaster?.posMasterNo
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
// : "",
};
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
if (_profile.child4Id != null) {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeDnaId = _profile.child4DnaId;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child4ShortName} ${posMaster?.posMasterNo}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeDnaId = _profile.child3DnaId;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child3ShortName} ${posMaster?.posMasterNo}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeDnaId = _profile.child2DnaId;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child2ShortName} ${posMaster?.posMasterNo}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeDnaId = _profile.child1DnaId;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child1ShortName} ${posMaster?.posMasterNo}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeDnaId = _profile.rootDnaId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
_profile.posNo = `${_profile.rootShortName} ${posMaster?.posMasterNo}`;
}
return new HttpSuccess(_profile);
}
@ -8794,21 +8796,32 @@ export class ProfileController extends Controller {
posMasterId: posMaster?.id,
},
});
const holder = profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id);
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const shortName =
holder == null
profile.current_holders.length == 0
? null
: holder.orgChild4 != null
? `${holder.orgChild4.orgChild4ShortName} ${numPart}`
: holder.orgChild3 != null
? `${holder.orgChild3.orgChild3ShortName} ${numPart}`
: holder.orgChild2 != null
? `${holder.orgChild2.orgChild2ShortName} ${numPart}`
: holder.orgChild1 != null
? `${holder.orgChild1.orgChild1ShortName} ${numPart}`
: holder.orgRoot != null
? `${holder.orgRoot.orgRootShortName} ${numPart}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild4 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild3 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild2 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild1 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgRoot != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: null;
// const posMasterActs = await this.posMasterActRepository.find({
// relations: [
@ -9170,32 +9183,26 @@ export class ProfileController extends Controller {
profile.avatar && profile.avatarName ? `${profile.avatar}/${profile.avatarName}` : null,
};
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
if (_profile.child4Id != null) {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
}
return new HttpSuccess(_profile);
}
@ -9325,32 +9332,26 @@ export class ProfileController extends Controller {
: "-",
};
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
if (_profile.child4Id != null) {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
}
return new HttpSuccess(_profile);
}
@ -9531,28 +9532,38 @@ export class ProfileController extends Controller {
const mapDataProfile = await Promise.all(
findProfile.map(async (item: Profile) => {
const fullName = `${item.prefix}${item.firstName} ${item.lastName}`;
const holder = item.current_holders?.find((x) => x.orgRevisionId == findRevision.id);
const _numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const shortName = !holder
const shortName =
item.current_holders.length == 0
? null
: holder.orgChild4 != null
? `${holder.orgChild4.orgChild4ShortName} ${_numPart}`
: holder.orgChild3 != null
? `${holder.orgChild3.orgChild3ShortName} ${_numPart}`
: holder.orgChild2 != null
? `${holder.orgChild2.orgChild2ShortName} ${_numPart}`
: holder.orgChild1 != null
? `${holder.orgChild1.orgChild1ShortName} ${_numPart}`
: holder.orgRoot != null
? `${holder.orgRoot.orgRootShortName} ${_numPart}`
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4 !=
null
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild4.orgChild4ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3 !=
null
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild3.orgChild3ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)
?.orgChild2 != null
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild2.orgChild2ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)
?.orgChild1 != null
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgChild1.orgChild1ShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id) !=
null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)
?.orgRoot != null
? `${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot.orgRootShortName} ${item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.posMasterNo}`
: null;
const root =
item.current_holders.length == 0 ||
(holder != null &&
holder?.orgRoot == null)
(item.current_holders.find((x) => x.orgRevisionId == findRevision.id) != null &&
item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot == null)
? null
: holder?.orgRoot;
: item.current_holders.find((x) => x.orgRevisionId == findRevision.id)?.orgRoot;
const rootHolder = item.current_holders?.find(
(x) => x.orgRevisionId == findRevision.id,

View file

@ -84,7 +84,6 @@ import { ProfileDuty } from "../entities/ProfileDuty";
import { CreatePosMasterHistoryEmployee, getTopDegrees } from "../services/PositionService";
import { ProfileLeaveService } from "../services/ProfileLeaveService";
import { CommandCode } from "../entities/CommandCode";
import { updateHolderProfileHistory } from "../services/PositionService";
@Route("api/v1/org/profile-employee")
@Tags("ProfileEmployee")
@Security("bearerAuth")
@ -400,19 +399,24 @@ export class ProfileEmployeeController extends Controller {
salary_raw.length > 0 && salary_raw[0].positionExecutive != null
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].positionExecutive))
: "",
org: `${salary_raw.length > 0 && salary_raw[0].orgChild4 && salary_raw[0].orgChild4 != "-"
org: `${
salary_raw.length > 0 && salary_raw[0].orgChild4 && salary_raw[0].orgChild4 != "-"
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild4)) + " "
: ""
}${salary_raw.length > 0 && salary_raw[0].orgChild3 && salary_raw[0].orgChild3 != "-"
}${
salary_raw.length > 0 && salary_raw[0].orgChild3 && salary_raw[0].orgChild3 != "-"
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild3)) + " "
: ""
}${salary_raw.length > 0 && salary_raw[0].orgChild2 && salary_raw[0].orgChild2 != "-"
}${
salary_raw.length > 0 && salary_raw[0].orgChild2 && salary_raw[0].orgChild2 != "-"
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild2)) + " "
: ""
}${salary_raw.length > 0 && salary_raw[0].orgChild1 && salary_raw[0].orgChild1 != "-"
}${
salary_raw.length > 0 && salary_raw[0].orgChild1 && salary_raw[0].orgChild1 != "-"
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgChild1)) + " "
: ""
}${salary_raw.length > 0 && salary_raw[0].orgRoot && salary_raw[0].orgRoot != "-"
}${
salary_raw.length > 0 && salary_raw[0].orgRoot && salary_raw[0].orgRoot != "-"
? Extension.ToThaiNumber(Extension.ToThaiNumber(salary_raw[0].orgRoot))
: ""
}`,
@ -2382,27 +2386,28 @@ export class ProfileEmployeeController extends Controller {
}
if (body.citizenId) {
Extension.CheckCitizen(body.citizenId);
const citizenIdDigits = body.citizenId.toString().split("").map(Number);
const cal =
citizenIdDigits[0] * 13 +
citizenIdDigits[1] * 12 +
citizenIdDigits[2] * 11 +
citizenIdDigits[3] * 10 +
citizenIdDigits[4] * 9 +
citizenIdDigits[5] * 8 +
citizenIdDigits[6] * 7 +
citizenIdDigits[7] * 6 +
citizenIdDigits[8] * 5 +
citizenIdDigits[9] * 4 +
citizenIdDigits[10] * 3 +
citizenIdDigits[11] * 2;
const calStp2 = cal % 11;
const chkDigit = (11 - calStp2) % 10;
if (citizenIdDigits[12] !== chkDigit) {
throw new HttpError(HttpStatus.NOT_FOUND, "ข้อมูลรหัสบัตรประจำตัวประชาชนไม่ถูกต้อง");
}
}
const record = await this.profileRepo.findOneBy({ id });
const before = structuredClone(record);
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
const historyCount = await this.profileHistoryRepo.count({
where: { profileEmployeeId: id },
});
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
if (historyCount === 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileEmployeeHistory(), {
...before,
birthDateOld: before?.birthDate,
profileEmployeeId: id,
id: undefined,
}),
);
}
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {
@ -2434,8 +2439,6 @@ export class ProfileEmployeeController extends Controller {
}),
);
await this.profileRepo.save(record);
// บันทึกประวัติคนครองตำแหน่ง (ถ้า profile นี้ครองตำแหน่งอยู่)
await updateHolderProfileHistory(record.id, request, "EMPLOYEE");
return new HttpSuccess();
}
@ -2850,11 +2853,11 @@ export class ProfileEmployeeController extends Controller {
} else if (searchField == "posNo") {
queryLike = `
CASE
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo)
ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo)
END LIKE :keyword
`;
}
@ -3108,7 +3111,7 @@ export class ProfileEmployeeController extends Controller {
@Query() sortBy: string = "profileEmployee.dateLeave",
@Query() sort: "ASC" | "DESC" = "DESC",
) {
let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_RETIRE_EMP");
let _data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_EMP");
const { data, total } = await this.profileLeaveService.getLeaveEmployees(request, {
page,
@ -3209,11 +3212,11 @@ export class ProfileEmployeeController extends Controller {
} else if (searchField == "posNo") {
queryLike = `
CASE
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT_WS(' ', orgChild4.orgChild4ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT_WS(' ', orgChild3.orgChild3ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT_WS(' ', orgChild2.orgChild2ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT_WS(' ', orgChild1.orgChild1ShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
ELSE CONCAT_WS(' ', orgRoot.orgRootShortName, NULLIF(current_holders.posMasterNoPrefix,''), current_holders.posMasterNo, NULLIF(current_holders.posMasterNoSuffix,''))
WHEN current_holders.orgChild4Id IS NOT NULL THEN CONCAT(orgChild4.orgChild4ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild3Id IS NOT NULL THEN CONCAT(orgChild3.orgChild3ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild2Id IS NOT NULL THEN CONCAT(orgChild2.orgChild2ShortName, " ", current_holders.posMasterNo)
WHEN current_holders.orgChild1Id IS NOT NULL THEN CONCAT(orgChild1.orgChild1ShortName, " ", current_holders.posMasterNo)
ELSE CONCAT(orgRoot.orgRootShortName, " ", current_holders.posMasterNo)
END LIKE :keyword
`;
}
@ -3366,7 +3369,7 @@ export class ProfileEmployeeController extends Controller {
const data = await Promise.all(
record.map((_data) => {
const holder = _data.current_holders.find((x) => x.orgRevisionId == findRevision.id);
const numPart = holder ? [holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const numPart = holder ? `${holder.posMasterNoPrefix ?? ''}${holder.posMasterNo ?? ''}${holder.posMasterNoSuffix ?? ''}` : '';
const shortName = !holder
? null
: holder.orgChild4 != null
@ -3843,7 +3846,7 @@ export class ProfileEmployeeController extends Controller {
holder.orgChild2?.orgChild2ShortName ||
holder.orgChild1?.orgChild1ShortName ||
holder.orgRoot?.orgRootShortName;
return `${shortName || ""} ${[holder.posMasterNoPrefix, holder.posMasterNo, holder.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`;
return `${shortName || ""} ${holder.posMasterNo || ""}`;
});
return profile.current_holders.map((holder, index) => {
const position = holder.positions.find((position) => position.posMasterId === holder.id);
@ -4021,38 +4024,40 @@ export class ProfileEmployeeController extends Controller {
salary: profile ? profile.amount : null,
amountSpecial: profile ? profile.amountSpecial : null,
posNo: null,
// root?.orgRootShortName && posMaster?.posMasterNo
// ? `${root?.orgRootShortName} ${posMaster?.posMasterNo}`
// : "",
};
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
if (_profile.child4Id != null) {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeDnaId = _profile.child4DnaId;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child4ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeDnaId = _profile.child3DnaId;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child3ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeDnaId = _profile.child2DnaId;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child2ShortName} ${_profile.posMasterNo}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeDnaId = _profile.child1DnaId;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
_profile.posNo = `${_profile.child1ShortName} ${_profile.posMasterNo}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeDnaId = _profile.rootDnaId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
_profile.posNo = `${_profile.rootShortName} ${_profile.posMasterNo}`;
}
return new HttpSuccess(_profile);
}
@ -6460,7 +6465,33 @@ export class ProfileEmployeeController extends Controller {
null
? null
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4;
const _numPart = posMaster ? [posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ') : '';
const shortName =
profile.current_holders.length == 0
? null
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild4 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild4.orgChild4ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) != null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild3 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild3.orgChild3ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild2 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild2.orgChild2ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgChild1 != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgChild1.orgChild1ShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id) !=
null &&
profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)
?.orgRoot != null
? `${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.orgRoot.orgRootShortName} ${profile.current_holders.find((x) => x.orgRevisionId == orgRevisionPublish.id)?.posMasterNo}`
: null;
const _profile: any = {
profileId: profile.id,
prefix: profile.prefix,
@ -6502,7 +6533,7 @@ export class ProfileEmployeeController extends Controller {
child4ShortName: child4 == null ? null : child4.orgChild4ShortName,
node: null,
nodeId: null,
posNo: null,
posNo: shortName,
salary: profile.amount,
education:
profile && profile.profileEducations.length > 0
@ -6517,27 +6548,22 @@ export class ProfileEmployeeController extends Controller {
_profile.node = 4;
_profile.nodeId = _profile.child4Id;
_profile.nodeShortName = _profile.child4ShortName;
_profile.posNo = `${_profile.child4ShortName} ${_numPart}`;
} else if (_profile.child3Id != null) {
_profile.node = 3;
_profile.nodeId = _profile.child3Id;
_profile.nodeShortName = _profile.child3ShortName;
_profile.posNo = `${_profile.child3ShortName} ${_numPart}`;
} else if (_profile.child2Id != null) {
_profile.node = 2;
_profile.nodeId = _profile.child2Id;
_profile.nodeShortName = _profile.child2ShortName;
_profile.posNo = `${_profile.child2ShortName} ${_numPart}`;
} else if (_profile.child1Id != null) {
_profile.node = 1;
_profile.nodeId = _profile.child1Id;
_profile.nodeShortName = _profile.child1ShortName;
_profile.posNo = `${_profile.child1ShortName} ${_numPart}`;
} else if (_profile.rootId != null) {
_profile.node = 0;
_profile.nodeId = _profile.rootId;
_profile.nodeShortName = _profile.rootShortName;
_profile.posNo = `${_profile.rootShortName} ${_numPart}`;
}
return new HttpSuccess(_profile);
}

View file

@ -1001,24 +1001,6 @@ export class ProfileEmployeeTempController extends Controller {
}
const record = await this.profileRepo.findOneBy({ id });
const before = structuredClone(record);
// เช็คว่ามี profileHistory ของ profile นี้หรือไม่
const historyCount = await this.profileHistoryRepo.count({
where: { profileEmployeeId: id },
});
// ถ้าไม่มีเลย ให้บันทึกข้อมูลเริ่มต้น (ก่อน update) ลงไปก่อน
if (historyCount === 0) {
await this.profileHistoryRepo.save(
Object.assign(new ProfileEmployeeHistory(), {
...before,
birthDateOld: before?.birthDate,
profileEmployeeId: id,
id: undefined,
}),
);
}
if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลโปรไฟล์นี้");
if (body.employeeClass == null || body.employeeClass == undefined || body.employeeClass == "") {

View file

@ -115,7 +115,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
record.posType == null && record.posLevel == null
? null
: `${record.posType.posTypeShortName} ${record.posLevel.posLevelName}`, //ระดับ
posMasterNo: posMaster == null ? null : `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`, //เลขที่ตำแหน่ง
posMasterNo: posMaster == null ? null : `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}`, //เลขที่ตำแหน่ง
posType: record.posType == null ? null : record.posType.posTypeName, //ประเภท
dateLeave: record.birthDate == null ? null : calculateRetireDate(record.birthDate),
dateRetireLaw: record.dateRetireLaw ?? null,
@ -281,7 +281,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
record?.isLeave == false
? posMaster == null
? null
: `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`
: `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}`
: posNoLeave /*record && record?.profileSalary.length > 0
? `${record?.profileSalary[0].posNoAbb} ${record?.profileSalary[0].posNo}`
: null*/, //
@ -441,7 +441,7 @@ export class ProfileGovernmentEmployeeController extends Controller {
record?.isLeave == false
? posMaster == null
? null
: `${orgShortName} ${[posMaster.posMasterNoPrefix, posMaster.posMasterNo, posMaster.posMasterNoSuffix].filter((p) => p !== null && p !== undefined && p !== '').join(' ')}`
: `${orgShortName} ${posMaster.posMasterNoPrefix ?? ''}${posMaster.posMasterNo ?? ''}${posMaster.posMasterNoSuffix ?? ''}`
: posNoLeave /*record && record.profileSalary.length > 0
? `${record?.profileSalary[0].posNoAbb} ${record?.profileSalary[0].posNo}`
: null*/, //

File diff suppressed because it is too large Load diff

View file

@ -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;
},

View file

@ -1791,56 +1791,4 @@ export class ProfileSalaryTempController extends Controller {
await this.salaryRepo.save(sortLevel);
return new HttpSuccess();
}
/**
* API
* @summary API
*/
@Put("sort-order")
public async reorderSalaryByCommandDate(
@Request() req: RequestWithUser,
@Body() body: { profileId: string; type: "OFFICER" | "EMPLOYEE" },
) {
const isOfficer = body.type.toUpperCase() === "OFFICER";
// Step 1: SELECT ข้อมูลตาม profileId และ type
const salaryTemps = await this.salaryRepo.find({
where: isOfficer ? { profileId: body.profileId } : { profileEmployeeId: body.profileId },
});
if (salaryTemps.length === 0) {
throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งเงินเดือน");
}
// Step 2: เรียงลำดับตาม commandDateAffect (ASC)
// ถ้า commandDateAffect เท่ากัน ให้ใช้ order เดิมเป็น secondary sort
const sortedSalary = salaryTemps.sort((a, b) => {
// ถ้า commandDateAffect เป็น null ให้ถือว่าเป็นค่าน้อยสุด
const dateA = a.commandDateAffect ? new Date(a.commandDateAffect).getTime() : 0;
const dateB = b.commandDateAffect ? new Date(b.commandDateAffect).getTime() : 0;
if (dateA !== dateB) {
return dateA - dateB; // เรียงตามวันที่คำสั่งมีผล
}
// ถ้าวันที่เท่ากัน ให้ใช้ order เดิม
const orderA = a.order ?? 0;
const orderB = b.order ?? 0;
return orderA - orderB;
});
// Step 3: UPDATE ฟิลด์ order ตามการเรียงใหม่
const dateNow = new Date();
const updatedSalary = sortedSalary.map((item, index) => ({
...item,
order: index + 1,
lastUpdateUserId: req.user.sub,
lastUpdateFullName: req.user.name,
lastUpdatedAt: dateNow,
}));
await this.salaryRepo.save(updatedSalary);
return new HttpSuccess();
}
}

View file

@ -38,10 +38,6 @@ export class ScriptProfileOrgController extends Controller {
process.env.CRONJOB_UPDATE_WINDOW_HOURS || "24",
10,
);
private readonly LEAVE_SERVICE_BATCH_SIZE = parseInt(
process.env.LEAVE_SERVICE_BATCH_SIZE || "50",
10,
);
/**
* Script to update profile's organizational structure in leave service and sync to Keycloak
@ -49,7 +45,7 @@ export class ScriptProfileOrgController extends Controller {
* @summary Update org structure for profiles updated within a certain time window and sync to Keycloak
*/
@Post("update-org")
public async cronjobUpdateOrg(@Request() _request: RequestWithUser) {
public async cronjobUpdateOrg(@Request() request: RequestWithUser) {
// Idempotency check - prevent concurrent runs
if (this.isRunning) {
console.log("cronjobUpdateOrg: Job already running, skipping this execution");
@ -180,6 +176,21 @@ export class ScriptProfileOrgController extends Controller {
});
}
// Update profile's org structure in leave service by calling API
console.log("cronjobUpdateOrg: Calling leave service API", {
payloadCount: payloads.length,
});
await axios.put(`${process.env.API_URL}/leave-beginning/schedule/update-dna`, payloads, {
headers: {
"Content-Type": "application/json",
api_key: process.env.API_KEY,
},
timeout: 30000, // 30 second timeout
});
console.log("cronjobUpdateOrg: Leave service API call successful");
// Group profile IDs by type for proper syncing
const profileIdsByType = this.groupProfileIdsByType(payloads);
@ -245,90 +256,16 @@ export class ScriptProfileOrgController extends Controller {
syncResults.failed += typeResult.failed;
}
// Update profile's org structure in leave service by calling API
console.log("cronjobUpdateOrg: Calling leave service API with chunking", {
payloadCount: payloads.length,
batchSize: this.LEAVE_SERVICE_BATCH_SIZE,
expectedBatches: Math.ceil(payloads.length / this.LEAVE_SERVICE_BATCH_SIZE),
});
const chunks = this.chunkArray(payloads, this.LEAVE_SERVICE_BATCH_SIZE);
const leaveServiceResults = {
total: payloads.length,
success: 0,
failed: 0,
batchesCompleted: 0,
batchesFailed: 0,
};
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const batchNumber = i + 1;
console.log(
`cronjobUpdateOrg: Processing leave service batch ${batchNumber}/${chunks.length}`,
{
batchSize: chunk.length,
batchRange: `${i * this.LEAVE_SERVICE_BATCH_SIZE + 1}-${Math.min(
(batchNumber + 1) * this.LEAVE_SERVICE_BATCH_SIZE,
payloads.length,
)}`,
},
);
try {
await axios.put(
`${process.env.API_URL}/leave-beginning/schedule/update-dna`,
chunk,
{
headers: {
"Content-Type": "application/json",
api_key: process.env.API_KEY,
},
timeout: 120000, // 120 second timeout per chunk
},
);
leaveServiceResults.success += chunk.length;
leaveServiceResults.batchesCompleted++;
console.log(`cronjobUpdateOrg: Leave service batch ${batchNumber}/${chunks.length} completed`, {
success: chunk.length,
});
} catch (error: any) {
leaveServiceResults.failed += chunk.length;
leaveServiceResults.batchesFailed++;
console.error(
`cronjobUpdateOrg: Leave service batch ${batchNumber}/${chunks.length} failed`,
{
error: error.message,
batchSize: chunk.length,
responseStatus: error.response?.status,
responseData: error.response?.data,
},
);
// Continue processing remaining batches
}
}
console.log("cronjobUpdateOrg: Leave service API call completed", {
...leaveServiceResults,
});
const duration = Date.now() - startTime;
console.log("cronjobUpdateOrg: Job completed", {
duration: `${duration}ms`,
processed: payloads.length,
leaveServiceResults,
syncResults,
});
return new HttpSuccess({
message: "Update org completed",
processed: payloads.length,
leaveServiceResults,
syncResults,
duration: `${duration}ms`,
});

View file

@ -1,6 +1,5 @@
import { Body, Controller, Post, Request, Route, Security } from "tsoa";
import { Body, Controller, Post, Route } from "tsoa";
import { sendWebSocket } from "../services/webSocket";
import { RequestWithUser } from "../middlewares/user";
@Route("/api/v1/org/through-socket")
export class SocketController extends Controller {
@ -23,39 +22,4 @@ export class SocketController extends Controller {
},
);
}
@Post("notify-from-token")
@Security("bearerAuth")
async notifyFromToken(
@Body()
payload: {
message: string;
targetUserId?: string | string[];
roles?: string | string[];
error?: boolean;
},
@Request() req: RequestWithUser,
) {
const toArray = (value?: string | string[]) => {
if (Array.isArray(value)) return value.filter(Boolean);
if (typeof value === "string" && value.trim()) return [value];
return [] as string[];
};
const targetUserIds = toArray(payload.targetUserId);
const targetRoles = toArray(payload.roles);
// If caller provides explicit user targets, do not combine with role targeting.
// This prevents accidental broad notifications when roles include common roles.
const recipients =
targetUserIds.length > 0
? { userId: targetUserIds, roles: [] as string[] }
: { userId: [req.user.sub], roles: targetRoles };
sendWebSocket(
"socket-notification",
{ success: !payload.error, message: payload.message },
recipients,
);
}
}

View file

@ -580,27 +580,18 @@ export class KeycloakController extends Controller {
new Brackets((qb) => {
qb.orWhere(
body.keyword != null && body.keyword != ""
? `profile.citizenId LIKE :keyword`
? `profile.citizenId like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
)
.orWhere(
body.keyword != null && body.keyword != ""
? `profile.email LIKE :keyword`
? `profile.email like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
)
.orWhere(
body.keyword != null && body.keyword != ""
? `CONCAT(profile.prefix, profile.firstName," ",profile.lastName) LIKE :keyword`
? `CONCAT(profile.prefix, profile.firstName," ",profile.lastName) like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
);
}),
)
@ -634,27 +625,18 @@ export class KeycloakController extends Controller {
new Brackets((qb) => {
qb.orWhere(
body.keyword != null && body.keyword != ""
? `profileEmployee.citizenId LIKE :keyword`
? `profileEmployee.citizenId like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
)
.orWhere(
body.keyword != null && body.keyword != ""
? `profileEmployee.email LIKE :keyword`
? `profileEmployee.email like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
)
.orWhere(
body.keyword != null && body.keyword != ""
? `CONCAT(profileEmployee.prefix, profileEmployee.firstName," ",profileEmployee.lastName) LIKE :keyword`
? `CONCAT(profileEmployee.prefix, profileEmployee.firstName," ",profileEmployee.lastName) like '%${body.keyword}%'`
: "1=1",
{
keyword: `%${body.keyword}%`,
}
);
}),
)

View file

@ -1088,10 +1088,11 @@ export class WorkflowController extends Controller {
if (body.isAct) {
// ตำแหน่งของคนที่เลือกไปรักษาการ
let childPosition = "";
if (x.positionSignChild) {
childPosition = x.positionSignChild;
} else if (x.posExecutiveName) {
childPosition = x.posExecutiveName;
if (x.posType === "อำนวยการ" || x.posType === "บริหาร") {
childPosition = x.posExecutiveName || "";
if (!childPosition) {
childPosition = `${x.position || ""}ระดับ${x.posLevel || ""}`.trim();
}
} else {
childPosition = `${x.position || ""}${x.posLevel || ""}`.trim();
}

View file

@ -34,14 +34,6 @@ export class Command extends EntityBase {
})
issue: string;
@Column({
nullable: true,
comment: "ชื่อย่อหน่วยงานที่ออกคำสั่ง",
length: 16,
default: null,
})
shortName: string;
@Column({
nullable: true,
comment: "เลขที่คำสั่ง",

View file

@ -99,51 +99,51 @@ export class PosMasterEmployeeHistory extends EntityBase {
})
ancestorDNA: string;
@Column({
nullable: true,
length: 40,
comment: "คีย์นอก(FK)ของตาราง profileEmployee",
default: null,
})
profileEmployeeId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "คีย์นอก(FK)ของตาราง profile",
// default: null,
// })
// profileId: string;
@Column({
nullable: true,
length: 40,
comment: "dna ของตาราง orgRoot",
default: null,
})
rootDnaId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "dna ของตาราง orgRoot",
// default: null,
// })
// rootDnaId: string;
@Column({
nullable: true,
length: 40,
comment: "dna ของตาราง orgChild1",
default: null,
})
child1DnaId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "dna ของตาราง orgChild1",
// default: null,
// })
// child1DnaId: string;
@Column({
nullable: true,
length: 40,
comment: "dna ของตาราง orgChild2",
default: null,
})
child2DnaId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "dna ของตาราง orgChild2",
// default: null,
// })
// child2DnaId: string;
@Column({
nullable: true,
length: 40,
comment: "dna ของตาราง orgChild3",
default: null,
})
child3DnaId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "dna ของตาราง orgChild3",
// default: null,
// })
// child3DnaId: string;
@Column({
nullable: true,
length: 40,
comment: "dna ของตาราง orgChild4",
default: null,
})
child4DnaId: string;
// @Column({
// nullable: true,
// length: 40,
// comment: "dna ของตาราง orgChild4",
// default: null,
// })
// child4DnaId: string;
}

View file

@ -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;

View file

@ -128,6 +128,4 @@ export class viewDirectorActing {
key: string;
@ViewColumn()
positionSign: string;
@ViewColumn()
positionSignChild: string;
}

View file

@ -39,7 +39,7 @@ class CheckAuth {
}
});
}
public async PermissionOrg(req: RequestWithUser, system: string, action: string, isDirector?: boolean) {
public async PermissionOrg(req: RequestWithUser, system: string, action: string) {
if (
req.headers.hasOwnProperty("api_key") &&
req.headers["api_key"] &&
@ -56,7 +56,7 @@ class CheckAuth {
return await new CallAPI()
.GetData(req, `/org/permission/org/${system}/${action}`)
.then(async (x) => {
let privilege = isDirector && isDirector === true ? "CHILD" : x.privilege;
let privilege = x.privilege;
let data: any = {
root: [null],
@ -288,9 +288,6 @@ class CheckAuth {
public async PermissionOrgList(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "LIST");
}
public async PermissionIsDirectorOrgList(req: RequestWithUser, system: string, isDirector: boolean) {
return await this.PermissionOrg(req, system, "LIST", isDirector);
}
public async PermissionOrgUpdate(req: RequestWithUser, system: string) {
return await this.PermissionOrg(req, system, "UPDATE");
}

View file

@ -280,7 +280,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
await AppDataSource.getRepository(PosMaster)
.createQueryBuilder()
.update(PosMaster)
.set({ current_holderId: null, isSit: false })
.set({ current_holderId: null })
.where("id = :id", { id: findProfileInposMaster?.id })
.execute();
@ -293,7 +293,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
await AppDataSource.getRepository(PosMaster)
.createQueryBuilder()
.update(PosMaster)
.set({ next_holderId: null, isSit: false })
.set({ next_holderId: null })
.where("id = :id", { id: findProfileInposMasterDraft?.id })
.execute();
@ -326,7 +326,7 @@ export async function removeProfileInOrganize(profileId: string, type: string) {
await AppDataSource.getRepository(EmployeePosMaster)
.createQueryBuilder()
.update(EmployeePosMaster)
.set({ current_holderId: null, isSit: false })
.set({ current_holderId: null })
.where("id = :id", { id: findProfileInEmpPosMaster?.id })
.execute();
@ -395,6 +395,43 @@ export async function checkReturnCommandType(commandId: string) {
return true;
}
export async function checkExceptCommandType(commandId: string) {
const commandRepository = AppDataSource.getRepository(Command);
const commandReciveRepository = AppDataSource.getRepository(CommandRecive);
const _type = await commandRepository.findOne({
where: {
id: commandId,
},
relations: ["commandType"],
});
if (!["C-PM-25", "C-PM-26"].includes(String(_type?.commandType.code))) {
return { status: false, LeaveType: null, leaveRemark: null };
}
const _commandRecive = await commandReciveRepository.findOne({
where: { commandId: commandId },
});
let _leaveType: string = "";
switch (String(_type?.commandType.code)) {
case "C-PM-25": {
_leaveType = "DISCIPLINE_SUSPEND"; //คำสั่งพักจากราชการ
break;
}
case "C-PM-26": {
_leaveType = "DISCIPLINE_TEMP_SUSPEND"; //คำสั่งให้ออกจากราชการไว้ก่อน
break;
}
default: {
_leaveType = "";
}
}
return {
status: true,
LeaveType: _leaveType,
leaveRemark: _commandRecive ? _commandRecive.remarkVertical : null,
};
}
export async function checkCommandType(commandId: string) {
const commandRepository = AppDataSource.getRepository(Command);
const commandReciveRepository = AppDataSource.getRepository(CommandRecive);
@ -414,8 +451,6 @@ export async function checkCommandType(commandId: string) {
"C-PM-23",
"C-PM-19",
"C-PM-20",
"C-PM-25",
"C-PM-26",
"C-PM-43",
].includes(String(_type?.commandType.code))
) {
@ -465,16 +500,6 @@ export async function checkCommandType(commandId: string) {
_retireTypeName = "ลาออกจากราชการ";
break;
}
case "C-PM-25": {
_leaveType = "DISCIPLINE_SUSPEND";
_retireTypeName = "พักจากราชการ";
break;
}
case "C-PM-26": {
_leaveType = "DISCIPLINE_TEMP_SUSPEND";
_retireTypeName = "ให้ออกจากราชการไว้ก่อน";
break;
}
case "C-PM-43": {
_leaveType = "RETIRE_OUT_EMP";
_retireTypeName = "ให้ออกจากราชการ";
@ -727,22 +752,3 @@ export function resolveNodeId(data: any) {
null
);
}
export function logPositionIsSelectedChange(
positionId: string,
oldValue: boolean,
newValue: boolean,
context: {
posMasterId?: string;
userId?: string;
endpoint?: string;
action?: string;
}
) {
if (oldValue !== newValue) {
console.log(`[positionIsSelected-DEBUG] Position ${positionId}: ${oldValue} -> ${newValue}`, {
...context,
timestamp: new Date().toISOString(),
});
}
}

View file

@ -1019,9 +1019,7 @@ export async function resetPassword(username: string) {
if (!users.ok) {
const errorText = await users.text();
console.error(
`[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`,
);
console.error(`[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`);
return false;
}
@ -1049,9 +1047,7 @@ export async function resetPassword(username: string) {
if (!resetResponse.ok) {
const errorText = await resetResponse.text();
console.error(
`[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`,
);
console.error(`[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`);
return false;
}
@ -1121,7 +1117,7 @@ export async function updateUserAttributes(
return false;
}
// console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`);
console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`);
return true;
} catch (error) {
console.error(`[updateUserAttributes] Error updating attributes for user ${userId}:`, error);

View file

@ -4,7 +4,6 @@ import { createDecoder, createVerifier } from "fast-jwt";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
import { handleWebServiceAuth } from "./authWebService";
import { handleInternalAuth } from "./authInternal";
if (!process.env.AUTH_PUBLIC_KEY && !process.env.AUTH_REALM_URL) {
throw new Error("Require keycloak AUTH_PUBLIC_KEY or AUTH_REALM_URL.");
@ -40,11 +39,6 @@ export async function expressAuthentication(
return { preferred_username: "bypassed" };
}
// เพิ่มการจัดการสำหรับ Internal Authentication (.NET service)
if (securityName === "internalAuth") {
return await handleInternalAuth(request);
}
// เพิ่มการจัดการสำหรับ Web Service Authentication
if (securityName === "webServiceAuth") {
return await handleWebServiceAuth(request);

View file

@ -1,30 +0,0 @@
import * as express from "express";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
// Internal Authentication (สำหรับ Internal Service เช่น .NET)
// ตรวจสอบ API Key จาก Environment Variable (API_KEY)
export async function handleInternalAuth(request: express.Request) {
// รองรับ header หลายรูปแบบ
const apiKey =
request.headers["api-key"] || request.headers["api_key"] || request.headers["apikey"];
if (!apiKey || typeof apiKey !== "string") {
throw new HttpError(HttpStatus.UNAUTHORIZED, "API Key is required");
}
// ตรวจสอบ API Key จาก Environment Variable (API_KEY)
if (apiKey !== process.env.API_KEY) {
console.log(`[InternalAuth] Invalid API key attempt: ${apiKey.substring(0, 5)}...`);
throw new HttpError(HttpStatus.UNAUTHORIZED, "Invalid API Key");
}
// console.log(`[InternalAuth] Authentication successful`);
return {
sub: "internal_service",
preferred_username: "internal_service",
name: "Internal Service",
internalKey: true,
};
}

View file

@ -17,17 +17,7 @@ export async function handleWebServiceAuth(request: express.Request) {
// ตรวจสอบ API Key กับฐานข้อมูล
const apiKeyData = await AppDataSource.getRepository(ApiKey).findOne({
select: {
id: true,
name: true,
keyApi: true,
accessType: true,
dnaRootId: true,
dnaChild1Id: true,
dnaChild2Id: true,
dnaChild3Id: true,
dnaChild4Id: true,
},
select: { id: true, name: true, keyApi: true },
where: { keyApi: apiKey },
relations: ["apiNames"],
});
@ -50,12 +40,6 @@ export async function handleWebServiceAuth(request: express.Request) {
name: apiKeyData.name,
type: "web-service",
accessApi: apiKeyData.apiNames.map((x) => x.id) ?? [],
accessType: apiKeyData.accessType,
dnaRootId: apiKeyData.dnaRootId,
dnaChild1Id: apiKeyData.dnaChild1Id,
dnaChild2Id: apiKeyData.dnaChild2Id,
dnaChild3Id: apiKeyData.dnaChild3Id,
dnaChild4Id: apiKeyData.dnaChild4Id,
};
}

View file

@ -25,11 +25,5 @@ export type RequestWithUserWebService = Request & {
id: string;
name: string;
accessApi: string[];
accessType?: string;
dnaRootId?: string | null;
dnaChild1Id?: string | null;
dnaChild2Id?: string | null;
dnaChild3Id?: string | null;
dnaChild4Id?: string | null;
};
};

View file

@ -1,23 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UpdatePosMasterEmpHisAddDna1779244154610 implements MigrationInterface {
name = 'UpdatePosMasterEmpHisAddDna1779244154610'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`profileEmployeeId\` varchar(40) NULL COMMENT 'คีย์นอก(FK)ของตาราง profileEmployee'`);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`rootDnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgRoot'`);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child1DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild1'`);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child2DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild2'`);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child3DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild3'`);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` ADD \`child4DnaId\` varchar(40) NULL COMMENT 'dna ของตาราง orgChild4'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child4DnaId\``);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child3DnaId\``);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child2DnaId\``);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`child1DnaId\``);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`rootDnaId\``);
await queryRunner.query(`ALTER TABLE \`posMasterEmployeeHistory\` DROP COLUMN \`profileEmployeeId\``);
}
}

View file

@ -1,14 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UpdateCommandAddShortName1779776860350 implements MigrationInterface {
name = 'UpdateCommandAddShortName1779776860350'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`command\` ADD \`shortName\` varchar(16) NULL COMMENT 'ชื่อย่อหน่วยงานที่ออกคำสั่ง'`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`command\` DROP COLUMN \`shortName\``);
}
}

View file

@ -1,27 +0,0 @@
import "dotenv/config";
import "reflect-metadata";
import { AppDataSource } from "../database/data-source";
import { clearOldOrgRevisionData } from "../services/ClearOldOrgRevisionService";
// "clear:old-org-revision": "ts-node src/scripts/ClearOldOrgRevision.ts",
const defaultOrgRevisionId = "24dacf63-d289-496c-8102-8b25079dbaf2";
async function main(): Promise<void> {
const orgRevisionId = process.argv[2] || defaultOrgRevisionId;
try {
await AppDataSource.initialize();
const result = await clearOldOrgRevisionData(orgRevisionId);
console.info(JSON.stringify(result, null, 2));
} catch (error) {
console.error("[ClearOldOrgRevision] Failed:", error);
process.exitCode = 1;
} finally {
if (AppDataSource.isInitialized) {
await AppDataSource.destroy();
}
}
}
void main();

View file

@ -1,232 +0,0 @@
import { EntityManager, EntityTarget, In } from "typeorm";
import { AppDataSource } from "../database/data-source";
import { OrgRevision } from "../entities/OrgRevision";
import { PosMaster } from "../entities/PosMaster";
import { Position } from "../entities/Position";
import { OrgRoot } from "../entities/OrgRoot";
import { OrgChild1 } from "../entities/OrgChild1";
import { OrgChild2 } from "../entities/OrgChild2";
import { OrgChild3 } from "../entities/OrgChild3";
import { OrgChild4 } from "../entities/OrgChild4";
import { PosMasterAct } from "../entities/PosMasterAct";
import { PosMasterAssign } from "../entities/PosMasterAssign";
import { PermissionOrg } from "../entities/PermissionOrg";
import { PermissionProfile } from "../entities/PermissionProfile";
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster";
import { EmployeePosition } from "../entities/EmployeePosition";
import { orgStructureCache } from "../utils/OrgStructureCache";
export interface ClearOldOrgRevisionSummary {
orgRevisionId: string;
orgRevisionName: string;
deleted: {
positions: number;
employeePositionsByPosMaster: number;
employeePositionsByTempPosMaster: number;
posMasterActsByParent: number;
posMasterActsByChild: number;
posMasterAssigns: number;
posMasters: number;
employeePosMasters: number;
employeeTempPosMasters: number;
permissionOrgs: number;
permissionProfiles: number;
orgChild4s: number;
orgChild3s: number;
orgChild2s: number;
orgChild1s: number;
orgRoots: number;
orgRevisions: number;
};
}
interface OrgRevisionSnapshot {
id: string;
orgRevisionName: string;
orgRevisionIsCurrent: boolean;
orgRevisionIsDraft: boolean;
}
export async function clearOldOrgRevisionData(
orgRevisionId: string,
): Promise<ClearOldOrgRevisionSummary> {
const result = await AppDataSource.transaction(async (manager) => {
const orgRevision = await manager.findOne(OrgRevision, {
where: { id: orgRevisionId },
select: ["id", "orgRevisionName", "orgRevisionIsCurrent", "orgRevisionIsDraft"],
});
if (!orgRevision) {
throw new Error(`ไม่พบ orgRevision ที่ต้องการล้างข้อมูล: ${orgRevisionId}`);
}
validateOrgRevisionForDeletion(orgRevision);
const [posMasters, orgRoots, employeePosMasters, employeeTempPosMasters] = await Promise.all([
manager.find(PosMaster, {
where: { orgRevisionId },
select: ["id"],
}),
manager.find(OrgRoot, {
where: { orgRevisionId },
select: ["id"],
}),
manager.find(EmployeePosMaster, {
where: { orgRevisionId },
select: ["id"],
}),
manager.find(EmployeeTempPosMaster, {
where: { orgRevisionId },
select: ["id"],
}),
]);
const posMasterIds = posMasters.map((item) => item.id);
const orgRootIds = orgRoots.map((item) => item.id);
const employeePosMasterIds = employeePosMasters.map((item) => item.id);
const employeeTempPosMasterIds = employeeTempPosMasters.map((item) => item.id);
const [
positionsCount,
employeePositionsByPosMasterCount,
employeePositionsByTempPosMasterCount,
posMasterActsByParentCount,
posMasterActsByChildCount,
posMasterAssignsCount,
permissionOrgsCount,
permissionProfilesCount,
orgChild4sCount,
orgChild3sCount,
orgChild2sCount,
orgChild1sCount,
] = await Promise.all([
countByIds(manager, Position, "posMasterId", posMasterIds),
countByIds(manager, EmployeePosition, "posMasterId", employeePosMasterIds),
countByIds(manager, EmployeePosition, "posMasterTempId", employeeTempPosMasterIds),
countByIds(manager, PosMasterAct, "posMasterId", posMasterIds),
countByIds(manager, PosMasterAct, "posMasterChildId", posMasterIds),
countByIds(manager, PosMasterAssign, "posMasterId", posMasterIds),
countByIds(manager, PermissionOrg, "orgRootId", orgRootIds),
countByIds(manager, PermissionProfile, "orgRootId", orgRootIds),
manager.count(OrgChild4, { where: { orgRevisionId } }),
manager.count(OrgChild3, { where: { orgRevisionId } }),
manager.count(OrgChild2, { where: { orgRevisionId } }),
manager.count(OrgChild1, { where: { orgRevisionId } }),
]);
if (positionsCount > 0) {
await manager.delete(Position, { posMasterId: In(posMasterIds) });
}
if (employeePositionsByPosMasterCount > 0) {
await manager.delete(EmployeePosition, { posMasterId: In(employeePosMasterIds) });
}
if (employeePositionsByTempPosMasterCount > 0) {
await manager.delete(EmployeePosition, { posMasterTempId: In(employeeTempPosMasterIds) });
}
if (posMasterActsByParentCount > 0) {
await manager.delete(PosMasterAct, { posMasterId: In(posMasterIds) });
}
if (posMasterActsByChildCount > 0) {
await manager.delete(PosMasterAct, { posMasterChildId: In(posMasterIds) });
}
if (posMasterAssignsCount > 0) {
await manager.delete(PosMasterAssign, { posMasterId: In(posMasterIds) });
}
const posMastersCount = posMasterIds.length;
const employeePosMastersCount = employeePosMasterIds.length;
const employeeTempPosMastersCount = employeeTempPosMasterIds.length;
if (posMastersCount > 0) {
await manager.delete(PosMaster, { orgRevisionId });
}
if (employeePosMastersCount > 0) {
await manager.delete(EmployeePosMaster, { orgRevisionId });
}
if (employeeTempPosMastersCount > 0) {
await manager.delete(EmployeeTempPosMaster, { orgRevisionId });
}
if (permissionOrgsCount > 0) {
await manager.delete(PermissionOrg, { orgRootId: In(orgRootIds) });
}
if (permissionProfilesCount > 0) {
await manager.delete(PermissionProfile, { orgRootId: In(orgRootIds) });
}
if (orgChild4sCount > 0) {
await manager.delete(OrgChild4, { orgRevisionId });
}
if (orgChild3sCount > 0) {
await manager.delete(OrgChild3, { orgRevisionId });
}
if (orgChild2sCount > 0) {
await manager.delete(OrgChild2, { orgRevisionId });
}
if (orgChild1sCount > 0) {
await manager.delete(OrgChild1, { orgRevisionId });
}
const orgRootsCount = orgRootIds.length;
if (orgRootsCount > 0) {
await manager.delete(OrgRoot, { orgRevisionId });
}
await manager.delete(OrgRevision, { id: orgRevisionId });
return {
orgRevisionId: orgRevision.id,
orgRevisionName: orgRevision.orgRevisionName,
deleted: {
positions: positionsCount,
employeePositionsByPosMaster: employeePositionsByPosMasterCount,
employeePositionsByTempPosMaster: employeePositionsByTempPosMasterCount,
posMasterActsByParent: posMasterActsByParentCount,
posMasterActsByChild: posMasterActsByChildCount,
posMasterAssigns: posMasterAssignsCount,
posMasters: posMastersCount,
employeePosMasters: employeePosMastersCount,
employeeTempPosMasters: employeeTempPosMastersCount,
permissionOrgs: permissionOrgsCount,
permissionProfiles: permissionProfilesCount,
orgChild4s: orgChild4sCount,
orgChild3s: orgChild3sCount,
orgChild2s: orgChild2sCount,
orgChild1s: orgChild1sCount,
orgRoots: orgRootsCount,
orgRevisions: 1,
},
};
});
orgStructureCache.invalidate(orgRevisionId);
return result;
}
function validateOrgRevisionForDeletion(orgRevision: OrgRevisionSnapshot): void {
if (orgRevision.orgRevisionIsCurrent) {
throw new Error(`ไม่สามารถลบ orgRevision ปัจจุบันได้: ${orgRevision.id}`);
}
if (orgRevision.orgRevisionIsDraft) {
throw new Error(`ไม่สามารถลบ orgRevision แบบร่างได้ด้วยสคริปต์นี้: ${orgRevision.id}`);
}
}
async function countByIds<Entity extends object>(
manager: EntityManager,
entity: EntityTarget<Entity>,
field: keyof Entity,
ids: string[],
): Promise<number> {
if (ids.length === 0) {
return 0;
}
const alias = "entity";
return manager
.createQueryBuilder(entity, alias)
.where(`${alias}.${String(field)} IN (:...ids)`, { ids })
.getCount();
}

View file

@ -530,20 +530,18 @@ export class KeycloakAttributeService {
// Initialize rate limiter if rate limiting is enabled
if (rateLimit && rateLimit > 0) {
rateLimiter = new RateLimiter(rateLimit);
console.log(
`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`,
);
console.log(`[syncMissingEmpTypeByMonth] Rate limiting enabled: ${rateLimit} requests/second`);
}
// Select repository based on profile type
const repo = profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo;
const repo =
profileType === "PROFILE" ? this.profileRepo : this.profileEmployeeRepo;
// Query profiles updated within the month
const profiles = await repo
.createQueryBuilder("p")
.where("p.keycloak IS NOT NULL")
.andWhere("p.keycloak != :empty", { empty: "" })
.andWhere("p.isDelete = :isDelete", { isDelete: false })
.andWhere("p.lastUpdatedAt BETWEEN :start AND :end", {
start: startDate,
end: endDate,
@ -581,7 +579,8 @@ export class KeycloakAttributeService {
try {
// Check if empType is empty in Keycloak
const { isEmpty, currentEmpType } = await this.checkEmpTypeEmpty(keycloakUserId);
const { isEmpty, currentEmpType } =
await this.checkEmpTypeEmpty(keycloakUserId);
result.profilesChecked++;
@ -608,7 +607,8 @@ export class KeycloakAttributeService {
// Sync the profile
const success = await withRetry(
async () => this.syncOnOrganizationChange(profile.id, profileType),
async () =>
this.syncOnOrganizationChange(profile.id, profileType),
3, // maxRetries
1000, // baseDelay
);
@ -768,13 +768,7 @@ export class KeycloakAttributeService {
maxRetries?: number; // Retry attempts for failed operations
rateLimit?: number; // Requests per second
clearProgress?: boolean; // Start fresh, ignore existing progress
}): Promise<{
total: number;
success: number;
failed: number;
details: any[];
resumed?: boolean;
}> {
}): Promise<{ total: number; success: number; failed: number; details: any[]; resumed?: boolean }> {
const limit = options?.limit;
const concurrency = options?.concurrency ?? 5;
const resume = options?.resume ?? false;
@ -928,10 +922,7 @@ export class KeycloakAttributeService {
// Save progress after each batch
SyncProgressManager.save(updatedState);
// Log progress every 50 items
if (
updatedState.lastSyncedIndex % 50 === 0 ||
updatedState.lastSyncedIndex === updatedState.totalProfiles
) {
if (updatedState.lastSyncedIndex % 50 === 0 || updatedState.lastSyncedIndex === updatedState.totalProfiles) {
SyncProgressManager.logProgress(updatedState);
}
},

View file

@ -188,7 +188,6 @@ export async function CreatePosMasterHistoryOfficer(
return true;
} catch (err) {
if (manager) {
console.error("CreatePosMasterHistoryOfficer error (external transaction):", err);
throw err;
}
console.error("CreatePosMasterHistoryOfficer transaction error:", err);
@ -231,7 +230,6 @@ export async function CreatePosMasterHistoryEmployee(
: null;
h.ancestorDNA = pm.ancestorDNA;
if (!type || type != "DELETE") {
h.profileEmployeeId = pm.current_holder?.id || _null;
h.prefix = pm.current_holder?.prefix || _null;
h.firstName = pm.current_holder?.firstName || _null;
h.lastName = pm.current_holder?.lastName || _null;
@ -239,11 +237,6 @@ export async function CreatePosMasterHistoryEmployee(
h.posType = selectedPosition?.posType?.posTypeName ?? _null;
h.posLevel = selectedPosition?.posLevel?.posLevelName ?? _null;
}
h.rootDnaId = pm.orgRoot?.ancestorDNA || _null;
h.child1DnaId = pm.orgChild1?.ancestorDNA || _null;
h.child2DnaId = pm.orgChild2?.ancestorDNA || _null;
h.child3DnaId = pm.orgChild3?.ancestorDNA || _null;
h.child4DnaId = pm.orgChild4?.ancestorDNA || _null;
h.posMasterNoPrefix = pm.posMasterNoPrefix ?? _null;
h.posMasterNo = pm.posMasterNo ?? _null;
h.posMasterNoSuffix = pm.posMasterNoSuffix ?? _null;
@ -501,61 +494,3 @@ export async function BatchSavePosMasterHistoryOfficer(
return false;
}
}
/**
* profile
* -
* profile
*
* @param profileId ID profile
* @param request RequestWithUser
* @param type "OFFICER" | "EMPLOYEE" (default: "OFFICER")
*/
export async function updateHolderProfileHistory(
profileId: string,
request: RequestWithUser,
type: "OFFICER" | "EMPLOYEE" = "OFFICER",
): Promise<void> {
try {
if (type === "OFFICER") {
const posMasterRepo = AppDataSource.getRepository(PosMaster);
const posMaster = await posMasterRepo.findOne({
where: {
current_holderId: profileId,
orgRevision: {
orgRevisionIsCurrent: true,
orgRevisionIsDraft: false,
}
},
relations: {
orgRevision : true
}
});
if (posMaster) {
await CreatePosMasterHistoryOfficer(posMaster.id, request);
}
} else if (type === "EMPLOYEE") {
const empPosMasterRepo = AppDataSource.getRepository(EmployeePosMaster);
const employeePosMaster = await empPosMasterRepo.findOne({
where: {
current_holderId: profileId,
orgRevision: {
orgRevisionIsCurrent: true,
orgRevisionIsDraft: false,
}
},
relations: {
orgRevision : true
}
});
if (employeePosMaster) {
await CreatePosMasterHistoryEmployee(employeePosMaster.id, request);
}
}
} catch (error) {
console.error("updateHolderProfileHistory error:", error);
throw error;
}
}

View file

@ -1,13 +1,12 @@
import { AppDataSource } from "../database/data-source";
import { Profile } from "./../entities/Profile";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import { ProfileSalary } from "./../entities/ProfileSalary";
import { OrgRoot } from "../entities/OrgRoot";
import { OrgChild1 } from "../entities/OrgChild1";
import { OrgChild2 } from "../entities/OrgChild2";
import { OrgChild3 } from "../entities/OrgChild3";
import { OrgChild4 } from "../entities/OrgChild4";
import { Brackets, In, Repository } from "typeorm";
import { Brackets, Repository } from "typeorm";
import Extension from "../interfaces/extension";
import { RequestWithUser } from "../middlewares/user";
@ -63,7 +62,6 @@ interface OrgParentName {
export class ProfileLeaveService {
private profileEmployeeRepo: Repository<ProfileEmployee>;
private profileRepo: Repository<Profile>;
private profileSalaryRepo: Repository<ProfileSalary>;
private orgRootRepository: Repository<OrgRoot>;
private child1Repository: Repository<OrgChild1>;
private child2Repository: Repository<OrgChild2>;
@ -74,7 +72,6 @@ export class ProfileLeaveService {
constructor() {
this.profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
this.profileRepo = AppDataSource.getRepository(Profile);
this.profileSalaryRepo = AppDataSource.getRepository(ProfileSalary);
this.orgRootRepository = AppDataSource.getRepository(OrgRoot);
this.child1Repository = AppDataSource.getRepository(OrgChild1);
this.child2Repository = AppDataSource.getRepository(OrgChild2);
@ -210,9 +207,10 @@ export class ProfileLeaveService {
let params: NodeParams = {};
const orgLists = await this.findOrgNodeParentAll(node, nodeId);
for (let index = 0; index <= node; index++) {
const config = this.nodeConfigs[index];
console.log("Org Hierarchy for Node Condition:", orgLists);
await Promise.all(
this.nodeConfigs.map(async (config, index) => {
if (index <= node) {
const orgName = orgLists[config.nameField as keyof OrgParentName] || null;
if (orgName) {
nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition;
@ -220,6 +218,8 @@ export class ProfileLeaveService {
params[config.paramKey] = orgName;
}
}
}),
);
return {
condition: nodeCondition,
@ -234,31 +234,53 @@ export class ProfileLeaveService {
child3: string | null;
child4: string | null;
}): Promise<OrgParentName> {
const [rootName, child1, child2, child3, child4] = await Promise.all([
orgIds.root
? this.orgRootRepository.findOne({ where: { id: orgIds.root }, select: ["orgRootName"] })
: Promise.resolve(null),
orgIds.child1
? this.child1Repository.findOne({ where: { id: orgIds.child1 }, select: ["orgChild1Name"] })
: Promise.resolve(null),
orgIds.child2
? this.child2Repository.findOne({ where: { id: orgIds.child2 }, select: ["orgChild2Name"] })
: Promise.resolve(null),
orgIds.child3
? this.child3Repository.findOne({ where: { id: orgIds.child3 }, select: ["orgChild3Name"] })
: Promise.resolve(null),
orgIds.child4
? this.child4Repository.findOne({ where: { id: orgIds.child4 }, select: ["orgChild4Name"] })
: Promise.resolve(null),
]);
return {
orgRootName: rootName?.orgRootName ?? null,
orgChild1Name: child1?.orgChild1Name ?? null,
orgChild2Name: child2?.orgChild2Name ?? null,
orgChild3Name: child3?.orgChild3Name ?? null,
orgChild4Name: child4?.orgChild4Name ?? null,
const orgNames: OrgParentName = {
orgRootName: null,
orgChild1Name: null,
orgChild2Name: null,
orgChild3Name: null,
orgChild4Name: null,
};
if (orgIds.root) {
const rootName = await this.orgRootRepository.findOne({
where: { id: orgIds.root },
select: ["orgRootName"],
});
orgNames.orgRootName = rootName ? rootName.orgRootName : null;
}
if (orgIds.child1) {
const child1 = await this.child1Repository.findOne({
where: { id: orgIds.child1 },
select: ["orgChild1Name"],
});
orgNames.orgChild1Name = child1 ? child1.orgChild1Name : null;
}
if (orgIds.child2) {
const child2 = await this.child2Repository.findOne({
where: { id: orgIds.child2 },
select: ["orgChild2Name"],
});
orgNames.orgChild2Name = child2 ? child2.orgChild2Name : null;
}
if (orgIds.child3) {
const child3 = await this.child3Repository.findOne({
where: { id: orgIds.child3 },
select: ["orgChild3Name"],
});
orgNames.orgChild3Name = child3 ? child3.orgChild3Name : null;
}
if (orgIds.child4) {
const child4 = await this.child4Repository.findOne({
where: { id: orgIds.child4 },
select: ["orgChild4Name"],
});
orgNames.orgChild4Name = child4 ? child4.orgChild4Name : null;
}
return orgNames;
}
/** สร้างเงื่อนไขการค้นหาตาม node และ nodeId และเช็คกับ permission */
@ -295,15 +317,16 @@ export class ProfileLeaveService {
return { condition: "1=0", params: {} }; // no access
}
for (let index = 0; index < this.nodeConfigs.length; index++) {
const config = this.nodeConfigs[index];
await Promise.all(
this.nodeConfigs.map(async (config, index) => {
const orgName = orgLists[config.nameField as keyof OrgParentName] || null;
if (orgName) {
nodeCondition += index > 0 ? ` AND ${config.condition}` : config.condition;
nodeCondition += isAll === false && config.isAllTrue ? ` AND ${config.isAllTrue}` : "";
params[config.paramKey] = orgName;
}
}
}),
);
return {
condition: nodeCondition,
@ -455,14 +478,33 @@ export class ProfileLeaveService {
_data,
} = filter;
const t0 = Date.now();
const searchQuery = this.buildSearchQuery(searchField, "profileEmployee");
// สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query
const baseWhere = (qb: any) => {
qb.where(
new Brackets((qb2) => {
qb2.where("profileEmployee.isLeave = :isLeave", { isLeave: true }).orWhere(
// สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary
const queryBuilder = this.profileEmployeeRepo
.createQueryBuilder("profileEmployee")
.leftJoinAndSelect("profileEmployee.posLevel", "posLevel")
.leftJoinAndSelect("profileEmployee.posType", "posType")
.leftJoinAndSelect("profileEmployee.profileEmployeeEmployment", "profileEmployeeEmployment")
.leftJoin(
"profileEmployee.profileSalary",
"profileSalary",
"profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id and ps.positionName != 'เกษียณอายุราชการ')",
)
.addSelect([
"profileSalary.id",
"profileSalary.order",
"profileSalary.posNo",
"profileSalary.posNoAbb",
"profileSalary.orgRoot",
"profileSalary.orgChild1",
"profileSalary.orgChild2",
"profileSalary.orgChild3",
"profileSalary.orgChild4",
])
.where(
new Brackets((qb) => {
qb.where("profileEmployee.isLeave = :isLeave", { isLeave: true }).orWhere(
"profileEmployee.isRetirement = :isRetirement",
{ isRetirement: true },
);
@ -470,131 +512,63 @@ export class ProfileLeaveService {
)
.andWhere("profileEmployee.employeeClass LIKE :type", { type: "PERM" })
.andWhere(
new Brackets((qb2) => {
qb2.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
new Brackets((qb) => {
qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
keyword: `%${searchKeyword}%`,
});
}),
);
// เพิ่มเงื่อนไขการค้นหา
if (posType) {
qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
}
if (posLevel) {
qb.andWhere(
queryBuilder.andWhere(
"CONCAT(posType.posTypeShortName, ' ', posLevel.posLevelName) LIKE :keyword2",
{ keyword2: `${posLevel}` },
);
}
if (isProbation !== undefined && isProbation !== null) {
qb.andWhere("profileEmployee.isProbation = :isProbation", { isProbation });
if (isProbation) {
queryBuilder.andWhere(`profileEmployee.isProbation = ${isProbation}`);
}
if (retireType) {
qb.andWhere("profileEmployee.leaveType = :retireType", { retireType });
queryBuilder.andWhere("profileEmployee.leaveType = :retireType", { retireType });
}
};
// Compute permission/node conditions เพียงครั้งเดียว
const conditions: { condition: string; params: Record<string, any> }[] = [];
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
conditions.push(await this.buildPermissionCondition(_data, isAll));
}
if (node !== null && node !== undefined && nodeId) {
conditions.push(await this.buildNodeCondition(node, nodeId, isAll));
}
const applyConditions = (qb: any) => {
for (const cond of conditions) {
qb.andWhere(cond.condition, cond.params);
}
};
const [nodeCondition, permissionCondition] = await Promise.all([
this.buildNodeCondition(node, nodeId, isAll),
this.buildPermissionCondition(_data, isAll),
]);
// console.log("Permission Condition:", permissionCondition);
// console.log("Node Condition:", nodeCondition);
// console.log(`[ProfileLeaveService] getLeaveEmployees conditions took ${Date.now() - t0}ms`);
queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params);
// สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2)
const applySalaryFilter = (qb: any) => {
if (conditions.length > 0) {
let existsCond = "profileSalary.positionName != :notRetire";
const existsParams: Record<string, any> = { notRetire: "เกษียณอายุราชการ" };
for (const cond of conditions) {
existsCond += ` AND ${cond.condition}`;
Object.assign(existsParams, cond.params);
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params);
}
qb.andWhere(
`EXISTS (SELECT 1 FROM profileSalary WHERE profileEmployeeId = profileEmployee.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileEmployeeId = profileEmployee.id AND ps.positionName != :notRetire2))`,
{ ...existsParams, notRetire2: "เกษียณอายุราชการ" }
}
// เพิ่ม sorting และ pagination
queryBuilder
.orderBy(sortBy, sort)
.skip((page - 1) * pageSize)
.take(pageSize);
const [records, total] = await queryBuilder.getManyAndCount();
// print query for debug
// console.log("SQL Query:", queryBuilder.getSql());
const data = await Promise.all(
records.map((record) => Promise.resolve(this.transformEmployeeData(record))),
);
}
};
// Step 1: Count query
const countQb = this.profileEmployeeRepo
.createQueryBuilder("profileEmployee")
.leftJoinAndSelect("profileEmployee.posLevel", "posLevel")
.leftJoinAndSelect("profileEmployee.posType", "posType");
baseWhere(countQb);
applySalaryFilter(countQb);
const total = await countQb.getCount();
// console.log(`[ProfileLeaveService] getLeaveEmployees count took ${Date.now() - t0}ms, total=${total}`);
// Step 2: ดึงเฉพาะ profileEmployee IDs ที่ผ่านเงื่อนไข
const idQb = this.profileEmployeeRepo
.createQueryBuilder("profileEmployee")
.select(["profileEmployee.id"])
.leftJoin("profileEmployee.posLevel", "posLevel")
.leftJoin("profileEmployee.posType", "posType");
baseWhere(idQb);
applySalaryFilter(idQb);
idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize);
const rawIds = await idQb.getRawMany();
const employeeIds = rawIds.map((r) => r.profileEmployee_id);
// console.log(`[ProfileLeaveService] getLeaveEmployees ids took ${Date.now() - t0}ms, ids=${employeeIds.length}`);
if (employeeIds.length === 0) {
return { data: [], total };
}
// Step 3: Load full data โดยไม่ JOIN salary
const records = await this.profileEmployeeRepo.find({
where: { id: In(employeeIds) },
relations: ["posLevel", "posType", "profileEmployeeEmployment"],
order: { [sortBy.split(".")[1]]: sort } as any,
});
// Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileEmployeeId (INNER JOIN + GROUP BY)
const salaries = await this.profileSalaryRepo
.createQueryBuilder("ps")
.innerJoin(
(subQuery) =>
subQuery
.select("ps2.profileEmployeeId", "pid")
.addSelect("MAX(ps2.order)", "maxOrd")
.from(ProfileSalary, "ps2")
.where("ps2.profileEmployeeId IN (:...employeeIds)", { employeeIds })
.andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" })
.groupBy("ps2.profileEmployeeId"),
"latest",
"latest.pid = ps.profileEmployeeId AND ps.order = latest.maxOrd"
)
.getMany();
// สร้าง map: profileEmployeeId → salary ที่มี order สูงสุด
const salaryMap = new Map<string, ProfileSalary>();
for (const s of salaries) {
salaryMap.set(s.profileEmployeeId, s);
}
// แปลงข้อมูลพร้อม salary
const data = records.map((record) => {
const salary = salaryMap.get(record.id);
if (salary) {
(record as any).profileSalary = [salary];
}
return this.transformEmployeeData(record);
});
// console.log(`[ProfileLeaveService] getLeaveEmployees total took ${Date.now() - t0}ms, total=${total}`);
return { data, total };
}
@ -675,143 +649,94 @@ export class ProfileLeaveService {
_data,
} = filter;
const t0 = Date.now();
const searchQuery = this.buildSearchQuery(searchField);
// สร้าง base WHERE conditions แชร์ระหว่าง count/id/data query
const baseWhere = (qb: any) => {
qb.where(
new Brackets((qb2) => {
qb2.where("profile.isLeave = :isLeave", { isLeave: true }).orWhere(
// สร้าง main query - เปลี่ยนจาก leftJoinAndSelect เป็น leftJoin สำหรับ profileSalary
const queryBuilder = this.profileRepo
.createQueryBuilder("profile")
.leftJoinAndSelect("profile.posLevel", "posLevel")
.leftJoinAndSelect("profile.posType", "posType")
.leftJoin(
"profile.profileSalary",
"profileSalary",
"profileSalary.order = (SELECT MAX(ps.order) FROM profileSalary ps WHERE ps.profileId = profile.id and ps.positionName != 'เกษียณอายุราชการ')",
)
.addSelect([
"profileSalary.id",
"profileSalary.order",
"profileSalary.posNo",
"profileSalary.posNoAbb",
"profileSalary.orgRoot",
"profileSalary.orgChild1",
"profileSalary.orgChild2",
"profileSalary.orgChild3",
"profileSalary.orgChild4",
"profileSalary.positionExecutive",
])
.where(
new Brackets((qb) => {
qb.where("profile.isLeave = :isLeave", { isLeave: true }).orWhere(
"profile.isRetirement = :isRetirement",
{ isRetirement: true },
);
}),
).andWhere(
new Brackets((qb2) => {
qb2.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
)
.andWhere(
new Brackets((qb) => {
qb.orWhere(searchKeyword && searchKeyword != "" ? searchQuery : "1=1", {
keyword: `%${searchKeyword}%`,
});
}),
);
if (posType) {
qb.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
queryBuilder.andWhere("posType.posTypeName LIKE :keyword1", { keyword1: `${posType}` });
}
if (posLevel) {
qb.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` });
queryBuilder.andWhere("posLevel.posLevelName LIKE :keyword2", { keyword2: `${posLevel}` });
}
if (isProbation !== undefined && isProbation !== null) {
qb.andWhere("profile.isProbation = :isProbation", { isProbation });
if (isProbation) {
queryBuilder.andWhere(`profile.isProbation = ${isProbation}`);
}
if (retireType) {
qb.andWhere("profile.leaveType = :retireType", { retireType });
queryBuilder.andWhere("profile.leaveType = :retireType", { retireType });
}
};
// Compute permission/node conditions เพียงครั้งเดียว
const conditions: { condition: string; params: Record<string, any> }[] = [];
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
conditions.push(await this.buildPermissionCondition(_data, isAll));
}
// เพิ่ม permission และ node conditions
if (node !== null && node !== undefined && nodeId) {
conditions.push(await this.buildNodeCondition(node, nodeId, isAll));
}
const applyConditions = (qb: any) => {
for (const cond of conditions) {
qb.andWhere(cond.condition, cond.params);
}
};
// สร้าง query conditions แบบ parallel
const [nodeCondition, permissionCondition] = await Promise.all([
this.buildNodeCondition(node, nodeId, isAll),
this.buildPermissionCondition(_data, isAll),
]);
console.log("Permission Condition:", permissionCondition);
console.log("Node Condition:", nodeCondition);
// console.log(`[ProfileLeaveService] getLeaveOfficer conditions took ${Date.now() - t0}ms`);
queryBuilder.andWhere(nodeCondition.condition, nodeCondition.params);
// สร้าง salary EXISTS filter (ใช้ซ้ำทั้ง step1, step2)
const applySalaryFilter = (qb: any) => {
if (conditions.length > 0) {
let existsCond = "profileSalary.positionName != :notRetire";
const existsParams: Record<string, any> = { notRetire: "เกษียณอายุราชการ" };
for (const cond of conditions) {
existsCond += ` AND ${cond.condition}`;
Object.assign(existsParams, cond.params);
if (_data.privilege !== "OWNER" && _data.privilege !== "PARENT") {
queryBuilder.andWhere(permissionCondition.condition, permissionCondition.params);
}
qb.andWhere(
`EXISTS (SELECT 1 FROM profileSalary WHERE profileId = profile.id AND ${existsCond} AND profileSalary.\`order\` = (SELECT MAX(ps.\`order\`) FROM profileSalary ps WHERE ps.profileId = profile.id AND ps.positionName != :notRetire2))`,
{ ...existsParams, notRetire2: "เกษียณอายุราชการ" }
}
// เพิ่ม sorting และ pagination
queryBuilder
.orderBy(sortBy, sort)
.skip((page - 1) * pageSize)
.take(pageSize);
const [records, total] = await queryBuilder.getManyAndCount();
// print query for debug
// console.log("SQL Query:", queryBuilder.getSql());
const data = await Promise.all(
records.map((record) => Promise.resolve(this.transformOfficerData(record))),
);
}
};
// Step 1: Count query
const countQb = this.profileRepo
.createQueryBuilder("profile")
.leftJoinAndSelect("profile.posLevel", "posLevel")
.leftJoinAndSelect("profile.posType", "posType");
baseWhere(countQb);
applySalaryFilter(countQb);
const total = await countQb.getCount();
// console.log(`[ProfileLeaveService] getLeaveOfficer count took ${Date.now() - t0}ms, total=${total}`);
// Step 2: ดึงเฉพาะ profile IDs ที่ผ่านเงื่อนไข
const idQb = this.profileRepo
.createQueryBuilder("profile")
.select(["profile.id"])
.leftJoin("profile.posLevel", "posLevel")
.leftJoin("profile.posType", "posType");
baseWhere(idQb);
applySalaryFilter(idQb);
idQb.orderBy(sortBy, sort).skip((page - 1) * pageSize).take(pageSize);
const rawIds = await idQb.getRawMany();
const profileIds = rawIds.map((r) => r.profile_id);
// console.log(`[ProfileLeaveService] getLeaveOfficer ids took ${Date.now() - t0}ms, ids=${profileIds.length}`);
if (profileIds.length === 0) {
return { data: [], total };
}
// Step 3: Load full data โดยไม่ JOIN salary
const records = await this.profileRepo.find({
where: { id: In(profileIds) },
relations: ["posLevel", "posType"],
order: { [sortBy.split(".")[1]]: sort } as any,
});
// console.log(`[ProfileLeaveService] getLeaveOfficer step3 (load profiles) took ${Date.now() - t0}ms`);
// Step 4: Load salary เฉพาะ row ที่มี order สูงสุดต่อ profileId (INNER JOIN + GROUP BY)
const salaries = await this.profileSalaryRepo
.createQueryBuilder("ps")
.innerJoin(
(subQuery) =>
subQuery
.select("ps2.profileId", "pid")
.addSelect("MAX(ps2.order)", "maxOrd")
.from(ProfileSalary, "ps2")
.where("ps2.profileId IN (:...profileIds)", { profileIds })
.andWhere("ps2.positionName != :notRetire", { notRetire: "เกษียณอายุราชการ" })
.groupBy("ps2.profileId"),
"latest",
"latest.pid = ps.profileId AND ps.order = latest.maxOrd"
)
.getMany();
// console.log(`[ProfileLeaveService] getLeaveOfficer step4 (load salaries) took ${Date.now() - t0}ms, salary rows=${salaries.length}`);
// สร้าง map: profileId → salary ที่มี order สูงสุด
const salaryMap = new Map<string, ProfileSalary>();
for (const s of salaries) {
salaryMap.set(s.profileId, s);
}
// แปลงข้อมูลพร้อม salary
const data = records.map((record) => {
const salary = salaryMap.get(record.id);
if (salary) {
(record as any).profileSalary = [salary];
}
return this.transformOfficerData(record);
});
// console.log(`[ProfileLeaveService] getLeaveOfficer total took ${Date.now() - t0}ms, total=${total}`);
return { data, total };
}
}

View file

@ -1,6 +1,5 @@
import { randomUUID } from "crypto";
import amqp from "amqplib";
import { promisify } from "util";
import { AppDataSource } from "../database/data-source";
import { Command } from "../entities/Command";
import { chunkArray, commandTypePath } from "../interfaces/utils";
@ -30,10 +29,6 @@ import { PayloadSendNoti } from "../interfaces/utils";
import { PermissionProfile } from "../entities/PermissionProfile";
import { PosMasterHistory } from "../entities/PosMasterHistory";
const redis = require("redis");
const REDIS_HOST = process.env.REDIS_HOST;
const REDIS_PORT = process.env.REDIS_PORT;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
function scheduleReconnect() {
@ -148,9 +143,7 @@ function createConsumer( //----> consumer
console.log("[AMQ] Process Consumer success");
return channel.ack(msg);
}
console.error(
`[AMQ] Process Consumer failed on queue ${queue}, acknowledging without retry`,
);
console.error(`[AMQ] Process Consumer failed on queue ${queue}, acknowledging without retry`);
return channel.ack(msg);
} catch (error) {
console.error(`[AMQ] Consumer processing error on queue ${queue}:`, error);
@ -554,19 +547,19 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
const repoPosmaster = AppDataSource.getRepository(PosMaster);
const posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign);
const posMasterActRepository = AppDataSource.getRepository(PosMasterAct);
// const permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile);
// const repoEmployeePosmaster = AppDataSource.getRepository(EmployeePosMaster);
// const repoEmployeeTempPosmaster = AppDataSource.getRepository(EmployeeTempPosMaster);
const permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile);
const repoEmployeePosmaster = AppDataSource.getRepository(EmployeePosMaster);
const repoEmployeeTempPosmaster = AppDataSource.getRepository(EmployeeTempPosMaster);
const repoProfile = AppDataSource.getRepository(Profile);
// const repoProfileEmployee = AppDataSource.getRepository(ProfileEmployee);
// const employeePositionRepository = AppDataSource.getRepository(EmployeePosition);
const repoProfileEmployee = AppDataSource.getRepository(ProfileEmployee);
const employeePositionRepository = AppDataSource.getRepository(EmployeePosition);
const repoOrgRevision = AppDataSource.getRepository(OrgRevision);
// const orgRootRepository = AppDataSource.getRepository(OrgRoot);
// const child1Repository = AppDataSource.getRepository(OrgChild1);
// const child2Repository = AppDataSource.getRepository(OrgChild2);
// const child3Repository = AppDataSource.getRepository(OrgChild3);
// const child4Repository = AppDataSource.getRepository(OrgChild4);
const { data, user } = JSON.parse(msg.content.toString());
const orgRootRepository = AppDataSource.getRepository(OrgRoot);
const child1Repository = AppDataSource.getRepository(OrgChild1);
const child2Repository = AppDataSource.getRepository(OrgChild2);
const child3Repository = AppDataSource.getRepository(OrgChild3);
const child4Repository = AppDataSource.getRepository(OrgChild4);
const { data, token, user } = JSON.parse(msg.content.toString());
const { id, status, lastUpdateUserId, lastUpdateFullName, lastUpdatedAt } = data;
console.log(`[AMQ] Received message - revisionId: ${id}, status: ${status}`);
@ -1001,7 +994,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
if (posMasterUpdates.length > 0) {
const chunks = chunkArray(posMasterUpdates, 500);
const posMasterTableName = repoPosmaster.metadata.tableName;
for (const chunk of chunks as (typeof posMasterUpdates)[]) {
for (const chunk of chunks as typeof posMasterUpdates[]) {
const caseClauses = chunk.map(() => "WHEN ? THEN ?").join(" ");
const wherePlaceholders = chunk.map(() => "?").join(", ");
const params = chunk.flatMap((update: (typeof posMasterUpdates)[number]) => [
@ -1214,15 +1207,13 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
? x.id
: x.ancestorDNA,
}));
const _orgemployeeTempPosMaster: EmployeeTempPosMaster[] = orgemployeeTempPosMaster.map(
(x) => ({
const _orgemployeeTempPosMaster: EmployeeTempPosMaster[] = orgemployeeTempPosMaster.map((x) => ({
...x,
ancestorDNA:
x.ancestorDNA == null || x.ancestorDNA == "00000000-0000-0000-0000-000000000000"
? x.id
: x.ancestorDNA,
}),
);
}));
console.time("[AMQ] insert_employeePosMaster");
await repoEmployeePosmaster
@ -1325,10 +1316,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
lastUpdateFullName: "System Administrator",
lastUpdatedAt: timestamp,
});
const buildColumnData = <T extends object>(
repository: Repository<T>,
source: T,
): Partial<T> => {
const buildColumnData = <T extends object>(repository: Repository<T>, source: T): Partial<T> => {
const row = {} as Partial<T>;
const target = row as Record<string, unknown>;
const sourceRecord = source as Record<string, unknown>;
@ -1375,7 +1363,8 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
...buildColumnData(employeePositionRepository, position),
id: randomUUID(),
posMasterId: positionParentKey === "posMasterId" ? parentId : undefined,
posMasterTempId: positionParentKey === "posMasterTempId" ? parentId : undefined,
posMasterTempId:
positionParentKey === "posMasterTempId" ? parentId : undefined,
...buildAuditFields(positionTimestamp),
});
}
@ -1420,8 +1409,7 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
const dataId = x.id;
const matchedOrgRoot = findMatchedNodeByAncestorDNA(orgRootCurrent, x);
const filteredEmployeePosMaster =
employeePosMasterByNode.get(getNodeKey("root", dataId)) ?? [];
const filteredEmployeePosMaster = employeePosMasterByNode.get(getNodeKey("root", dataId)) ?? [];
await cloneEmployeeNodeBatch(
filteredEmployeePosMaster,
@ -1676,8 +1664,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
console.log(`[AMQ] handler_org SUCCESS - Total time: ${Date.now() - startTime}ms`);
console.timeEnd("[AMQ] handler_org_total");
await clearMenuAndRoleCache();
return true;
} catch (error) {
const totalTime = Date.now() - startTime;
@ -1697,32 +1683,6 @@ async function handler_org(msg: amqp.ConsumeMessage): Promise<boolean> {
}
}
async function clearMenuAndRoleCache(): Promise<void> {
const redisClient = redis.createClient({
host: REDIS_HOST,
port: REDIS_PORT,
});
const keysAsync = promisify(redisClient.keys).bind(redisClient);
const delAsync = promisify(redisClient.del).bind(redisClient);
try {
const menuKeys = await keysAsync("menu_*");
if (menuKeys.length > 0) {
await delAsync(...menuKeys);
console.log(`[AMQ] Cleared ${menuKeys.length} menu cache keys`);
}
const roleKeys = await keysAsync("role_*");
if (roleKeys.length > 0) {
await delAsync(...roleKeys);
console.log(`[AMQ] Cleared ${roleKeys.length} role cache keys`);
}
} finally {
redisClient.quit();
}
}
async function handler_org_draft(msg: amqp.ConsumeMessage): Promise<boolean> {
const { data, token, user } = JSON.parse(msg.content.toString());
const { requestBody, request, revision } = data;

View file

@ -22,7 +22,7 @@ export function initWebSocket() {
});
io.on("connection", (ws) => {
// console.log("✅ Client connected to WebSocket");
console.log("✅ Client connected to WebSocket");
ws.on("close", () => {
console.log("❌ Client disconnected");

View file

@ -1,37 +1,23 @@
/**
* 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 }
*
* @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) {
// 1. แปลงเป็น year เต็ม
const year = Math.floor(totalDays / 365.2524);
let totalMonths = months;
let totalDays = days;
// 2. วันที่เหลือหลังหัก year ออก
const remainAfterYear = totalDays - year * 365.2524;
// 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) };
// 3. แปลงเป็น month เต็ม
const month = Math.floor(remainAfterYear / 30.4375);
// 4. วันที่เหลือหลังหัก month ออก
const remainAfterMonth = remainAfterYear - month * 30.4375;
// 5. ปัดลง เฉพาะวัน
const day = Math.floor(remainAfterMonth);
return { year, month, day };
}

View file

@ -29,12 +29,6 @@
"name": "X-API-Key",
"description": "API KEY สำหรับ Web Service",
"in": "header"
},
"internalAuth": {
"type": "apiKey",
"name": "api-key",
"description": "API KEY สำหรับ Internal Service (.NET, HRMS)",
"in": "header"
}
},
"tags": [