feat(G1,P16A,P16B): 收口主数据核心与节假日日历增强
这个提交包含在:
@@ -1,6 +1,7 @@
|
||||
const FEATURE_FLAG_KEYS = {
|
||||
erpSync: "erp_sync",
|
||||
invoiceVerification: "invoice_verification",
|
||||
leaveHolidayCalendar: "leave_holiday_calendar",
|
||||
};
|
||||
|
||||
const FEATURE_FLAG_KEY_LIST = Object.freeze(Object.values(FEATURE_FLAG_KEYS));
|
||||
@@ -62,6 +63,10 @@ function resolveEnvGate(config, flagKey) {
|
||||
return Boolean(config.invoiceVerificationEnabled);
|
||||
}
|
||||
|
||||
if (flagKey === FEATURE_FLAG_KEYS.leaveHolidayCalendar) {
|
||||
return Boolean(config.leaveHolidayCalendarEnabled);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,100 @@ function calculateRequestedDays({ startDate, endDate, startSession, endSession }
|
||||
return fromHalfDayUnits(endSlot - startSlot + 1);
|
||||
}
|
||||
|
||||
function isWeekendDate(dateValue) {
|
||||
const date = normalizeDateOnly(dateValue);
|
||||
const weekday = date.getUTCDay();
|
||||
return weekday === 0 || weekday === 6;
|
||||
}
|
||||
|
||||
function buildHolidayCalendarDayMap(calendarDays = []) {
|
||||
const map = new Map();
|
||||
for (const item of calendarDays) {
|
||||
const key = formatDateOnly(item.date);
|
||||
if (key) {
|
||||
map.set(key, item.day_type ?? item.dayType ?? null);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function isWorkingDateByCalendar(dateValue, calendarDayMap) {
|
||||
const dateKey = formatDateOnly(dateValue);
|
||||
const configuredType = dateKey ? calendarDayMap.get(dateKey) ?? null : null;
|
||||
|
||||
if (configuredType === "holiday") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (configuredType === "workday" || configuredType === "adjusted_workday") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isWeekendDate(dateValue);
|
||||
}
|
||||
|
||||
function countHalfDayUnitsForDate({ date, startDate, endDate, startSession, endSession }) {
|
||||
const normalizedDate = formatDateOnly(date);
|
||||
const startDateOnly = formatDateOnly(startDate);
|
||||
const endDateOnly = formatDateOnly(endDate);
|
||||
|
||||
if (normalizedDate === startDateOnly && normalizedDate === endDateOnly) {
|
||||
return toHalfDaySlot(endDate, endSession) - toHalfDaySlot(startDate, startSession) + 1;
|
||||
}
|
||||
|
||||
if (normalizedDate === startDateOnly) {
|
||||
return startSession === "pm" ? 1 : 2;
|
||||
}
|
||||
|
||||
if (normalizedDate === endDateOnly) {
|
||||
return endSession === "am" ? 1 : 2;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
function calculateRequestedDaysByWorkingCalendar({
|
||||
startDate,
|
||||
endDate,
|
||||
startSession,
|
||||
endSession,
|
||||
calendarDays = [],
|
||||
}) {
|
||||
const startSlot = toHalfDaySlot(startDate, startSession);
|
||||
const endSlot = toHalfDaySlot(endDate, endSession);
|
||||
|
||||
if (endSlot < startSlot) {
|
||||
throw buildLeaveValidationError({ leave: "invalid_range" });
|
||||
}
|
||||
|
||||
const calendarDayMap = buildHolidayCalendarDayMap(calendarDays);
|
||||
const current = normalizeDateOnly(startDate);
|
||||
const finalDate = normalizeDateOnly(endDate);
|
||||
let cursor = current;
|
||||
let totalUnits = 0;
|
||||
|
||||
while (cursor.getTime() <= finalDate.getTime()) {
|
||||
if (isWorkingDateByCalendar(cursor, calendarDayMap)) {
|
||||
totalUnits += countHalfDayUnitsForDate({
|
||||
date: cursor,
|
||||
startDate,
|
||||
endDate,
|
||||
startSession,
|
||||
endSession,
|
||||
});
|
||||
}
|
||||
|
||||
cursor = new Date(`${formatDateOnly(cursor)}T00:00:00.000Z`);
|
||||
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
||||
}
|
||||
|
||||
if (totalUnits <= 0) {
|
||||
throw buildLeaveValidationError({ leave: "non_working_range" });
|
||||
}
|
||||
|
||||
return fromHalfDayUnits(totalUnits);
|
||||
}
|
||||
|
||||
function leaveRangesOverlap(left, right) {
|
||||
const leftStart = toHalfDaySlot(left.startDate, left.startSession);
|
||||
const leftEnd = toHalfDaySlot(left.endDate, left.endSession);
|
||||
@@ -478,6 +572,7 @@ async function persistLeaveAttachment({ config, employeeId, filePart }) {
|
||||
module.exports = {
|
||||
DEFAULT_ATTACHMENT_MIME_TYPES,
|
||||
calculateRequestedDays,
|
||||
calculateRequestedDaysByWorkingCalendar,
|
||||
createLeaveLedgerEntry,
|
||||
consumeReservedLeave,
|
||||
currentLeavePeriod,
|
||||
|
||||
在新工单中引用
屏蔽一个用户