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

这个提交包含在:
2026-03-07 17:29:03 +08:00
父节点 32a3d3724d
当前提交 195f46a2e3
共修改 35 个文件,包含 4556 行新增和 27 行删除
@@ -21,6 +21,14 @@ describe("Permission Constants", () => {
expect(hasPermission(resolvePermissionsForRoles(["hr"]), "leave.approve")).toBe(false);
});
it("grants master data permissions to finance and finance manager roles", () => {
expect(hasPermission(resolvePermissionsForRoles(["finance"]), "masterdata.view")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["finance"]), "masterdata.manage")).toBe(false);
expect(hasPermission(resolvePermissionsForRoles(["finance_mgr"]), "masterdata.manage")).toBe(true);
expect(hasPermission(resolvePermissionsForRoles(["ceo"]), "masterdata.view")).toBe(true);
});
it("grants hr leave view-all and manage permissions", () => {
const hrPermissions = resolvePermissionsForRoles(["hr"]);
+25 -11
查看文件
@@ -39,7 +39,7 @@ describe("evaluateFeatureFlag", () => {
it("returns env_hard_off when env gate is closed", async () => {
const result = await evaluateFeatureFlag({
prisma: buildPrisma(),
config: { erpSyncEnabled: false, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: false, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
});
@@ -50,7 +50,7 @@ describe("evaluateFeatureFlag", () => {
it("returns missing_flag_record when record does not exist", async () => {
const result = await evaluateFeatureFlag({
prisma: buildPrisma(),
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
});
@@ -68,7 +68,7 @@ describe("evaluateFeatureFlag", () => {
const result = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
});
@@ -87,7 +87,7 @@ describe("evaluateFeatureFlag", () => {
const result = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
});
@@ -106,14 +106,14 @@ describe("evaluateFeatureFlag", () => {
const missingDepartment = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
strictDepartment: true,
});
const blockedDepartment = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
departmentId: "dept-b",
strictDepartment: true,
@@ -121,7 +121,7 @@ describe("evaluateFeatureFlag", () => {
const allowedDepartment = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.erpSync,
departmentId: "dept-a",
strictDepartment: true,
@@ -144,7 +144,7 @@ describe("evaluateFeatureFlag", () => {
const result = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.invoiceVerification,
strictDepartment: false,
});
@@ -164,7 +164,7 @@ describe("evaluateFeatureFlag", () => {
const result = await evaluateFeatureFlag({
prisma,
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true },
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: FEATURE_FLAG_KEYS.invoiceVerification,
strictDepartment: true,
});
@@ -174,6 +174,19 @@ describe("evaluateFeatureFlag", () => {
});
});
describe("leave holiday calendar flag", () => {
it("uses the dedicated env gate", async () => {
const result = await evaluateFeatureFlag({
prisma: buildPrisma(),
config: { erpSyncEnabled: true, invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: false },
flagKey: FEATURE_FLAG_KEYS.leaveHolidayCalendar,
});
expect(result.enabled).toBe(false);
expect(result.reason).toBe("env_hard_off");
});
});
describe("listFeatureFlags", () => {
it("returns all known keys even if part of records are missing", async () => {
const prisma = buildPrisma({
@@ -188,13 +201,14 @@ describe("listFeatureFlags", () => {
invoiceVerificationEnabled: true,
});
expect(items).toHaveLength(2);
expect(items).toHaveLength(3);
expect(items.map((item) => item.flagKey).sort()).toEqual([
FEATURE_FLAG_KEYS.erpSync,
FEATURE_FLAG_KEYS.invoiceVerification,
FEATURE_FLAG_KEYS.leaveHolidayCalendar,
]);
const missingItem = items.find((item) => item.flagKey === FEATURE_FLAG_KEYS.invoiceVerification);
const missingItem = items.find((item) => item.flagKey === FEATURE_FLAG_KEYS.leaveHolidayCalendar);
expect(missingItem.id).toBeNull();
expect(missingItem.effectiveEnabled).toBe(false);
});
@@ -13,7 +13,7 @@ describe("FeatureFlags Service", () => {
it("returns env_hard_off when config disables it globally", async () => {
const result = await featureFlags.evaluateFeatureFlag({
prisma: {},
config: { invoiceVerificationEnabled: false },
config: { invoiceVerificationEnabled: false, leaveHolidayCalendarEnabled: true },
flagKey: featureFlags.FEATURE_FLAG_KEYS.invoiceVerification,
});
expect(result.enabled).toBe(false);
@@ -34,7 +34,7 @@ describe("FeatureFlags Service", () => {
const result = await featureFlags.evaluateFeatureFlag({
prisma: mockPrisma,
config: { invoiceVerificationEnabled: true },
config: { invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: featureFlags.FEATURE_FLAG_KEYS.invoiceVerification,
});
@@ -56,7 +56,7 @@ describe("FeatureFlags Service", () => {
const result = await featureFlags.evaluateFeatureFlag({
prisma: mockPrisma,
config: { invoiceVerificationEnabled: true },
config: { invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: featureFlags.FEATURE_FLAG_KEYS.invoiceVerification,
departmentId: "dep_B",
strictDepartment: true
@@ -80,7 +80,7 @@ describe("FeatureFlags Service", () => {
const result = await featureFlags.evaluateFeatureFlag({
prisma: mockPrisma,
config: { invoiceVerificationEnabled: true },
config: { invoiceVerificationEnabled: true, leaveHolidayCalendarEnabled: true },
flagKey: featureFlags.FEATURE_FLAG_KEYS.invoiceVerification,
departmentId: "dep_B",
});
+49
查看文件
@@ -1,5 +1,6 @@
const {
calculateRequestedDays,
calculateRequestedDaysByWorkingCalendar,
leaveRangesOverlap,
summarizeLeaveLedgerEntries,
} = require("../../../src/services/leave");
@@ -49,6 +50,54 @@ describe("Leave Service", () => {
});
});
describe("calculateRequestedDaysByWorkingCalendar", () => {
it("skips weekend days by default and respects adjusted workdays", () => {
expect(
calculateRequestedDaysByWorkingCalendar({
startDate: "2026-03-20",
endDate: "2026-03-22",
startSession: "am",
endSession: "pm",
calendarDays: [],
}),
).toBe(1);
expect(
calculateRequestedDaysByWorkingCalendar({
startDate: "2026-03-20",
endDate: "2026-03-22",
startSession: "am",
endSession: "pm",
calendarDays: [{ date: "2026-03-21", day_type: "adjusted_workday" }],
}),
).toBe(2);
});
it("skips configured holidays inside a weekday span", () => {
expect(
calculateRequestedDaysByWorkingCalendar({
startDate: "2026-03-16",
endDate: "2026-03-17",
startSession: "am",
endSession: "pm",
calendarDays: [{ date: "2026-03-17", day_type: "holiday" }],
}),
).toBe(1);
});
it("rejects ranges that become fully non-working after calendar adjustment", () => {
expect(() =>
calculateRequestedDaysByWorkingCalendar({
startDate: "2026-03-21",
endDate: "2026-03-22",
startSession: "am",
endSession: "pm",
calendarDays: [],
}),
).toThrow(/参数校验失败/);
});
});
describe("leaveRangesOverlap", () => {
it("detects overlapping half-day ranges", () => {
expect(