P2AUTO 收口自动化回归和样票门禁

这个提交包含在:
2026-05-16 20:53:08 +08:00
父节点 7a59fd715f
当前提交 edabce8561
共修改 7 个文件,包含 387 行新增和 8 行删除
+15 -4
查看文件
@@ -6,6 +6,7 @@ import { TEST_IDS } from "./helpers/testIds";
test.use({ serviceWorkers: "block" }); test.use({ serviceWorkers: "block" });
const ADMIN = { identifier: "[email protected]", password: "AdminPassword1234", name: "Admin" }; const ADMIN = { identifier: "[email protected]", password: "AdminPassword1234", name: "Admin" };
const MANAGER = { identifier: "[email protected]", password: "ManagerPassword1234", name: "Manager" };
function envelope(data: unknown) { function envelope(data: unknown) {
return { return {
@@ -52,6 +53,16 @@ function mockPasswordLogin(page: import("@playwright/test").Page) {
})), })),
}); });
}), }),
page.route("**/api/auth/refresh", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(envelope({
accessToken: "test-access-token",
expiresIn: 900,
})),
});
}),
]); ]);
} }
@@ -310,9 +321,9 @@ test("Portal 管理视角重排正确,备用模块默认折叠", async ({ page
await mockPortalHome(page, "manager"); await mockPortalHome(page, "manager");
await mockAttendanceToday(page); await mockAttendanceToday(page);
await mockNotificationList(page); await mockNotificationList(page);
await mockMe(page, ["admin"], "Admin"); await mockMe(page, ["manager"], "Manager");
await login(page, ADMIN); await login(page, MANAGER);
await expect(page.locator('[aria-label^="portal-card-"]').first()).toBeVisible(); await expect(page.locator('[aria-label^="portal-card-"]').first()).toBeVisible();
@@ -326,7 +337,7 @@ test("Portal 管理视角重排正确,备用模块默认折叠", async ({ page
"portal-card-weekly_help", "portal-card-weekly_help",
]); ]);
await expect(page.locator("summary").filter({ hasText: "今日管理重点 / 常用入口" })).toBeVisible(); await expect(page.locator("summary").filter({ hasText: "角色重点 / 常用入口" })).toBeVisible();
await expect(page.getByText("试点观测摘要")).toBeVisible(); await expect(page.getByText("试点观测摘要")).toBeVisible();
await expect(page.getByRole("link", { name: "采购申请" })).not.toBeVisible(); await expect(page.getByRole("link", { name: "采购申请" })).not.toBeVisible();
await page.locator("summary").filter({ hasText: "备用模块" }).click(); await page.locator("summary").filter({ hasText: "备用模块" }).click();
@@ -419,7 +430,7 @@ test("Portal 域管理角色也进入经理视图", async ({ page }) => {
await login(page, EMPLOYEE); await login(page, EMPLOYEE);
await expect(page.locator("summary").filter({ hasText: "今日管理重点 / 常用入口" })).toBeVisible(); await expect(page.locator("summary").filter({ hasText: "角色重点 / 常用入口" })).toBeVisible();
await expect(page.locator('[aria-label="portal-card-pending_approvals"]')).toBeVisible(); await expect(page.locator('[aria-label="portal-card-pending_approvals"]')).toBeVisible();
await expect(page.locator('[aria-label="portal-card-team_presence"]')).toBeVisible(); await expect(page.locator('[aria-label="portal-card-team_presence"]')).toBeVisible();
}); });
@@ -554,7 +554,7 @@ async function exerciseMobileRendering(page: Page, key: string, label: string) {
const detailMetrics = await expectRenderingStable(page, label, `${key}-mobile-reimbursement-detail`); const detailMetrics = await expectRenderingStable(page, label, `${key}-mobile-reimbursement-detail`);
expect(detailMetrics.stickyActionsVisible, `${label} sticky actions visible`).toBe(true); expect(detailMetrics.stickyActionsVisible, `${label} sticky actions visible`).toBe(true);
expect(detailMetrics.stickyButtonsSameRow, `${label} sticky buttons same row`).toBe(true); expect(detailMetrics.stickyButtonsSameRow, `${label} sticky buttons same row`).toBe(true);
expect(detailMetrics.stickyButtonMinHeight, `${label} sticky buttons touch height`).toBeGreaterThanOrEqual(44); expect(Math.round(detailMetrics.stickyButtonMinHeight), `${label} sticky buttons touch height`).toBeGreaterThanOrEqual(44);
} }
test.describe("WCM6 企业微信多手机移动渲染稳定化", () => { test.describe("WCM6 企业微信多手机移动渲染稳定化", () => {
+3
查看文件
@@ -22,6 +22,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
- 2026-05-16 P2-FIN-V1 已完成付款批次启用前本地/隔离硬化:后端 focused 回归、前端 typecheck/build、隔离付款批次闭环、权限直连审计、公网 readiness 和只读 review 均通过;本轮只修补 E2E 对当前状态胶囊和二次确认 UI 的断言,不改业务 API/schema/UI,不部署,公网 `payment_batch_v1` 继续关闭。 - 2026-05-16 P2-FIN-V1 已完成付款批次启用前本地/隔离硬化:后端 focused 回归、前端 typecheck/build、隔离付款批次闭环、权限直连审计、公网 readiness 和只读 review 均通过;本轮只修补 E2E 对当前状态胶囊和二次确认 UI 的断言,不改业务 API/schema/UI,不部署,公网 `payment_batch_v1` 继续关闭。
- 2026-05-16 P2-OCR-SAMPLE 已完成图片 OCR 样票治理和实现前置确认:当前 PDF-only 口径、`SAMPLE-IMG-*` 授权登记、脱敏证据、provider 分层和回滚设计已锁定;结论为 `HOLD_FOR_SAMPLE`,授权图片样票和真实图片 provider 回归证据形成前不进入 `P2-OCR-V1`,不开放图片 OCR。 - 2026-05-16 P2-OCR-SAMPLE 已完成图片 OCR 样票治理和实现前置确认:当前 PDF-only 口径、`SAMPLE-IMG-*` 授权登记、脱敏证据、provider 分层和回滚设计已锁定;结论为 `HOLD_FOR_SAMPLE`,授权图片样票和真实图片 provider 回归证据形成前不进入 `P2-OCR-V1`,不开放图片 OCR。
- 2026-05-16 P2Q 已完成 P2OCRS 后续任务队列规划:后续默认按图片样票 provider 回归门槛、UX8 真实反馈池、P2-FIN-LIVE 公网启用窗口、P2-OCR-V1、P2-WF、P2-BIZ 和持续上线门禁拆独立合同推进;本轮不改业务代码、不部署。 - 2026-05-16 P2Q 已完成 P2OCRS 后续任务队列规划:后续默认按图片样票 provider 回归门槛、UX8 真实反馈池、P2-FIN-LIVE 公网启用窗口、P2-OCR-V1、P2-WF、P2-BIZ 和持续上线门禁拆独立合同推进;本轮不改业务代码、不部署。
- 2026-05-16 P2AUTO 已完成自动化优先回归:后端 focused、前端 typecheck/build、高风险确认、隔离核心业务流、隔离付款批次闭环、公网 readiness/PDF live OCR、公网移动矩阵和企业微信 UA/渲染自动化均通过;PDF 样票和 live smoke 凭据可用,图片 provider 回归仍需仓库外 manifest 或 `PLAYWRIGHT_OCR_IMAGE_*` 安全登记授权图片样票,不开放图片 OCR,不启用公网 `payment_batch_v1`,未部署。
## 关键证据索引 ## 关键证据索引
@@ -51,6 +52,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
| P2-FIN-V1 | 已收口:付款批次启用前本地/隔离硬化完成;隔离付款批次闭环 1 passed、权限直连审计 8 passed、公网 readiness ready | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv`、`artifacts/qa-flow/business-flow-20260516-095719/summary.txt`、`artifacts/qa-flow/business-flow-20260516-095900/summary.txt` | | P2-FIN-V1 | 已收口:付款批次启用前本地/隔离硬化完成;隔离付款批次闭环 1 passed、权限直连审计 8 passed、公网 readiness ready | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv`、`artifacts/qa-flow/business-flow-20260516-095719/summary.txt`、`artifacts/qa-flow/business-flow-20260516-095900/summary.txt` |
| P2-OCR-SAMPLE | 已收口:图片 OCR 样票治理前置完成,结论 `HOLD_FOR_SAMPLE`;PDF live OCR 基线继续有效但不代表图片 OCR 通过 | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` | | P2-OCR-SAMPLE | 已收口:图片 OCR 样票治理前置完成,结论 `HOLD_FOR_SAMPLE`;PDF live OCR 基线继续有效但不代表图片 OCR 通过 | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` |
| P2Q | 已收口:P2OCRS 后续队列和启动门槛完成;所有实现或公网启用均需另建独立合同 | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` | | P2Q | 已收口:P2OCRS 后续队列和启动门槛完成;所有实现或公网启用均需另建独立合同 | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` |
| P2AUTO | 已收口:自动化优先回归和样票 provider 门禁完成;核心隔离业务流通过、付款批次隔离闭环 1 passed、公网 readiness/PDF live OCR 通过、公网移动矩阵 6 passed、企业微信渲染矩阵 8 passed;图片 provider 回归保持 `HOLD_FOR_SAMPLE` | `issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv`、`artifacts/qa-flow/business-flow-20260516-203001/summary.txt`、`artifacts/qa-flow/business-flow-20260516-203856/summary.txt`、`artifacts/qa-flow/live-smoke-readiness-20260516-204157/` |
## 当前门禁 ## 当前门禁
@@ -70,6 +72,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
| P2-FIN-V1 启用前硬化 | 通过,未部署 | 后端 focused 回归 8 tests passed;`client` typecheck/build 通过;隔离临时库 `reimbursement-payment-batch-flow` 1 passed,覆盖创建批次、复核、二次确认执行、报销 paid、票据 archived、AP 台账和 ERP outbox;权限直连审计 8 passed;公网只做 readiness,不启用 `payment_batch_v1` | | P2-FIN-V1 启用前硬化 | 通过,未部署 | 后端 focused 回归 8 tests passed;`client` typecheck/build 通过;隔离临时库 `reimbursement-payment-batch-flow` 1 passed,覆盖创建批次、复核、二次确认执行、报销 paid、票据 archived、AP 台账和 ERP outbox;权限直连审计 8 passed;公网只做 readiness,不启用 `payment_batch_v1` |
| P2-OCR-SAMPLE 样票治理 | HOLD,未部署 | 已确认当前发票 OCR 仍为 PDF-only,图片仅作为普通附件;腾讯云 `image_native` 只证明技术路径存在,正式图片 OCR 仍需授权图片样票、脱敏确认和真实图片 provider 回归;未调用真实图片 OCR | | P2-OCR-SAMPLE 样票治理 | HOLD,未部署 | 已确认当前发票 OCR 仍为 PDF-only,图片仅作为普通附件;腾讯云 `image_native` 只证明技术路径存在,正式图片 OCR 仍需授权图片样票、脱敏确认和真实图片 provider 回归;未调用真实图片 OCR |
| P2Q 后续队列 | 已收口,未部署 | 图片 OCR provider 回归等待授权图片样票;UX8 等待真实反馈;P2-FIN-LIVE 等待公网启用窗口批准;后续 P2-OCR-V1/P2-WF/P2-BIZ 均需独立合同 | | P2Q 后续队列 | 已收口,未部署 | 图片 OCR provider 回归等待授权图片样票;UX8 等待真实反馈;P2-FIN-LIVE 等待公网启用窗口批准;后续 P2-OCR-V1/P2-WF/P2-BIZ 均需独立合同 |
| P2AUTO 自动化优先回归 | 通过,未部署 | 后端 focused 43 tests passed;`client` typecheck/build 通过;核心隔离业务流 auth/reimbursement/approval/leave/procurement/governance PASS;付款批次隔离 flag-on 1 passed;公网 `/ready`、public bundle、live smoke 3 passed、PDF live OCR 1 passed;公网移动矩阵 6 passed;企业微信渲染矩阵 8 passed;provider-only 样票脚本输出 `HOLD_FOR_SAMPLE`,因为未通过安全 manifest/env 登记授权图片样票且本地无 Tencent provider 凭据 |
## R7EP / OPS8 / QAF8 / UX8 / P2T 预检推进 ## R7EP / OPS8 / QAF8 / UX8 / P2T 预检推进
+3 -2
查看文件
@@ -43,6 +43,7 @@
| `P2-FIN-V1` | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv` | `EVIDENCE` | 付款批次启用前本地/隔离硬化已完成;后端 focused 回归、前端构建、隔离闭环、权限审计和公网 readiness 通过,公网仍不启用 `payment_batch_v1` | | `P2-FIN-V1` | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv` | `EVIDENCE` | 付款批次启用前本地/隔离硬化已完成;后端 focused 回归、前端构建、隔离闭环、权限审计和公网 readiness 通过,公网仍不启用 `payment_batch_v1` |
| `P2-OCR-SAMPLE` | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` | `EVIDENCE` | 图片 OCR 样票治理和实现前置确认已完成;结论 `HOLD_FOR_SAMPLE`,当前保持 PDF-only,不开放图片 OCR | | `P2-OCR-SAMPLE` | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` | `EVIDENCE` | 图片 OCR 样票治理和实现前置确认已完成;结论 `HOLD_FOR_SAMPLE`,当前保持 PDF-only,不开放图片 OCR |
| `P2Q` | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` | `EVIDENCE` | P2OCRS 后续任务队列已锁定;后续实现均需另建独立合同,当前不部署、不改业务代码 | | `P2Q` | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` | `EVIDENCE` | P2OCRS 后续任务队列已锁定;后续实现均需另建独立合同,当前不部署、不改业务代码 |
| `P2AUTO` | `issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv` | `EVIDENCE` | 自动化优先回归已收口;后端/前端/隔离业务流/公网 readiness/PDF live OCR/移动与企业微信自动化均有证据,图片 provider 回归仍需安全登记的授权图片样票和本地 provider 凭据 |
旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。 旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。
@@ -50,8 +51,8 @@
| Doc_ID | Path | Category | Authority | Status | Owner | Related_Issues | Evidence | Supersedes | Last_Audited | Audit_Method | Risk | Notes | | Doc_ID | Path | Category | Authority | Status | Owner | Related_Issues | Evidence | Supersedes | Last_Audited | Audit_Method | Risk | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `IDX` | `docs/文档索引.md` | 入口索引 | 最高文档入口 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q` | `rg` | none | 2026-05-16 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么 | | `IDX` | `docs/文档索引.md` | 入口索引 | 最高文档入口 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO` | `rg` | none | 2026-05-16 | `node scripts/qa/audit-docs.mjs --check-registry` | medium | 判断当前该读什么 |
| `REG` | `docs/文档事实源状态矩阵.md` | 文档台账 | 文档分类和状态事实源 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q` | `audit-docs` | none | 2026-05-16 | `audit-docs --check-registry` | medium | 判断文档权威性 | | `REG` | `docs/文档事实源状态矩阵.md` | 文档台账 | 文档分类和状态事实源 | `CURRENT` | product/dev | `GOV8,QAOPS9,P2D,P2-ORG,P2-FIN-DECISION,P2-FIN-V1,P2-OCR-SAMPLE,P2Q,P2AUTO` | `audit-docs` | none | 2026-05-16 | `audit-docs --check-registry` | medium | 判断文档权威性 |
| `PRD-MVP` | `docs/海音达OA-MVP需求规格说明书.md` | 需求事实源 | PRD 主事实源 | `CURRENT` | product | `REQ8,P2T,GOV8` | Issue CSV | `docs/requirements-specification.md` | 2026-05-13 | `rg` | high | 定义做什么、不做什么和验收口径 | | `PRD-MVP` | `docs/海音达OA-MVP需求规格说明书.md` | 需求事实源 | PRD 主事实源 | `CURRENT` | product | `REQ8,P2T,GOV8` | Issue CSV | `docs/requirements-specification.md` | 2026-05-13 | `rg` | high | 定义做什么、不做什么和验收口径 |
| `PRODUCT-SCOPE` | `docs/产品口径.md` | 需求事实源 | 产品边界事实源 | `CURRENT` | product | `REQ8,P2T` | `rg` | none | 2026-05-13 | `rg` | high | 记录已拍板产品边界 | | `PRODUCT-SCOPE` | `docs/产品口径.md` | 需求事实源 | 产品边界事实源 | `CURRENT` | product | `REQ8,P2T` | `rg` | none | 2026-05-13 | `rg` | high | 记录已拍板产品边界 |
| `DESIGN-UI` | `docs/产品设计规则.md` | 设计事实源 | PC/移动/企业微信设计事实源 | `CURRENT` | design/frontend | `MIA7,MIF7,GOV8,DG8,DG9` | Playwright/视觉审查/DG8/DG9 | none | 2026-05-13 | `rg` | high | 页面改动必须读取;DG8 后飞书为主 UI/布局参考;DG9 后按页面分层和样板选型 | | `DESIGN-UI` | `docs/产品设计规则.md` | 设计事实源 | PC/移动/企业微信设计事实源 | `CURRENT` | design/frontend | `MIA7,MIF7,GOV8,DG8,DG9` | Playwright/视觉审查/DG8/DG9 | none | 2026-05-13 | `rg` | high | 页面改动必须读取;DG8 后飞书为主 UI/布局参考;DG9 后按页面分层和样板选型 |
+3 -1
查看文件
@@ -20,7 +20,7 @@
## 最近事实源链 ## 最近事实源链
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q。 R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO。
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。 - R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。 - ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。
@@ -37,6 +37,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
- P2-FIN-V1 已完成付款批次启用前本地/隔离硬化:后端 focused 回归、前端 typecheck/build、隔离付款批次闭环、权限直连审计和公网 readiness 均通过;本轮只硬化 E2E 断言,不新增 API/schema/feature flag/UI,不部署,公网 `payment_batch_v1` 继续关闭。 - P2-FIN-V1 已完成付款批次启用前本地/隔离硬化:后端 focused 回归、前端 typecheck/build、隔离付款批次闭环、权限直连审计和公网 readiness 均通过;本轮只硬化 E2E 断言,不新增 API/schema/feature flag/UI,不部署,公网 `payment_batch_v1` 继续关闭。
- P2-OCR-SAMPLE 已完成图片 OCR 样票治理和实现前置确认:当前 PDF-only 口径、样票脱敏、provider 回归分层和回滚条件已锁定;结论为 `HOLD_FOR_SAMPLE`,需授权图片样票和真实图片 provider 回归证据后才可进入 `P2-OCR-V1`。 - P2-OCR-SAMPLE 已完成图片 OCR 样票治理和实现前置确认:当前 PDF-only 口径、样票脱敏、provider 回归分层和回滚条件已锁定;结论为 `HOLD_FOR_SAMPLE`,需授权图片样票和真实图片 provider 回归证据后才可进入 `P2-OCR-V1`。
- P2Q 是 P2OCRS 后续任务队列合同:只锁定 `P2-OCR-IMAGE-REGRESSION`、`UX8`、`P2-FIN-LIVE`、`P2-OCR-V1`、`P2-WF`、`P2-BIZ` 和持续上线门禁的启动条件,不启动实现、不部署。 - P2Q 是 P2OCRS 后续任务队列合同:只锁定 `P2-OCR-IMAGE-REGRESSION`、`UX8`、`P2-FIN-LIVE`、`P2-OCR-V1`、`P2-WF`、`P2-BIZ` 和持续上线门禁的启动条件,不启动实现、不部署。
- P2AUTO 是 P2Q 后自动化优先回归合同:后端 focused、前端构建、高风险确认、隔离核心业务流、隔离付款批次闭环、公网 readiness/PDF live OCR、公网移动矩阵和企业微信 UA/渲染自动化均已完成;PDF 样票和 live smoke 凭据可用,图片样票仍需通过仓库外 manifest 或 `PLAYWRIGHT_OCR_IMAGE_*` 安全登记后才能进入真实图片 provider 回归。
## 当前活跃治理流 ## 当前活跃治理流
@@ -60,6 +61,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
| `P2-FIN-V1` | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv` | 已收口:付款批次启用前本地/隔离硬化完成 | 后端付款批次/AP/导出 focused 回归、前端构建、隔离闭环、权限审计、公网 readiness、flag off 回滚口径 | 不公网启用 `payment_batch_v1`、不部署、不接真实支付、不写 ERP 实时凭证 | | `P2-FIN-V1` | `issues/2026-05-16_09-42-43-p2-fin-payment-batch-v1.csv` | 已收口:付款批次启用前本地/隔离硬化完成 | 后端付款批次/AP/导出 focused 回归、前端构建、隔离闭环、权限审计、公网 readiness、flag off 回滚口径 | 不公网启用 `payment_batch_v1`、不部署、不接真实支付、不写 ERP 实时凭证 |
| `P2-OCR-SAMPLE` | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` | 已收口:图片 OCR 样票治理前置完成,结论 `HOLD_FOR_SAMPLE` | PDF-only 当前边界、`SAMPLE-IMG-*` 授权登记口径、脱敏证据规则、mock/PDF/真实图片 provider 分层、feature flag 和回滚设计 | 不开放图片 OCR、不新增入口或 feature flag、不调用真实图片 provider、不保存真实票据、不部署 | | `P2-OCR-SAMPLE` | `issues/2026-05-16_10-15-21-p2-ocr-image-sample-governance.csv` | 已收口:图片 OCR 样票治理前置完成,结论 `HOLD_FOR_SAMPLE` | PDF-only 当前边界、`SAMPLE-IMG-*` 授权登记口径、脱敏证据规则、mock/PDF/真实图片 provider 分层、feature flag 和回滚设计 | 不开放图片 OCR、不新增入口或 feature flag、不调用真实图片 provider、不保存真实票据、不部署 |
| `P2Q` | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` | 已收口:P2OCRS 后续队列和启动门槛完成 | 图片 OCR provider 回归、UX8、付款批次公网启用、图片 OCR V1、审批通知、轻业务增强和持续上线门禁的后续合同顺序 | 不直接启动 HOLD 项、不新建综合规划文档、不改业务代码、不部署 | | `P2Q` | `issues/2026-05-16_10-35-22-p2-post-p2ocrs-followup-queue.csv` | 已收口:P2OCRS 后续队列和启动门槛完成 | 图片 OCR provider 回归、UX8、付款批次公网启用、图片 OCR V1、审批通知、轻业务增强和持续上线门禁的后续合同顺序 | 不直接启动 HOLD 项、不新建综合规划文档、不改业务代码、不部署 |
| `P2AUTO` | `issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv` | 已收口:自动化优先回归和样票 provider 门禁完成 | 凭据/样票掩码核对、后端 focused、前端构建、高风险确认、隔离业务流、隔离付款批次、公网 readiness/PDF live OCR、移动/企业微信自动化、OCR provider-only 脚本 | 不部署、不开放图片 OCR、不打开公网 `payment_batch_v1`、不自动选择未登记图片样票 |
旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。 旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
@@ -0,0 +1,11 @@
"ID","Title","Description","Acceptance","Test_Method","Tools","Dev_Status","Review1_Status","Regression_Status","Files","Dependencies","Notes"
"P2AUTO-0","P2 自动化回归合同建档","建立 R7E 后 P2 自动化优先回归合同,只做自动化验证、样票安全核对和证据回填,不启动业务功能开发。","1) 本 CSV 校验通过;2) 范围限定自动化、QA 脚本和既有证据文档;3) 不部署、不执行生产恢复、不重置数据库;4) 不打开公网 payment_batch_v1;5) 不开放图片 OCR。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv;人工核对不改业务 API/schema/UI。","python3|manual","DONE","DONE","DONE","issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv","[]","Scope=automation-first; baseline=7a59fd7; validate_issues_csv passed; no runtime change; no deployment; public payment_batch_v1 remains off."
"P2AUTO-1","凭据与样票掩码核对","加载本机安全 live smoke 配置并核对公网、企业微信、PDF live OCR readiness;只输出 ready 状态,不输出 secret、真实样票路径或票面信息。","1) liveSmokeReady=true;2) liveOcrReady=true;3) PDF 样票不再被记录为缺失;4) 输出不含密码、token 或真实路径;5) 图片样票若未登记则只作为后续缺口记录。","受影响流程:公网 live smoke、PDF live OCR、企业微信 config readiness;验证步骤:set -a && source ~/.hyb-oa/live-smoke.env && set +a;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com node scripts/qa/check-live-smoke-env.mjs --strict;补充掩码脚本只统计 PLAYWRIGHT_OCR_IMAGE_* 是否存在,不打印路径。","bash|node|manual","DONE","DONE","DONE","scripts/qa/check-live-smoke-env.mjs|external","[""P2AUTO-0""]","live smoke env strict check PASS: publicReady=true, liveSmokeReady=true, liveOcrReady=true; PDF sample env present; image sample is not registered via OCR_PROVIDER_SAMPLE_MANIFEST or PLAYWRIGHT_OCR_IMAGE_*; no secret/path printed."
"P2AUTO-2","合同与文档门禁","校验当前 P2Q、P2OCRS、P2FINV1、UX8、P2AUTO 合同和文档登记,确认事实源链没有把 PDF 样票误写为缺失。","1) 相关 CSV 校验通过;2) audit-docs 无 error;3) 文档关键词命中可解释;4) PDF 样票已具备口径被登记;5) 不新建综合规划文档。","流程级验证:validate_issues_csv.py 本 CSV、P2Q、P2-OCR-SAMPLE、P2-FIN-V1、UX8;node scripts/qa/audit-docs.mjs --check-registry;rg -n 'P2AUTO|P2Q|P2OCRS|P2-FIN-V1|UX8|PDF live OCR|图片样票|HOLD_FOR_SAMPLE' docs issues。","python3|node|rg","DONE","DONE","DONE","issues/*.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md","[""P2AUTO-0""]","CSV gates passed for P2AUTO, P2Q, P2OCRS, P2FINV1 and UX8; audit-docs errors=0 warnings=1 existing unregistered docs files=24; docs updated in existing index/matrix/report only."
"P2AUTO-3","后端自动化门禁","执行 Prisma validate、关键路由/服务语法检查和 OCR、付款批次、导出、企业微信、通知 focused 单测。","1) Prisma validate 通过;2) node --check 通过;3) focused npm test 通过或有明确非业务前置失败;4) 不改 server/src 业务逻辑;5) 不打印 secret。","受影响流程:发票 OCR、OCR job、付款批次、AP 导出、workflow、企业微信认证、通知;验证步骤:cd server && npm run prisma:validate;cd server && node --check src/routes/invoicePool.js src/jobs/ocrJob.js src/services/ocr/tencent.js src/routes/paymentBatches.js src/routes/exports.js src/routes/workflow.js src/routes/wecomAuth.js;cd server && npm test -- tests/unit/services/ocr.tencent.test.js tests/unit/jobs/ocrJob.test.js tests/unit/routes/invoicePool.upload.test.js tests/unit/routes/paymentBatches.reimbursement.test.js tests/unit/routes/exports.apOperations.test.js tests/unit/routes/wecomAuth.test.js tests/unit/routes/notifications.test.js。","npm|node","DONE","DONE","DONE","server/src|server/tests/unit","[""P2AUTO-1""]","PASS: server prisma:validate; node --check invoicePool/ocrJob/tencent/paymentBatches/exports/workflow/wecomAuth; focused npm test 7 files/43 tests passed; no server/src business change."
"P2AUTO-4","前端自动化门禁","执行前端 typecheck、build 和高风险确认、审批确认、发票 PDF-only、权限直连等 Playwright 自动化。","1) typecheck 通过;2) build 通过;3) 指定 Playwright 用例通过或失败可归因;4) 不新增前端页面或业务入口;5) 企业微信真机不被自动化伪装为通过。","受影响流程:PC 高风险动作、审批确认、发票上传、权限直连、移动入口;验证步骤:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/system-action-confirmation.spec.ts tests/e2e/workflow-action-confirm-ui.spec.ts tests/e2e/r111-invoice-reimbursement-guard.spec.ts tests/e2e/permission-direct-access.spec.ts --workers=1 --reporter=list。","npm|playwright","DONE","DONE","DONE","client/src|client/tests/e2e","[""P2AUTO-3""]","PASS: client typecheck/build; high-risk confirmation 4 passed; r111 invoice guard and permission direct access covered by business-flow report business-flow-20260516-203001; test-only stale assertions updated for current labels/role boundary."
"P2AUTO-5","隔离业务流自动化","在隔离临时库中运行核心业务流 flag-off 门禁,并单独运行付款批次 flag-on 闭环;付款批次结果不得解释为公网启用。","1) 核心业务流通过或失败有明确用例;2) payment_batch_v1 公网保持关闭;3) 付款批次仅隔离环境 flag-on;4) 报告路径写入 Notes;5) 不执行生产数据写入。","受影响流程:登录、企业微信入口、报销、审批、请假、采购、权限治理、付款批次隔离闭环;验证步骤:BUSINESS_FLOW_GROUPS=auth,reimbursement,approval,leave,procurement,governance BUSINESS_FLOW_EXCLUDE_SPECS=tests/e2e/reimbursement-payment-batch-flow.spec.ts bash scripts/qa/run-business-flow-gates.sh local;cd client && E2E_BASE_URL=<local-or-isolated-url> npx playwright test tests/e2e/reimbursement-payment-batch-flow.spec.ts --workers=1 --reporter=list。","bash|playwright","DONE","DONE","DONE","scripts/qa/run-business-flow-gates.sh|client/tests/e2e/reimbursement-payment-batch-flow.spec.ts|artifacts/qa-flow","[""P2AUTO-4""]","PASS: core isolated business flow auth/reimbursement/approval/leave/procurement/governance passed, report artifacts/qa-flow/business-flow-20260516-203001/summary.txt; payment batch isolated flag-on flow 1 passed, report artifacts/qa-flow/business-flow-20260516-203856/summary.txt; public payment_batch_v1 remains off."
"P2AUTO-6","公网自动化门禁","在公网只跑 /ready、public bundle、live smoke、PDF live OCR、pending OCR 扫描和 live smoke Playwright,不执行公网付款批次写入。","1) /ready 通过;2) public bundle findings=0;3) live smoke 通过;4) PDF live OCR 进入可解释终态;5) pending OCR 缺 DATABASE_URL 时只作非阻断 SKIP。","受影响流程:公网健康、登录 smoke、PDF live OCR、企业微信 config、公网资源;验证步骤:set -a && source ~/.hyb-oa/live-smoke.env && set +a;QA_ISSUE_ID=P2AUTO LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;cd client && E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/live-smoke.spec.ts --workers=1 --reporter=list。","bash|node|playwright","DONE","DONE","DONE","scripts/qa/run-live-smoke-readiness.sh|client/tests/e2e/live-smoke.spec.ts|artifacts/qa-flow","[""P2AUTO-5""]","PASS: live readiness report artifacts/qa-flow/live-smoke-readiness-20260516-204157; /ready ok, public bundle findings=0, live smoke 3 passed, PDF live OCR 1 passed with duplicate-invoice explainable terminal state; pending OCR missing local DATABASE_URL recorded SKIP; no public payment batch writes."
"P2AUTO-7","移动与企业微信自动化门禁","执行移动矩阵、企业微信 UA、入口/绑定/deeplink 自动化;真机只保留少量真实差异确认,不阻断可自动化收口。","1) 移动矩阵通过;2) WeCom UA/入口/绑定自动化通过;3) 企业微信真机差异不被自动化替代;4) 发现移动缺陷进入 UX8;5) 不新增移动业务能力。","受影响流程:/mobile、审批、报销、企业微信入口、绑定、deeplink 和移动布局;验证步骤:cd client && E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list;cd client && npx playwright test tests/e2e/wecom-entry.spec.ts tests/e2e/wecom-first-bind.spec.ts tests/e2e/r6-wecom-mobile-rendering-hardening.spec.ts --workers=1 --reporter=list。","playwright|manual","DONE","DONE","DONE","client/tests/e2e|docs/trialrun/人工试运行报告.md","[""P2AUTO-6""]","PASS: public DG7/R124 mobile matrix 6 passed; local auth business-flow covered wecom-entry/wecom-first-bind; R6 WeCom rendering matrix 8 passed after subpixel test assertion normalization; real-device delta remains manual-only when user requests."
"P2AUTO-8","OCR provider 样票自动化缺口","新增并运行 provider-only 样票 QA 脚本;PDF 样票可回归,图片样票未在 manifest 或环境变量登记时记录 SKIP,不得写 PASS。","1) scripts/qa/check-ocr-provider-samples.mjs 存在且 node --check 通过;2) 无样票时输出 SKIP;3) 有样票时输出脱敏 JSON 摘要;4) 不输出票面、税号、手机号、二维码、完整号码或真实路径;5) 不开放图片 OCR。","受影响流程:PDF/image provider 样票 -> Tencent OCR -> 字段命中摘要 -> GO/HOLD/FREEZE 决策;验证步骤:node --check scripts/qa/check-ocr-provider-samples.mjs;OCR_PROVIDER_SAMPLE_MANIFEST=/safe/ocr-samples.json node scripts/qa/check-ocr-provider-samples.mjs --provider=tencent --json;无授权图片样票或 manifest 时写 SKIP。","node|manual","DONE","DONE","DONE","scripts/qa/check-ocr-provider-samples.mjs|external","[""P2AUTO-1""]","QA script added and node --check passed. Provider-only run with current safe env found PDF sample only and no image manifest/env; local Tencent provider credentials not present, so output status=SKIP decision=HOLD_FOR_SAMPLE. Do not auto-select arbitrary image candidates; image OCR remains closed."
"P2AUTO-9","收口回填与中文提交","汇总 P2AUTO 自动化证据,回填 CSV、人工试运行报告、索引和事实源矩阵,完成最终校验和中文 commit。","1) 已执行项按真实 Test_Method 回填;2) 未执行外部项记录 SKIP 或后续条件;3) validate、audit-docs、rg、git diff --check 通过;4) 中文 commit 引用 P2AUTO;5) 明确未部署。","流程级验证:validate_issues_csv.py 本 CSV;node scripts/qa/audit-docs.mjs --check-registry;rg -n 'P2AUTO|PDF live OCR|图片样票|payment_batch_v1|SKIP|live smoke|企业微信' docs issues scripts;git diff --check;git status --short。","python3|node|rg|git|manual","DONE","DONE","DONE","issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md","[""P2AUTO-2"",""P2AUTO-3"",""P2AUTO-4"",""P2AUTO-5"",""P2AUTO-6"",""P2AUTO-7"",""P2AUTO-8""]","Closeout=PASS; validate_issues_csv passed; audit-docs errors=0 warnings=1 existing unregistered docs files=24; rg keyword sweep explainable; git diff --check passed; not deployed; no business API/schema/UI/runtime change."
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 P2AUTO-0 P2 自动化回归合同建档 建立 R7E 后 P2 自动化优先回归合同,只做自动化验证、样票安全核对和证据回填,不启动业务功能开发。 1) 本 CSV 校验通过;2) 范围限定自动化、QA 脚本和既有证据文档;3) 不部署、不执行生产恢复、不重置数据库;4) 不打开公网 payment_batch_v1;5) 不开放图片 OCR。 流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv;人工核对不改业务 API/schema/UI。 python3|manual DONE DONE DONE issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv [] Scope=automation-first; baseline=7a59fd7; validate_issues_csv passed; no runtime change; no deployment; public payment_batch_v1 remains off.
3 P2AUTO-1 凭据与样票掩码核对 加载本机安全 live smoke 配置并核对公网、企业微信、PDF live OCR readiness;只输出 ready 状态,不输出 secret、真实样票路径或票面信息。 1) liveSmokeReady=true;2) liveOcrReady=true;3) PDF 样票不再被记录为缺失;4) 输出不含密码、token 或真实路径;5) 图片样票若未登记则只作为后续缺口记录。 受影响流程:公网 live smoke、PDF live OCR、企业微信 config readiness;验证步骤:set -a && source ~/.hyb-oa/live-smoke.env && set +a;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com node scripts/qa/check-live-smoke-env.mjs --strict;补充掩码脚本只统计 PLAYWRIGHT_OCR_IMAGE_* 是否存在,不打印路径。 bash|node|manual DONE DONE DONE scripts/qa/check-live-smoke-env.mjs|external ["P2AUTO-0"] live smoke env strict check PASS: publicReady=true, liveSmokeReady=true, liveOcrReady=true; PDF sample env present; image sample is not registered via OCR_PROVIDER_SAMPLE_MANIFEST or PLAYWRIGHT_OCR_IMAGE_*; no secret/path printed.
4 P2AUTO-2 合同与文档门禁 校验当前 P2Q、P2OCRS、P2FINV1、UX8、P2AUTO 合同和文档登记,确认事实源链没有把 PDF 样票误写为缺失。 1) 相关 CSV 校验通过;2) audit-docs 无 error;3) 文档关键词命中可解释;4) PDF 样票已具备口径被登记;5) 不新建综合规划文档。 流程级验证:validate_issues_csv.py 本 CSV、P2Q、P2-OCR-SAMPLE、P2-FIN-V1、UX8;node scripts/qa/audit-docs.mjs --check-registry;rg -n 'P2AUTO|P2Q|P2OCRS|P2-FIN-V1|UX8|PDF live OCR|图片样票|HOLD_FOR_SAMPLE' docs issues。 python3|node|rg DONE DONE DONE issues/*.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md ["P2AUTO-0"] CSV gates passed for P2AUTO, P2Q, P2OCRS, P2FINV1 and UX8; audit-docs errors=0 warnings=1 existing unregistered docs files=24; docs updated in existing index/matrix/report only.
5 P2AUTO-3 后端自动化门禁 执行 Prisma validate、关键路由/服务语法检查和 OCR、付款批次、导出、企业微信、通知 focused 单测。 1) Prisma validate 通过;2) node --check 通过;3) focused npm test 通过或有明确非业务前置失败;4) 不改 server/src 业务逻辑;5) 不打印 secret。 受影响流程:发票 OCR、OCR job、付款批次、AP 导出、workflow、企业微信认证、通知;验证步骤:cd server && npm run prisma:validate;cd server && node --check src/routes/invoicePool.js src/jobs/ocrJob.js src/services/ocr/tencent.js src/routes/paymentBatches.js src/routes/exports.js src/routes/workflow.js src/routes/wecomAuth.js;cd server && npm test -- tests/unit/services/ocr.tencent.test.js tests/unit/jobs/ocrJob.test.js tests/unit/routes/invoicePool.upload.test.js tests/unit/routes/paymentBatches.reimbursement.test.js tests/unit/routes/exports.apOperations.test.js tests/unit/routes/wecomAuth.test.js tests/unit/routes/notifications.test.js。 npm|node DONE DONE DONE server/src|server/tests/unit ["P2AUTO-1"] PASS: server prisma:validate; node --check invoicePool/ocrJob/tencent/paymentBatches/exports/workflow/wecomAuth; focused npm test 7 files/43 tests passed; no server/src business change.
6 P2AUTO-4 前端自动化门禁 执行前端 typecheck、build 和高风险确认、审批确认、发票 PDF-only、权限直连等 Playwright 自动化。 1) typecheck 通过;2) build 通过;3) 指定 Playwright 用例通过或失败可归因;4) 不新增前端页面或业务入口;5) 企业微信真机不被自动化伪装为通过。 受影响流程:PC 高风险动作、审批确认、发票上传、权限直连、移动入口;验证步骤:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/system-action-confirmation.spec.ts tests/e2e/workflow-action-confirm-ui.spec.ts tests/e2e/r111-invoice-reimbursement-guard.spec.ts tests/e2e/permission-direct-access.spec.ts --workers=1 --reporter=list。 npm|playwright DONE DONE DONE client/src|client/tests/e2e ["P2AUTO-3"] PASS: client typecheck/build; high-risk confirmation 4 passed; r111 invoice guard and permission direct access covered by business-flow report business-flow-20260516-203001; test-only stale assertions updated for current labels/role boundary.
7 P2AUTO-5 隔离业务流自动化 在隔离临时库中运行核心业务流 flag-off 门禁,并单独运行付款批次 flag-on 闭环;付款批次结果不得解释为公网启用。 1) 核心业务流通过或失败有明确用例;2) payment_batch_v1 公网保持关闭;3) 付款批次仅隔离环境 flag-on;4) 报告路径写入 Notes;5) 不执行生产数据写入。 受影响流程:登录、企业微信入口、报销、审批、请假、采购、权限治理、付款批次隔离闭环;验证步骤:BUSINESS_FLOW_GROUPS=auth,reimbursement,approval,leave,procurement,governance BUSINESS_FLOW_EXCLUDE_SPECS=tests/e2e/reimbursement-payment-batch-flow.spec.ts bash scripts/qa/run-business-flow-gates.sh local;cd client && E2E_BASE_URL=<local-or-isolated-url> npx playwright test tests/e2e/reimbursement-payment-batch-flow.spec.ts --workers=1 --reporter=list。 bash|playwright DONE DONE DONE scripts/qa/run-business-flow-gates.sh|client/tests/e2e/reimbursement-payment-batch-flow.spec.ts|artifacts/qa-flow ["P2AUTO-4"] PASS: core isolated business flow auth/reimbursement/approval/leave/procurement/governance passed, report artifacts/qa-flow/business-flow-20260516-203001/summary.txt; payment batch isolated flag-on flow 1 passed, report artifacts/qa-flow/business-flow-20260516-203856/summary.txt; public payment_batch_v1 remains off.
8 P2AUTO-6 公网自动化门禁 在公网只跑 /ready、public bundle、live smoke、PDF live OCR、pending OCR 扫描和 live smoke Playwright,不执行公网付款批次写入。 1) /ready 通过;2) public bundle findings=0;3) live smoke 通过;4) PDF live OCR 进入可解释终态;5) pending OCR 缺 DATABASE_URL 时只作非阻断 SKIP。 受影响流程:公网健康、登录 smoke、PDF live OCR、企业微信 config、公网资源;验证步骤:set -a && source ~/.hyb-oa/live-smoke.env && set +a;QA_ISSUE_ID=P2AUTO LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;cd client && E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/live-smoke.spec.ts --workers=1 --reporter=list。 bash|node|playwright DONE DONE DONE scripts/qa/run-live-smoke-readiness.sh|client/tests/e2e/live-smoke.spec.ts|artifacts/qa-flow ["P2AUTO-5"] PASS: live readiness report artifacts/qa-flow/live-smoke-readiness-20260516-204157; /ready ok, public bundle findings=0, live smoke 3 passed, PDF live OCR 1 passed with duplicate-invoice explainable terminal state; pending OCR missing local DATABASE_URL recorded SKIP; no public payment batch writes.
9 P2AUTO-7 移动与企业微信自动化门禁 执行移动矩阵、企业微信 UA、入口/绑定/deeplink 自动化;真机只保留少量真实差异确认,不阻断可自动化收口。 1) 移动矩阵通过;2) WeCom UA/入口/绑定自动化通过;3) 企业微信真机差异不被自动化替代;4) 发现移动缺陷进入 UX8;5) 不新增移动业务能力。 受影响流程:/mobile、审批、报销、企业微信入口、绑定、deeplink 和移动布局;验证步骤:cd client && E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list;cd client && npx playwright test tests/e2e/wecom-entry.spec.ts tests/e2e/wecom-first-bind.spec.ts tests/e2e/r6-wecom-mobile-rendering-hardening.spec.ts --workers=1 --reporter=list。 playwright|manual DONE DONE DONE client/tests/e2e|docs/trialrun/人工试运行报告.md ["P2AUTO-6"] PASS: public DG7/R124 mobile matrix 6 passed; local auth business-flow covered wecom-entry/wecom-first-bind; R6 WeCom rendering matrix 8 passed after subpixel test assertion normalization; real-device delta remains manual-only when user requests.
10 P2AUTO-8 OCR provider 样票自动化缺口 新增并运行 provider-only 样票 QA 脚本;PDF 样票可回归,图片样票未在 manifest 或环境变量登记时记录 SKIP,不得写 PASS。 1) scripts/qa/check-ocr-provider-samples.mjs 存在且 node --check 通过;2) 无样票时输出 SKIP;3) 有样票时输出脱敏 JSON 摘要;4) 不输出票面、税号、手机号、二维码、完整号码或真实路径;5) 不开放图片 OCR。 受影响流程:PDF/image provider 样票 -> Tencent OCR -> 字段命中摘要 -> GO/HOLD/FREEZE 决策;验证步骤:node --check scripts/qa/check-ocr-provider-samples.mjs;OCR_PROVIDER_SAMPLE_MANIFEST=/safe/ocr-samples.json node scripts/qa/check-ocr-provider-samples.mjs --provider=tencent --json;无授权图片样票或 manifest 时写 SKIP。 node|manual DONE DONE DONE scripts/qa/check-ocr-provider-samples.mjs|external ["P2AUTO-1"] QA script added and node --check passed. Provider-only run with current safe env found PDF sample only and no image manifest/env; local Tencent provider credentials not present, so output status=SKIP decision=HOLD_FOR_SAMPLE. Do not auto-select arbitrary image candidates; image OCR remains closed.
11 P2AUTO-9 收口回填与中文提交 汇总 P2AUTO 自动化证据,回填 CSV、人工试运行报告、索引和事实源矩阵,完成最终校验和中文 commit。 1) 已执行项按真实 Test_Method 回填;2) 未执行外部项记录 SKIP 或后续条件;3) validate、audit-docs、rg、git diff --check 通过;4) 中文 commit 引用 P2AUTO;5) 明确未部署。 流程级验证:validate_issues_csv.py 本 CSV;node scripts/qa/audit-docs.mjs --check-registry;rg -n 'P2AUTO|PDF live OCR|图片样票|payment_batch_v1|SKIP|live smoke|企业微信' docs issues scripts;git diff --check;git status --short。 python3|node|rg|git|manual DONE DONE DONE issues/2026-05-16_20-09-49-p2-automated-regression-sweep.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md ["P2AUTO-2","P2AUTO-3","P2AUTO-4","P2AUTO-5","P2AUTO-6","P2AUTO-7","P2AUTO-8"] Closeout=PASS; validate_issues_csv passed; audit-docs errors=0 warnings=1 existing unregistered docs files=24; rg keyword sweep explainable; git diff --check passed; not deployed; no business API/schema/UI/runtime change.
+351
查看文件
@@ -0,0 +1,351 @@
#!/usr/bin/env node
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
process.noDeprecation = true;
const require = createRequire(import.meta.url);
const { runTencentVatOcr } = require("../../server/src/services/ocr/tencent");
const SUPPORTED_PROVIDERS = new Set(["tencent"]);
const MIME_BY_EXT = new Map([
[".pdf", "application/pdf"],
[".jpg", "image/jpeg"],
[".jpeg", "image/jpeg"],
[".png", "image/png"],
]);
const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png"]);
const PDF_EXTENSIONS = new Set([".pdf"]);
const REQUIRED_FIELDS = ["invoiceCode", "invoiceNumber", "invoiceDate", "totalAmount", "sellerName"];
const OPTIONAL_FIELDS = ["buyerName", "amount", "taxAmount"];
function parseArgs(argv) {
const options = {
provider: process.env.OCR_PROVIDER_SAMPLE_PROVIDER || "tencent",
json: false,
strict: false,
manifest: process.env.OCR_PROVIDER_SAMPLE_MANIFEST || "",
};
for (const arg of argv) {
if (arg === "--json") {
options.json = true;
} else if (arg === "--strict") {
options.strict = true;
} else if (arg.startsWith("--provider=")) {
options.provider = arg.slice("--provider=".length);
} else if (arg.startsWith("--manifest=")) {
options.manifest = arg.slice("--manifest=".length);
}
}
return options;
}
function splitEnvList(value) {
return String(value || "")
.split(/[\n,]/)
.map((item) => item.trim())
.filter(Boolean);
}
function resolveSamplePath(rawPath, baseDir) {
if (!rawPath || typeof rawPath !== "string") return "";
return path.isAbsolute(rawPath) ? rawPath : path.resolve(baseDir, rawPath);
}
function inferKind(filePath, declaredKind) {
const normalizedKind = String(declaredKind || "").trim().toLowerCase();
if (["pdf", "pdf_vat", "vat_pdf"].includes(normalizedKind)) return "pdf";
if (["image", "image_vat", "vat_image"].includes(normalizedKind)) return "image";
const ext = path.extname(filePath).toLowerCase();
if (PDF_EXTENSIONS.has(ext)) return "pdf";
if (IMAGE_EXTENSIONS.has(ext)) return "image";
return "unknown";
}
function safeAlias(value, fallback) {
const alias = String(value || "").trim();
if (!alias) return fallback;
if (/^SAMPLE-(PDF|IMG)-[A-Z0-9_-]+$/i.test(alias)) return alias.toUpperCase();
return fallback;
}
function pushSample(samples, source) {
const kind = inferKind(source.path, source.kind);
const fallbackPrefix = kind === "pdf" ? "SAMPLE-PDF" : kind === "image" ? "SAMPLE-IMG" : "SAMPLE-UNKNOWN";
samples.push({
alias: safeAlias(source.alias, `${fallbackPrefix}-${String(samples.length + 1).padStart(3, "0")}`),
filePath: source.path,
kind,
source: source.source,
});
}
function loadManifestSamples(manifestPath) {
if (!manifestPath) return { samples: [], error: null };
if (!fs.existsSync(manifestPath)) {
return { samples: [], error: "manifest_not_found" };
}
try {
const baseDir = path.dirname(manifestPath);
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.samples) ? parsed.samples : [];
const samples = [];
for (const item of items) {
const samplePath = resolveSamplePath(item?.path, baseDir);
if (!samplePath) continue;
pushSample(samples, {
alias: item?.alias,
path: samplePath,
kind: item?.kind,
source: "manifest",
});
}
return { samples, error: null };
} catch {
return { samples: [], error: "manifest_parse_failed" };
}
}
function loadEnvSamples() {
const samples = [];
const pdfFiles = [
...splitEnvList(process.env.PLAYWRIGHT_OCR_LIVE_FILE),
...splitEnvList(process.env.LIVE_OCR_INVOICE_FILE),
...splitEnvList(process.env.PLAYWRIGHT_OCR_LIVE_FILES),
];
for (const item of pdfFiles) {
pushSample(samples, { path: item, kind: "pdf", source: "env_pdf" });
}
const pdfDir = process.env.PLAYWRIGHT_OCR_LIVE_DIR?.trim();
if (pdfDir && fs.existsSync(pdfDir)) {
for (const entry of fs.readdirSync(pdfDir).sort((left, right) => left.localeCompare(right, "zh-CN"))) {
const filePath = path.join(pdfDir, entry);
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile() && PDF_EXTENSIONS.has(path.extname(entry).toLowerCase())) {
pushSample(samples, { path: filePath, kind: "pdf", source: "env_pdf_dir" });
}
}
}
const imageFiles = [
...splitEnvList(process.env.PLAYWRIGHT_OCR_IMAGE_FILE),
...splitEnvList(process.env.PLAYWRIGHT_OCR_IMAGE_FILES),
...splitEnvList(process.env.LIVE_OCR_IMAGE_FILE),
];
for (const item of imageFiles) {
pushSample(samples, { path: item, kind: "image", source: "env_image" });
}
const imageDirs = [
process.env.PLAYWRIGHT_OCR_IMAGE_DIR?.trim(),
process.env.LIVE_OCR_IMAGE_DIR?.trim(),
].filter(Boolean);
for (const dir of imageDirs) {
if (!fs.existsSync(dir)) continue;
for (const entry of fs.readdirSync(dir).sort((left, right) => left.localeCompare(right, "zh-CN"))) {
const filePath = path.join(dir, entry);
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile() && IMAGE_EXTENSIONS.has(path.extname(entry).toLowerCase())) {
pushSample(samples, { path: filePath, kind: "image", source: "env_image_dir" });
}
}
}
return samples;
}
function dedupeSamples(samples) {
const seen = new Set();
const result = [];
for (const sample of samples) {
const key = `${sample.alias}:${sample.filePath}`;
if (seen.has(key)) continue;
seen.add(key);
result.push(sample);
}
return result;
}
function getTencentConfig() {
return {
tencentSecretId: process.env.TENCENT_SECRET_ID || "",
tencentSecretKey: process.env.TENCENT_SECRET_KEY || "",
tencentOcrRegion: process.env.TENCENT_OCR_REGION || "ap-beijing",
ocrTimeoutMs: Number(process.env.OCR_TIMEOUT_MS || 5000),
};
}
function hasTencentCredentials(config) {
return Boolean(config.tencentSecretId && config.tencentSecretKey);
}
function summarizeFile(sample) {
try {
if (!fs.existsSync(sample.filePath)) {
return { ok: false, errorCode: "sample_not_found" };
}
const stat = fs.statSync(sample.filePath);
if (!stat.isFile()) {
return { ok: false, errorCode: "sample_not_file" };
}
const ext = path.extname(sample.filePath).toLowerCase();
const mimeType = MIME_BY_EXT.get(ext);
if (!mimeType) {
return { ok: false, errorCode: "unsupported_sample_type" };
}
return {
ok: true,
mimeType,
fileSizeBytes: stat.size,
transportMode: mimeType === "application/pdf" ? "pdf_native" : "image_native",
};
} catch {
return { ok: false, errorCode: "sample_stat_failed" };
}
}
function summarizeNormalizedResult(result) {
const fieldHits = {};
for (const field of [...REQUIRED_FIELDS, ...OPTIONAL_FIELDS]) {
const value = result?.[field];
fieldHits[field] = value != null && value !== "";
}
const missingRequiredFields = REQUIRED_FIELDS.filter((field) => !fieldHits[field]);
return {
fieldHits,
missingRequiredFields,
transportMode: result?.raw?.transportMode || null,
};
}
async function checkSample(provider, config, sample) {
const fileSummary = summarizeFile(sample);
const base = {
alias: sample.alias,
kind: sample.kind,
source: sample.source,
status: "SKIP",
mimeType: fileSummary.mimeType || null,
fileSizeBytes: fileSummary.fileSizeBytes || null,
transportMode: fileSummary.transportMode || null,
};
if (!fileSummary.ok) {
return { ...base, status: "SKIP", errorCode: fileSummary.errorCode };
}
if (provider !== "tencent") {
return { ...base, status: "SKIP", errorCode: "unsupported_provider" };
}
if (!hasTencentCredentials(config)) {
return { ...base, status: "SKIP", errorCode: "missing_tencent_credentials" };
}
try {
const imageBase64 = fs.readFileSync(sample.filePath).toString("base64");
const result = await runTencentVatOcr(config, { imageBase64, sourceMimeType: fileSummary.mimeType });
const normalizedSummary = summarizeNormalizedResult(result);
const missingRequired = normalizedSummary.missingRequiredFields;
return {
...base,
status: missingRequired.length ? "HOLD" : "PASS",
fieldHits: normalizedSummary.fieldHits,
missingRequiredFields: missingRequired,
transportMode: normalizedSummary.transportMode || base.transportMode,
};
} catch (error) {
return {
...base,
status: "HOLD",
errorCode: typeof error?.code === "string" ? error.code : "provider_failed",
transportMode: error?.transportMode || base.transportMode,
};
}
}
function buildDecision(results, manifestError) {
if (manifestError) return "HOLD_FOR_SAMPLE";
if (!results.length) return "HOLD_FOR_SAMPLE";
const imageResults = results.filter((item) => item.kind === "image");
if (!imageResults.length) return "HOLD_FOR_SAMPLE";
if (imageResults.every((item) => item.status === "PASS")) return "GO_TO_IMPLEMENT";
return "HOLD_FOR_SAMPLE";
}
function printHuman(summary) {
console.log(`[ocr-provider-samples] status=${summary.status} decision=${summary.decision}`);
console.log(`[ocr-provider-samples] provider=${summary.provider} samples=${summary.counts.total} pdf=${summary.counts.pdf} image=${summary.counts.image}`);
if (summary.manifestError) {
console.log(`[ocr-provider-samples] manifest=${summary.manifestError}`);
}
for (const result of summary.results) {
const error = result.errorCode ? ` error=${result.errorCode}` : "";
console.log(
`[ocr-provider-samples] ${result.alias} kind=${result.kind} status=${result.status} transport=${result.transportMode || "-"} size=${result.fileSizeBytes ?? "-"}${error}`,
);
}
}
async function main() {
const options = parseArgs(process.argv.slice(2));
if (!SUPPORTED_PROVIDERS.has(options.provider)) {
throw new Error(`unsupported provider: ${options.provider}`);
}
const manifest = loadManifestSamples(options.manifest);
const samples = dedupeSamples([...manifest.samples, ...loadEnvSamples()]);
const config = getTencentConfig();
const results = [];
for (const sample of samples) {
results.push(await checkSample(options.provider, config, sample));
}
const decision = buildDecision(results, manifest.error);
const failed = results.some((item) => item.status === "HOLD");
const skipped = !results.length || results.some((item) => item.status === "SKIP");
const summary = {
checkedAt: new Date().toISOString(),
provider: options.provider,
status: failed ? "HOLD" : skipped ? "SKIP" : "PASS",
decision,
manifestProvided: Boolean(options.manifest),
manifestError: manifest.error,
credentialReady: hasTencentCredentials(config),
counts: {
total: results.length,
pdf: results.filter((item) => item.kind === "pdf").length,
image: results.filter((item) => item.kind === "image").length,
pass: results.filter((item) => item.status === "PASS").length,
hold: results.filter((item) => item.status === "HOLD").length,
skip: results.filter((item) => item.status === "SKIP").length,
},
results,
};
if (options.json) {
console.log(JSON.stringify(summary, null, 2));
} else {
printHuman(summary);
}
if (options.strict && summary.status !== "PASS") {
process.exit(1);
}
}
main().catch((error) => {
console.error(`[ocr-provider-samples][ERROR] ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
});