完善借款、编号、付款与预算并发及验收(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 行删除
@@ -2,6 +2,13 @@ import { expect, test, type Page, type Route } from "@playwright/test";
import { ADMIN, EMPLOYEE, login } from "./helpers/auth";
// These APIs are page-level fixtures; a service worker must not bypass them.
test.use({ serviceWorkers: "block" });
// Use the same scenario clock in the simulated API and browser. Wall-clock
// passage must not turn the pending-acknowledgement case into an overdue one.
const SCENARIO_NOW = new Date("2026-05-28T03:00:00.000Z");
type MockUserKey = "admin" | "employee";
type MockAnnouncement = {
@@ -71,7 +78,7 @@ function buildMe(user: MockUserKey) {
function mapAnnouncementForUser(item: MockAnnouncement, currentUser: MockUserKey) {
const me = buildMe(currentUser);
const acknowledged = item.acknowledgedEmployeeIds.has(me.id);
const overdue = Boolean(item.acknowledgementDueAt && new Date(item.acknowledgementDueAt).getTime() < Date.now());
const overdue = Boolean(item.acknowledgementDueAt && new Date(item.acknowledgementDueAt).getTime() < SCENARIO_NOW.getTime());
return {
...item,
acknowledgedEmployeeIds: undefined,
@@ -275,16 +282,21 @@ async function clearBrowserSession(page: Page) {
});
}
test("制度签收公告:管理员创建、员工签收、管理员看到统计变化", async ({ page }) => {
for (const scenario of [
{ title: "制度签收公告:管理员创建、员工签收、管理员看到统计变化", dueDate: "2026-06-03", status: "待签收", action: "确认签收" },
{ title: "制度签收公告:逾期员工补签后管理员统计同步变化", dueDate: "2026-05-27", status: "已逾期", action: "逾期补签" },
]) {
test(scenario.title, async ({ page }) => {
await page.clock.setFixedTime(SCENARIO_NOW);
await mockPolicyAckApis(page);
const title = `自动化制度签收 ${Date.now()}`;
const title = `自动化制度签收 ${scenario.dueDate}`;
await login(page, ADMIN);
await page.goto("/system/announcements");
await page.getByRole("tab", { name: "制度签收" }).click();
await page.getByPlaceholder("例如:费用报销制度签收").fill(title);
await page.getByPlaceholder("写清楚适用范围、入口和处理要求。").fill("请全员阅读并确认制度签收流程。");
await page.locator('input[type="date"]').fill("2026-06-03");
await page.locator('input[type="date"]').fill(scenario.dueDate);
await page.getByRole("button", { name: "创建制度签收公告" }).click();
await expect(page.getByText(title)).toBeVisible();
await expect(page.getByText("已签 0/2 · 待签 2")).toBeVisible();
@@ -293,9 +305,9 @@ test("制度签收公告:管理员创建、员工签收、管理员看到统
await login(page, EMPLOYEE);
await page.goto("/announcements");
await expect(page.getByText(title)).toBeVisible();
await expect(page.getByText("待签收")).toBeVisible();
await expect(page.getByText(scenario.status, { exact: true })).toBeVisible();
await page.getByLabel("我已阅读并理解该制度内容,确认按公司制度执行。").check();
await page.getByRole("button", { name: "确认签收" }).click();
await page.getByRole("button", { name: scenario.action, exact: true }).click();
await expect(page.getByText("已签收")).toBeVisible();
await expect(page.getByText(/完成本人签收/)).toBeVisible();
@@ -305,3 +317,4 @@ test("制度签收公告:管理员创建、员工签收、管理员看到统
await expect(page.getByText(title)).toBeVisible();
await expect(page.getByText("已签 1/2 · 待签 1")).toBeVisible();
});
}