取消报销资产事前批准强制(08-30-reimbursement-assets-prior-approval-correction)

这个提交包含在:
2026-08-30 12:44:58 +08:00
父节点 0f9f40389e
当前提交 36c86f1dca
共修改 17 个文件,包含 489 行新增和 130 行删除
@@ -30,6 +30,10 @@ duplicate detection or attachment behavior.
require the same own-or-`expense.view_all` guard as reimbursement detail reads
before invoking the precheck service; an ordinary nonparticipant must receive
403 without service-side evaluation.
- 取消或放宽必填校验时,必须继续审计该校验之后的代码是否依赖它提供的
非空、类型或状态保证,并为生产真实存在的空值路径补回归测试。不得只
删除前置错误分支;可空字段的 `.trim()`、`.length`、字符串拼接和传给
非空参数的调用都应逐处确认。
## Scenario: OCR Disabled Without Mock Data
@@ -996,8 +1000,10 @@ await selectApprovalRule(reimbursement.total_amount);
- Only `travel_meal` and `travel_local_transport_allowance` are invoice-free in
the initial policy. Invoice-free means skip invoice presence/amount checks;
it does not remove explanation, formula, approval or attachment obligations.
- Every `assets` item requires a trimmed approval basis on create, update and
submit. Switching an item to a non-assets type clears the stored basis.
- 公司当前没有 assets 事前审批制度。Item create/update 继续兼容可空的
`assetPriorApprovalBasis`:创建时缺失或空白落 null;更新时 omitted 保留
现值,显式 null/空白清空;切换到非 assets 类型仍清空。当前 PC/移动 UI
不展示或发送该字段,历史值不得因普通编辑被静默清空。
- `office_supplies` at or above CNY 2,000 per item shows the same non-blocking
office-electronics hint on editable PC/mobile forms and read-only finance
detail. It does not change the type or decide accounting fixed-asset status.
@@ -1027,9 +1033,9 @@ await selectApprovalRule(reimbursement.total_amount);
### 4. Validation & Error Matrix
- Assets item without basis -> `400 VALID_001` with
`assetPriorApprovalBasis=required_for_assets`; submit returns affected item
IDs under `asset_prior_approval_basis_required`.
- Assets item without basis -> 允许创建、编辑和提交;字段缺失、数据库现值
为 null 或显式清空均不得产生空值解引用或 `500`,其他金额和票据门禁照常
执行。
- Guard-on bind without `linkedItemId` -> `400 VALID_001` with
`required_when_invoice_guard_enabled`; an item outside the form remains
`item_not_in_reimbursement` with zero writes.
@@ -1052,9 +1058,12 @@ await selectApprovalRule(reimbursement.total_amount);
guard either on or off.
- Base: guard off preserves an older draft and its auto-created link; later
disabling the guard preserves links created during a guarded rollout.
- Good: assets 编辑请求 omitted 兼容字段时,数据库 null 保持 null、历史非空值
原样保留;显式 null/空白才清空。
- Bad: compare every positive item to a zero invoice total, silently create an
item while explicit selection is required, use JavaScript floating-point
sums for money, or let repeated init erase finance account mappings.
sums for money, let repeated init erase finance account mappings, or 删除前置
必填校验后继续对可空值直接调用 `.trim()`。
### 6. Tests Required
@@ -1064,13 +1073,15 @@ await selectApprovalRule(reimbursement.total_amount);
- Expense-type route tests assert list mapping, create/update persistence and
audit changes for `requiresInvoice`; seed tests assert the two allowances,
disabled legacy types, default-off flag and account-code preservation.
- Item/submit route tests cover assets create/update/submit, invoice-free zero
invoice, missing link, unlinked bound invoice, declared-over-invoice and
participant/nonparticipant side effects.
- Item/submit route tests 覆盖 assets 创建 omitted→null、编辑 null+omitted→null、
有值+omitted→保留、显式清空、切换非 assets 清空和无依据提交,并继续覆盖
invoice-free zero invoice、missing link、unlinked bound invoice、
declared-over-invoice 与 participant/nonparticipant side effects。
- Bind tests cover flag on/off, missing/foreign/successful explicit links and
prove the guard-on path does not create a reimbursement item.
- PC/mobile Playwright covers descriptions, approval-basis input, the CNY 2,000
warning, allowance-only submit projection, disabled add action until every
- PC/mobile Playwright covers asset 登记导向说明、批准依据输入/展示均不存在且
保存 payload omission、CNY 2,000 warning、allowance-only submit projection、
disabled add action until every
selected invoice has an item, and pending-OCR binding retaining that item.
- Run full backend Vitest with the pinned BIFF8 Python dependencies, Prisma
validate/generate check, frontend typecheck/build, focused Playwright,
@@ -1090,6 +1101,11 @@ if (item.amount > linkedInvoices.reduce((sum, invoice) => sum + Number(invoice.t
update: { account_code_default: null }; // erases finance configuration on init
```
```js
// 旧必填校验删除后,omitted/null 会在这里变成 500。
asset_prior_approval_basis: targetApprovalBasis.trim();
```
#### Correct
```js
@@ -1107,6 +1123,10 @@ update: { name, description, requires_invoice }; // preserve account_code_defaul
create: { name, description, requires_invoice, account_code_default: null };
```
```js
asset_prior_approval_basis: targetApprovalBasis?.trim() || null;
```
## Scenario: Payment Batch Receipt Attention Count
### 1. Scope / Trigger