From 51a134782710d927e363e792097fa867b2d7c179 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 30 May 2026 12:22:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=B4=A2=E5=8A=A1=E5=8F=B0?= =?UTF-8?q?=E8=B4=A6=E5=B7=A5=E5=85=B7=E4=B8=8E=E5=BC=82=E5=B8=B8=E4=BC=98?= =?UTF-8?q?=E5=85=88=E9=A1=B5=E9=9D=A2=EF=BC=8805-30-finance-ledger-tools-?= =?UTF-8?q?redesign=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spec/hyb-oa/frontend-react-vite-design.md | 7 + .../spec/hyb-oa/reimbursement-invoice-ocr.md | 64 ++++++++ .../prd.md | 8 +- .../task.json | 2 +- client/src/api/paymentBatches.ts | 1 + client/src/pages/ApOperationsPage.tsx | 53 +++++-- client/src/pages/InvoicePoolPage.tsx | 62 +++++--- client/src/pages/PaymentBatchesPage.tsx | 148 +++++++++++------- client/tests/e2e/main-flow.spec.ts | 5 +- .../r7e-browser-business-trialrun.spec.ts | 15 +- .../e2e/oafront3-pc-readonly-visual.spec.ts | 4 +- .../e2e/pc-finance-admin-visual-wave4.spec.ts | 25 +++ .../e2e/permission-direct-access.spec.ts | 2 +- .../reimbursement-payment-batch-flow.spec.ts | 7 +- docs/海音达OA-MVP需求规格说明书.md | 2 +- docs/管理员手册.md | 8 +- server/src/routes/paymentBatches.js | 9 +- 17 files changed, 305 insertions(+), 117 deletions(-) diff --git a/.trellis/spec/hyb-oa/frontend-react-vite-design.md b/.trellis/spec/hyb-oa/frontend-react-vite-design.md index f6877bc9..d3685254 100644 --- a/.trellis/spec/hyb-oa/frontend-react-vite-design.md +++ b/.trellis/spec/hyb-oa/frontend-react-vite-design.md @@ -558,6 +558,13 @@ screen. filing or full accounting software. Real payment stays in offline banking or existing finance/accounting workflows; OA records status, evidence and handover summaries. +- Payment, AP and invoice pages default to task/exception visibility: `付款` + shows review, payment-marking, receipt and exception attention before batch + creation; `应付账款` shows pending payment, exceptions, risks and differences + before export; `票据` shows OCR status, failed invoices and bindable invoices + before broad-scope handover tools. Batch creation, export, full-scope views + and agency-accounting handover controls live in `台账工具` or selected-context + panels, never as the first-screen primary action. - Payment actions require the finance/payment context: amount, payee or target object, source records, offline payment explanation, evidence completeness, receipt requirement and correction boundary. Do not place `标记已付款`, diff --git a/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md b/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md index 77b610e1..08254167 100644 --- a/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md +++ b/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md @@ -14,3 +14,67 @@ duplicate detection or attachment behavior. - Reimbursement flow tests should cover upload/OCR, duplicate handling, submission, approval, payment batch, receipt attachment and AP export when the Trellis task scope touches those areas. + +## Scenario: Payment Batch Receipt Attention Count + +### 1. Scope / Trigger + +- Trigger: payment list pages need a first-screen `待补回单` metric without + loading or exposing every receipt attachment. +- Applies to payment batch list/detail APIs and the PC `付款` page. + +### 2. Signatures + +- `GET /api/payment-batches` returns each batch with + `receiptAttachmentCount: number`. +- `GET /api/payment-batches/:id` returns the same field on `data.item`. +- The count comes from Prisma `_count.receipt_attachments`; the list response + must not include receipt file names, hashes, storage paths or download URLs. + +### 3. Contracts + +- Only users who can read payment batches may receive the count. +- Frontend `待补回单` is list-level attention: batch status is `reviewed` or + `failed`, at least one pending/failed item resolves to bank channel, and + `receiptAttachmentCount` is `0`. +- Receipt preview, upload, delete and download stay in the selected batch detail + panel and continue to use receipt attachment endpoints with their own + permission, governance and audit checks. + +### 4. Validation & Error Matrix + +- `payment_batch_v1` disabled -> existing `409 VALID_001`. +- Reader lacks batch permission -> existing `403 PERM_001`. +- Count missing from an older mocked payload -> frontend treats it as `0` for + backward-compatible tests, but real backend responses should include it. + +### 5. Good/Base/Bad Cases + +- Good: a reviewed bank batch with no receipt contributes `1` to `待补回单`. +- Base: a reviewed bank batch with uploaded receipt contributes `0`. +- Bad: counting only the currently selected batch, which hides missing receipts + elsewhere in the list. + +### 6. Tests Required + +- Server focused route tests when payment batch mapping changes. +- `cd client && npm run typecheck && npm run build`. +- Focused Playwright around `付款`, `应付账款`, `票据` and finance ledger tools. + +### 7. Wrong vs Correct + +#### Wrong + +```ts +const selectedNeedsReceipt = selectedBatch && selectedReceiptCount === 0 ? 1 : 0; +``` + +#### Correct + +```ts +const needsReceipt = batches.filter((batch) => + ["reviewed", "failed"].includes(batch.status) && + (batch.receiptAttachmentCount ?? 0) === 0 && + batch.items.some((item) => (item.paymentChannelOverride ?? batch.defaultPaymentChannel) === "bank"), +).length; +``` diff --git a/.trellis/tasks/05-30-finance-ledger-tools-redesign/prd.md b/.trellis/tasks/05-30-finance-ledger-tools-redesign/prd.md index bb359a0b..0c89cb49 100644 --- a/.trellis/tasks/05-30-finance-ledger-tools-redesign/prd.md +++ b/.trellis/tasks/05-30-finance-ledger-tools-redesign/prd.md @@ -32,10 +32,10 @@ ## Acceptance Criteria -- [ ] 财务/出纳默认看到最需要处理的财务事项和异常。 -- [ ] 报销、采购、合同、项目普通详情不直接执行付款动作。 -- [ ] 财务导出和批量操作不作为首屏主按钮,并说明范围和敏感内容。 -- [ ] 票据、AP、付款页面文案使用自然中文,不使用 `AP 运营` 等内部平台词作为用户主语义。 +- [x] 财务/出纳默认看到最需要处理的财务事项和异常。 +- [x] 报销、采购、合同、项目普通详情不直接执行付款动作。 +- [x] 财务导出和批量操作不作为首屏主按钮,并说明范围和敏感内容。 +- [x] 票据、AP、付款页面文案使用自然中文,不使用 `AP 运营` 等内部平台词作为用户主语义。 ## Test Plan diff --git a/.trellis/tasks/05-30-finance-ledger-tools-redesign/task.json b/.trellis/tasks/05-30-finance-ledger-tools-redesign/task.json index 6831ec70..3a9f06d2 100644 --- a/.trellis/tasks/05-30-finance-ledger-tools-redesign/task.json +++ b/.trellis/tasks/05-30-finance-ledger-tools-redesign/task.json @@ -3,7 +3,7 @@ "name": "finance-ledger-tools-redesign", "title": "财务台账工具与异常优先重构", "description": "统一付款、应付账款、票据页面的台账工具、异常优先、付款动作集中和代账交接摘要边界。", - "status": "planning", + "status": "in_progress", "dev_type": null, "scope": "frontend-page-redesign", "package": null, diff --git a/client/src/api/paymentBatches.ts b/client/src/api/paymentBatches.ts index 601918ac..e30ad303 100644 --- a/client/src/api/paymentBatches.ts +++ b/client/src/api/paymentBatches.ts @@ -46,6 +46,7 @@ export type PaymentBatch = { reviewComment: string | null; secondaryReviewComment: string | null; executionSummaryJson: Record | null; + receiptAttachmentCount?: number; createdAt: string | null; createdBy: string | null; reviewedAt: string | null; diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index 062933d3..567c4e03 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -284,6 +284,7 @@ export function ApOperationsPage() { const [ledgerDateTo, setLedgerDateTo] = useState(""); const [ledgerSearch, setLedgerSearch] = useState(""); const [ledgerPage, setLedgerPage] = useState(1); + const [showLedgerTools, setShowLedgerTools] = useState(false); const [reviewTarget, setReviewTarget] = useState(null); const [reviewComment, setReviewComment] = useState(""); @@ -617,8 +618,8 @@ export function ApOperationsPage() { return ( - } /> {summaryQuery.isError ? ( - + ) : null} {paymentBatchesQuery.isError ? ( @@ -677,6 +670,40 @@ export function ApOperationsPage() { /> + {showLedgerTools ? ( +
+
+
+

