PRELAUNCH 收口正式启用前公网预回归

这个提交包含在:
2026-05-17 02:04:22 +08:00
父节点 20a783543b
当前提交 94d2cc8bf9
共修改 7 个文件,包含 79 行新增和 44 行删除
@@ -82,14 +82,15 @@ type LiveSummary = {
error?: string; 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 baseUrl = process.env.LIVE_SMOKE_BASE_URL?.trim() || "https://oa.hyinda.com";
const reportDir = process.env.P2_FIN_LIVE_REPORT_DIR?.trim() || const reportDir = process.env.P2_FIN_LIVE_REPORT_DIR?.trim() ||
path.resolve(process.cwd(), "..", "artifacts/qa-flow/p2-fin-live-local"); path.resolve(process.cwd(), "..", "artifacts/qa-flow/p2-fin-live-local");
const summaryPath = path.join(reportDir, "live-result.json"); const summaryPath = path.join(reportDir, "live-result.json");
function nowMarker() { 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() { function today() {
@@ -210,7 +211,7 @@ async function firstEnabledExpenseTypeId(api: APIRequestContext) {
async function createTestReimbursement(api: APIRequestContext, marker: string) { async function createTestReimbursement(api: APIRequestContext, marker: string) {
const reimbursement = await apiPostData<ReimbursementDetail>(api, "/api/reimbursements", { const reimbursement = await apiPostData<ReimbursementDetail>(api, "/api/reimbursements", {
description: `P2FINLIVE 公网付款测试 ${marker}`, description: `${markerPrefix} 公网付款测试 ${marker}`,
reimbursementDate: today(), reimbursementDate: today(),
}); });
const expenseTypeId = await firstEnabledExpenseTypeId(api); const expenseTypeId = await firstEnabledExpenseTypeId(api);
@@ -218,7 +219,7 @@ async function createTestReimbursement(api: APIRequestContext, marker: string) {
expenseTypeId, expenseTypeId,
expenseDate: today(), expenseDate: today(),
amount: "168.30", amount: "168.30",
description: `P2FINLIVE 小额测试 ${marker}`, description: `${markerPrefix} 小额测试 ${marker}`,
}); });
return apiGetData<ReimbursementDetail>(api, `/api/reimbursements/${reimbursement.id}`); return apiGetData<ReimbursementDetail>(api, `/api/reimbursements/${reimbursement.id}`);
} }
@@ -298,7 +299,7 @@ async function createPaymentBatch(api: APIRequestContext, input: { reimbursement
entityIds: [input.reimbursementId], entityIds: [input.reimbursementId],
defaultPaymentDate: today(), defaultPaymentDate: today(),
defaultPaymentChannel: "bank", defaultPaymentChannel: "bank",
voucherPrefix: `P2LIVE-${input.marker.slice(-6)}`, voucherPrefix: `${markerPrefix.slice(0, 12)}-${input.marker.slice(-6)}`,
}); });
return data.item; return data.item;
} }
@@ -402,14 +403,14 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => {
admin.api, admin.api,
"payment_batch_v1", "payment_batch_v1",
"force_on", "force_on",
`P2FLIVE short window ${marker}`, `${issueId} short window ${marker}`,
); );
summary.stages.flagOn = { paymentFlagOn: snapshotFlag(paymentFlagOn) }; summary.stages.flagOn = { paymentFlagOn: snapshotFlag(paymentFlagOn) };
expect(paymentFlagOn.effectiveEnabled).toBe(true); expect(paymentFlagOn.effectiveEnabled).toBe(true);
const erpFlagAfterPaymentOn = await loadFlag(admin.api, "erp_sync"); 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 adminWildcard = isWildcardSuperAdmin(admin);
const rejectedCreate: Record<string, string | number> = { const rejectedCreate: Record<string, string | number> = {
employee: await expectPostRejected(employee.api, "/api/payment-batches", { 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"); : 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`, { batch = (await apiPostData<{ item: PaymentBatch }>(financeMgr.api, `/api/payment-batches/${batch.id}/review`, {
comment: `P2FLIVE 复核 ${marker}`, comment: `${issueId} 复核 ${marker}`,
})).item; })).item;
expect(batch.reviewRequirement.completedReviewCount).toBeGreaterThanOrEqual(1); expect(batch.reviewRequirement.completedReviewCount).toBeGreaterThanOrEqual(1);
@@ -493,7 +494,7 @@ test.describe("P2-FIN-LIVE 公网付款批次短窗口", () => {
} finally { } finally {
if (admin) { if (admin) {
try { 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) }; summary.stages.rollback = { paymentFlagAfterRollback: snapshotFlag(rolledBack) };
expect(rolledBack.effectiveEnabled).toBe(false); expect(rolledBack.effectiveEnabled).toBe(false);
} catch (error) { } catch (error) {
@@ -98,10 +98,25 @@ type TrialState = {
}; };
const rootDir = path.resolve(process.cwd(), ".."); 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 reportPath = path.join(reportDir, "report.md");
const issueCandidatesPath = path.join(reportDir, "issues.csv"); 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<FlowKey>([ const validFlows = new Set<FlowKey>([
"preflight", "preflight",
"reimbursement-finance", "reimbursement-finance",
@@ -114,13 +129,14 @@ const validFlows = new Set<FlowKey>([
"all", "all",
]); ]);
const loginRateLimitRetryMs = Number.parseInt(process.env.R7E_LOGIN_RETRY_WAIT_MS ?? "65000", 10); 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 defaultCloseoutReimbursementId = "40876ef3-0652-4972-aa14-1aad1a855ae0";
const defaultCloseoutReimbursementCode = "BX-20260514-0016"; const defaultCloseoutReimbursementCode = "BX-20260514-0016";
const wecomUa = 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"; "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() { function nowStamp() {
return String(Date.now()); return `${trialrunMarkerPrefix}-${Date.now()}`;
} }
function formatDate(offsetDays: number) { function formatDate(offsetDays: number) {
@@ -282,13 +298,13 @@ async function createPaymentBatchViaApi(api: APIRequestContext, input: {
entityIds: [input.reimbursementId], entityIds: [input.reimbursementId],
defaultPaymentDate: new Date().toISOString().slice(0, 10), defaultPaymentDate: new Date().toISOString().slice(0, 10),
defaultPaymentChannel: "bank", 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) { async function reviewPaymentBatchViaApi(api: APIRequestContext, batchId: string, marker: string) {
return apiPostData<{ item: PaymentBatch }>(api, `/api/payment-batches/${batchId}/review`, { 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, expenseTypeId,
expenseDate: new Date().toISOString().slice(0, 10), expenseDate: new Date().toISOString().slice(0, 10),
amount: "168.30", amount: "168.30",
description: `R7E 浏览器报销 ${marker}`, description: `${trialrunMarkerPrefix} 浏览器报销 ${marker}`,
}, },
}); });
if (!response.ok()) { if (!response.ok()) {
@@ -471,15 +487,17 @@ function writeReports(steps: TrialStep[]) {
const flowIssueCandidatesPath = path.join(reportDir, `issues-${safeName(flowName)}.csv`); const flowIssueCandidatesPath = path.join(reportDir, `issues-${safeName(flowName)}.csv`);
const lines = [ const lines = [
"# R7E 浏览器业务先行试运行报告", `# ${trialrunReportTitle}`,
"", "",
`生成时间:${generatedAt}`, `生成时间:${generatedAt}`,
`执行 flow:${flowName}`, `执行 flow:${flowName}`,
`Issue:${trialrunIssueId}`,
`测试数据标记:${trialrunMarkerPrefix}`,
"", "",
"## 结论", "## 结论",
"", "",
failures.length failures.length
? `发现 ${failures.length} 个失败步骤,需分流到 R7E/R7E-16 或 QAOPS8。` ? `发现 ${failures.length} 个失败步骤,需分流到 ${trialrunIssueId} 或后续 Issue。`
: skips.length : skips.length
? `未发现失败步骤,但有 ${skips.length} 个 SKIP 步骤,不能据此关闭对应人工验收。` ? `未发现失败步骤,但有 ${skips.length} 个 SKIP 步骤,不能据此关闭对应人工验收。`
: "公网浏览器业务线自动辅助试运行未发现阻断步骤。", : "公网浏览器业务线自动辅助试运行未发现阻断步骤。",
@@ -513,15 +531,15 @@ function writeReports(steps: TrialStep[]) {
const issueHeader = ["ID", "Flow", "Severity", "Title", "Actual", "Screenshot", "Disposition", "Status", "Notes"]; const issueHeader = ["ID", "Flow", "Severity", "Title", "Actual", "Screenshot", "Disposition", "Status", "Notes"];
const issueRows = failures.map((item, index) => 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,
item.flow === "公共前置" ? "BLOCKER" : "HIGH", item.flow === "公共前置" ? "BLOCKER" : "HIGH",
item.title, item.title,
item.detail, item.detail,
item.screenshot || "N/A", 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", "OPEN",
"由 R7E 浏览器业务先行试运行生成,需人工复核严重级别。", `由 ${trialrunIssueId} 浏览器业务先行试运行生成,需人工复核严重级别。`,
].map(csvCell).join(","), ].map(csvCell).join(","),
); );
const issueContent = `${issueHeader.map(csvCell).join(",")}\n${issueRows.join("\n")}${issueRows.length ? "\n" : ""}`; 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"); 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.PLAYWRIGHT_MANUAL !== "1", "仅在显式手工 Playwright 模式下执行");
test.skip(process.env.E2E_MODE !== "live", "仅在 live 模式下执行"); test.skip(process.env.E2E_MODE !== "live", "仅在 live 模式下执行");
test.skip(process.env.R7E_BROWSER_TRIALRUN !== "1", "仅在 R7E 浏览器业务试运行模式下执行"); test.skip(process.env.R7E_BROWSER_TRIALRUN !== "1", `仅在 ${trialrunIssueId} 浏览器业务试运行模式下执行`);
test.setTimeout(900_000); test.setTimeout(Number.isFinite(trialrunTestTimeoutMs) && trialrunTestTimeoutMs > 0 ? trialrunTestTimeoutMs : 900_000);
mkdirSync(screenshotDir, { recursive: true }); mkdirSync(screenshotDir, { recursive: true });
const steps: TrialStep[] = []; const steps: TrialStep[] = [];
@@ -695,7 +713,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
if (existingBatch && ["draft", "reviewed", "failed"].includes(existingBatch.status)) { if (existingBatch && ["draft", "reviewed", "failed"].includes(existingBatch.status)) {
batch = existingBatch; batch = existingBatch;
} else { } else {
const batchName = `R7E 非人工付款批次 ${marker}`; const batchName = `${trialrunMarkerPrefix} 非人工付款批次 ${marker}`;
const created = await createPaymentBatchViaApi(financeApi.api, { reimbursementId, name: batchName, marker }); const created = await createPaymentBatchViaApi(financeApi.api, { reimbursementId, name: batchName, marker });
batch = created.item; batch = created.item;
} }
@@ -763,7 +781,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
await (await openNavLink(page, "请假")).click(); await (await openNavLink(page, "请假")).click();
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible(); 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 submitResult = await apiPostData<{ leaveRequestId: string; currentTaskId: string }>(employeeApi.api, `/api/leave-requests/${leaveId}/submit`, {});
const taskId = submitResult.currentTaskId; const taskId = submitResult.currentTaskId;
if (!taskId) { if (!taskId) {
@@ -853,7 +871,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
await page.goto(`/reimbursements/${reimbursementId}`); await page.goto(`/reimbursements/${reimbursementId}`);
await reimbursementDetail.assertLoaded(); 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) { if (!uploadResult.invoiceId) {
return { return {
status: "SKIP", status: "SKIP",
@@ -937,7 +955,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
await (await openNavLink(page, "请假")).click(); await (await openNavLink(page, "请假")).click();
await expect(page.getByRole("heading", { name: "请假申请" })).toBeVisible(); 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.leaveRequestId = leaveId;
state.leaveDate = startDate; state.leaveDate = startDate;
const submitResult = await apiPostData<{ leaveRequestId: string; currentTaskId: string }>(employeeApi.api, `/api/leave-requests/${leaveId}/submit`, {}); const submitResult = await apiPostData<{ leaveRequestId: string; currentTaskId: string }>(employeeApi.api, `/api/leave-requests/${leaveId}/submit`, {});
@@ -996,7 +1014,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
state.purchaseRequestId = purchaseRequestId; state.purchaseRequestId = purchaseRequestId;
await page.goto(`/purchase-requests?id=${purchaseRequestId}`); await page.goto(`/purchase-requests?id=${purchaseRequestId}`);
await expect(page.getByTestId("purchase-title")).toBeVisible(); 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-legal-entity"));
await selectFirstEnabledOption(page.getByTestId("purchase-cost-center")); await selectFirstEnabledOption(page.getByTestId("purchase-cost-center"));
await selectFirstEnabledOption(page.getByTestId("purchase-supplier")); await selectFirstEnabledOption(page.getByTestId("purchase-supplier"));
@@ -1007,11 +1025,11 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
await page.getByTestId("purchase-add-item").click(); await page.getByTestId("purchase-add-item").click();
} }
await expect(itemDescriptionInputs.first()).toBeVisible({ timeout: 30_000 }); 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-quantity-"]:visible').first().fill("1");
await page.locator('[data-testid^="purchase-item-unit-price-"]:visible').first().fill("188"); 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 uploadAttachmentResponsePromise = page.waitForResponse((response) => {
const url = new URL(response.url()); const url = new URL(response.url());
return response.request().method() === "POST" && url.pathname === `/api/purchase-requests/${purchaseRequestId}/attachments`; 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({ await page.getByTestId("purchase-attachment-file-input").setInputFiles({
name: purchaseAttachmentName, name: purchaseAttachmentName,
mimeType: "text/plain", 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; const uploadAttachmentResponse = await uploadAttachmentResponsePromise;
if (!uploadAttachmentResponse.ok()) { if (!uploadAttachmentResponse.ok()) {
@@ -1137,7 +1155,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
}; };
} }
const batchName = `R7E 浏览器付款批次 ${marker}`; const batchName = `${trialrunMarkerPrefix} 浏览器付款批次 ${marker}`;
state.paymentBatchName = batchName; state.paymentBatchName = batchName;
await page.goto("/payment-batches"); await page.goto("/payment-batches");
await expect(page.getByRole("heading", { name: "支付批次", exact: true })).toBeVisible(); 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.getByPlaceholder("用逗号或换行分隔 reimbursement / travel_order ID").fill(state.reimbursementId);
await createCard.locator('input[type="date"]').fill(new Date().toISOString().slice(0, 10)); await createCard.locator('input[type="date"]').fill(new Date().toISOString().slice(0, 10));
await createCard.locator("select").nth(1).selectOption("bank"); 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(); await createCard.getByRole("button", { name: "创建批次" }).click();
const createBatchResponse = await createBatchResponsePromise; const createBatchResponse = await createBatchResponsePromise;
if (!createBatchResponse.ok()) { if (!createBatchResponse.ok()) {
@@ -1182,7 +1200,7 @@ test("R7E 浏览器业务先行试运行:报销、审批、请假、采购、
await expect(pendingRow).toBeVisible({ timeout: 30_000 }); await expect(pendingRow).toBeVisible({ timeout: 30_000 });
await pendingRow.getByRole("button", { name: "快捷复核" }).click(); await pendingRow.getByRole("button", { name: "快捷复核" }).click();
await expect(page.getByRole("dialog")).toContainText(batchName); 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 page.getByRole("button", { name: "提交复核" }).click();
await expect(pendingRow).toContainText(/复核 1\/1|复核 1\/2/, { timeout: 30_000 }); await expect(pendingRow).toContainText(/复核 1\/1|复核 1\/2/, { timeout: 30_000 });
await logoutLiveUser(page, { name: financeMgr.name }).catch(() => undefined); await logoutLiveUser(page, { name: financeMgr.name }).catch(() => undefined);
+3
查看文件
@@ -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 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-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 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` | | 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-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` | | 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,不做业务实现 | | 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-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` | | 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 预检推进 ## R7EP / OPS8 / QAF8 / UX8 / P2T 预检推进
+5 -4
查看文件
@@ -4,7 +4,7 @@
**分类**:文档台账。 **分类**:文档台账。
**适用时间**:DCL7 之后。 **适用时间**:DCL7 之后。
**关联 Issue**:`GOV8`。 **关联 Issue**:`GOV8`。
**最近审计**:2026-05-16。 **最近审计**:2026-05-17。
**不得误读**:本文登记文档权威性和状态,不替代具体 PRD、模块规约、测试规约或 Issue CSV。 **不得误读**:本文登记文档权威性和状态,不替代具体 PRD、模块规约、测试规约或 Issue CSV。
**治理口径**:中文优先,物理精简,优先复用已有文档。历史资料不建 `docs/archive/`,已确认不用的文档从当前树移除,通过 git 历史恢复。 **治理口径**:中文优先,物理精简,优先复用已有文档。历史资料不建 `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/` | | `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-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`,未部署 | | `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。 旧 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 | | 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 | 判断当前该读什么 | | `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` | `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,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 | 定义做什么、不做什么和验收口径 | | `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 后按页面分层和样板选型 |
@@ -160,4 +161,4 @@
- `ME6-3` 代表性企业微信真机门禁已关闭。 - `ME6-3` 代表性企业微信真机门禁已关闭。
- MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检。 - 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 功能开发、部署、付款批次常开和正式启用公告必须另建或延续对应连续合同。
+4 -2
查看文件
@@ -4,7 +4,7 @@
**分类**:入口索引。 **分类**:入口索引。
**适用时间**:DCL7 之后。 **适用时间**:DCL7 之后。
**关联 Issue**:`GOV8`。 **关联 Issue**:`GOV8`。
**最近审计**:2026-05-16。 **最近审计**:2026-05-17。
**不得误读**:本文只指示当前事实源和读取顺序;历史 Issue CSV 只能用于追溯,不直接生成新开发任务。 **不得误读**:本文只指示当前事实源和读取顺序;历史 Issue CSV 只能用于追溯,不直接生成新开发任务。
**治理口径**:中文优先,物理精简,优先复用已有文档。已确认不用的历史文档不放 `docs/archive/`,直接从当前工作树移除;需要时从 git 历史恢复。 **治理口径**:中文优先,物理精简,优先复用已有文档。已确认不用的历史文档不放 `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 已完成并部署。 - R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。 - 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。 - 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-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。 - 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`、不做业务实现 | | `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-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 | | `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,旧文件保留历史审计语义。 旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
@@ -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 [email protected] 只读执行 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 待提交。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 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。
3 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 变更。
4 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 [email protected] 只读执行 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/。
5 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 覆盖。
6 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。
7 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。
8 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、审批设计器、完整差旅、重预算。
9 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 待提交。
+4 -3
查看文件
@@ -51,6 +51,7 @@ force_payment_batch_flag_off() {
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").replace(/\/$/, ""); const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").replace(/\/$/, "");
const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim(); const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim();
const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim(); const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim();
const issueId = process.env.QA_ISSUE_ID?.trim() || "P2FLIVE";
if (!identifier || !password) { if (!identifier || !password) {
console.error("[p2-fin-live][rollback] missing admin credentials"); 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", overrideMode: "force_off",
rolloutType: "global", rolloutType: "global",
departmentIds: [], departmentIds: [],
note: "P2FLIVE runner final rollback", note: `${issueId} runner final rollback`,
expectedVersion: flag.version, expectedVersion: flag.version,
}), }),
}); });
@@ -145,7 +146,7 @@ write_summary_json() {
QA_BASE_URL="$PUBLIC_BASE_URL" \ QA_BASE_URL="$PUBLIC_BASE_URL" \
QA_MODE="p2-fin-live-window" \ QA_MODE="p2-fin-live-window" \
QA_STATUS="$qa_status" \ 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 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 "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 "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 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" echo "[p2-fin-live][SKIP] mobile-wecom P2_FIN_LIVE_SKIP_MOBILE=1" | tee -a "$SUMMARY_TXT"