@platform-modules/foreign-ministry 1.3.326 → 1.3.332
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/employee-evaluation-request.utils.d.ts +2 -2
- package/dist/helpers/employee-evaluation-request.utils.js +6 -8
- package/dist/helpers/evaluation-eligibility.utils.d.ts +24 -3
- package/dist/helpers/evaluation-eligibility.utils.js +101 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -2
- package/dist/models/EvaluationEligibilitySettingModel.d.ts +12 -12
- package/dist/models/EvaluationEligibilitySettingModel.js +14 -14
- package/package.json +35 -35
- package/src/helpers/employee-evaluation-request.utils.ts +178 -181
- package/src/helpers/evaluation-eligibility.utils.ts +126 -36
- package/src/index.ts +5 -0
- package/src/models/EvaluationEligibilitySettingModel.ts +17 -17
|
@@ -16,8 +16,8 @@ export declare function normalizeEmployeeSubmissions(raw: unknown): {
|
|
|
16
16
|
employees: NormalizedEmployeeSubmission[];
|
|
17
17
|
error?: string;
|
|
18
18
|
};
|
|
19
|
-
/** Resolve max employees from active
|
|
20
|
-
export declare function resolveMaxEmployeesPerRequest(manager: EntityManager,
|
|
19
|
+
/** Resolve max employees from active exception cycle for the evaluation month. */
|
|
20
|
+
export declare function resolveMaxEmployeesPerRequest(manager: EntityManager, month: number): Promise<number>;
|
|
21
21
|
export declare function persistEmployeeEvaluationScores(manager: EntityManager, opts: {
|
|
22
22
|
requestId: number;
|
|
23
23
|
formId: number;
|
|
@@ -9,6 +9,7 @@ const EmployeeEvaluationPersonScoreModel_1 = require("../models/EmployeeEvaluati
|
|
|
9
9
|
const EmployeeEvaluationRequestModel_1 = require("../models/EmployeeEvaluationRequestModel");
|
|
10
10
|
const EvaluationEligibilitySettingModel_1 = require("../models/EvaluationEligibilitySettingModel");
|
|
11
11
|
const EvaluationFormQuestionModel_1 = require("../models/EvaluationFormQuestionModel");
|
|
12
|
+
const evaluation_eligibility_utils_1 = require("./evaluation-eligibility.utils");
|
|
12
13
|
exports.DEFAULT_MAX_EMPLOYEES_PER_REQUEST = 50;
|
|
13
14
|
function normalizeEmployeeSubmissions(raw) {
|
|
14
15
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
@@ -68,19 +69,16 @@ function normalizeEmployeeSubmissions(raw) {
|
|
|
68
69
|
}
|
|
69
70
|
return { employees };
|
|
70
71
|
}
|
|
71
|
-
/** Resolve max employees from active
|
|
72
|
-
async function resolveMaxEmployeesPerRequest(manager,
|
|
73
|
-
const
|
|
72
|
+
/** Resolve max employees from active exception cycle for the evaluation month. */
|
|
73
|
+
async function resolveMaxEmployeesPerRequest(manager, month) {
|
|
74
|
+
const rows = await manager
|
|
74
75
|
.getRepository(EvaluationEligibilitySettingModel_1.EvaluationEligibilitySetting)
|
|
75
76
|
.createQueryBuilder('s')
|
|
76
77
|
.where('s.is_deleted = false')
|
|
77
78
|
.andWhere('s.is_active = true')
|
|
78
|
-
.andWhere('s.department_id = :department_id', { department_id })
|
|
79
|
-
.andWhere('s.section_id = :section_id', { section_id })
|
|
80
|
-
.andWhere('s.from_month <= :month', { month })
|
|
81
|
-
.andWhere('s.to_month >= :month', { month })
|
|
82
79
|
.orderBy('s.id', 'DESC')
|
|
83
|
-
.
|
|
80
|
+
.getMany();
|
|
81
|
+
const setting = rows.find((s) => (0, evaluation_eligibility_utils_1.isCalendarMonthInEligibilityRange)(s.from_month, s.to_month, month)) ?? null;
|
|
84
82
|
const max = setting?.max_employees_per_request;
|
|
85
83
|
if (max != null && Number.isFinite(Number(max)) && Number(max) > 0) {
|
|
86
84
|
return Math.floor(Number(max));
|
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* `evaluation_end_date` is the last calendar day (1–31) of each month in the range.
|
|
2
|
+
* Evaluation exception cycle dates are stored as MM-DD strings (e.g. 01-01, 05-31, 04-12).
|
|
4
3
|
*/
|
|
5
|
-
export
|
|
4
|
+
export type MmDdInterval = {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
};
|
|
8
|
+
/** Validate and normalize MM-DD (e.g. 1-1 → 01-01). */
|
|
9
|
+
export declare function parseMonthDay(value: unknown): string | null;
|
|
10
|
+
/** Sortable key for MM-DD overlap checks (month * 100 + day). */
|
|
11
|
+
export declare function mmDdSortKey(mmDd: string): number;
|
|
12
|
+
/** True when two MM-DD windows share any calendar period. */
|
|
13
|
+
export declare function mmDdWindowsOverlap(startA: string, endA: string, startB: string, endB: string): boolean;
|
|
14
|
+
/** Whether calendar month (1–12) falls within the inclusive MM-DD window. */
|
|
15
|
+
export declare function isCalendarMonthInEligibilityRange(fromMmDd: string, toMmDd: string, calendarMonth: number): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether HOD submissions are still open for the active cycle on the reference date.
|
|
18
|
+
* Uses the day component of `evaluationEndMmDd` as the last submission day of each month in range.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isEvaluationEligibilityWindowOpen(fromMmDd: string, toMmDd: string, evaluationEndMmDd: string, referenceDate?: Date): boolean;
|
|
21
|
+
export declare function parseEligibilityDateRange(fromInput: unknown, toInput: unknown): {
|
|
22
|
+
from_month: string;
|
|
23
|
+
to_month: string;
|
|
24
|
+
} | null;
|
|
25
|
+
/** @deprecated Use parseMonthDay for MM-DD strings. */
|
|
6
26
|
export declare function parseEvaluationEndDay(input: unknown): number | null;
|
|
27
|
+
/** @deprecated Use parseEligibilityDateRange for MM-DD strings. */
|
|
7
28
|
export declare function parseMonthRange(fromInput: unknown, toInput: unknown): {
|
|
8
29
|
from_month: number;
|
|
9
30
|
to_month: number;
|
|
@@ -1,39 +1,118 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Evaluation exception cycle dates are stored as MM-DD strings (e.g. 01-01, 05-31, 04-12).
|
|
4
|
+
*/
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseMonthDay = parseMonthDay;
|
|
7
|
+
exports.mmDdSortKey = mmDdSortKey;
|
|
8
|
+
exports.mmDdWindowsOverlap = mmDdWindowsOverlap;
|
|
9
|
+
exports.isCalendarMonthInEligibilityRange = isCalendarMonthInEligibilityRange;
|
|
3
10
|
exports.isEvaluationEligibilityWindowOpen = isEvaluationEligibilityWindowOpen;
|
|
11
|
+
exports.parseEligibilityDateRange = parseEligibilityDateRange;
|
|
4
12
|
exports.parseEvaluationEndDay = parseEvaluationEndDay;
|
|
5
13
|
exports.parseMonthRange = parseMonthRange;
|
|
14
|
+
/** Validate and normalize MM-DD (e.g. 1-1 → 01-01). */
|
|
15
|
+
function parseMonthDay(value) {
|
|
16
|
+
if (value == null || value === '')
|
|
17
|
+
return null;
|
|
18
|
+
const s = String(value).trim();
|
|
19
|
+
const m = /^(\d{1,2})-(\d{1,2})$/.exec(s);
|
|
20
|
+
if (!m)
|
|
21
|
+
return null;
|
|
22
|
+
const month = parseInt(m[1], 10);
|
|
23
|
+
const day = parseInt(m[2], 10);
|
|
24
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
25
|
+
return null;
|
|
26
|
+
return `${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
27
|
+
}
|
|
28
|
+
function parseMmDdParts(mmDd) {
|
|
29
|
+
const normalized = parseMonthDay(mmDd);
|
|
30
|
+
if (!normalized)
|
|
31
|
+
return null;
|
|
32
|
+
const [month, day] = normalized.split('-').map(Number);
|
|
33
|
+
return { month, day };
|
|
34
|
+
}
|
|
35
|
+
/** Sortable key for MM-DD overlap checks (month * 100 + day). */
|
|
36
|
+
function mmDdSortKey(mmDd) {
|
|
37
|
+
const parts = parseMmDdParts(mmDd);
|
|
38
|
+
if (!parts)
|
|
39
|
+
return 0;
|
|
40
|
+
return parts.month * 100 + parts.day;
|
|
41
|
+
}
|
|
42
|
+
function expandMmDdWindow(startMmDd, endMmDd) {
|
|
43
|
+
const start = mmDdSortKey(startMmDd);
|
|
44
|
+
const end = mmDdSortKey(endMmDd);
|
|
45
|
+
if (start <= end)
|
|
46
|
+
return [{ start, end }];
|
|
47
|
+
return [
|
|
48
|
+
{ start, end: 1231 },
|
|
49
|
+
{ start: 101, end },
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
function linearIntervalsOverlap(a, b) {
|
|
53
|
+
return a.start <= b.end && b.start <= a.end;
|
|
54
|
+
}
|
|
55
|
+
/** True when two MM-DD windows share any calendar period. */
|
|
56
|
+
function mmDdWindowsOverlap(startA, endA, startB, endB) {
|
|
57
|
+
const intervalsA = expandMmDdWindow(startA, endA);
|
|
58
|
+
const intervalsB = expandMmDdWindow(startB, endB);
|
|
59
|
+
for (const ia of intervalsA) {
|
|
60
|
+
for (const ib of intervalsB) {
|
|
61
|
+
if (linearIntervalsOverlap(ia, ib))
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/** Whether calendar month (1–12) falls within the inclusive MM-DD window. */
|
|
68
|
+
function isCalendarMonthInEligibilityRange(fromMmDd, toMmDd, calendarMonth) {
|
|
69
|
+
const from = parseMmDdParts(fromMmDd);
|
|
70
|
+
const to = parseMmDdParts(toMmDd);
|
|
71
|
+
if (!from || !to || calendarMonth < 1 || calendarMonth > 12)
|
|
72
|
+
return false;
|
|
73
|
+
const fm = from.month;
|
|
74
|
+
const tm = to.month;
|
|
75
|
+
if (fm <= tm)
|
|
76
|
+
return calendarMonth >= fm && calendarMonth <= tm;
|
|
77
|
+
return calendarMonth >= fm || calendarMonth <= tm;
|
|
78
|
+
}
|
|
6
79
|
/**
|
|
7
|
-
* Whether
|
|
8
|
-
* `
|
|
80
|
+
* Whether HOD submissions are still open for the active cycle on the reference date.
|
|
81
|
+
* Uses the day component of `evaluationEndMmDd` as the last submission day of each month in range.
|
|
9
82
|
*/
|
|
10
|
-
function isEvaluationEligibilityWindowOpen(
|
|
11
|
-
const month = referenceDate.
|
|
12
|
-
const day = referenceDate.
|
|
13
|
-
if (
|
|
83
|
+
function isEvaluationEligibilityWindowOpen(fromMmDd, toMmDd, evaluationEndMmDd, referenceDate = new Date()) {
|
|
84
|
+
const month = referenceDate.getMonth() + 1;
|
|
85
|
+
const day = referenceDate.getDate();
|
|
86
|
+
if (!isCalendarMonthInEligibilityRange(fromMmDd, toMmDd, month))
|
|
14
87
|
return false;
|
|
15
|
-
|
|
88
|
+
const endParts = parseMmDdParts(evaluationEndMmDd);
|
|
89
|
+
if (!endParts)
|
|
16
90
|
return false;
|
|
17
|
-
return day <=
|
|
91
|
+
return day <= endParts.day;
|
|
18
92
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
93
|
+
function parseEligibilityDateRange(fromInput, toInput) {
|
|
94
|
+
const from_month = parseMonthDay(fromInput);
|
|
95
|
+
const to_month = parseMonthDay(toInput);
|
|
96
|
+
if (!from_month || !to_month)
|
|
21
97
|
return null;
|
|
22
|
-
|
|
23
|
-
|
|
98
|
+
return { from_month, to_month };
|
|
99
|
+
}
|
|
100
|
+
/** @deprecated Use parseMonthDay for MM-DD strings. */
|
|
101
|
+
function parseEvaluationEndDay(input) {
|
|
102
|
+
const mmDd = parseMonthDay(input);
|
|
103
|
+
if (!mmDd)
|
|
24
104
|
return null;
|
|
25
|
-
|
|
105
|
+
const parts = parseMmDdParts(mmDd);
|
|
106
|
+
return parts?.day ?? null;
|
|
26
107
|
}
|
|
108
|
+
/** @deprecated Use parseEligibilityDateRange for MM-DD strings. */
|
|
27
109
|
function parseMonthRange(fromInput, toInput) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (!Number.isFinite(from_month) || !Number.isFinite(to_month))
|
|
31
|
-
return null;
|
|
32
|
-
if (!Number.isInteger(from_month) || !Number.isInteger(to_month))
|
|
110
|
+
const range = parseEligibilityDateRange(fromInput, toInput);
|
|
111
|
+
if (!range)
|
|
33
112
|
return null;
|
|
34
|
-
|
|
113
|
+
const from = parseMmDdParts(range.from_month);
|
|
114
|
+
const to = parseMmDdParts(range.to_month);
|
|
115
|
+
if (!from || !to)
|
|
35
116
|
return null;
|
|
36
|
-
|
|
37
|
-
return null;
|
|
38
|
-
return { from_month, to_month };
|
|
117
|
+
return { from_month: from.month, to_month: to.month };
|
|
39
118
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -409,7 +409,7 @@ export { parsePortalUserIdFromRequest, userHasPortalAdminRole, isPortalAdminFrom
|
|
|
409
409
|
export type { FmServicesNotificationConfigRecipient, CollectFmServicesNotificationConfigRecipientsParams, SendFmServicesNotificationConfigNotificationsParams, } from './helpers/services-notification-config.helper';
|
|
410
410
|
export * from './models/MoodleUsersModel';
|
|
411
411
|
export { EvaluationEligibilitySetting, EvaluationEligibilitySettingEmployee, } from './models/EvaluationEligibilitySettingModel';
|
|
412
|
-
export { isEvaluationEligibilityWindowOpen, parseEvaluationEndDay, parseMonthRange, } from './helpers/evaluation-eligibility.utils';
|
|
412
|
+
export { isEvaluationEligibilityWindowOpen, isCalendarMonthInEligibilityRange, mmDdWindowsOverlap, mmDdSortKey, parseMonthDay, parseEligibilityDateRange, parseEvaluationEndDay, parseMonthRange, } from './helpers/evaluation-eligibility.utils';
|
|
413
413
|
export { DEFAULT_MAX_EMPLOYEES_PER_REQUEST, normalizeEmployeeSubmissions, resolveMaxEmployeesPerRequest, persistEmployeeEvaluationScores, } from './helpers/employee-evaluation-request.utils';
|
|
414
414
|
export type { EmployeeEvaluationSubmitAnswer, EmployeeEvaluationSubmitEmployee, NormalizedEmployeeSubmission, } from './helpers/employee-evaluation-request.utils';
|
|
415
415
|
export { EmployeeEvaluationRequests, EmployeeEvaluationRequestStatus, EmployeeEvaluationRoutingBucket, } from './models/EmployeeEvaluationRequestModel';
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.collectFmServicesNotificationConfigRecipients = exports.ServicesNotificationTriggerType = exports.AppointmentWorkFlowStatus = exports.AppointmentMessageType = exports.AppointmentAttendeeStatus = exports.AppointmentApprovalStatus = exports.AppointmentAttendees = exports.AppointmentWorkFlow = exports.AppointmentRequestChat = exports.AppointmentRequestAttachment = exports.AppointmentApprovalDetails = exports.AppointmentRequestStatus = exports.AppointmentRequests = exports.TravelClass = exports.MissionTravelWorkFlowStatus = exports.MissionTravelApprovalStatus = exports.AllowanceRatio = exports.DecisionType = exports.MissionType = exports.MissionTravelStatus = exports.GatePassMessageType = exports.GatePassWorkFlowStatus = exports.GatePassApprovalStatus = exports.GatePassType = exports.GatePassRequestStatus = exports.SecurityDeptMessageType = exports.SecurityDeptAccessType = exports.SecurityDeptRequestStatus = exports.RetiredCardMessageType = exports.RetiredCardWorkFlowStatus = exports.RetiredCardApprovalStatus = exports.RetiredCardAccessType = exports.RetiredCardRequestStatus = exports.TitleCategory = exports.ProfileUpdateRequestStatus = exports.StayAfterHoursTransactionStatus = exports.StayAfterHoursTransaction = exports.StayAfterHoursBalance = exports.EarlyCheckoutTransactionStatus = exports.EarlyCheckoutTransaction = exports.EarlyCheckoutFrequency = exports.EarlyCheckoutConfiguration = exports.EarlyCheckoutBalance = exports.LeaveTransactionStatus = exports.LeaveTransaction = exports.LeaveConfigurationGrades = exports.enumFrequency = exports.LeaveConfiguration = exports.ParcelDepartmentCategory = exports.RegisterCandidateExperienceActivity = void 0;
|
|
18
|
-
exports.
|
|
19
|
-
exports.
|
|
18
|
+
exports.InitiatorEmployeeNominationApprovalStatus = exports.InitiatorEmployeeNominationApprovalDetails = exports.InitiatorInitiativeType = exports.InitiatorEmployeeNominationRequestStatus = exports.InitiatorEmployeeNominationRequests = exports.IpeGrievanceRequestAttachment = exports.IpeGrievanceMessageType = exports.IpeGrievanceRequestChat = exports.IpeGrievanceWorkFlowStatus = exports.IpeGrievanceWorkFlow = exports.IpeGrievanceApprovalStatus = exports.IpeGrievanceApprovalDetails = exports.IpeGrievancePeriodRating = exports.IpeGrievanceExemptionReason = exports.IpeGrievancePeriodType = exports.IpeGrievanceFinalEvaluationResult = exports.IpeGrievanceRequestStatus = exports.IpeGrievanceRequests = exports.EmployeeEvaluationRequestAttachment = exports.EmployeeEvaluationMessageType = exports.EmployeeEvaluationRequestChat = exports.EmployeeEvaluationWorkFlowStatus = exports.EmployeeEvaluationWorkFlow = exports.EmployeeEvaluationApprovalStatus = exports.EmployeeEvaluationApprovalDetails = exports.EmployeeEvaluationPersonScore = exports.EmployeeEvaluationAnswers = exports.EmployeeEvaluationUsFeedback = exports.EmployeeEvaluation = exports.EmployeeEvaluationRoutingBucket = exports.EmployeeEvaluationRequestStatus = exports.EmployeeEvaluationRequests = exports.persistEmployeeEvaluationScores = exports.resolveMaxEmployeesPerRequest = exports.normalizeEmployeeSubmissions = exports.DEFAULT_MAX_EMPLOYEES_PER_REQUEST = exports.parseMonthRange = exports.parseEvaluationEndDay = exports.parseEligibilityDateRange = exports.parseMonthDay = exports.mmDdSortKey = exports.mmDdWindowsOverlap = exports.isCalendarMonthInEligibilityRange = exports.isEvaluationEligibilityWindowOpen = exports.EvaluationEligibilitySettingEmployee = exports.EvaluationEligibilitySetting = exports.isPortalAdminFromRequest = exports.userHasPortalAdminRole = exports.parsePortalUserIdFromRequest = exports.sendFmServicesNotificationConfigNotifications = void 0;
|
|
19
|
+
exports.EvaluationFormQuestion = exports.EvaluationFormSection = exports.EvaluationFormType = exports.EvaluationForm = exports.EmbassyEvaluationRequestAttachment = exports.EmbassyEvaluationMessageType = exports.EmbassyEvaluationRequestChat = exports.EmbassyEvaluationWorkFlowStatus = exports.EmbassyEvaluationWorkFlow = exports.EmbassyEvaluationApprovalStatus = exports.EmbassyEvaluationApprovalDetails = exports.EmbassyEvaluationRequestStatus = exports.EmbassyEvaluationRequests = exports.EmbassyEvaluationResponse = exports.EmbassyEvaluationAssignmentStatus = exports.EmbassyEvaluationAssignment = exports.EmbassyEvaluationCycleStatus = exports.EmbassyEvaluationCycle = exports.EmployeeOfMonthSupportNominationRequestAttachment = exports.EmployeeOfMonthSupportNominationMessageType = exports.EmployeeOfMonthSupportNominationRequestChat = exports.EmployeeOfMonthSupportNominationWorkFlowStatus = exports.EmployeeOfMonthSupportNominationWorkFlow = exports.EmployeeOfMonthSupportNominationApprovalStatus = exports.EmployeeOfMonthSupportNominationApprovalDetails = exports.EmployeeOfMonthSupportNominationRequestStatus = exports.EmployeeOfMonthSupportNominationRequests = exports.EmployeeOfMonthNominationRequestAttachment = exports.EmployeeOfMonthNominationMessageType = exports.EmployeeOfMonthNominationRequestChat = exports.EmployeeOfMonthNominationWorkFlowStatus = exports.EmployeeOfMonthNominationWorkFlow = exports.EmployeeOfMonthNominationApprovalStatus = exports.EmployeeOfMonthNominationApprovalDetails = exports.EmployeeOfMonthNominationRequestStatus = exports.EmployeeOfMonthNominationRequests = exports.InnovativeEmployeeNominationRequestAttachment = exports.InnovativeEmployeeNominationMessageType = exports.InnovativeEmployeeNominationRequestChat = exports.InnovativeEmployeeNominationWorkFlowStatus = exports.InnovativeEmployeeNominationWorkFlow = exports.InnovativeEmployeeNominationApprovalStatus = exports.InnovativeEmployeeNominationApprovalDetails = exports.InnovativeEmployeeNominationRequestStatus = exports.InnovativeEmployeeNominationRequests = exports.InitiatorEmployeeNominationRequestAttachment = exports.InitiatorEmployeeNominationMessageType = exports.InitiatorEmployeeNominationRequestChat = exports.InitiatorEmployeeNominationWorkFlowStatus = exports.InitiatorEmployeeNominationWorkFlow = void 0;
|
|
20
|
+
exports.EvaluationFormQuestionType = void 0;
|
|
20
21
|
__exportStar(require("./models/user"), exports);
|
|
21
22
|
__exportStar(require("./models/role"), exports);
|
|
22
23
|
__exportStar(require("./models/user-sessions"), exports);
|
|
@@ -505,6 +506,11 @@ Object.defineProperty(exports, "EvaluationEligibilitySetting", { enumerable: tru
|
|
|
505
506
|
Object.defineProperty(exports, "EvaluationEligibilitySettingEmployee", { enumerable: true, get: function () { return EvaluationEligibilitySettingModel_1.EvaluationEligibilitySettingEmployee; } });
|
|
506
507
|
var evaluation_eligibility_utils_1 = require("./helpers/evaluation-eligibility.utils");
|
|
507
508
|
Object.defineProperty(exports, "isEvaluationEligibilityWindowOpen", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.isEvaluationEligibilityWindowOpen; } });
|
|
509
|
+
Object.defineProperty(exports, "isCalendarMonthInEligibilityRange", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.isCalendarMonthInEligibilityRange; } });
|
|
510
|
+
Object.defineProperty(exports, "mmDdWindowsOverlap", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.mmDdWindowsOverlap; } });
|
|
511
|
+
Object.defineProperty(exports, "mmDdSortKey", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.mmDdSortKey; } });
|
|
512
|
+
Object.defineProperty(exports, "parseMonthDay", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.parseMonthDay; } });
|
|
513
|
+
Object.defineProperty(exports, "parseEligibilityDateRange", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.parseEligibilityDateRange; } });
|
|
508
514
|
Object.defineProperty(exports, "parseEvaluationEndDay", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.parseEvaluationEndDay; } });
|
|
509
515
|
Object.defineProperty(exports, "parseMonthRange", { enumerable: true, get: function () { return evaluation_eligibility_utils_1.parseMonthRange; } });
|
|
510
516
|
var employee_evaluation_request_utils_1 = require("./helpers/employee-evaluation-request.utils");
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { BaseModel } from './BaseModel';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Cycle-based evaluation exception list: defines the evaluation window (MM-DD dates),
|
|
4
|
+
* last submission day, and employees excluded from evaluation for that cycle.
|
|
5
5
|
*/
|
|
6
6
|
export declare class EvaluationEligibilitySetting extends BaseModel {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** Evaluation window start
|
|
10
|
-
from_month:
|
|
11
|
-
/** Evaluation window end
|
|
12
|
-
to_month:
|
|
13
|
-
/** Last day
|
|
14
|
-
evaluation_end_date:
|
|
7
|
+
cycle_name: string;
|
|
8
|
+
cycle_code: string;
|
|
9
|
+
/** Evaluation window start date as MM-DD (e.g. 01-01). */
|
|
10
|
+
from_month: string;
|
|
11
|
+
/** Evaluation window end date as MM-DD (e.g. 05-31), inclusive. */
|
|
12
|
+
to_month: string;
|
|
13
|
+
/** Last submission date as MM-DD; day applies each month in the window (e.g. 04-12 → through the 12th). */
|
|
14
|
+
evaluation_end_date: string;
|
|
15
15
|
is_active: boolean;
|
|
16
|
-
/** Maximum employees allowed in one evaluation request for this
|
|
16
|
+
/** Maximum employees allowed in one evaluation request for this cycle window. */
|
|
17
17
|
max_employees_per_request: number;
|
|
18
18
|
employees?: EvaluationEligibilitySettingEmployee[];
|
|
19
19
|
}
|
|
20
20
|
export declare class EvaluationEligibilitySettingEmployee extends BaseModel {
|
|
21
21
|
evaluation_eligibility_setting: EvaluationEligibilitySetting;
|
|
22
22
|
employee_id: number;
|
|
23
|
-
/** When true, employee is excluded from evaluation for this
|
|
23
|
+
/** When true, employee is excluded from evaluation for this cycle window. */
|
|
24
24
|
is_excluded_from_evaluation: boolean;
|
|
25
25
|
}
|
|
@@ -13,31 +13,31 @@ exports.EvaluationEligibilitySettingEmployee = exports.EvaluationEligibilitySett
|
|
|
13
13
|
const typeorm_1 = require("typeorm");
|
|
14
14
|
const BaseModel_1 = require("./BaseModel");
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Cycle-based evaluation exception list: defines the evaluation window (MM-DD dates),
|
|
17
|
+
* last submission day, and employees excluded from evaluation for that cycle.
|
|
18
18
|
*/
|
|
19
19
|
let EvaluationEligibilitySetting = class EvaluationEligibilitySetting extends BaseModel_1.BaseModel {
|
|
20
20
|
};
|
|
21
21
|
exports.EvaluationEligibilitySetting = EvaluationEligibilitySetting;
|
|
22
22
|
__decorate([
|
|
23
|
-
(0, typeorm_1.Column)({ type: '
|
|
24
|
-
__metadata("design:type",
|
|
25
|
-
], EvaluationEligibilitySetting.prototype, "
|
|
23
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 255 }),
|
|
24
|
+
__metadata("design:type", String)
|
|
25
|
+
], EvaluationEligibilitySetting.prototype, "cycle_name", void 0);
|
|
26
26
|
__decorate([
|
|
27
|
-
(0, typeorm_1.Column)({ type: '
|
|
28
|
-
__metadata("design:type",
|
|
29
|
-
], EvaluationEligibilitySetting.prototype, "
|
|
27
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 100 }),
|
|
28
|
+
__metadata("design:type", String)
|
|
29
|
+
], EvaluationEligibilitySetting.prototype, "cycle_code", void 0);
|
|
30
30
|
__decorate([
|
|
31
|
-
(0, typeorm_1.Column)({ type: '
|
|
32
|
-
__metadata("design:type",
|
|
31
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 5 }),
|
|
32
|
+
__metadata("design:type", String)
|
|
33
33
|
], EvaluationEligibilitySetting.prototype, "from_month", void 0);
|
|
34
34
|
__decorate([
|
|
35
|
-
(0, typeorm_1.Column)({ type: '
|
|
36
|
-
__metadata("design:type",
|
|
35
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 5 }),
|
|
36
|
+
__metadata("design:type", String)
|
|
37
37
|
], EvaluationEligibilitySetting.prototype, "to_month", void 0);
|
|
38
38
|
__decorate([
|
|
39
|
-
(0, typeorm_1.Column)({ type: '
|
|
40
|
-
__metadata("design:type",
|
|
39
|
+
(0, typeorm_1.Column)({ type: 'varchar', length: 5 }),
|
|
40
|
+
__metadata("design:type", String)
|
|
41
41
|
], EvaluationEligibilitySetting.prototype, "evaluation_end_date", void 0);
|
|
42
42
|
__decorate([
|
|
43
43
|
(0, typeorm_1.Column)({ type: 'boolean', default: true }),
|
package/package.json
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@platform-modules/foreign-ministry",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"main": "dist/index.js",
|
|
5
|
-
"types": "dist/index.d.ts",
|
|
6
|
-
"exports": {
|
|
7
|
-
".": {
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"default": "./dist/index.js"
|
|
10
|
-
},
|
|
11
|
-
"./sla-report-views": {
|
|
12
|
-
"types": "./dist/sla-report-views.d.ts",
|
|
13
|
-
"default": "./dist/sla-report-views.js"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsc",
|
|
18
|
-
"dev": "ts-node src/scripts.ts",
|
|
19
|
-
"sync:sla-sql": "node scripts/sync-sla-reports-sql.js"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"moment-timezone": "^0.6.0",
|
|
26
|
-
"pg": "^8.16.0",
|
|
27
|
-
"typeorm": "^0.3.17"
|
|
28
|
-
},
|
|
29
|
-
"devDependencies": {
|
|
30
|
-
"@types/moment-timezone": "^0.5.30",
|
|
31
|
-
"dotenv": "^16.5.0",
|
|
32
|
-
"ts-node": "^10.9.2",
|
|
33
|
-
"typescript": "^5.2.0"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@platform-modules/foreign-ministry",
|
|
3
|
+
"version": "1.3.332",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./sla-report-views": {
|
|
12
|
+
"types": "./dist/sla-report-views.d.ts",
|
|
13
|
+
"default": "./dist/sla-report-views.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "ts-node src/scripts.ts",
|
|
19
|
+
"sync:sla-sql": "node scripts/sync-sla-reports-sql.js"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"moment-timezone": "^0.6.0",
|
|
26
|
+
"pg": "^8.16.0",
|
|
27
|
+
"typeorm": "^0.3.17"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/moment-timezone": "^0.5.30",
|
|
31
|
+
"dotenv": "^16.5.0",
|
|
32
|
+
"ts-node": "^10.9.2",
|
|
33
|
+
"typescript": "^5.2.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,181 +1,178 @@
|
|
|
1
|
-
import type { EntityManager } from 'typeorm';
|
|
2
|
-
import { EmployeeEvaluationAnswers } from '../models/EmployeeEvaluationAnswerModel';
|
|
3
|
-
import { EmployeeEvaluationPersonScore } from '../models/EmployeeEvaluationPersonScoreModel';
|
|
4
|
-
import { EmployeeEvaluationRequests } from '../models/EmployeeEvaluationRequestModel';
|
|
5
|
-
import { EvaluationEligibilitySetting } from '../models/EvaluationEligibilitySettingModel';
|
|
6
|
-
import { EvaluationFormQuestion } from '../models/EvaluationFormQuestionModel';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.
|
|
96
|
-
.
|
|
97
|
-
.
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
throw new Error(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return { totalScore: requestTotal, averageScore, employeeCount };
|
|
181
|
-
}
|
|
1
|
+
import type { EntityManager } from 'typeorm';
|
|
2
|
+
import { EmployeeEvaluationAnswers } from '../models/EmployeeEvaluationAnswerModel';
|
|
3
|
+
import { EmployeeEvaluationPersonScore } from '../models/EmployeeEvaluationPersonScoreModel';
|
|
4
|
+
import { EmployeeEvaluationRequests } from '../models/EmployeeEvaluationRequestModel';
|
|
5
|
+
import { EvaluationEligibilitySetting } from '../models/EvaluationEligibilitySettingModel';
|
|
6
|
+
import { EvaluationFormQuestion } from '../models/EvaluationFormQuestionModel';
|
|
7
|
+
import { isCalendarMonthInEligibilityRange } from './evaluation-eligibility.utils';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_MAX_EMPLOYEES_PER_REQUEST = 50;
|
|
10
|
+
|
|
11
|
+
export type EmployeeEvaluationSubmitAnswer = {
|
|
12
|
+
section_id: number;
|
|
13
|
+
question_id: number;
|
|
14
|
+
score: number;
|
|
15
|
+
remarks?: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type EmployeeEvaluationSubmitEmployee = {
|
|
19
|
+
user_id: number;
|
|
20
|
+
is_rca?: boolean;
|
|
21
|
+
answers: EmployeeEvaluationSubmitAnswer[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type NormalizedEmployeeSubmission = EmployeeEvaluationSubmitEmployee;
|
|
25
|
+
|
|
26
|
+
export function normalizeEmployeeSubmissions(raw: unknown): {
|
|
27
|
+
employees: NormalizedEmployeeSubmission[];
|
|
28
|
+
error?: string;
|
|
29
|
+
} {
|
|
30
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
31
|
+
return { employees: [], error: 'employees must be a non-empty array' };
|
|
32
|
+
}
|
|
33
|
+
const seen = new Set<number>();
|
|
34
|
+
const employees: NormalizedEmployeeSubmission[] = [];
|
|
35
|
+
for (const item of raw) {
|
|
36
|
+
if (item == null || typeof item !== 'object') {
|
|
37
|
+
return { employees: [], error: 'Each employee entry must be an object' };
|
|
38
|
+
}
|
|
39
|
+
const o = item as Record<string, unknown>;
|
|
40
|
+
const user_id = Number(o.user_id ?? o.employee_id);
|
|
41
|
+
if (!Number.isFinite(user_id) || user_id <= 0) {
|
|
42
|
+
return { employees: [], error: 'Each employee must have a valid user_id' };
|
|
43
|
+
}
|
|
44
|
+
if (seen.has(user_id)) {
|
|
45
|
+
return { employees: [], error: `Duplicate user_id ${user_id} in employees list` };
|
|
46
|
+
}
|
|
47
|
+
seen.add(user_id);
|
|
48
|
+
const answersRaw = o.answers;
|
|
49
|
+
if (!Array.isArray(answersRaw) || answersRaw.length === 0) {
|
|
50
|
+
return { employees: [], error: `Employee ${user_id} must include a non-empty answers array` };
|
|
51
|
+
}
|
|
52
|
+
const answers: EmployeeEvaluationSubmitAnswer[] = [];
|
|
53
|
+
const qSeen = new Set<number>();
|
|
54
|
+
for (const a of answersRaw) {
|
|
55
|
+
if (a == null || typeof a !== 'object') {
|
|
56
|
+
return { employees: [], error: `Invalid answer for employee ${user_id}` };
|
|
57
|
+
}
|
|
58
|
+
const ar = a as Record<string, unknown>;
|
|
59
|
+
const section_id = Number(ar.section_id);
|
|
60
|
+
const question_id = Number(ar.question_id);
|
|
61
|
+
const score = Number(ar.score);
|
|
62
|
+
if (!Number.isFinite(section_id) || !Number.isFinite(question_id)) {
|
|
63
|
+
return { employees: [], error: `section_id and question_id required for employee ${user_id}` };
|
|
64
|
+
}
|
|
65
|
+
if (!Number.isFinite(score) || score < 0) {
|
|
66
|
+
return { employees: [], error: `score must be a non-negative number for employee ${user_id}, question ${question_id}` };
|
|
67
|
+
}
|
|
68
|
+
if (qSeen.has(question_id)) {
|
|
69
|
+
return { employees: [], error: `Duplicate question_id ${question_id} for employee ${user_id}` };
|
|
70
|
+
}
|
|
71
|
+
qSeen.add(question_id);
|
|
72
|
+
answers.push({
|
|
73
|
+
section_id,
|
|
74
|
+
question_id,
|
|
75
|
+
score,
|
|
76
|
+
remarks: ar.remarks != null ? String(ar.remarks) : null,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
employees.push({
|
|
80
|
+
user_id,
|
|
81
|
+
is_rca: o.is_rca !== undefined ? Boolean(o.is_rca) : false,
|
|
82
|
+
answers,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { employees };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Resolve max employees from active exception cycle for the evaluation month. */
|
|
89
|
+
export async function resolveMaxEmployeesPerRequest(
|
|
90
|
+
manager: EntityManager,
|
|
91
|
+
month: number
|
|
92
|
+
): Promise<number> {
|
|
93
|
+
const rows = await manager
|
|
94
|
+
.getRepository(EvaluationEligibilitySetting)
|
|
95
|
+
.createQueryBuilder('s')
|
|
96
|
+
.where('s.is_deleted = false')
|
|
97
|
+
.andWhere('s.is_active = true')
|
|
98
|
+
.orderBy('s.id', 'DESC')
|
|
99
|
+
.getMany();
|
|
100
|
+
const setting =
|
|
101
|
+
rows.find((s) => isCalendarMonthInEligibilityRange(s.from_month, s.to_month, month)) ?? null;
|
|
102
|
+
const max = setting?.max_employees_per_request;
|
|
103
|
+
if (max != null && Number.isFinite(Number(max)) && Number(max) > 0) {
|
|
104
|
+
return Math.floor(Number(max));
|
|
105
|
+
}
|
|
106
|
+
return DEFAULT_MAX_EMPLOYEES_PER_REQUEST;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function persistEmployeeEvaluationScores(
|
|
110
|
+
manager: EntityManager,
|
|
111
|
+
opts: {
|
|
112
|
+
requestId: number;
|
|
113
|
+
formId: number;
|
|
114
|
+
createdBy: string;
|
|
115
|
+
employees: NormalizedEmployeeSubmission[];
|
|
116
|
+
}
|
|
117
|
+
): Promise<{ totalScore: number; averageScore: number; employeeCount: number }> {
|
|
118
|
+
const { requestId, formId, createdBy, employees } = opts;
|
|
119
|
+
let requestTotal = 0;
|
|
120
|
+
|
|
121
|
+
for (const emp of employees) {
|
|
122
|
+
let personTotal = 0;
|
|
123
|
+
for (const ans of emp.answers) {
|
|
124
|
+
const qMeta = await manager.findOne(EvaluationFormQuestion, {
|
|
125
|
+
where: { id: ans.question_id, is_deleted: false },
|
|
126
|
+
});
|
|
127
|
+
const questionSectionId = qMeta?.form_section_id;
|
|
128
|
+
if (!qMeta || questionSectionId == null || questionSectionId !== ans.section_id) {
|
|
129
|
+
throw new Error(`Invalid question ${ans.question_id} for section ${ans.section_id}`);
|
|
130
|
+
}
|
|
131
|
+
if (qMeta.max_score != null && ans.score > qMeta.max_score) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`score ${ans.score} exceeds max_score ${qMeta.max_score} for question ${ans.question_id}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
personTotal += ans.score;
|
|
137
|
+
await manager.save(
|
|
138
|
+
EmployeeEvaluationAnswers,
|
|
139
|
+
manager.create(EmployeeEvaluationAnswers, {
|
|
140
|
+
request_id: requestId,
|
|
141
|
+
user_id: emp.user_id,
|
|
142
|
+
form_id: formId,
|
|
143
|
+
section_id: ans.section_id,
|
|
144
|
+
question_id: ans.question_id,
|
|
145
|
+
score: ans.score,
|
|
146
|
+
remarks: ans.remarks ?? null,
|
|
147
|
+
created_by: createdBy,
|
|
148
|
+
is_deleted: false,
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await manager.save(
|
|
154
|
+
EmployeeEvaluationPersonScore,
|
|
155
|
+
manager.create(EmployeeEvaluationPersonScore, {
|
|
156
|
+
request_id: requestId,
|
|
157
|
+
user_id: emp.user_id,
|
|
158
|
+
is_rca: Boolean(emp.is_rca),
|
|
159
|
+
us_feedback: null,
|
|
160
|
+
total_score: personTotal,
|
|
161
|
+
created_by: createdBy,
|
|
162
|
+
is_deleted: false,
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
requestTotal += personTotal;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const employeeCount = employees.length;
|
|
169
|
+
const averageScore = employeeCount ? requestTotal / employeeCount : 0;
|
|
170
|
+
await manager.update(EmployeeEvaluationRequests, { id: requestId }, {
|
|
171
|
+
total_score: requestTotal,
|
|
172
|
+
average_score: averageScore,
|
|
173
|
+
employee_count: employeeCount,
|
|
174
|
+
updated_by: createdBy,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return { totalScore: requestTotal, averageScore, employeeCount };
|
|
178
|
+
}
|
|
@@ -1,36 +1,126 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Evaluation exception cycle dates are stored as MM-DD strings (e.g. 01-01, 05-31, 04-12).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type MmDdInterval = { start: number; end: number };
|
|
6
|
+
|
|
7
|
+
/** Validate and normalize MM-DD (e.g. 1-1 → 01-01). */
|
|
8
|
+
export function parseMonthDay(value: unknown): string | null {
|
|
9
|
+
if (value == null || value === '') return null;
|
|
10
|
+
const s = String(value).trim();
|
|
11
|
+
const m = /^(\d{1,2})-(\d{1,2})$/.exec(s);
|
|
12
|
+
if (!m) return null;
|
|
13
|
+
const month = parseInt(m[1], 10);
|
|
14
|
+
const day = parseInt(m[2], 10);
|
|
15
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) return null;
|
|
16
|
+
return `${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseMmDdParts(mmDd: string): { month: number; day: number } | null {
|
|
20
|
+
const normalized = parseMonthDay(mmDd);
|
|
21
|
+
if (!normalized) return null;
|
|
22
|
+
const [month, day] = normalized.split('-').map(Number);
|
|
23
|
+
return { month, day };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Sortable key for MM-DD overlap checks (month * 100 + day). */
|
|
27
|
+
export function mmDdSortKey(mmDd: string): number {
|
|
28
|
+
const parts = parseMmDdParts(mmDd);
|
|
29
|
+
if (!parts) return 0;
|
|
30
|
+
return parts.month * 100 + parts.day;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function expandMmDdWindow(startMmDd: string, endMmDd: string): MmDdInterval[] {
|
|
34
|
+
const start = mmDdSortKey(startMmDd);
|
|
35
|
+
const end = mmDdSortKey(endMmDd);
|
|
36
|
+
if (start <= end) return [{ start, end }];
|
|
37
|
+
return [
|
|
38
|
+
{ start, end: 1231 },
|
|
39
|
+
{ start: 101, end },
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function linearIntervalsOverlap(a: MmDdInterval, b: MmDdInterval): boolean {
|
|
44
|
+
return a.start <= b.end && b.start <= a.end;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** True when two MM-DD windows share any calendar period. */
|
|
48
|
+
export function mmDdWindowsOverlap(
|
|
49
|
+
startA: string,
|
|
50
|
+
endA: string,
|
|
51
|
+
startB: string,
|
|
52
|
+
endB: string
|
|
53
|
+
): boolean {
|
|
54
|
+
const intervalsA = expandMmDdWindow(startA, endA);
|
|
55
|
+
const intervalsB = expandMmDdWindow(startB, endB);
|
|
56
|
+
for (const ia of intervalsA) {
|
|
57
|
+
for (const ib of intervalsB) {
|
|
58
|
+
if (linearIntervalsOverlap(ia, ib)) return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Whether calendar month (1–12) falls within the inclusive MM-DD window. */
|
|
65
|
+
export function isCalendarMonthInEligibilityRange(
|
|
66
|
+
fromMmDd: string,
|
|
67
|
+
toMmDd: string,
|
|
68
|
+
calendarMonth: number
|
|
69
|
+
): boolean {
|
|
70
|
+
const from = parseMmDdParts(fromMmDd);
|
|
71
|
+
const to = parseMmDdParts(toMmDd);
|
|
72
|
+
if (!from || !to || calendarMonth < 1 || calendarMonth > 12) return false;
|
|
73
|
+
const fm = from.month;
|
|
74
|
+
const tm = to.month;
|
|
75
|
+
if (fm <= tm) return calendarMonth >= fm && calendarMonth <= tm;
|
|
76
|
+
return calendarMonth >= fm || calendarMonth <= tm;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Whether HOD submissions are still open for the active cycle on the reference date.
|
|
81
|
+
* Uses the day component of `evaluationEndMmDd` as the last submission day of each month in range.
|
|
82
|
+
*/
|
|
83
|
+
export function isEvaluationEligibilityWindowOpen(
|
|
84
|
+
fromMmDd: string,
|
|
85
|
+
toMmDd: string,
|
|
86
|
+
evaluationEndMmDd: string,
|
|
87
|
+
referenceDate: Date = new Date()
|
|
88
|
+
): boolean {
|
|
89
|
+
const month = referenceDate.getMonth() + 1;
|
|
90
|
+
const day = referenceDate.getDate();
|
|
91
|
+
if (!isCalendarMonthInEligibilityRange(fromMmDd, toMmDd, month)) return false;
|
|
92
|
+
const endParts = parseMmDdParts(evaluationEndMmDd);
|
|
93
|
+
if (!endParts) return false;
|
|
94
|
+
return day <= endParts.day;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseEligibilityDateRange(
|
|
98
|
+
fromInput: unknown,
|
|
99
|
+
toInput: unknown
|
|
100
|
+
): { from_month: string; to_month: string } | null {
|
|
101
|
+
const from_month = parseMonthDay(fromInput);
|
|
102
|
+
const to_month = parseMonthDay(toInput);
|
|
103
|
+
if (!from_month || !to_month) return null;
|
|
104
|
+
return { from_month, to_month };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @deprecated Use parseMonthDay for MM-DD strings. */
|
|
108
|
+
export function parseEvaluationEndDay(input: unknown): number | null {
|
|
109
|
+
const mmDd = parseMonthDay(input);
|
|
110
|
+
if (!mmDd) return null;
|
|
111
|
+
const parts = parseMmDdParts(mmDd);
|
|
112
|
+
return parts?.day ?? null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @deprecated Use parseEligibilityDateRange for MM-DD strings. */
|
|
116
|
+
export function parseMonthRange(
|
|
117
|
+
fromInput: unknown,
|
|
118
|
+
toInput: unknown
|
|
119
|
+
): { from_month: number; to_month: number } | null {
|
|
120
|
+
const range = parseEligibilityDateRange(fromInput, toInput);
|
|
121
|
+
if (!range) return null;
|
|
122
|
+
const from = parseMmDdParts(range.from_month);
|
|
123
|
+
const to = parseMmDdParts(range.to_month);
|
|
124
|
+
if (!from || !to) return null;
|
|
125
|
+
return { from_month: from.month, to_month: to.month };
|
|
126
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -448,6 +448,11 @@ export {
|
|
|
448
448
|
} from './models/EvaluationEligibilitySettingModel';
|
|
449
449
|
export {
|
|
450
450
|
isEvaluationEligibilityWindowOpen,
|
|
451
|
+
isCalendarMonthInEligibilityRange,
|
|
452
|
+
mmDdWindowsOverlap,
|
|
453
|
+
mmDdSortKey,
|
|
454
|
+
parseMonthDay,
|
|
455
|
+
parseEligibilityDateRange,
|
|
451
456
|
parseEvaluationEndDay,
|
|
452
457
|
parseMonthRange,
|
|
453
458
|
} from './helpers/evaluation-eligibility.utils';
|
|
@@ -2,33 +2,33 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
|
|
2
2
|
import { BaseModel } from './BaseModel';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Cycle-based evaluation exception list: defines the evaluation window (MM-DD dates),
|
|
6
|
+
* last submission day, and employees excluded from evaluation for that cycle.
|
|
7
7
|
*/
|
|
8
8
|
@Entity({ name: 'evaluation_eligibility_settings' })
|
|
9
9
|
export class EvaluationEligibilitySetting extends BaseModel {
|
|
10
|
-
@Column({ type: '
|
|
11
|
-
|
|
10
|
+
@Column({ type: 'varchar', length: 255 })
|
|
11
|
+
cycle_name: string;
|
|
12
12
|
|
|
13
|
-
@Column({ type: '
|
|
14
|
-
|
|
13
|
+
@Column({ type: 'varchar', length: 100 })
|
|
14
|
+
cycle_code: string;
|
|
15
15
|
|
|
16
|
-
/** Evaluation window start
|
|
17
|
-
@Column({ type: '
|
|
18
|
-
from_month:
|
|
16
|
+
/** Evaluation window start date as MM-DD (e.g. 01-01). */
|
|
17
|
+
@Column({ type: 'varchar', length: 5 })
|
|
18
|
+
from_month: string;
|
|
19
19
|
|
|
20
|
-
/** Evaluation window end
|
|
21
|
-
@Column({ type: '
|
|
22
|
-
to_month:
|
|
20
|
+
/** Evaluation window end date as MM-DD (e.g. 05-31), inclusive. */
|
|
21
|
+
@Column({ type: 'varchar', length: 5 })
|
|
22
|
+
to_month: string;
|
|
23
23
|
|
|
24
|
-
/** Last day
|
|
25
|
-
@Column({ type: '
|
|
26
|
-
evaluation_end_date:
|
|
24
|
+
/** Last submission date as MM-DD; day applies each month in the window (e.g. 04-12 → through the 12th). */
|
|
25
|
+
@Column({ type: 'varchar', length: 5 })
|
|
26
|
+
evaluation_end_date: string;
|
|
27
27
|
|
|
28
28
|
@Column({ type: 'boolean', default: true })
|
|
29
29
|
is_active: boolean;
|
|
30
30
|
|
|
31
|
-
/** Maximum employees allowed in one evaluation request for this
|
|
31
|
+
/** Maximum employees allowed in one evaluation request for this cycle window. */
|
|
32
32
|
@Column({ type: 'int', default: 50 })
|
|
33
33
|
max_employees_per_request: number;
|
|
34
34
|
|
|
@@ -45,7 +45,7 @@ export class EvaluationEligibilitySettingEmployee extends BaseModel {
|
|
|
45
45
|
@Column({ type: 'int' })
|
|
46
46
|
employee_id: number;
|
|
47
47
|
|
|
48
|
-
/** When true, employee is excluded from evaluation for this
|
|
48
|
+
/** When true, employee is excluded from evaluation for this cycle window. */
|
|
49
49
|
@Column({ type: 'boolean', default: true })
|
|
50
50
|
is_excluded_from_evaluation: boolean;
|
|
51
51
|
}
|