台账工具

+

+ 导出只用于财务复核和代账交接,范围按当前筛选条件生成;文件可能包含付款、票据和员工摘要信息。 +

+
+
+ + +
+
+
+ 当前工具不会执行真实付款,也不会生成正式总账凭证;付款仍由线下银行或既有财务流程完成。 +
+
+ ) : null} +
setActiveTab(value as OperationsTab)}> diff --git a/client/src/pages/InvoicePoolPage.tsx b/client/src/pages/InvoicePoolPage.tsx index f3736465..99c626b0 100644 --- a/client/src/pages/InvoicePoolPage.tsx +++ b/client/src/pages/InvoicePoolPage.tsx @@ -85,6 +85,7 @@ export function InvoicePoolPage() { const [editTotalAmount, setEditTotalAmount] = useState(""); const [error, setError] = useState(""); const [confirmAction, setConfirmAction] = useState<{ type: string; invoiceId: string } | null>(null); + const [showLedgerTools, setShowLedgerTools] = useState(false); const previousDetailStatusRef = useRef(null); // --- Queries --- @@ -459,28 +460,11 @@ export function InvoicePoolPage() { - - - - - exportMutation.mutate("csv")}> - 导出 CSV - - exportMutation.mutate("xlsx")}> - 导出 Excel - - - + } /> @@ -490,6 +474,42 @@ export function InvoicePoolPage() {

) : null} + {showLedgerTools ? ( +
+
+
+

台账工具

+

+ 导出按当前状态和范围生成,只用于财务复核、OCR 排障和代账交接;普通员工日常补票据仍从报销详情或移动上传进入。 +

+
+ + + + + + exportMutation.mutate("csv")}> + 导出 CSV + + exportMutation.mutate("xlsx")}> + 导出 Excel + + + +
+
+ 票据原文、预览和下载仍按详情页权限与审计控制处理,列表导出不作为敏感材料分发入口。 +
+
+ ) : null} +
票据总数
diff --git a/client/src/pages/PaymentBatchesPage.tsx b/client/src/pages/PaymentBatchesPage.tsx index a7d03d63..d09fdd50 100644 --- a/client/src/pages/PaymentBatchesPage.tsx +++ b/client/src/pages/PaymentBatchesPage.tsx @@ -113,6 +113,7 @@ export function PaymentBatchesPage() { const receiptInputRef = useRef(null); const [selectedReceiptFiles, setSelectedReceiptFiles] = useState([]); const [error, setError] = useState(""); + const [showBatchTools, setShowBatchTools] = useState(false); const [confirmPaymentAction, setConfirmPaymentAction] = useState<{ kind: "financeReview" | "financePayment" | "cancel"; batchId: string; @@ -132,9 +133,17 @@ export function PaymentBatchesPage() { const batchStats = useMemo(() => { return { total: batches.length, - actionable: batches.filter((item) => ["draft", "reviewed", "failed"].includes(item.status)).length, - highRisk: batches.filter((item) => item.highRisk).length, - failed: batches.filter((item) => item.status === "failed").length, + needsReview: batches.filter((item) => item.reviewRequirement.nextStep !== "ready_for_execute" && ["draft", "reviewed"].includes(item.status)).length, + readyToPay: batches.filter((item) => item.reviewRequirement.nextStep === "ready_for_execute" && ["reviewed", "failed"].includes(item.status)).length, + needsReceipt: batches.filter((item) => + ["reviewed", "failed"].includes(item.status) && + (item.receiptAttachmentCount ?? 0) === 0 && + item.items.some((entry) => + ["pending", "failed"].includes(entry.status) && + (entry.paymentChannelOverride ?? item.defaultPaymentChannel) === "bank", + ), + ).length, + exceptions: batches.filter((item) => item.highRisk || item.status === "failed").length, }; }, [batches]); @@ -143,7 +152,6 @@ export function PaymentBatchesPage() { enabled: Boolean(selectedBatch?.id), queryFn: () => fetchPaymentBatchReceiptAttachments(selectedBatch!.id), }); - useEffect(() => { if (!selectedBatch) { return; @@ -353,68 +361,88 @@ export function PaymentBatchesPage() { return ( {downloadReasonDialog} - + + + + + } + /> {error ?
{error}
: null}
+
+
待复核
+
{batchStats.needsReview}
+
+
+
待付款标记
+
{batchStats.readyToPay}
+
+
+
待补回单
+
{batchStats.needsReceipt}
+
+
+
异常批次
+
{batchStats.exceptions}
+
批次数
{batchStats.total}
-
-
可处理
-
{batchStats.actionable}
-
-
-
高风险
-
{batchStats.highRisk}
-
-
-
失败批次
-
{batchStats.failed}
-
-
-
当前批次
-
{selectedBatch?.batchNo ?? "-"}
-
-
+ {showBatchTools ? (
-

新建批次

+
+

台账工具 · 新建批次

+

+ 仅把已确认待付款的单据归入批次。真实付款仍在银行或既有财务流程完成,OA 记录复核、回单和状态。 +

+
{!canManageBatch ? (
当前账号仅可查看批次并执行复核,创建与编辑入口仅对付款批次管理员开放。
) : null} -
- - -
-
- - setName(event.target.value)} - placeholder="例如:2026Q2-财务付款批次-01" - /> +
+
+ + +
+
+ + setName(event.target.value)} + placeholder="例如:2026Q2-财务付款批次-01" + /> +