完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)

汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。
统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。
纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。

关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。
预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。
完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
这个提交包含在:
2026-09-19 13:11:04 +08:00
父节点 729d90576f
当前提交 1f8d5b658a
共修改 367 个文件,包含 21079 行新增和 12287 行删除
@@ -351,6 +351,7 @@ describe("payroll payslip routes", () => {
});
expect(frozenResponse.statusCode).toBe(409);
expect(frozenResponse.json().error.details).toEqual({ payslip: "payslip_not_editable", reasonCode: "payroll.payslip_not_editable" });
await app.close();
@@ -388,8 +389,111 @@ describe("payroll payslip routes", () => {
expect(response.statusCode).toBe(409);
expect(response.json().error.details.month).toBe("salary_profile_missing");
expect(response.json().error.details.reasonCode).toBe("payroll.profiles_missing");
expect(state.payslips).toHaveLength(0);
await app.close();
});
});
describe("payroll month guards", () => {
it.each(["2026-00", "2026-13", "2026-1", "invalid", ""].flatMap((month) => [
["GET", "/api/payroll/payslips", month],
["POST", "/api/payroll/payslips/generate", month],
["POST", "/api/payroll/payslips/issue", month],
]))("rejects %s %s month=%s before business access", async (method, url, month) => {
const { app } = await buildApp();
try {
const response = await app.inject({ method, url: method === "GET" ? `${url}?month=${month}` : url, ...(method === "POST" ? { payload: { month } } : {}) });
expect(response.statusCode).toBe(400);
expect(response.json().error.code).toBe("VALID_001");
expect(response.json().error.details.reasonCode).toBe("payroll.month_invalid");
if (["2026-00", "2026-13"].includes(month)) expect(response.json().error.details).toEqual({ month: "invalid_month", reasonCode: "payroll.month_invalid" });
for (const [key, group] of Object.entries(app.prisma)) {
if (key === "$transaction") expect(group).not.toHaveBeenCalled();
else for (const fn of Object.values(group)) expect(fn).not.toHaveBeenCalled();
}
} finally { await app.close(); }
});
it.each(["2024-01", "2024-02", "2024-12"])("queries the exact UTC month for %s", async (month) => {
const { app } = await buildApp();
try {
const response = await app.inject({ method: "GET", url: `/api/payroll/payslips?month=${month}` });
expect(response.statusCode).toBe(200);
expect(response.json().data.month).toBe(month);
expect(app.prisma.payslip.findMany.mock.calls[0][0].where.pay_month).toEqual(new Date(`${month}-01T00:00:00.000Z`));
} finally { await app.close(); }
});
});
describe("payslip operation reasons", () => {
function expectNoWrites(app) {
for (const fn of [app.prisma.payslip.create, app.prisma.payslip.update, app.prisma.payslip.updateMany, app.prisma.payslipItem.createMany, app.prisma.payslipItem.deleteMany, app.prisma.auditLog.create]) {
expect(fn).not.toHaveBeenCalled();
}
}
it.each([
["already_generated", "month_already_generated", { month: "payslips_month_exists" }],
["no_employees", "active_employees_missing", { month: "no_active_employees" }],
["no_items", "enabled_items_missing", { items: "salary_items_empty" }],
])("explains generation blocker %s before writes", async (scenario, reason, details) => {
const { app, state } = await buildApp();
if (scenario === "already_generated") state.payslips.push({ pay_month: new Date("2026-04-01T00:00:00.000Z") });
if (scenario === "no_employees") state.employees.length = 0;
if (scenario === "no_items") state.salaryItems.length = 0;
try {
const response = await app.inject({ method: "POST", url: "/api/payroll/payslips/generate", payload: { month: "2026-04" } });
expect(response.statusCode).toBe(409);
expect(response.json().error.details).toEqual({ ...details, reasonCode: `payroll.${reason}` });
expectNoWrites(app);
} finally { await app.close(); }
});
it("explains a concurrent month uniqueness conflict without success audit", async () => {
const { app } = await buildApp();
app.prisma.payslip.create.mockRejectedValueOnce(Object.assign(new Error("synthetic conflict"), { code: "P2002" }));
try {
const response = await app.inject({ method: "POST", url: "/api/payroll/payslips/generate", payload: { month: "2026-04" } });
expect(response.statusCode).toBe(409);
expect(response.json().error.details).toEqual({ month: "payslips_month_exists", reasonCode: "payroll.month_already_generated" });
expect(app.prisma.payslipItem.createMany).not.toHaveBeenCalled();
expect(app.prisma.auditLog.create).not.toHaveBeenCalled();
} finally { await app.close(); }
});
it.each([false, true])("explains issue rejection with existing issued slips=%s", async (existing) => {
const { app, state } = await buildApp();
if (existing) state.payslips.push({ id: PAYSLIP_IDS[0], pay_month: new Date("2026-04-01T00:00:00.000Z"), status: "issued" });
try {
const response = await app.inject({ method: "POST", url: "/api/payroll/payslips/issue", payload: { month: "2026-04" } });
expect(response.statusCode).toBe(409);
expect(response.json().error.details).toEqual({ month: existing ? "payslips_already_issued" : "payslips_not_generated", reasonCode: existing ? "payroll.month_already_issued" : "payroll.month_not_generated" });
expectNoWrites(app);
} finally { await app.close(); }
});
it.each([
[{ items: [] }, "payroll.payslip_update_input_invalid"],
[{ items: [{ salaryItemId: "11111111-1111-4111-8111-111111111111", amount: "1.234" }] }, "payroll.amount_invalid"],
])("explains invalid payslip edit %j before reads", async (payload, reasonCode) => {
const { app } = await buildApp();
try {
const response = await app.inject({ method: "PATCH", url: `/api/payroll/payslips/${PAYSLIP_IDS[0]}`, payload });
expect(response.statusCode).toBe(400);
expect(response.json().error.details.reasonCode).toBe(reasonCode);
if (reasonCode === "payroll.amount_invalid") expect(response.json().error.details).toEqual({ items: { 0: { amount: "invalid_money_amount" } }, reasonCode });
expect(app.prisma.payslip.findUnique).not.toHaveBeenCalled();
expectNoWrites(app);
} finally { await app.close(); }
});
it("explains an unavailable item in an editable payslip before replacement", async () => {
const { app, state } = await buildApp();
state.payslips.push({ id: PAYSLIP_IDS[0], pay_month: new Date("2026-04-01T00:00:00.000Z"), employee_id: EMPLOYEE_ID, status: "draft" });
app.prisma.salaryItem.findMany.mockResolvedValueOnce([]);
try {
const response = await app.inject({ method: "PATCH", url: `/api/payroll/payslips/${PAYSLIP_IDS[0]}`, payload: { items: [{ salaryItemId: "11111111-1111-4111-8111-111111111111", amount: 100 }] } });
expect(response.statusCode).toBe(400);
expect(response.json().error.details).toEqual({ items: "salary_item_not_found", reasonCode: "payroll.item_not_found" });
expectNoWrites(app);
expect(app.prisma.$transaction).not.toHaveBeenCalled();
} finally { await app.close(); }
});
});