完善借款、编号、付款与预算并发及验收(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 行删除
@@ -185,6 +185,7 @@ async function buildApp(options = {}) {
appTimezone: "Asia/Shanghai",
presenceBoardEnabled: true,
});
app.setErrorHandler((error, request, reply) => reply.status(error.statusCode || 500).send({ error: { code: error.code, details: error.details ?? {} } }));
app.decorate("prisma", {
employee: {
findMany: employeeFindMany,
@@ -394,3 +395,53 @@ describe("presence admin override routes", () => {
await app.close();
});
});
describe("presence calendar boundaries", () => {
const routes = [
["GET", "/presence/me", "date"], ["GET", "/presence/team", "date"], ["GET", "/presence/summary", "date"],
["GET", "/presence/me/history", "dateFrom"], ["GET", "/presence/me/history", "dateTo"],
["GET", "/presence/team/history", "dateFrom"], ["GET", "/presence/team/history", "dateTo"],
["PUT", "/presence/me", "date"], ["PUT", `/presence/team/${EMPLOYEE_ID}`, "date"],
];
it.each(routes)("rejects invalid calendar %s %s %s before reads/writes", async (method, url, field) => {
const { app, auditCreate, employeePresenceUpsert } = await buildApp();
try {
const input = { [field]: "2026-02-30" };
const response = await app.inject({ method, url: `/api${url}` + (method === "GET" ? `?${new URLSearchParams(input)}` : ""), ...(method === "PUT" ? { payload: { ...input, status: "office" } } : {}) });
expect(response.statusCode).toBe(400);
const reason = method === "PUT" ? (url === "/presence/me" ? "write_input_invalid" : "override_input_invalid") : url.includes("team/history") ? "team_history_query_invalid" : url.includes("history") ? "history_query_invalid" : "query_invalid";
expect(response.json().error.details.reasonCode).toBe(`presence.${reason}`);
expect(app.prisma.employeePresence.findMany).not.toHaveBeenCalled();
expect(employeePresenceUpsert).not.toHaveBeenCalled();
expect(auditCreate).not.toHaveBeenCalled();
} finally { await app.close(); }
});
it.each(["2024-02-29", "2026-04-30"])("accepts %s in self and admin writes", async (date) => {
const { app, employeePresenceUpsert } = await buildApp();
try {
for (const url of ["/api/presence/me", `/api/presence/team/${EMPLOYEE_ID}`]) {
const response = await app.inject({ method: "PUT", url, payload: { date, status: "office" } });
expect(response.statusCode).toBe(200);
expect(employeePresenceUpsert.mock.calls.at(-1)[0].create.date).toEqual(new Date(`${date}T00:00:00.000Z`));
}
} finally { await app.close(); }
});
it("keeps omitted date supported and null rejected for self writes", async () => {
const { app, employeePresenceUpsert } = await buildApp();
try {
const omitted = await app.inject({ method: "PUT", url: "/api/presence/me", payload: { status: "office" } });
expect(omitted.statusCode).toBe(200);
employeePresenceUpsert.mockClear();
const nullDate = await app.inject({ method: "PUT", url: "/api/presence/me", payload: { date: null, status: "office" } });
expect(nullDate.statusCode).toBe(400);
expect(employeePresenceUpsert).not.toHaveBeenCalled();
} finally { await app.close(); }
});
});
describe("presence specific error contracts", () => {
it("rejects invalid override id before employee reads", async () => { const { app, auditCreate, employeePresenceUpsert } = await buildApp(); try { const r = await app.inject({ method: "PUT", url: "/api/presence/team/bad", payload: { date: "2026-03-27", status: "office" } }); expect(r.statusCode).toBe(400); expect(r.json().error.details.reasonCode).toBe("presence.employee_id_invalid"); expect(app.prisma.employee.findUnique).not.toHaveBeenCalled(); expect(employeePresenceUpsert).not.toHaveBeenCalled(); expect(auditCreate).not.toHaveBeenCalled(); } finally { await app.close(); } });
it("retains feature rejection before queries", async () => { const { app, auditCreate, employeePresenceUpsert } = await buildApp(); app.config.presenceBoardEnabled = false; try { const r = await app.inject({ method: "GET", url: "/api/presence/me?date=bad" }); expect(r.statusCode).toBe(409); expect(r.json().error.details).toMatchObject({ featureFlag: "presence_board_v1_disabled", reasonCode: "presence.feature_disabled" }); expect(app.prisma.employeePresence.findMany).not.toHaveBeenCalled(); expect(employeePresenceUpsert).not.toHaveBeenCalled(); expect(auditCreate).not.toHaveBeenCalled(); } finally { await app.close(); } });
it("keeps approved leave readonly for self writes", async () => { const { app, auditCreate, employeePresenceUpsert } = await buildApp({ actor: { id: EMPLOYEE_ID, permissionCodes: [] }, leaveRows: [buildLeaveRow()] }); try { const r = await app.inject({ method: "PUT", url: "/api/presence/me", payload: { date: "2026-03-27", status: "office" } }); expect(r.statusCode).toBe(409); expect(r.json().error.details).toEqual({ date: "covered_by_approved_leave", reasonCode: "presence.approved_leave_readonly" }); expect(employeePresenceUpsert).not.toHaveBeenCalled(); expect(auditCreate).not.toHaveBeenCalled(); } finally { await app.close(); } });
});