feat(G1,P16A,P16B): 收口主数据核心与节假日日历增强

这个提交包含在:
2026-03-07 17:29:03 +08:00
父节点 32a3d3724d
当前提交 195f46a2e3
共修改 35 个文件,包含 4556 行新增和 27 行删除
+95
查看文件
@@ -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,