From f945c0bfb05ffe2b3063d228c9d6676ffe138c4f Mon Sep 17 00:00:00 2001 From: shunli Date: Fri, 15 May 2026 18:24:03 +0800 Subject: [PATCH] =?UTF-8?q?R7E=20=E7=9C=9F=E6=9C=BA=E4=BA=8C=E6=AC=A1?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=E7=A7=BB=E5=8A=A8=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引用 R7E-16C BIZ8-12B --- .../components/mobile/MobileDetailHeader.tsx | 22 ++++-- client/src/pages/MobileApprovalsPage.tsx | 38 ++++++---- client/src/pages/MobileMePage.tsx | 2 +- client/src/pages/MobilePortalPage.tsx | 17 ++++- client/src/styles/mobile-touch.css | 28 +++++++ .../e2e/dg7-mobile-information-dedupe.spec.ts | 76 +++++++++++++++++++ .../e2e/r124-mobile-entry-matrix.spec.ts | 18 ++++- docs/trialrun/人工试运行报告.md | 14 ++++ ...2026-05-13_17-11-44-r7e-human-trialrun.csv | 3 +- ...5-13_18-23-53-biz8-finance-ocr-project.csv | 3 +- 10 files changed, 197 insertions(+), 24 deletions(-) diff --git a/client/src/components/mobile/MobileDetailHeader.tsx b/client/src/components/mobile/MobileDetailHeader.tsx index 3bacd924..47c10bf5 100644 --- a/client/src/components/mobile/MobileDetailHeader.tsx +++ b/client/src/components/mobile/MobileDetailHeader.tsx @@ -44,16 +44,26 @@ export function MobileDetailHeader({ title, subtitle, status, fallbackTo, testId }; return ( -
- -
+
{title}
{subtitle ?
{subtitle}
: null}
- {status ?
{status}
: null} + {status ?
{status}
: null}
); } diff --git a/client/src/pages/MobileApprovalsPage.tsx b/client/src/pages/MobileApprovalsPage.tsx index 282deabc..a0119cf9 100644 --- a/client/src/pages/MobileApprovalsPage.tsx +++ b/client/src/pages/MobileApprovalsPage.tsx @@ -1037,13 +1037,13 @@ export function MobileApprovalsPage() { to={item.to} state={{ from: currentMobileSource }} > -
+
{item.title}
{item.subtitle ?
{item.subtitle}
: null}
- - + +
@@ -1223,10 +1223,14 @@ export function MobileApprovalsPage() { data-testid={`mobile-approvals-task-${task.id}`} onClick={() => toggleBatchTask(task.id)} > -
+
{task.title}
- - + +
{task.subtitle}
@@ -1249,10 +1253,14 @@ export function MobileApprovalsPage() { onClick={() => handleTaskSelection(task.id)} type="button" > -
+
{task.title}
- - + +
{task.subtitle}
@@ -1292,13 +1300,17 @@ export function MobileApprovalsPage() { {selectedTask ? ( <>
-
+
{selectedDetailTitle}
{joinBusinessMeta([`节点:${selectedTask.task.stepName}`, `创建:${formatDateTime(selectedTask.task.createdAt)}`])}
- - + +
@@ -1389,7 +1401,7 @@ export function MobileApprovalsPage() {

离线仅保存通过 / 驳回草稿,联网同步成功后才会正式提交。

) : null} -
+
diff --git a/client/src/pages/MobilePortalPage.tsx b/client/src/pages/MobilePortalPage.tsx index 70d0a5b1..376211da 100644 --- a/client/src/pages/MobilePortalPage.tsx +++ b/client/src/pages/MobilePortalPage.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; @@ -204,6 +205,20 @@ export function MobilePortalPage() { queryFn: () => fetchPortalHome(), }); + useEffect(() => { + if (location.hash !== "#finance" || !canUseFinance) { + return; + } + + const frameId = window.requestAnimationFrame(() => { + const financeSection = document.getElementById("finance"); + financeSection?.scrollIntoView({ block: "start", behavior: "smooth" }); + financeSection?.focus({ preventScroll: true }); + }); + + return () => window.cancelAnimationFrame(frameId); + }, [canUseFinance, location.hash, portalHomeQuery.isError, portalHomeQuery.isLoading]); + const markHomeNotificationReadMutation = useMutation({ mutationFn: (notificationId: string) => markNotificationRead(notificationId), onSuccess: async () => { @@ -379,7 +394,7 @@ export function MobilePortalPage() { ) : null} {canUseFinance ? ( -
+
财务
{paymentBatchEnabled ? "复核与付款" : "审批与台账"} diff --git a/client/src/styles/mobile-touch.css b/client/src/styles/mobile-touch.css index ab45bcb0..6afa8eb7 100644 --- a/client/src/styles/mobile-touch.css +++ b/client/src/styles/mobile-touch.css @@ -259,6 +259,11 @@ min-width: 0; } +.mobile-list-card__header--approval { + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.75rem; +} + .mobile-list-card__title { min-width: 0; overflow: hidden; @@ -295,6 +300,19 @@ white-space: nowrap; } +.mobile-status-slot--approval { + min-width: 4.5rem; + max-width: none; + justify-content: flex-end; +} + +.mobile-status-slot--approval > *, +.mobile-status-badge { + min-width: 4.25rem; + justify-content: center; + overflow: visible; +} + .mobile-subcard { min-width: 0; border: 1px solid rgb(228 228 231); @@ -383,6 +401,16 @@ min-height: 46px; } +.mobile-approval-action-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.mobile-approval-action-grid > * { + min-width: 0; + padding-left: 0.5rem; + padding-right: 0.5rem; +} + @media (max-width: 359px) { :root { --mobile-page-x: 8px; diff --git a/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts b/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts index be2ac6d5..12a1ba90 100644 --- a/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts +++ b/client/tests/e2e/dg7-mobile-information-dedupe.spec.ts @@ -778,6 +778,74 @@ async function expectNoMobileShellRegression(page: Page) { expect(metrics.clippedStatusSlots).toEqual([]); } +async function expectMobileApprovalStatusSlots(page: Page) { + const slots = await page.evaluate(() => + Array.from(document.querySelectorAll(".mobile-status-slot--approval")) + .filter((slot) => { + const rect = slot.getBoundingClientRect(); + const style = getComputedStyle(slot); + return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none"; + }) + .map((slot) => { + const rect = slot.getBoundingClientRect(); + const badge = slot.querySelector(".mobile-status-badge"); + const badgeRect = badge?.getBoundingClientRect(); + return { + text: slot.innerText.trim(), + width: rect.width, + badgeWidth: badgeRect?.width ?? 0, + }; + }), + ); + + expect(slots.length).toBeGreaterThan(0); + for (const slot of slots) { + expect(slot.text.length).toBeGreaterThanOrEqual(2); + expect(slot.width).toBeGreaterThanOrEqual(54); + expect(slot.badgeWidth).toBeGreaterThanOrEqual(54); + } +} + +async function expectMobileDetailHeaderSpacing(page: Page, testId: string) { + const metrics = await page.getByTestId(testId).evaluate((header) => { + const backButton = header.querySelector("button[aria-label='返回']"); + const title = header.querySelector(".mobile-detail-header__title"); + const backRect = backButton?.getBoundingClientRect(); + const titleRect = title?.getBoundingClientRect(); + return backRect && titleRect ? { + backWidth: backRect.width, + gap: titleRect.left - backRect.right, + } : null; + }); + + expect(metrics).not.toBeNull(); + expect(metrics!.backWidth).toBeGreaterThanOrEqual(44); + expect(metrics!.gap).toBeGreaterThanOrEqual(12); +} + +async function expectApprovalActionsStable(page: Page) { + const actions = await page.evaluate(() => + Array.from(document.querySelectorAll(".mobile-approval-action-grid > button")).map((button) => { + const rect = button.getBoundingClientRect(); + return { + left: rect.left, + right: rect.right, + top: rect.top, + width: rect.width, + }; + }), + ); + + expect(actions).toHaveLength(3); + const firstTop = actions[0].top; + for (const action of actions) { + expect(Math.abs(action.top - firstTop)).toBeLessThanOrEqual(2); + expect(action.width).toBeGreaterThanOrEqual(64); + } + expect(actions[0].right).toBeLessThanOrEqual(actions[1].left + 2); + expect(actions[1].right).toBeLessThanOrEqual(actions[2].left + 2); +} + async function expectActionTileGapStable(page: Page) { const gaps = await page.evaluate(() => Array.from(document.querySelectorAll("[data-testid='mobile-action-grid'] .mobile-icon-action")).map((tile) => { @@ -843,6 +911,7 @@ test.describe("MIF7 移动信息架构整改", () => { await expect(mobile.page.getByTestId("mobile-approvals-list")).toHaveCount(0); await expect(mobile.page.getByTestId("mobile-approvals-approve")).toHaveCount(0); await expect(mobile.page.getByTestId("mobile-approval-progress-card")).toHaveCount(3); + await expectMobileApprovalStatusSlots(mobile.page); await expectNoMobileShellRegression(mobile.page); await mobile.page.screenshot({ path: screenshotPath(`employee-progress-w${width}`), fullPage: true }); @@ -908,6 +977,9 @@ test.describe("MIF7 移动信息架构整改", () => { await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("提交 · 测试员工"); await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("转审 · 测试审批人"); await expect(mobile.page.getByTestId("mobile-approvals-history")).not.toContainText("transferToEmployeeId"); + await expectMobileApprovalStatusSlots(mobile.page); + await expectMobileDetailHeaderSpacing(mobile.page, "mobile-approvals-detail-header"); + await expectApprovalActionsStable(mobile.page); await expectNoMobileShellRegression(mobile.page); await mobile.page.screenshot({ path: screenshotPath("manager-approval-push-detail"), fullPage: true }); @@ -1050,6 +1122,10 @@ test.describe("MIF7 移动信息架构整改", () => { await financeMobile.page.screenshot({ path: screenshotPath("home-finance-summary"), fullPage: true }); await financeMobile.page.goto("/mobile/me"); await expect(financeMobile.page.getByRole("link", { name: /财务/ })).toBeVisible(); + await financeMobile.page.getByRole("link", { name: /财务/ }).click(); + await expect(financeMobile.page).toHaveURL(/\/mobile#finance$/); + await expect(financeMobile.page.getByTestId("mobile-finance-summary")).toBeInViewport(); + await expect(financeMobile.page.getByTestId("mobile-finance-disabled-state")).toBeInViewport(); await expectNoMobileShellRegression(financeMobile.page); } finally { await financeMobile.context.close(); diff --git a/client/tests/e2e/r124-mobile-entry-matrix.spec.ts b/client/tests/e2e/r124-mobile-entry-matrix.spec.ts index 040451ce..15736b3a 100644 --- a/client/tests/e2e/r124-mobile-entry-matrix.spec.ts +++ b/client/tests/e2e/r124-mobile-entry-matrix.spec.ts @@ -55,7 +55,7 @@ function buildMe() { directManagerId: null, status: "active", notificationPushEnabled: true, - roleCodes: ["manager"], + roleCodes: ["manager", "finance"], profileCompleteness: { hasEmergencyContact: true, hasBankAccount: true, @@ -230,6 +230,13 @@ async function mockApis(page: Page, state: ReturnType) { weeklySummary: null, todoSummary: null, projectSummary: null, + financeSummary: { + paymentBatchEnabled: false, + href: "/mobile#finance", + pendingBatchCount: 0, + failedItemCount: 0, + riskCount: 0, + }, peopleLifecycle: null, adoptionSummary: null, recentActivities: [], @@ -518,6 +525,7 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.getByTestId("mobile-nav-approvals").click(); await expect(mobile.page).toHaveURL(/\/mobile\/approvals$/); await expect(mobile.page.getByText("审批").first()).toBeVisible(); + await expect(mobile.page.locator(".mobile-status-slot--approval").first()).toContainText("待处理"); await expectMobilePageContract(mobile.page); await mobile.page.getByTestId("mobile-nav-me").click(); @@ -526,6 +534,14 @@ test.describe("R124 企业微信移动入口矩阵", () => { await mobile.page.screenshot({ path: screenshotPath("mobile-me") }); await expectMobilePageContract(mobile.page); + await expect(mobile.page.getByRole("link", { name: /财务/ })).toBeVisible(); + await mobile.page.getByRole("link", { name: /财务/ }).click(); + await expect(mobile.page).toHaveURL(/\/mobile#finance$/); + await expect(mobile.page.getByTestId("mobile-finance-summary")).toBeInViewport(); + await expect(mobile.page.getByTestId("mobile-finance-disabled-state")).toBeInViewport(); + await expectMobilePageContract(mobile.page); + + await mobile.page.goto("/mobile/me"); await mobile.page.getByRole("link", { name: /个人设置/ }).click(); await expect(mobile.page).toHaveURL(/\/mobile\/profile$/); await expect(mobile.page.getByTestId("mobile-profile-page")).toBeVisible(); diff --git a/docs/trialrun/人工试运行报告.md b/docs/trialrun/人工试运行报告.md index 8a4e55d0..470d9e0b 100644 --- a/docs/trialrun/人工试运行报告.md +++ b/docs/trialrun/人工试运行报告.md @@ -265,6 +265,20 @@ R7EP 历史结论:公网、live smoke/OCR、移动矩阵、核心业务流、 当前剩余真机项:移动状态标签完整显示、Android 选择器 PDF-only 口径、财务角色 `/mobile` 与 `/mobile/me` 入口,以及 `R7E-18` 阶段验收结论。 +## R7E 真机反馈二次修复记录 + +执行时间:2026-05-15。用户补充使用员工、主管、财务和 `wangshunli` 账号复测后的真机反馈。 + +| 反馈项 | 级别 | 归属 | 本轮处理 | +| --- | --- | --- | --- | +| 通知首页卡片只显示“新”,浏览详情后“新”消失 | PASS | `R7E-15` | 按未读提醒行为接受,不改成“已读/未读”常驻状态 | +| 移动详情页左上返回按钮和报销单号贴得太近 | MEDIUM | `R7E-16C` | 保留左上返回,改为 44px 图标返回槽和独立标题槽,避免挤压单号 | +| 审批卡片右侧仍只显示“待”,详情处理按钮居中且三按钮分两行 | MEDIUM | `R7E-16C` | 增加审批专用状态槽和在线三按钮同排布局 | +| 财务 `/mobile/me` 有入口,但点击后像回首页,付款未启用提示不可发现 | MEDIUM | `BIZ8-12B` | 财务入口定位到首页财务卡片并显示“付款批次未启用”空态 | +| `wangshunli` 当前没有通知 | INFO | `R7E-15` | 不作为阻断;deeplink 已由此前代表消息验证关闭 | + +本节对应修复为本地代码修复,尚未部署到 `https://oa.hyinda.com`。本地验证:`cd client && npm run typecheck` 通过,`cd client && npm run build` 通过,`cd client && E2E_BASE_URL=http://127.0.0.1:3000 npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list` 通过 6 项。部署和真机复测需另走受控部署步骤。 + ## QAOPS8 Go/No-Go 当前结论 执行时间:2026-05-15。 diff --git a/issues/2026-05-13_17-11-44-r7e-human-trialrun.csv b/issues/2026-05-13_17-11-44-r7e-human-trialrun.csv index 93511724..7a6077be 100644 --- a/issues/2026-05-13_17-11-44-r7e-human-trialrun.csv +++ b/issues/2026-05-13_17-11-44-r7e-human-trialrun.csv @@ -15,9 +15,10 @@ R7E-12,请假流程试运行,"员工提交请假,审批人审批,员工和 R7E-13,采购流程试运行,"员工提交或查看采购申请,审批人处理,状态和附件可追溯。","1) 采购申请路径可用;2) 审批处理可用;3) 附件和状态可追溯;4) 移动采购创建如缺失则记录为后续 UX/P2。","流程级验证:员工提交/查看采购 -> 审批处理 -> 查看状态和附件。",manual|playwright,DONE,DONE,DONE,docs/trialrun/人工试运行报告.md|client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts,"[""R7E-8""]",2026-05-14 无人工自动化补测:采购 CG-20260514-0005 完成 PC 创建 明细 无敏感测试附件上传 提交 审批 状态和审批历史回看;移动采购列表 详情和附件可见且无横向滚动;证据 artifacts/r7e/browser-business-trialrun/report-purchase-attachment.md。 R7E-14,财务审批和付款延期试运行,"财务按真实路径复核到财务审批的报销单;本轮不启用 payment_batch_v1,不追付款批次闭环,付款批次作为 P2 单独窗口处理。","1) 财务角色入口正确;2) 到财务审批的报销单可查看;3) payment_batch_v1 关闭时有明确空态或错误契约;4) 管理员不自动获得财务能力;5) 不执行真实付款批次。","流程级验证:财务进入移动入口或台账 -> 查看到财务审批的报销单或付款批次未启用提示 -> 检查管理员无财务执行能力 -> 记录付款批次延期到 P2。",manual|playwright,DONE,DONE,DONE,docs/trialrun/人工试运行报告.md|client/tests/e2e/manual/r7e-browser-business-trialrun.spec.ts,"[""R7E-8""]",2026-05-14 finance-closeout 诊断:报销 40876ef3-0652-4972-aa14-1aad1a855ae0 / BX-20260514-0016 状态 approved;/api/ap-operations/reimbursement-ledger 和 /api/payment-batches 均返回 payment_batch_v1_disabled / flag_force_off;未创建付款批次 未复核 未执行付款 未验证导出。2026-05-14 用户确认 R7E 财务付款策略为先不启用,付款批次延期到 P2;22:08 最新部署后 R7E_BROWSER_FLOW=finance-closeout 通过但 AP API 按 flag_force_off SKIP,R7E_BROWSER_FLOW=mobile 通过并确认员工 审批人 财务移动入口可见且无横向滚动;本行按延期口径收口,不代表付款批次已启用。 R7E-15,移动端和真机反馈,"收集 R7E 参与者在移动浏览器和企业微信中的真实体验反馈。","1) 至少覆盖代表性 iPhone/Android 或企业微信设备;2) 记录页面、视口、角色和问题级别;3) 阻断问题进入修复分流;4) 体验优化进入 UX8。","流程级验证:按真机公网测试清单人工复核;必要时补 Playwright 截图。",manual|playwright,DOING,TODO,TODO,docs/trialrun/人工试运行报告.md|output/playwright,"[""R7E-9""]",2026-05-14 自动化移动浏览器员工 审批人 财务核心入口通过且无横向滚动;用户真机补充:所有设备没有 PC 壳和横向滚动 但通知卡片和待审批卡片右侧状态只露第一个字 归属 R7E-16A;iOS 只能上传 PDF Android 企业微信选择器仍显示拍照和选择文件 归属 R7E-16B;2026-05-14 22:09 最新修复 5491e92 已部署 DG7+R124 公网移动矩阵 6 passed 且 R7E_BROWSER_FLOW=mobile 通过;2026-05-15 已补代表设备企业微信消息 deeplink 真机通过;本行仍等待修复后的状态标签完整显示 财务入口和 Android PDF-only 口径在代表设备上的最终反馈。 -R7E-16,每日问题清单,"R7E 期间每天登记问题、级别、归属治理流、处理状态和证据路径。","1) 问题均有级别;2) 阻断问题当天决策;3) 新需求不混入即时修复;4) 每日 closeout 可追溯。","流程级验证:每日人工 closeout;问题分流到 R7E/UX8/ORG8/WF8/BIZ8/QAOPS8。",manual,DOING,TODO,TODO,docs/trialrun/人工试运行报告.md,"[""R7E-10"",""R7E-11"",""R7E-12"",""R7E-13"",""R7E-14"",""R7E-15""]",2026-05-14 用户真机反馈分流:免登录通过;企业微信消息 deeplink 全设备提示 requestid tokenexpired/深链已过期 为 HIGH 归属 WF8-11A;通知和待审批卡片右侧状态只露首字 为 MEDIUM 归属 R7E-16A;Android 企业微信 PDF 上传系统选择器仍显示拍照和选择文件 为 MEDIUM 归属 R7E-16B;财务移动入口和付款未启用空态不清楚 为 MEDIUM 归属 BIZ8-12A;2026-05-14 22:05 最新修复 5491e92 已部署并通过 /ready public bundle live readiness DG7+R124 mobile R7E mobile/wecom-ua 自动化前置;2026-05-15 用户确认代表设备企业微信消息 deeplink 可免登录进对应详情 WF8-11 已关闭;财务付款闭环按用户确认延期到 P2;当前剩余为修复后移动状态标签 财务移动入口 Android PDF-only 口径的代表设备复核和 R7E-18 人工验收结论。 +R7E-16,每日问题清单,"R7E 期间每天登记问题、级别、归属治理流、处理状态和证据路径。","1) 问题均有级别;2) 阻断问题当天决策;3) 新需求不混入即时修复;4) 每日 closeout 可追溯。","流程级验证:每日人工 closeout;问题分流到 R7E/UX8/ORG8/WF8/BIZ8/QAOPS8。",manual,DOING,TODO,TODO,docs/trialrun/人工试运行报告.md,"[""R7E-10"",""R7E-11"",""R7E-12"",""R7E-13"",""R7E-14"",""R7E-15""]",2026-05-14 用户真机反馈分流:免登录通过;企业微信消息 deeplink 全设备提示 requestid tokenexpired/深链已过期 为 HIGH 归属 WF8-11A;通知和待审批卡片右侧状态只露首字 为 MEDIUM 归属 R7E-16A;Android 企业微信 PDF 上传系统选择器仍显示拍照和选择文件 为 MEDIUM 归属 R7E-16B;财务移动入口和付款未启用空态不清楚 为 MEDIUM 归属 BIZ8-12A;2026-05-15 用户确认代表设备企业微信消息 deeplink 可免登录进对应详情 WF8-11 已关闭;二次反馈中通知新标记按未读行为接受,审批状态槽/详情头部/按钮布局归 R7E-16C 且本地验证通过;财务付款闭环延期到 P2;当前剩余为部署后二次修复真机复核 Android PDF-only 口径确认和 R7E-18 人工验收结论。 R7E-16A,移动通知和待审批状态槽修复,"修复移动通知卡片和待审批卡片右侧状态徽标只显示第一个字的问题,保持无 PC 壳和无横向滚动。","1) 未读 已读 审批中 已处理等状态在 320/360/390/430px 可完整显示;2) 通知和审批卡片标题仍可省略但不挤压状态;3) 页面 scrollWidth 不超过视口;4) Playwright 移动矩阵通过。","流程级验证:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list。",npm|playwright,DONE,DONE,DONE,client/src/styles/mobile-touch.css|client/src/pages/MobilePortalPage.tsx|client/src/pages/MobileNotificationsPage.tsx|client/src/pages/MobileApprovalsPage.tsx|client/tests/e2e,"[""R7E-16""]",回滚路径:恢复 mobile-status-slot 旧样式和相关测试断言;不改变业务数据或路由。 R7E-16B,Android PDF 上传兜底校验,"补强移动发票上传的前端 PDF 过滤,记录 Android 企业微信系统选择器可能仍显示拍照和选择文件但 OA 不接收非 PDF 发票。","1) PDF 上传入口 accept 为 .pdf,application/pdf 且无 capture;2) 选择非 PDF 时前端拒绝并提示;3) 不把图片 OCR 误报为支持;4) 既有 PDF 上传测试通过。","流程级验证:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/r111-invoice-reimbursement-guard.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list。",npm|playwright,DONE,DONE,DONE,client/src/lib/pdfFiles.ts|client/src/pages/MobileCapturePage.tsx|client/src/pages/MobileReimbursementDetailPage.tsx|client/src/pages/ReimbursementDetailPage.tsx|client/src/pages/invoice-pool/InvoiceUploadCard.tsx|client/tests/e2e,"[""R7E-16""]",Android WebView 文件选择器 UI 受系统和企业微信控制;本轮只保证 OA 端 PDF-only 校验和可解释提示 不新增图片 OCR。 +R7E-16C,移动审批和详情头部二次修复,"根据 R7E 真机反馈修复审批卡片右侧只露待字、审批详情操作按钮布局和移动详情返回按钮与单号贴边问题。","1) 移动审批卡片和详情状态完整显示待处理/已处理等文本;2) 审批详情通过/驳回/退回在线操作在 320px 不挤压乱换行;3) 移动详情返回按钮保留左上且与标题至少 12px 间距;4) 不改变审批动作和 deeplink 语义。","流程级验证:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list;SMOKE101 真机复核 /mobile/approvals。",npm|playwright|manual,DONE,DONE,DONE,client/src/components/mobile/MobileDetailHeader.tsx|client/src/pages/MobileApprovalsPage.tsx|client/src/styles/mobile-touch.css|client/tests/e2e,"[""R7E-16A""]",本地完成:client typecheck/build 通过;DG7+R124 移动专项 6 passed 覆盖 320/360/375/390/393/414/430 员工进度状态槽、审批详情头部间距、审批操作按钮同排和无横向滚动;未部署,仍需 SMOKE101 真机复核。回滚路径:恢复移动详情头部和审批操作区样式;不改后端 API 权限 workflow 状态机或企业微信 deeplink。 R7E-17,阻断问题修复分流,"根据每日问题清单把阻断问题分流到当前 R7E 修复或后续连续治理流。","1) 阻断问题有 owner;2) 能在 R7E 内修的有方案和验证;3) 范围外问题进入 UX8/ORG8/WF8/BIZ8/QAOPS8;4) 不顺手扩大。","流程级验证:人工核对问题清单和分流结果;必要时创建后续连续 CSV。",manual,TODO,TODO,TODO,issues/*.csv|docs/trialrun/人工试运行报告.md,"[""R7E-16""]",已知分流:deeplink 归 WF8 并已代表设备验证关闭;状态槽归 R7E-16A 已部署并自动化验证;Android 选择器归 R7E-16B 按平台限制和 OA PDF-only 校验处理;财务付款批次延期到 P2;本行待 R7E-16 和 R7E-15 真机反馈收口后再关闭。 R7E-18,验收结论,"汇总 R7E 真实试运行结果,给出通过、暂停、回滚或继续观察结论。","1) 主流程结论完整;2) 阻断问题为 0 或明确暂停/回滚;3) 用户验收结论记录;4) 未完成项进入后续治理流。","流程级验证:人工验收;报告中列出流程结论、问题清单和后续动作。",manual,TODO,TODO,TODO,docs/trialrun/人工试运行报告.md,"[""R7E-17""]",不能仅凭自动化通过宣布 R7E 完成。 R7E-19,P2 排序调整,"根据 R7E 真实反馈调整 P2 候选优先级和后续连续治理流顺序。","1) P2 排序基于真实反馈;2) ORG8/WF8/BIZ8/QAOPS8 入口明确;3) 冻结项仍不进入;4) 如需变更 PRD 则另走事实源更新。","流程级验证:人工核对 PRD P2T 表、问题清单和后续 Issue 计划。",manual|rg,TODO,TODO,TODO,docs/海音达OA-MVP需求规格说明书.md|issues/*.csv,"[""R7E-18""]",P2 功能开发必须另建或延续对应连续合同。 diff --git a/issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv b/issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv index 4fb9899f..8ce1c5de 100644 --- a/issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv +++ b/issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv @@ -11,8 +11,9 @@ BIZ8-8,财务台账扫读性,"提升财务台账的扫读性,优先解决状 BIZ8-9,付款和导出增强,"增强或验证财务付款标记和导出路径,保证权限、状态和审计一致。","1) 只有财务可执行付款或导出;2) 状态转换合法;3) 导出不泄露无权限数据;4) 操作可追溯。","流程级验证:财务付款或导出 -> 普通员工和管理员直连复核 -> 检查状态和权限。",node|bash|manual,DONE,DONE,DONE,server/src|client/src|server/scripts,"[""BIZ8-8""]",完成:仅补强 AP export 权限单测和测试错误契约 不改业务代码;覆盖财务导出成功并写 EXPORT_AP_OPERATIONS 审计 普通员工和 admin-only 无财务权限导出 403 且不写审计;付款批次报销流保持 reviewed 到 paid invoice archived ERP outbox notification audit followup 一致;验证 prisma validate 通过;node --check exports paymentBatches financeLedger 通过;npm test exports.apOperations paymentBatches.reimbursement financeLedger 3 files 8 tests passed;继续冻结直接付款按钮 ERP 实时集成和新付款状态机。 BIZ8-10,费用提示轻量规则,"设计轻量费用提示规则,作为填报辅助而非重预算控制。","1) 提示不阻断合法报销;2) 规则来源清楚;3) 不承诺预算控制;4) 用户可理解。","流程级验证:填写不同金额或类型报销 -> 检查费用提示 -> 提交和审批不被误阻断。",manual|playwright,DONE,DONE,DONE,docs/报销发票附件规则.md|client/src|server/src,"[""BIZ8-1""]",已在 docs/需求-报销发票OCR规约.md 增加 BIZ8 费用提示轻量规则,明确材料提示、金额提示、费用标准 warn、费用标准 block 和预算提示边界;npm test -- tests/unit/services/expenseStandards.check.test.js tests/unit/routes/reimbursements.expense-standards.test.js 通过 2 files 9 tests,覆盖 warn 不阻断、block 阻断和 manual_review 优先级;未改业务代码。 BIZ8-11,项目关联增强,"增强报销、采购或合同与项目轻管理的关联,保持轻量不扩展成重型项目管理。","1) 项目选择和展示清楚;2) 无项目时可解释;3) 项目权限不泄露;4) 不扩展复杂 WBS。","流程级验证:创建或选择项目 -> 保存采购草稿 -> 项目负责人查看项目采购摘要 -> 采购详情权限直连复核;自动化覆盖 projectId 列表过滤 归档项目新关联拦截 已有关联不重复阻断 项目成员不能越过采购详情权限。",node|npm|git|manual,DONE,DONE,DONE,server/prisma|server/src|server/tests|client/src,"[""BIZ8-1""]",完成:采购新增 nullable project_id 外键 ON DELETE SET NULL 和索引;采购列表详情草稿更新补项目字段;新关联校验项目可读且未归档;已有项目无关编辑不重复阻断;项目详情新增轻量采购摘要;前端采购表单列表详情和项目详情补展示;验证 npm run prisma:validate 通过;node --check purchaseRequests projects 通过;npm test purchase.project-link projects purchase.submit-budget-precheck 3 files 12 tests passed;client typecheck 和 build 通过;git diff --check 通过;reviewer 已复核并修复阻断问题;回滚:代码回滚可保留 nullable 列,物理回滚需先 drop constraint purchase_requests_project_id_fkey 再 drop index idx_purchase_requests_project 再 drop column purchase_requests.project_id 且关联数据会丢失;继续冻结复杂 WBS 重预算 ERP 实时集成。 -BIZ8-12,移动端业务入口评估,"评估报销、OCR、财务和项目轻量能力在移动端和企业微信入口的可用性。","1) 移动入口不重复;2) 企业微信入口回 OA;3) 财务敏感能力不误暴露;4) 新增入口进入移动矩阵。","流程级验证:员工和财务角色在移动端进入业务入口 -> 检查报销/OCR/项目状态和权限。",playwright|manual,DOING,TODO,TODO,client/src|docs/产品设计规则.md|docs/trialrun/真机公网测试清单.md,"[""BIZ8-8"",""BIZ8-11""]",自动化评估已启动但不能关闭:只读审查确认 BIZ8-12 必须保留跨视口自动化加代表性真机确认;代码路径显示移动首页按权限裁剪报销 OCR 采购 资产入口 财务摘要仅财务角色可见 PC 财务路由在移动上下文回 /mobile;MOBILE_VISUAL_OUTPUT_DIR=biz8-12-mobile-entry npx playwright test r6-mobile-unified-design-rollout r124-mobile-entry-matrix r110-mobile-pc-routing wecom-entry --workers=1 --reporter=list 在临时 Vite 3000 下执行,r124 1 passed、r6 移动统一设计 7 passed、wecom-entry 企业微信未绑定场景 1 passed,生成 output/playwright/biz8-12-mobile-entry 77 张截图;r110 和 wecom 普通浏览器登录依赖本地后端/API 环境失败,不能作为页面回归失败;2026-05-14 22:05 最新修复 5491e92 已部署,public bundle findings=0,DG7+R124 公网移动矩阵 6 passed,R7E_BROWSER_FLOW=mobile 通过并确认员工 审批人 财务移动入口可见且无横向滚动;2026-05-15 企业微信消息 deeplink 代表设备已可免登录回 OA 但财务角色移动入口和付款未启用空态仍缺代表真机人工复核;SMOKE301/SMOKE302 无企业微信不可作为企微成员;保持 DOING 不标 DONE。 +BIZ8-12,移动端业务入口评估,"评估报销、OCR、财务和项目轻量能力在移动端和企业微信入口的可用性。","1) 移动入口不重复;2) 企业微信入口回 OA;3) 财务敏感能力不误暴露;4) 新增入口进入移动矩阵。","流程级验证:员工和财务角色在移动端进入业务入口 -> 检查报销/OCR/项目状态和权限。",playwright|manual,DOING,TODO,TODO,client/src|docs/产品设计规则.md|docs/trialrun/真机公网测试清单.md,"[""BIZ8-8"",""BIZ8-11""]",自动化评估已启动但不能关闭:只读审查确认 BIZ8-12 必须保留跨视口自动化加代表性真机确认;代码路径显示移动首页按权限裁剪报销 OCR 采购 资产入口 财务摘要仅财务角色可见 PC 财务路由在移动上下文回 /mobile;2026-05-14 最新修复 5491e92 已部署,public bundle findings=0,DG7+R124 公网移动矩阵 6 passed,R7E_BROWSER_FLOW=mobile 通过并确认员工 审批人 财务移动入口可见且无横向滚动;2026-05-15 企业微信消息 deeplink 代表设备已可免登录回 OA;财务入口定位问题归 BIZ8-12B 且本地验证通过但未部署,仍缺 wangzhiling 代表真机复核;SMOKE301/SMOKE302 无企业微信不可作为企微成员;保持 DOING 不标 DONE。 BIZ8-12A,财务移动入口和付款未启用空态,"在移动首页和我的页补财务角色入口,并在 payment_batch_v1 关闭时展示付款批次未启用的明确状态,不启用付款能力。","1) 财务角色可在 /mobile 和 /mobile/me 找到财务入口;2) 非财务角色不显示财务入口;3) payment_batch_v1 关闭时不显示误导性待付款 0 或台账导出可用;4) 不改变权限和付款状态机。","流程级验证:cd server && node --check src/routes/portal.js;cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list。",node|npm|playwright,DONE,DONE,DONE,client/src/pages/MobilePortalPage.tsx|client/src/pages/MobileMePage.tsx|client/src/api/portal.ts|server/src/routes/portal.js|client/tests/e2e,"[""BIZ8-12""]",本轮只开入口和解释性空态 不开启 payment_batch_v1 不修改 AP/payment-batches API 权限或状态机;公网付款闭环仍待功能门禁决策。 +BIZ8-12B,财务移动入口定位修复,"修复财务角色在 /mobile/me 点击财务入口后只像回首页一样无法定位付款未启用提示的问题。","1) 财务入口仅财务角色可见;2) 点击后进入或定位到 /mobile 的财务卡片;3) 付款批次关闭时 mobile-finance-disabled-state 可见且文案清楚;4) 不开启付款批次或修改财务权限。","流程级验证:cd client && npm run typecheck && npm run build;cd client && npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts tests/e2e/r124-mobile-entry-matrix.spec.ts --workers=1 --reporter=list;wangzhiling 真机从 /mobile/me 点击财务入口。",npm|playwright|manual,DONE,DONE,DONE,client/src/pages/MobilePortalPage.tsx|client/src/pages/MobileMePage.tsx|client/tests/e2e,"[""BIZ8-12A""]",本地完成:client typecheck/build 通过;DG7+R124 移动专项 6 passed,覆盖财务角色从 /mobile/me 点击财务入口定位到 /mobile#finance 且 mobile-finance-disabled-state 在视口内;未部署,仍需 wangzhiling 真机复核。回滚路径:恢复财务入口旧跳转;不改后端 API 权限 payment_batch_v1 或付款状态机。 BIZ8-13,财务和项目权限回归,"回归财务台账、付款导出、项目关联和报销详情的权限边界。","1) 普通员工只能看自己的数据;2) 财务只看授权财务数据;3) 管理员不自动获得财务处理;4) 项目关联不越权。","流程级验证:不同角色直连财务、项目、报销详情和导出 API -> 检查允许和拒绝结果。",bash|node|manual,TODO,TODO,TODO,scripts/qa|server/src|client/src,"[""BIZ8-9"",""BIZ8-11"",""BIZ8-12"",""BIZ8-12A""]",高风险权限变更必须 reviewer。 BIZ8-14,BIZ8 收口准备,"整理 BIZ8 验证摘要、OCR 边界、财务和项目权限结论、未完成项。","1) DONE 行均有证据;2) OCR 真实与 mock 结论分离;3) 权限回归通过或有阻断;4) 未完成项不伪装完成。","流程级验证:人工核对 BIZ8 CSV 状态、验证摘要、OCR 证据和权限回归结果。",manual|git,TODO,TODO,TODO,issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv|docs/文档索引.md,"[""BIZ8-13""]",对应原规划 BIZ8-14。 BIZ8-CLOSE,BIZ8 阶段 CLOSE,"阶段性关闭 BIZ8,完成状态回填、验证摘要和中文提交。","1) CSV 校验通过;2) git diff --check 通过;3) 所有 DONE 有命令或人工证据;4) 中文提交引用 BIZ8;5) 后续风险有归属。","流程级验证:validate_issues_csv.py 本 CSV;git diff --check;人工核对最终摘要。",python3|git|manual,TODO,TODO,TODO,issues/2026-05-13_18-23-53-biz8-finance-ocr-project.csv,"[""BIZ8-14""]",每个连续 CSV 保留 CLOSE 行。