205 行
7.3 KiB
JavaScript
205 行
7.3 KiB
JavaScript
const {
|
|
DEFAULT_APPROVAL_RULES,
|
|
DEFAULT_FEATURE_FLAGS,
|
|
ENABLED_EXPENSE_TYPES,
|
|
seedApprovalRules,
|
|
seedExpenseTypes,
|
|
seedFeatureFlags,
|
|
} = require("../../../src/scripts/seed-config");
|
|
|
|
describe("default travel expense types", () => {
|
|
it("keeps intercity bus tickets separate from the two travel allowances", () => {
|
|
expect(ENABLED_EXPENSE_TYPES).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ code: "travel_bus", name: "大巴车票", category: "travel" }),
|
|
expect.objectContaining({ code: "travel_meal", name: "伙食补助费", category: "travel" }),
|
|
expect.objectContaining({
|
|
code: "travel_local_transport_allowance",
|
|
name: "市内交通补助费",
|
|
category: "travel",
|
|
}),
|
|
]),
|
|
);
|
|
|
|
const travelCodes = ENABLED_EXPENSE_TYPES.filter((item) =>
|
|
["travel_bus", "travel_meal", "travel_local_transport_allowance"].includes(item.code),
|
|
);
|
|
expect(travelCodes).toHaveLength(3);
|
|
});
|
|
|
|
it("upserts the distinct travel types by stable code", async () => {
|
|
const upsert = vi.fn().mockResolvedValue({});
|
|
|
|
await seedExpenseTypes({ expenseType: { upsert } });
|
|
|
|
for (const [code, name] of [
|
|
["travel_bus", "大巴车票"],
|
|
["travel_meal", "伙食补助费"],
|
|
["travel_local_transport_allowance", "市内交通补助费"],
|
|
]) {
|
|
expect(upsert).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: { code },
|
|
update: expect.objectContaining({ name, enabled: true }),
|
|
create: expect.objectContaining({ code, name, enabled: true }),
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("reimbursement expense policy defaults", () => {
|
|
it("keeps only the two allowances invoice-free and replaces ambiguous selectable types", () => {
|
|
expect(ENABLED_EXPENSE_TYPES).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ code: "business_client_meal", name: "业务招待费" }),
|
|
expect.objectContaining({ code: "business_meeting", name: "会议费" }),
|
|
expect.objectContaining({ code: "business_training_fee", name: "培训费" }),
|
|
expect.objectContaining({ code: "assets_office_electronics", name: "办公电子设备" }),
|
|
expect.objectContaining({ code: "travel_meal", requiresInvoice: false }),
|
|
expect.objectContaining({ code: "travel_local_transport_allowance", requiresInvoice: false }),
|
|
]));
|
|
expect(ENABLED_EXPENSE_TYPES.some((item) => item.code === "business_training")).toBe(false);
|
|
expect(ENABLED_EXPENSE_TYPES.some((item) => item.code === "assets_it_hardware")).toBe(false);
|
|
});
|
|
|
|
it("provides employee-readable guidance for every selectable expense type", () => {
|
|
for (const expenseType of ENABLED_EXPENSE_TYPES) {
|
|
expect(expenseType.description, expenseType.code).toEqual(expect.any(String));
|
|
expect(expenseType.description.trim().length, expenseType.code).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("uses asset-registration guidance without inventing a prior-approval policy", () => {
|
|
const assetTypes = ENABLED_EXPENSE_TYPES.filter((item) => item.category === "assets");
|
|
expect(assetTypes.map((item) => item.code)).toEqual([
|
|
"assets_equipment",
|
|
"assets_furniture",
|
|
"assets_office_electronics",
|
|
]);
|
|
for (const expenseType of assetTypes) {
|
|
expect(expenseType.description).toContain("物品名称");
|
|
expect(expenseType.description).toContain("实际使用人或责任人");
|
|
expect(expenseType.description).toContain("存放位置");
|
|
expect(expenseType.description).not.toMatch(/事前批准|批准人|批准时间|批准依据/);
|
|
}
|
|
expect(assetTypes.find((item) => item.code === "assets_office_electronics")?.description)
|
|
.toContain("不等同于会计固定资产确认标准");
|
|
});
|
|
|
|
it("seeds the invoice guard disabled by default", () => {
|
|
expect(DEFAULT_FEATURE_FLAGS).toContainEqual({
|
|
flag_key: "reimbursement_invoice_guard_v1",
|
|
override_mode: "force_off",
|
|
rollout_type: "global",
|
|
department_ids: [],
|
|
note: "bootstrap default: force_off",
|
|
});
|
|
});
|
|
|
|
it("preserves finance-maintained account mappings during repeated init", async () => {
|
|
const upsert = vi.fn().mockResolvedValue({});
|
|
|
|
await seedExpenseTypes({ expenseType: { upsert } });
|
|
|
|
for (const call of upsert.mock.calls) {
|
|
expect(call[0].update).not.toHaveProperty("account_code_default");
|
|
expect(call[0].create).toEqual(expect.objectContaining({ account_code_default: null }));
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("small-team default reimbursement approval rules", () => {
|
|
it("uses finance for every owner fallback and finance review node", () => {
|
|
expect(DEFAULT_APPROVAL_RULES).toEqual([
|
|
expect.objectContaining({
|
|
name: "小额",
|
|
level1_role: "manager",
|
|
level2_role: "finance",
|
|
level3_role: null,
|
|
owner_self_fallback_role: "finance",
|
|
}),
|
|
expect.objectContaining({
|
|
name: "中额",
|
|
level1_role: "manager",
|
|
level2_role: "finance",
|
|
level3_role: null,
|
|
owner_self_fallback_role: "finance",
|
|
}),
|
|
expect.objectContaining({
|
|
name: "大额",
|
|
level1_role: "manager",
|
|
level2_role: "ceo",
|
|
level3_role: "finance",
|
|
owner_self_fallback_role: "finance",
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("preserves existing production approval rules instead of replacing them", async () => {
|
|
const approvalRule = {
|
|
count: vi.fn().mockResolvedValue(3),
|
|
createMany: vi.fn(),
|
|
deleteMany: vi.fn(),
|
|
};
|
|
|
|
await expect(seedApprovalRules({ approvalRule })).resolves.toEqual({
|
|
created: 0,
|
|
preserved: 3,
|
|
});
|
|
expect(approvalRule.createMany).not.toHaveBeenCalled();
|
|
expect(approvalRule.deleteMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("creates the three defaults only for an empty approval-rule table", async () => {
|
|
const approvalRule = {
|
|
count: vi.fn().mockResolvedValue(0),
|
|
createMany: vi.fn().mockResolvedValue({ count: 3 }),
|
|
};
|
|
|
|
await expect(seedApprovalRules({ approvalRule })).resolves.toEqual({
|
|
created: 3,
|
|
preserved: 0,
|
|
});
|
|
expect(approvalRule.createMany).toHaveBeenCalledWith({
|
|
data: DEFAULT_APPROVAL_RULES.map((item) => ({
|
|
...item,
|
|
enabled: true,
|
|
created_by: null,
|
|
updated_by: null,
|
|
})),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("trialrun payment batch baseline", () => {
|
|
it("creates payment_batch_v1 as globally enabled", () => {
|
|
expect(DEFAULT_FEATURE_FLAGS).toContainEqual({
|
|
flag_key: "payment_batch_v1",
|
|
override_mode: "force_on",
|
|
rollout_type: "global",
|
|
department_ids: [],
|
|
note: "bootstrap default: force_on",
|
|
});
|
|
});
|
|
|
|
it("preserves an existing payment flag instead of overwriting it", async () => {
|
|
const create = vi.fn(async ({ data }) => {
|
|
if (data.flag_key === "payment_batch_v1") {
|
|
throw Object.assign(new Error("unique conflict"), { code: "P2002" });
|
|
}
|
|
return data;
|
|
});
|
|
const prisma = { featureFlag: { create } };
|
|
|
|
await expect(seedFeatureFlags(prisma)).resolves.toBeUndefined();
|
|
expect(create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({
|
|
flag_key: "payment_batch_v1",
|
|
override_mode: "force_on",
|
|
}),
|
|
});
|
|
expect(prisma.featureFlag.update).toBeUndefined();
|
|
expect(prisma.featureFlag.upsert).toBeUndefined();
|
|
});
|
|
});
|