diff --git a/client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts b/client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts index 8d5f1bf0..8dc02528 100644 --- a/client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts +++ b/client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts @@ -82,14 +82,15 @@ type LiveSummary = { error?: string; }; -const issueId = "P2FLIVE"; +const issueId = process.env.QA_ISSUE_ID?.trim() || "P2FLIVE"; +const markerPrefix = process.env.P2_FIN_LIVE_MARKER_PREFIX?.trim() || issueId; const baseUrl = process.env.LIVE_SMOKE_BASE_URL?.trim() || "https://oa.hyinda.com"; const reportDir = process.env.P2_FIN_LIVE_REPORT_DIR?.trim() || path.resolve(process.cwd(), "..", "artifacts/qa-flow/p2-fin-live-local"); const summaryPath = path.join(reportDir, "live-result.json"); function nowMarker() { - return `P2FLIVE-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; + return `${markerPrefix}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; } function today() { @@ -210,7 +211,7 @@ async function firstEnabledExpenseTypeId(api: APIRequestContext) { async function createTestReimbursement(api: APIRequestContext, marker: string) { const reimbursement = await apiPostData(api, "/api/reimbursements", { - description: `P2FINLIVE 公网付款测试 ${marker}`, + description: `${markerPrefix} 公网付款测试 ${marker}`, reimbursementDate: today(), }); const expenseTypeId = await firstEnabledExpenseTypeId(api); @@ -218,7 +219,7 @@ async function createTestReimbursement(api: APIRequestContext, marker: string) { expenseTypeId, expenseDate: today(), amount: "168.30", - description: `P2FINLIVE 小额测试 ${marker}`, + description: `${markerPrefix} 小额测试 ${marker}`, }); return apiGetData(api, `/api/reimbursements/${reimbursement.id}`); } @@ -298,7 +299,7 @@ async function createPaymentBatch(api: APIRequestContext, input: { reimbursement entityIds: [input.reimbursementId], defaultPaymentDate: today(), defaultPaymentChannel: "bank", - voucherPrefix: `P2LIVE-${input.marker.slice(-6)}`, + voucherPrefix: `${markerPrefix.slice(0, 12)}-${input.marker.slice(-6)}`, }); return data.item; } @@ -402,14 +403,14 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => { admin.api, "payment_batch_v1", "force_on", - `P2FLIVE short window ${marker}`, + `${issueId} short window ${marker}`, ); summary.stages.flagOn = { paymentFlagOn: snapshotFlag(paymentFlagOn) }; expect(paymentFlagOn.effectiveEnabled).toBe(true); const erpFlagAfterPaymentOn = await loadFlag(admin.api, "erp_sync"); - expect(erpFlagAfterPaymentOn.effectiveEnabled, "erp_sync must not be opened by P2FLIVE").toBe(false); + expect(erpFlagAfterPaymentOn.effectiveEnabled, `erp_sync must not be opened by ${issueId}`).toBe(false); - const batchName = `P2FINLIVE 公网付款批次 ${marker}`; + const batchName = `${markerPrefix} 公网付款批次 ${marker}`; const adminWildcard = isWildcardSuperAdmin(admin); const rejectedCreate: Record = { employee: await expectPostRejected(employee.api, "/api/payment-batches", { @@ -443,7 +444,7 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => { : await expectPostRejected(admin.api, `/api/payment-batches/${batch.id}/execute`, {}, "admin-only execute payment batch"); batch = (await apiPostData<{ item: PaymentBatch }>(financeMgr.api, `/api/payment-batches/${batch.id}/review`, { - comment: `P2FLIVE 复核 ${marker}`, + comment: `${issueId} 复核 ${marker}`, })).item; expect(batch.reviewRequirement.completedReviewCount).toBeGreaterThanOrEqual(1); @@ -493,7 +494,7 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => { } finally { if (admin) { try { - const rolledBack = await forcePaymentBatchOff(admin.api, `P2FLIVE rollback ${marker}`); + const rolledBack = await forcePaymentBatchOff(admin.api, `${issueId} rollback ${marker}`); summary.stages.rollback = { paymentFlagAfterRollback: snapshotFlag(rolledBack) }; expect(rolledBack.effectiveEnabled).toBe(false); } catch (error) { diff --git a/client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts b/client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts index 7f6e0a53..8a9bee9a 100644 --- a/client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts +++ b/client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts @@ -98,10 +98,25 @@ type TrialState = { }; const rootDir = path.resolve(process.cwd(), ".."); -const reportDir = path.join(rootDir, "artifacts/r7e/browser-business-trialrun"); +const trialrunIssueId = process.env.TRIALRUN_ISSUE_ID?.trim() || process.env.QA_ISSUE_ID?.trim() || "R7E"; +const trialrunMarkerPrefix = process.env.TRIALRUN_MARKER_PREFIX?.trim() || trialrunIssueId; +const trialrunSafePrefix = safeName(trialrunMarkerPrefix).toUpperCase().slice(0, 12) || "R7E"; +const trialrunReportTitle = process.env.TRIALRUN_REPORT_TITLE?.trim() || `${trialrunMarkerPrefix} 浏览器业务先行试运行报告`; +function resolveRootOutputPath(value: string | undefined, fallback: string) { + const trimmed = value?.trim(); + if (!trimmed) return fallback; + return path.isAbsolute(trimmed) ? trimmed : path.resolve(process.cwd(), trimmed); +} +const reportDir = resolveRootOutputPath( + process.env.TRIALRUN_REPORT_DIR, + path.join(rootDir, "artifacts/r7e/browser-business-trialrun"), +); const reportPath = path.join(reportDir, "report.md"); const issueCandidatesPath = path.join(reportDir, "issues.csv"); -const screenshotDir = path.join(rootDir, "output/playwright/r7e-browser-business-trialrun"); +const screenshotDir = resolveRootOutputPath( + process.env.TRIALRUN_SCREENSHOT_DIR, + path.join(rootDir, "output/playwright/r7e-browser-business-trialrun"), +); const validFlows = new Set([ "preflight", "reimbursement-finance", @@ -114,13 +129,14 @@ const validFlows = new Set([ "all", ]); const loginRateLimitRetryMs = Number.parseInt(process.env.R7E_LOGIN_RETRY_WAIT_MS ?? "65000", 10); +const trialrunTestTimeoutMs = Number.parseInt(process.env.TRIALRUN_TEST_TIMEOUT_MS ?? "900000", 10); const defaultCloseoutReimbursementId = "40876ef3-0652-4972-aa14-1aad1a855ae0"; const defaultCloseoutReimbursementCode = "BX-20260514-0016"; const wecomUa = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/8.0.1 Language/zh_CN"; function nowStamp() { - return String(Date.now()); + return `${trialrunMarkerPrefix}-${Date.now()}`; } function formatDate(offsetDays: number) { @@ -282,13 +298,13 @@ async function createPaymentBatchViaApi(api: APIRequestContext, input: { entityIds: [input.reimbursementId], defaultPaymentDate: new Date().toISOString().slice(0, 10), defaultPaymentChannel: "bank", - voucherPrefix: `R7E-PAY-${input.marker.slice(-8)}`, + voucherPrefix: `${trialrunSafePrefix}-PAY-${input.marker.slice(-8)}`, }); } async function reviewPaymentBatchViaApi(api: APIRequestContext, batchId: string, marker: string) { return apiPostData<{ item: PaymentBatch }>(api, `/api/payment-batches/${batchId}/review`, { - comment: `R7E 非人工付款复核 ${marker}`, + comment: `${trialrunMarkerPrefix} 非人工付款复核 ${marker}`, }); } @@ -303,7 +319,7 @@ async function createReimbursementItem(api: APIRequestContext, reimbursementId: expenseTypeId, expenseDate: new Date().toISOString().slice(0, 10), amount: "168.30", - description: `R7E 浏览器报销 ${marker}`, + description: `${trialrunMarkerPrefix} 浏览器报销 ${marker}`, }, }); if (!response.ok()) { @@ -471,15 +487,17 @@ function writeReports(steps: TrialStep[]) { const flowIssueCandidatesPath = path.join(reportDir, `issues-${safeName(flowName)}.csv`); const lines = [ - "# R7E 浏览器业务先行试运行报告", + `# ${trialrunReportTitle}`, "", `生成时间:${generatedAt}`, `执行 flow:${flowName}`, + `Issue:${trialrunIssueId}`, + `测试数据标记:${trialrunMarkerPrefix}`, "", "## 结论", "", failures.length - ? `发现 ${failures.length} 个失败步骤,需分流到 R7E/R7E-16 或 QAOPS8。` + ? `发现 ${failures.length} 个失败步骤,需分流到 ${trialrunIssueId} 或后续 Issue。` : skips.length ? `未发现失败步骤,但有 ${skips.length} 个 SKIP 步骤,不能据此关闭对应人工验收。` : "公网浏览器业务线自动辅助试运行未发现阻断步骤。", @@ -513,15 +531,15 @@ function writeReports(steps: TrialStep[]) { const issueHeader = ["ID", "Flow", "Severity", "Title", "Actual", "Screenshot", "Disposition", "Status", "Notes"]; const issueRows = failures.map((item, index) => [ - `R7E-BROWSER-${String(index + 1).padStart(3, "0")}`, + `${trialrunSafePrefix}-BROWSER-${String(index + 1).padStart(3, "0")}`, item.flow, item.flow === "公共前置" ? "BLOCKER" : "HIGH", item.title, item.detail, item.screenshot || "N/A", - item.flow === "移动" || item.flow === "企业微信UA" ? "QAOPS8/UX8/R7E" : "R7E/WF8/BIZ8", + item.flow === "移动" || item.flow === "企业微信UA" ? `QAOPS/UX/${trialrunIssueId}` : `${trialrunIssueId}/WF/BIZ`, "OPEN", - "由 R7E 浏览器业务先行试运行生成,需人工复核严重级别。", + `由 ${trialrunIssueId} 浏览器业务先行试运行生成,需人工复核严重级别。`, ].map(csvCell).join(","), ); const issueContent = `${issueHeader.map(csvCell).join(",")}\n${issueRows.join("\n")}${issueRows.length ? "\n" : ""}`; @@ -529,11 +547,11 @@ function writeReports(steps: TrialStep[]) { writeFileSync(flowIssueCandidatesPath, issueContent, "utf-8"); } -test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、财务、移动浏览器", async ({ page, browser, reimbursementDetail, workflowDetail }) => { +test(`${trialrunIssueId} 浏览器业务先行试运行:报销、审批、请假、采购、财务、移动浏览器`, async ({ page, browser, reimbursementDetail, workflowDetail }) => { test.skip(process.env.PLAYWRIGHT_MANUAL !== "1", "仅在显式手工 Playwright 模式下执行"); test.skip(process.env.E2E_MODE !== "live", "仅在 live 模式下执行"); - test.skip(process.env.R7E_BROWSER_TRIALRUN !== "1", "仅在 R7E 浏览器业务试运行模式下执行"); - test.setTimeout(900_000); + test.skip(process.env.R7E_BROWSER_TRIALRUN !== "1", `仅在 ${trialrunIssueId} 浏览器业务试运行模式下执行`); + test.setTimeout(Number.isFinite(trialrunTestTimeoutMs) && trialrunTestTimeoutMs > 0 ? trialrunTestTimeoutMs : 900_000); mkdirSync(screenshotDir, { recursive: true }); const steps: TrialStep[] = []; @@ -695,7 +713,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 if (existingBatch && ["draft", "reviewed", "failed"].includes(existingBatch.status)) { batch = existingBatch; } else { - const batchName = `R7E 非人工付款批次 ${marker}`; + const batchName = `${trialrunMarkerPrefix} 非人工付款批次 ${marker}`; const created = await createPaymentBatchViaApi(financeApi.api, { reimbursementId, name: batchName, marker }); batch = created.item; } @@ -763,7 +781,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await (await openNavLink(page, "请假")).click(); await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible(); - const leaveId = await createLeaveDraft(page, startDate, `R7E 审批边界 ${marker}`); + const leaveId = await createLeaveDraft(page, startDate, `${trialrunMarkerPrefix} 审批边界 ${marker}`); const submitResult = await apiPostData<{ leaveRequestId: string; currentTaskId: string }>(employeeApi.api, `/api/leave-requests/${leaveId}/submit`, {}); const taskId = submitResult.currentTaskId; if (!taskId) { @@ -853,7 +871,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await page.goto(`/reimbursements/${reimbursementId}`); await reimbursementDetail.assertLoaded(); - const uploadResult = await uploadLiveInvoicePdf(page, `r7e-${marker}.pdf`, readFileSync(pdfSample)); + const uploadResult = await uploadLiveInvoicePdf(page, `${trialrunSafePrefix.toLowerCase()}-${marker}.pdf`, readFileSync(pdfSample)); if (!uploadResult.invoiceId) { return { status: "SKIP", @@ -937,7 +955,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await (await openNavLink(page, "请假")).click(); await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible(); - const leaveId = await createLeaveDraft(page, startDate, `R7E 浏览器请假 ${marker}`); + const leaveId = await createLeaveDraft(page, startDate, `${trialrunMarkerPrefix} 浏览器请假 ${marker}`); state.leaveRequestId = leaveId; state.leaveDate = startDate; const submitResult = await apiPostData<{ leaveRequestId: string; currentTaskId: string }>(employeeApi.api, `/api/leave-requests/${leaveId}/submit`, {}); @@ -996,7 +1014,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 state.purchaseRequestId = purchaseRequestId; await page.goto(`/purchase-requests?id=${purchaseRequestId}`); await expect(page.getByTestId("purchase-title")).toBeVisible(); - await page.getByTestId("purchase-title").fill(`R7E 浏览器采购 ${marker}`); + await page.getByTestId("purchase-title").fill(`${trialrunMarkerPrefix} 浏览器采购 ${marker}`); await selectFirstEnabledOption(page.getByTestId("purchase-legal-entity")); await selectFirstEnabledOption(page.getByTestId("purchase-cost-center")); await selectFirstEnabledOption(page.getByTestId("purchase-supplier")); @@ -1007,11 +1025,11 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await page.getByTestId("purchase-add-item").click(); } await expect(itemDescriptionInputs.first()).toBeVisible({ timeout: 30_000 }); - await itemDescriptionInputs.first().fill(`R7E 采购明细 ${marker}`); + await itemDescriptionInputs.first().fill(`${trialrunMarkerPrefix} 采购明细 ${marker}`); await page.locator('[data-testid^="purchase-item-quantity-"]:visible').first().fill("1"); await page.locator('[data-testid^="purchase-item-unit-price-"]:visible').first().fill("188"); - const purchaseAttachmentName = `r7e-purchase-attachment-${marker}.txt`; + const purchaseAttachmentName = `${trialrunSafePrefix.toLowerCase()}-purchase-attachment-${marker}.txt`; const uploadAttachmentResponsePromise = page.waitForResponse((response) => { const url = new URL(response.url()); return response.request().method() === "POST" && url.pathname === `/api/purchase-requests/${purchaseRequestId}/attachments`; @@ -1019,7 +1037,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await page.getByTestId("purchase-attachment-file-input").setInputFiles({ name: purchaseAttachmentName, mimeType: "text/plain", - buffer: Buffer.from(`R7E purchase attachment ${marker}\n`, "utf-8"), + buffer: Buffer.from(`${trialrunMarkerPrefix} purchase attachment ${marker}\n`, "utf-8"), }); const uploadAttachmentResponse = await uploadAttachmentResponsePromise; if (!uploadAttachmentResponse.ok()) { @@ -1137,7 +1155,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 }; } - const batchName = `R7E 浏览器付款批次 ${marker}`; + const batchName = `${trialrunMarkerPrefix} 浏览器付款批次 ${marker}`; state.paymentBatchName = batchName; await page.goto("/payment-batches"); await expect(page.getByRole("heading", { name: "支付批次", exact: true })).toBeVisible(); @@ -1161,7 +1179,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await createCard.getByPlaceholder("用逗号或换行分隔 reimbursement / travel_order ID").fill(state.reimbursementId); await createCard.locator('input[type="date"]').fill(new Date().toISOString().slice(0, 10)); await createCard.locator("select").nth(1).selectOption("bank"); - await createCard.getByPlaceholder("例如 PBX").fill(`R7E-${marker.slice(-6)}`); + await createCard.getByPlaceholder("例如 PBX").fill(`${trialrunSafePrefix}-${marker.slice(-6)}`); await createCard.getByRole("button", { name: "创建批次" }).click(); const createBatchResponse = await createBatchResponsePromise; if (!createBatchResponse.ok()) { @@ -1182,7 +1200,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、 await expect(pendingRow).toBeVisible({ timeout: 30_000 }); await pendingRow.getByRole("button", { name: "快捷复核" }).click(); await expect(page.getByRole("dialog")).toContainText(batchName); - await page.getByPlaceholder("请输入复核意见").fill(`R7E 浏览器复核 ${marker}`); + await page.getByPlaceholder("请输入复核意见").fill(`${trialrunMarkerPrefix} 浏览器复核 ${marker}`); await page.getByRole("button", { name: "提交复核" }).click(); await expect(pendingRow).toContainText(/复核 1\/1|复核 1\/2/, { timeout: 30_000 }); await logoutLiveUser(page, { name: financeMgr.name }).catch(() => undefined); diff --git a/docs/trialrun/人工试运行报告.md b/docs/trialrun/人工试运行报告.md index 6acb270f..100d8f56 100644 --- a/docs/trialrun/人工试运行报告.md +++ b/docs/trialrun/人工试运行报告.md @@ -26,6 +26,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开 - 2026-05-16 P2AF 已完成可自动实施收口:图片 OCR 默认 `FREEZE_BY_DEFAULT`,PDF-only OCR 继续维护;新增 QA-only 回归聚合和付款批次公网关闭态只读门禁,公网 `payment_batch_v1` 继续 `flag_force_off`,本轮不做业务实现、不部署。 - 2026-05-16 P2-FIN-LIVE-DECISION 已完成公网付款批次启用窗口决策:live smoke/PDF OCR readiness 通过,付款批次公网关闭态只读门禁通过;初始结论为 `HOLD_FOR_WINDOW`,随后用户批准立即短窗口、现有 live smoke 测试账号和新建测试报销,进入 `P2-FIN-LIVE`。 - 2026-05-17 P2-FIN-LIVE 已按用户批准完成公网付款批次短窗口受控测试:使用现有 live smoke 测试账号新建专用测试报销,临时打开 `payment_batch_v1`,完成付款批次创建、复核、执行、AP 导出和审计核验后强制回到 `flag_force_off`;未部署,不接真实支付,不写 ERP 实时凭证,不开放图片 OCR。 +- 2026-05-17 PRELAUNCH 已按“系统尚未正式使用”的新前提完成正式启用前公网预回归:公网 `/ready`、public bundle、live smoke、PDF live OCR、远端 Caddy/compose/migration 只读基线、隔离核心业务流、付款批次公网 PRELAUNCH 窗口、公网移动矩阵和企业微信 UA/渲染矩阵均形成证据;公网测试数据均带 `PRELAUNCH` marker。最终 `payment_batch_v1=flag_force_off`,未部署、不接真实支付、不写 ERP 实时凭证、不开放图片 OCR。浏览器全链路 `all` 中报销 PDF 因样票重复记可解释 `SKIP`,采购分流程功能报告通过但手工 live spec 存在长等待/退出码稳定性缺口,后续作为 QA 自动化维护项。 ## 关键证据索引 @@ -59,6 +60,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开 | P2AF | 已收口:可自动实施收口完成;安全回归聚合 PASS,付款批次公网关闭态只读门禁 PASS,UX8 自动矩阵通过且 UX8-1..5 保持真实反馈驱动 | `issues/2026-05-16_22-11-10-p2-automatable-followup.csv`、`artifacts/qa-flow/p2-safe-regression-20260516-222222/summary.json` | | P2-FIN-LIVE-DECISION | 已收口:公网付款批次启用窗口决策完成,初始结论 `HOLD_FOR_WINDOW`;readiness 和 flag-off 证据通过,后续由用户批准进入 P2-FIN-LIVE 短窗口 | `issues/2026-05-16_22-49-05-p2-fin-payment-batch-live-decision.csv` | | P2-FIN-LIVE | 已收口:公网短窗口受控测试 PASS;专用测试报销 `BX-20260517-0022` 付款闭环到 paid,票据 archived,AP 台账 paid,导出和审计核验通过;公网移动矩阵 6 passed,企业微信渲染矩阵 8 passed;最终 `payment_batch_v1=flag_force_off` | `issues/2026-05-16_23-42-08-p2-fin-payment-batch-live-enable.csv`、`artifacts/qa-flow/p2-fin-live-20260517-001022/summary.json`、`artifacts/qa-flow/p2-fin-live-20260517-001022/live-result.json` | +| PRELAUNCH | 已收口:正式启用前公网预回归完成;公网 readiness/PDF live OCR、远端只读基线、隔离核心业务流、PRELAUNCH 付款批次窗口、公网移动和企业微信渲染矩阵通过;最终 `payment_batch_v1=flag_force_off` | `issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv`、`artifacts/qa-flow/p2-fin-live-20260517-014144/live-result.json`、`artifacts/qa-flow/p2-safe-regression-20260517-014436/summary.json`、`artifacts/prelaunch/browser-business/report.md`、`artifacts/prelaunch/browser-business-purchase-rerun/report.md` | ## 当前门禁 @@ -82,6 +84,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开 | P2AF 可自动实施收口 | 通过,未部署 | `run-p2-safe-regression.sh` PASS:后端 focused 43 tests passed,`client` typecheck/build 通过,核心隔离业务流 auth/reimbursement/approval/leave/procurement/governance PASS,公网 readiness/PDF live OCR 通过,付款批次公网关闭态 409 `payment_batch_v1_disabled / flag_force_off`,公网移动矩阵 6 passed,企业微信渲染矩阵 8 passed,OCR provider-only 默认 `FREEZE_BY_DEFAULT`;不启用公网 `payment_batch_v1`,不开放图片 OCR,不做业务实现 | | P2-FIN-LIVE-DECISION | 已完成,未部署 | `check-live-smoke-env --strict` 通过,`check-payment-batch-flag-off --json` 通过,财务/员工/admin 对付款批次公网只读访问均为 409 `payment_batch_v1_disabled / flag_force_off`;初始 `HOLD_FOR_WINDOW`,后续用户批准立即短窗口并由 P2-FIN-LIVE 承接 | | P2-FIN-LIVE 公网短窗口 | 通过,未部署,最终关闭 | `run-p2-fin-live-window.sh` PASS:live-env strict PASS,pre flag-off PASS,公网付款批次 live spec 1 passed,移动矩阵 6 passed,企业微信渲染矩阵 8 passed,final flag-off check PASS;测试中只临时开启 `payment_batch_v1`,未开启 `erp_sync`,未接真实支付,最终恢复 409 `payment_batch_v1_disabled / flag_force_off` | +| PRELAUNCH 正式启用前预回归 | 通过,含 QA 稳定性观察项,未部署 | 公网 live readiness PASS,远端 Caddy active、compose db/server Up、Prisma migration up to date;脱敏数据清点 activeEmployees=12、activeWecomBindings=5、pendingWorkflowTasks=7;`run-p2-fin-live-window.sh` PRELAUNCH PASS,测试报销 `BX-20260517-0024` 付款闭环 paid、票据 archived、AP 台账 paid;`run-p2-safe-regression.sh` PASS。浏览器 `all` 长链路中 PDF 样票重复和采购超时按证据分流:采购分流程报告 PASS,本地隔离 procurement PASS,记录为手工 live spec 稳定性维护项,不作为业务 P0/P1 阻断。 | ## R7EP / OPS8 / QAF8 / UX8 / P2T 预检推进 diff --git a/docs/文档事实源状态矩阵.md b/docs/文档事实源状态矩阵.md index 81e34419..323f2b0b 100644 --- a/docs/文档事实源状态矩阵.md +++ b/docs/文档事实源状态矩阵.md @@ -4,7 +4,7 @@ **分类**:文档台账。 **适用时间**:DCL7 之后。 **关联 Issue**:`GOV8`。 -**最近审计**:2026-05-16。 +**最近审计**:2026-05-17。 **不得误读**:本文登记文档权威性和状态,不替代具体 PRD、模块规约、测试规约或 Issue CSV。 **治理口径**:中文优先,物理精简,优先复用已有文档。历史资料不建 `docs/archive/`,已确认不用的文档从当前树移除,通过 git 历史恢复。 @@ -47,6 +47,7 @@ | `P2AF` | `issues/2026-05-16_22-11-10-p2-automatable-followup.csv` | `EVIDENCE` | 可自动实施收口已完成;纠正图片 OCR 默认冻结口径,补 QA-only 安全回归、付款批次公网关闭态只读门禁和 UX8 自动矩阵扫尾;报告 `artifacts/qa-flow/p2-safe-regression-20260516-222222/` | | `P2-FIN-LIVE-DECISION` | `issues/2026-05-16_22-49-05-p2-fin-payment-batch-live-decision.csv` | `EVIDENCE` | 公网付款批次启用窗口决策已收口;readiness 和 flag-off 只读门禁通过,初始结论 `HOLD_FOR_WINDOW`,随后用户批准进入 `P2-FIN-LIVE` | | `P2-FIN-LIVE` | `issues/2026-05-16_23-42-08-p2-fin-payment-batch-live-enable.csv` | `EVIDENCE` | 公网付款批次短窗口受控测试已收口;测试报销、付款批次、AP 导出和审计核验通过,最终 `payment_batch_v1=flag_force_off`,未部署 | +| `PRELAUNCH` | `issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv` | `EVIDENCE` | 正式启用前公网预回归已收口;公网 readiness、远端只读基线、隔离核心业务流、付款批次 PRELAUNCH 窗口、移动/企业微信矩阵和 Go/No-Go 摘要完成;最终 `payment_batch_v1=flag_force_off`,未部署 | 旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。 @@ -54,8 +55,8 @@ | 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,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE` | `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,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE` | `audit-docs` | none | 2026-05-16 | `audit-docs --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,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH` | `rg` | none | 2026-05-17 | `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,P2AUTO,P2AF,P2-FIN-LIVE-DECISION,P2-FIN-LIVE,PRELAUNCH` | `audit-docs` | none | 2026-05-17 | `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 | 定义做什么、不做什么和验收口径 | | `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 后按页面分层和样板选型 | @@ -160,4 +161,4 @@ - `ME6-3` 代表性企业微信真机门禁已关闭。 - MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检。 -- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口和 QAOPS9 上线硬化连续合同均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同;P2-FIN-LIVE 已完成公网付款批次短窗口受控测试并回到 `payment_batch_v1=flag_force_off`;P2 功能开发必须另建或延续对应连续合同。 +- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口、QAOPS9 上线硬化连续合同和 PRELAUNCH 正式启用前公网预回归均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同;P2-FIN-LIVE 和 PRELAUNCH 均验证付款批次公网短窗口并回到 `payment_batch_v1=flag_force_off`;P2 功能开发、部署、付款批次常开和正式启用公告必须另建或延续对应连续合同。 diff --git a/docs/文档索引.md b/docs/文档索引.md index d2f60d9c..fcbbee27 100644 --- a/docs/文档索引.md +++ b/docs/文档索引.md @@ -4,7 +4,7 @@ **分类**:入口索引。 **适用时间**:DCL7 之后。 **关联 Issue**:`GOV8`。 -**最近审计**:2026-05-16。 +**最近审计**:2026-05-17。 **不得误读**:本文只指示当前事实源和读取顺序;历史 Issue CSV 只能用于追溯,不直接生成新开发任务。 **治理口径**:中文优先,物理精简,优先复用已有文档。已确认不用的历史文档不放 `docs/archive/`,直接从当前工作树移除;需要时从 git 历史恢复。 @@ -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 -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE。 +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 -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH。 - R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。 - ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。 @@ -41,6 +41,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M - P2AF 是 P2AUTO 后可自动实施收口合同:纠正图片 OCR 默认冻结口径,补 QA-only provider 决策语义、安全回归聚合、付款批次公网关闭态只读门禁和 UX8 自动矩阵扫尾;不部署、不启用公网付款批次、不开放图片 OCR。 - P2-FIN-LIVE-DECISION 是 P2AF 后付款批次公网启用窗口决策合同:公网 readiness 和 flag-off 只读门禁通过,初始结论为 `HOLD_FOR_WINDOW`;随后用户批准立即短窗口、现有 live smoke 测试账号和新建测试报销,已进入并完成 `P2-FIN-LIVE`。 - P2-FIN-LIVE 已按用户批准完成公网付款批次短窗口受控测试:使用 live smoke 测试账号新建专用测试报销,临时打开 `payment_batch_v1`,完成创建/复核/执行、AP 导出和审计核验后强制回到 `flag_force_off`;不部署、不接真实支付、不写 ERP 实时凭证,不开放图片 OCR。 +- PRELAUNCH 是未正式使用前的正式启用准备合同:公网作为准生产预启用环境承载带 `PRELAUNCH` marker 的测试数据和自动化回归;公网 readiness、远端只读基线、隔离核心业务流、付款批次 PRELAUNCH 窗口、移动/企业微信自动化和 Go/No-Go 摘要已完成。最终 `payment_batch_v1=flag_force_off`,未部署、不接真实支付、不写 ERP 实时凭证、不开放图片 OCR;采购公网手工 live spec 长等待/退出码问题作为 QA 自动化稳定性维护项。 ## 当前活跃治理流 @@ -68,6 +69,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M | `P2AF` | `issues/2026-05-16_22-11-10-p2-automatable-followup.csv` | 已收口:可自动实施收口完成 | 图片 OCR 默认冻结纠偏、QA 脚本语义、安全回归聚合、付款批次公网关闭态只读门禁、UX8 自动矩阵扫尾;报告 `artifacts/qa-flow/p2-safe-regression-20260516-222222/` | 不部署、不开放图片 OCR、不启用公网 `payment_batch_v1`、不做业务实现 | | `P2-FIN-LIVE-DECISION` | `issues/2026-05-16_22-49-05-p2-fin-payment-batch-live-decision.csv` | 已收口:公网启用窗口决策完成,初始结论 `HOLD_FOR_WINDOW`,后续用户批准并进入 `P2-FIN-LIVE` | 公网 readiness、付款批次 flag-off 只读门禁、窗口/参与人/回滚联系人/测试数据前置、后续 P2-FIN-LIVE 启动条件 | 不在决策合同中打开公网 `payment_batch_v1`、不写付款批次、不部署、不接真实支付、不写 ERP 实时凭证 | | `P2-FIN-LIVE` | `issues/2026-05-16_23-42-08-p2-fin-payment-batch-live-enable.csv` | 已收口:公网短窗口付款批次受控测试通过,最终 `payment_batch_v1=flag_force_off` | 公网测试报销、PDF 样票绑定、审批到 approved、付款批次创建/复核/执行、AP 导出审计、权限边界、移动/企业微信自动化 | 不保持公网开启、不部署、不接真实支付、不写 ERP 实时凭证、不开放图片 OCR | +| `PRELAUNCH` | `issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv` | 已收口:正式启用前公网预回归完成,最终 `payment_batch_v1=flag_force_off` | 公网环境基线、远端只读状态、PRELAUNCH 测试数据、核心业务自动化、付款批次公网窗口、移动/企业微信矩阵、启用范围和 Go/No-Go 摘要 | 不部署、不执行生产恢复、不重置数据库、不打印远端 env、不接真实支付、不写 ERP 实时凭证、不开放图片 OCR | 旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。 diff --git a/issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv b/issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv new file mode 100644 index 00000000..93a5bf7b --- /dev/null +++ b/issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv @@ -0,0 +1,9 @@ +"ID","Title","Description","Acceptance","Test_Method","Tools","Dev_Status","Review1_Status","Regression_Status","Files","Dependencies","Notes" +"PRELAUNCH-0","正式启用前合同建档","建立未正式使用前的全量启用准备合同;公网作为准生产预启用环境,可承载带 PRELAUNCH marker 的测试数据和短窗口 feature flag 验证。","1) CSV 校验通过;2) 本轮不部署、不执行生产恢复、不重置数据库、不打印 secret 或远端 env;3) 不接真实支付、不写 ERP 实时凭证、不开放图片 OCR;4) 新增公网测试数据必须带 PRELAUNCH 或本 Issue marker;5) 最终明确 payment_batch_v1 状态。","受影响流程:R7E/P2 收口证据 -> 正式启用前公网回归 -> Go/No-Go 摘要;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv;人工核对范围。","python3|manual","DONE","DONE","DONE","issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv","[]","完成:CSV 校验通过;基线 20a7835,系统未正式使用;公网 PRELAUNCH 标记测试获批;全程未部署、未恢复生产、未重置数据库、未打印 secret、未接真实支付/ERP、未开放图片 OCR。" +"PRELAUNCH-1","QA marker 与证据目录准备","调整既有 QA-only live spec/runner,使付款批次公网验证可使用 PRELAUNCH marker 和独立证据目录;不改业务运行时代码。","1) p2-fin-live spec 支持 QA_ISSUE_ID/P2_FIN_LIVE_MARKER_PREFIX;2) 生成报销、批次、voucherPrefix、审计 note 均含 PRELAUNCH marker;3) runner 缺 P2_FIN_LIVE_APPROVED=1 仍拒绝公网写入;4) node/typecheck/list 校验通过;5) 不改 server/src 或 client/src 业务页面。","受影响流程:公网付款批次测试数据标记和证据归档;验证步骤:node --check scripts/qa/check-payment-batch-flag-off.mjs;bash -n scripts/qa/run-p2-fin-live-window.sh;cd client && PLAYWRIGHT_MANUAL=1 E2E_MODE=live npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --list。","node|bash|playwright","DONE","DONE","DONE","client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts|scripts/qa/run-p2-fin-live-window.sh","[""PRELAUNCH-0""]","完成:QA_ISSUE_ID/P2_FIN_LIVE_MARKER_PREFIX/TRIALRUN_* 支持已补;node --check、bash -n、Playwright --list、client typecheck、git diff --check 通过;仅 QA-only spec/runner 变更。" +"PRELAUNCH-2","公网环境与数据基线复核","复核公网 /ready、public bundle、live smoke、PDF live OCR、企业微信配置、远端 Caddy/compose/migration 状态和测试账号/组织主数据只读状态。","1) live smoke env strict 通过;2) live readiness 通过或 SKIP 可解释;3) 远端只读检查不修改服务、不打印 env;4) 组织/账号只读巡检输出脱敏摘要;5) 形成正式启用前基线证据路径。","受影响流程:正式启用前环境健康、账号角色、企业微信和 OCR 前置;验证步骤: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;QA_ISSUE_ID=PRELAUNCH LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-live-smoke-readiness.sh;ssh deploy@118.89.77.218 只读执行 Caddy/compose/prisma status;远端执行 node src/scripts/audit-org-master-data.js --format=json。","bash|node|ssh","DONE","DONE","DONE","scripts/qa/check-live-smoke-env.mjs|scripts/qa/run-live-smoke-readiness.sh|server/src/scripts/audit-org-master-data.js|external","[""PRELAUNCH-1""]","完成:check-live-smoke-env --strict PASS;live readiness report=artifacts/qa-flow/live-smoke-readiness-20260517-005425;远端 Caddy active、compose db/server Up、Prisma 47 migrations up to date;远端 audit-org 脚本未部署记 SKIP_REMOTE_SCRIPT_MISSING,改用脱敏 SQL 清点 activeEmployees=12、activeWecomBindings=5、pendingWorkflowTasks=7,证据 artifacts/prelaunch/baseline/。" +"PRELAUNCH-3","公网全链路预启用业务回归","在公网跑报销、PDF OCR、审批、请假、采购、移动入口和企业微信 UA 预启用回归,新增测试数据统一带 PRELAUNCH marker。","1) 公网业务流未发现 P0/P1 阻断;2) 报销/PDF OCR/审批有可追溯单据;3) 请假和采购主流程通过或失败可解释;4) 移动入口和 WeCom UA 自动化通过;5) 报告不得包含密码、token、票面内容或隐私字段。","受影响流程:员工提交报销/PDF 票据/请假/采购 -> 审批人处理 -> 移动和企业微信 UA 查看;验证步骤:cd client && PLAYWRIGHT_MANUAL=1 E2E_MODE=live R7E_BROWSER_TRIALRUN=1 R7E_BROWSER_FLOW=all TRIALRUN_MARKER_PREFIX=PRELAUNCH TRIALRUN_REPORT_DIR=../artifacts/prelaunch/browser-business TRIALRUN_SCREENSHOT_DIR=../output/playwright/prelaunch-browser-business npx playwright test tests/e2e/manual/r7e-browser-business-trialrun.spec.ts --workers=1 --reporter=list。","playwright","DONE","DONE","DONE","client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts|artifacts/prelaunch|output/playwright","[""PRELAUNCH-2""]","完成:all 报告 artifacts/prelaunch/browser-business/report.md 显示 ready/登录/审批边界/请假/移动/WeCom UA PASS,付款 flag-off 与 PDF 样票重复为可解释 SKIP;采购分流程功能报告 artifacts/prelaunch/browser-business-purchase-rerun/report.md PASS(CG-20260517-0007),但手工 live spec 存在长等待/退出码稳定性缺口,已在 docs 记录为 QA 自动化维护项;付款闭环由 PRELAUNCH-4 覆盖。" +"PRELAUNCH-4","公网付款批次 PRELAUNCH 验证","在公网预启用环境使用 PRELAUNCH marker 临时打开 payment_batch_v1,完成创建、复核、执行、AP 导出和审计核验,然后强制回到 flag_force_off。","1) pre flag-off check 通过;2) PRELAUNCH 测试报销 approved;3) payment_batch_v1 短窗口 force_on 且 erp_sync 保持关闭;4) 付款批次执行后报销 paid、票据 archived、AP 台账 paid;5) PAYMENT_BATCH_*、REIMBURSEMENT_PAYMENT_MARKED、EXPORT_AP_OPERATIONS 审计可查;6) final flag-off check 通过。","受影响流程:approved 测试报销 -> 付款批次创建 -> 复核 -> 执行 -> AP 导出 -> 审计 -> flag off 回滚;验证步骤:P2_FIN_LIVE_APPROVED=1 QA_ISSUE_ID=PRELAUNCH P2_FIN_LIVE_MARKER_PREFIX=PRELAUNCH LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-p2-fin-live-window.sh;随后 check-payment-batch-flag-off.mjs --json。","bash|node|playwright","DONE","DONE","DONE","scripts/qa/run-p2-fin-live-window.sh|client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts|artifacts/qa-flow","[""PRELAUNCH-3""]","完成:report=artifacts/qa-flow/p2-fin-live-20260517-014144;PRELAUNCH 报销 BX-20260517-0024 approved -> payment batch executed -> reimbursement paid、invoice archived、ledger paid、AP export 200;employee/manager 403,admin-only 因 wildcard superadmin 记 SKIP;final flag-off PASS。" +"PRELAUNCH-5","自动化门禁聚合复跑","复跑 P2AF 安全自动回归,确认后端 focused、前端构建、隔离业务流、公网 readiness、移动矩阵、WeCom UA 和 OCR 默认冻结仍然通过。","1) run-p2-safe-regression.sh 返回 PASS;2) 后端 focused 和前端构建通过;3) 隔离核心业务流通过;4) 公网 readiness/PDF OCR、移动矩阵、WeCom UA 通过;5) OCR provider 默认 FREEZE_BY_DEFAULT。","受影响流程:正式启用前全端自动化门禁;验证步骤:QA_ISSUE_ID=PRELAUNCH bash scripts/qa/run-p2-safe-regression.sh;检查 artifacts/qa-flow/p2-safe-regression-*/summary.json。","bash|node|npm|playwright","DONE","DONE","DONE","scripts/qa/run-p2-safe-regression.sh|artifacts/qa-flow","[""PRELAUNCH-4""]","完成:artifacts/qa-flow/p2-safe-regression-20260517-014436/summary.json status=PASS;后端 focused 43 passed,client typecheck/build PASS,隔离 auth/reimbursement/approval/leave/procurement/governance PASS,live readiness PASS,flag-off PASS,公网移动 6 passed,WeCom 渲染 8 passed,OCR decision FREEZE_BY_DEFAULT。" +"PRELAUNCH-6","启用范围与 Go/No-Go 摘要","整理正式启用范围、冻结项、回滚方式和 Go/Pause/No-Go 结论;默认启用报销、PDF OCR、审批、请假、采购、通知、移动/企业微信入口和财务台账,付款批次正式常开另拆合同。","1) 明确可正式启用能力;2) 明确不含真实支付、ERP 实时凭证、图片 OCR、复杂 BPM、审批设计器、完整差旅、重预算;3) payment_batch_v1 最终状态记录清楚;4) 若需部署或常开付款批次,登记为后续合同;5) 支持联系人和暂停条件可执行。","受影响流程:正式启用决策和后续派工;验证步骤:人工核对 PRELAUNCH-2..5 证据;rg -n 'PRELAUNCH|正式启用|Go/No-Go|payment_batch_v1|FREEZE_BY_DEFAULT' docs issues artifacts。","manual|rg","DONE","DONE","DONE","docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md|issues","[""PRELAUNCH-5""]","结论:Go for formal-use preparation with caveats。可启用报销/PDF-only OCR/审批/请假/采购/通知/移动和企业微信入口/财务台账;暂停项为部署最新本地 QA-only 代码、付款批次正式常开公告、采购手工 live spec 稳定性维护、fresh PDF 样票非重复链路。冻结真实支付、ERP 实时凭证、图片 OCR、复杂 BPM、审批设计器、完整差旅、重预算。" +"PRELAUNCH-7","收口回填与提交","回填 PRELAUNCH CSV、文档索引、事实源矩阵和人工试运行报告,运行最终门禁并提交中文 commit。","1) PRELAUNCH 各行按真实证据回填;2) 既有文档状态更新,不新建综合规划文档;3) validate、audit-docs、rg、git diff --check 通过;4) 中文 commit 引用 PRELAUNCH;5) 明确未部署和公网最终 flag 状态。","流程级验证:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv;node scripts/qa/audit-docs.mjs --check-registry;rg -n 'PRELAUNCH|正式启用|Go/No-Go|payment_batch_v1|FREEZE_BY_DEFAULT' docs issues scripts client/tests;git diff --check;git status --short。","python3|node|rg|git","DONE","DONE","DONE","issues/2026-05-17_00-46-49-prelaunch-production-readiness.csv|docs/文档索引.md|docs/文档事实源状态矩阵.md|docs/trialrun/人工试运行报告.md","[""PRELAUNCH-6""]","完成:validate_issues_csv PASS;audit-docs errors=0 warnings=1(既有未登记文档 24);关键词核对通过;git diff --check PASS;未部署,公网最终 payment_batch_v1=flag_force_off;中文 commit 待提交。" diff --git a/scripts/qa/run-p2-fin-live-window.sh b/scripts/qa/run-p2-fin-live-window.sh index df19c9ec..a667defc 100755 --- a/scripts/qa/run-p2-fin-live-window.sh +++ b/scripts/qa/run-p2-fin-live-window.sh @@ -51,6 +51,7 @@ force_payment_batch_flag_off() { const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").replace(/\/$/, ""); const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim(); const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim(); +const issueId = process.env.QA_ISSUE_ID?.trim() || "P2FLIVE"; if (!identifier || !password) { console.error("[p2-fin-live][rollback] missing admin credentials"); @@ -121,7 +122,7 @@ const updated = await fetchJson(`${baseUrl}/api/system/feature-flags/payment_bat overrideMode: "force_off", rolloutType: "global", departmentIds: [], - note: "P2FLIVE runner final rollback", + note: `${issueId} runner final rollback`, expectedVersion: flag.version, }), }); @@ -145,7 +146,7 @@ write_summary_json() { QA_BASE_URL="$PUBLIC_BASE_URL" \ QA_MODE="p2-fin-live-window" \ QA_STATUS="$qa_status" \ - QA_SUMMARY_NOTES="P2-FIN-LIVE 公网短窗口:临时启用 payment_batch_v1,完成测试付款闭环后强制回滚到 flag_force_off;不部署、不接真实支付、不开放图片 OCR。" \ + QA_SUMMARY_NOTES="${QA_ISSUE_ID:-P2FLIVE} 公网短窗口:临时启用 payment_batch_v1,完成测试付款闭环后强制回滚到 flag_force_off;不部署、不接真实支付、不开放图片 OCR。" \ node "$ROOT_DIR/scripts/qa/write-qa-summary.mjs" >/dev/null 2>&1 || true } @@ -187,7 +188,7 @@ fi run_shell_step "live-env-strict" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-live-smoke-env.mjs' --strict" run_shell_step "pre-flag-off-check" "LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' node '$ROOT_DIR/scripts/qa/check-payment-batch-flag-off.mjs' --json" -run_shell_step "p2-fin-live-spec" "cd '$CLIENT_DIR' && PLAYWRIGHT_MANUAL=1 E2E_MODE=live P2_FIN_LIVE_APPROVED=1 P2_FIN_LIVE_REPORT_DIR='$REPORT_DIR' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --workers=1 --reporter=list" +run_shell_step "p2-fin-live-spec" "cd '$CLIENT_DIR' && PLAYWRIGHT_MANUAL=1 E2E_MODE=live P2_FIN_LIVE_APPROVED=1 QA_ISSUE_ID='${QA_ISSUE_ID:-P2FLIVE}' P2_FIN_LIVE_MARKER_PREFIX='${P2_FIN_LIVE_MARKER_PREFIX:-${QA_ISSUE_ID:-P2FLIVE}}' P2_FIN_LIVE_REPORT_DIR='$REPORT_DIR' LIVE_SMOKE_BASE_URL='$PUBLIC_BASE_URL' npx playwright test tests/e2e/manual/p2-fin-live-payment-batch.spec.ts --workers=1 --reporter=list" if [[ "${P2_FIN_LIVE_SKIP_MOBILE:-0}" == "1" ]]; then echo "[p2-fin-live][SKIP] mobile-wecom P2_FIN_LIVE_SKIP_MOBILE=1" | tee -a "$SUMMARY_TXT"