Skip to content

Finance records its own review through a Record Finance Review action - #194

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-190-finance-review-action
Nov 3, 2026
Merged

zhuangjianguo merged 2 commits into
mainfrom
claude/issue-190-finance-review-action

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #190

Clause-②: no
Metadata-protocol: none

Ruling D (comment 6607878401): finance records its own finance review through a declared action while the contract is In Review or In Approval. Finance still gets no edit on the contract, and the direct writes it was refused before are still refused.

What changed

  • src/actions/finance-review.actions.ts (new): record_finance_review (记录财务审查) on clm_contract. type: 'flow', record_header, visible while status is in_review or in_approval, requiredPermissions: ['clm_finance.access']. The dialog has three field-backed params with objectOverride: 'clm_review': decision (required), risk_level_assessed, comments. They inherit the review's labels and options in both locales.
  • src/flows/finance-review.flow.ts (new): the record_finance_review flow. type: 'screen', runAs: 'system', requiredPermissions: ['clm_finance.access']. This is the same shape as executed_upload ([Decision] §06 F16 说补录动作「档案岗**或**法务可用」,但实现只能给档案岗——权限是 AND,没有两边都持有的能力 #43, PR §06 F16 说补录动作「档案岗或法务可用」,但实现只能给档案岗——权限是 AND,没有两边都持有的能力 #177), because 18.2 refuses non-system starts of a self-triggered system flow. The flow:
    • takes the contract from record, the row the action route loaded in the caller's scope. A contract finance cannot read answers 404 before the flow runs. The flow declares no input that could name a different contract.
    • refuses with a translated sentence when there is no record (for example the trigger route) or when the status is outside the two states. Nothing is written in either case.
    • looks for the caller's own stage: 'finance' review (reviewer == current_user.id), updates it if it exists, and creates it otherwise. On the update path, a field left empty in the dialog keeps its stored value.
    • writes nothing on the contract.
  • src/flows/index.ts: registers the flow.
  • src/pages/contract_detail.page.ts: adds record_finance_review to the header's actions. The custom record page replaces the default header, so without this line the action has no button (start_renewal hit the same thing).
  • Translations: the action's label, description, success message and comments help in en / zh-CN objects.contract.ts, and the two refusal sentences in en / zh-CN flows.ts.
  • src/data/keys.ts: reviewerOf now deals all 23 finance reviews to the finance controller. The 7 on contracts that are not yet approved (6 in_review, 1 in_approval, all routed to finance) used to go to the head of legal. The doc comment is rewritten to match.
  • README.md: persona table: Head of Legal now has 1 review, Finance Controller has 23. The review-dealing paragraph is updated.

No capability added. clm_finance.access is held by clm_finance alone, and clm_legal.access already serves the legal-only actions the same way. clm_admin does not hold it, but admin writes reviews directly.

Measured (18.2.0, pnpm demo:zh, port 3163, throwaway DB, declared demo staff, zh-CN)

Before (origin/main 7e7c2dc):

  • reviews: Finance · Head of Legal 7 · Finance · Finance Controller 16 · Legal · Head of Legal 1.
  • finance@ POST /api/v1/actions/clm_contract/record_finance_review/ID -> 404 "has no declaration".
  • finance@ POST /api/v1/data/clm_review (stage finance, in_review contract) -> 403 "requires edit access to its master record". The same 403 for PATCH of the in_approval contract's finance review.
  • Browser as finance@: there is no 记录财务审查 on the in_review or in_approval contract.

After (HEAD 1692389), API as finance@:

  • in_review contract with no finance review: press 1 -> 200. One review is created with reviewer finance@ (Finance · Finance Controller), decision changes_requested, risk medium, the comment, decided_at stamped, and created_by/updated_by = finance@.
  • Press 2 with only decision: approved -> 200. It updates the same row (the review id is unchanged and there is still 1 finance review). The comments and risk are kept.
  • in_approval contract with no finance review: create, then a second press -> one row, updated.
  • in_approval contract with the seeded finance review (now finance@'s): press -> 200, and the seeded row is updated in place.
  • Direct writes are still refused: PATCH clm_contract -> 403 PERMISSION_DENIED. POST clm_review -> 403 "insert on 'clm_review' requires edit access to its master record". PATCH of finance@'s own in_review review -> 403 "update ... requires edit access to its master record".
  • submitted contract and active contract -> the run is refused with 「财务审查在合同处于「审查中」或「审批中」时记录。从「已批准」起,财务直接在合同上修改这条审查。」. Nothing is written.
  • in_review contract not routed to finance -> 404 RECORD_NOT_FOUND. Nothing is written.
  • Trigger route POST /api/v1/automation/trigger/record_finance_review, with a record smuggled into params -> refused with 「财务审查要从它所审查的合同上记录。…」. Nothing is written.
  • requester1: action route -> 403 "requires capability [clm_finance.access]". Trigger route -> 403 "Starting this flow requires capability [clm_finance.access]".

After, browser (Chromium, zh-CN):

  • finance@ sees the header button 记录财务审查 on in_review: YES, in_approval: YES, submitted: NO, active: NO.
  • finance@ on in_review LSE-2026-0004:
    • The dialog reads 「记录财务审查 / 记录你对这份合同的财务审查。… / 结论* / 评定风险 / 审查意见 (发起人可以看到。) / 取消 · 确认」. The decision options are 待定/通过/要求修改/驳回 and the risk options are 低/中/高.
    • Picked 要求修改 + 中 + a comment, then 确认: POST /api/v1/actions/clm_contract/record_finance_review -> 200 and the toast 「财务审查已记录。」.
    • Pressed again with 通过 only: 200, the same row with decision approved, and the comment is kept.
    • The 审查与偏离 tab lists Finance · Finance Controller · 财务 · Finance Controller · 通过 · 中.
    • Console errors: the usual 404/401 resource loads only.
  • requester1 on an in_review contract they can read (MSA-2026-0001): no 记录财务审查 button.

Re-run safety: pnpm demo:zh run 1 on a clean DB -> 1,130 rows (contracts 120, versions 300, reviews 60, deviations 25, signatures 30, obligations 200, payment plans 300, parties 40). Reviews dealt: Finance · Finance Controller 23, Legal · Head of Legal 1, Legal Counsel 1/2 16/18, Business 1/1. Run 2 on the same DB -> banner Seeds: app.objectstack.hotclm 1130 rows, nothing refused. Reviews 62 = the 60 seeded plus the 2 the action created, which were left alone. No duplicates.

Gates (HEAD 1692389)

  • pnpm install -> exit 0
  • pnpm validate -> exit 0, ✓ Validation passed. The 7 warnings are the pre-existing hierarchy-security and contract_approval ones.
  • pnpm lint -> exit 0, 6 suggestion(s), 0 warnings (same as main)
  • pnpm typecheck -> exit 0
  • pnpm lint:i18n-gate -> exit 0, ✓ i18n gate · COVERAGE : 0 missing keys across 2 locale(s)
  • CLM_DEMO_SEED=1 pnpm validate -> exit 0, ✓ Validation passed

Acceptance notes

  • The contract's updated_at moves on every review write. It is not the flow writing the contract: the platform recomputes the contract's summary roll-ups on every child write, and that stamps the parent. Measured: after a press only updated_at changed. Control: a legal head's direct PATCH of a review moved updated_at and updated_by the same way. No contract field the flow could write is touched.
  • started_at is left empty on a review the action creates. The ruling names three fields, and only decided_at is stamped (review_decision_stamp).
  • The seed upserts reviews on externalId: ['contract', 'stage'], while the action keys on (contract, stage, reviewer) as the ruling asks. On a database seeded before this PR and not yet re-seeded, a finance press on a contract whose seeded finance review still names the head of legal would add a second finance-stage row. Not measured. A pnpm demo re-run moves the reviewer to finance first.
  • The 审查与偏离 tab badge read 1 while the list showed 2 rows after the in-page refresh. Not investigated.
  • Claim file surface: the claim names an actions file, translations, src/data/keys.ts/negotiation.seed.ts and the README. This PR also touches src/flows/finance-review.flow.ts, src/flows/index.ts, src/translations/{en,zh-CN}/flows.ts (the flow the dispatch prescribed, after the [Decision] §06 F16 说补录动作「档案岗**或**法务可用」,但实现只能给档案岗——权限是 AND,没有两边都持有的能力 #43 precedent) and src/pages/contract_detail.page.ts (without it the button is unreachable). src/profiles/**, objectstack.config.ts and negotiation.seed.ts are untouched.

Governed text (proposed, not edited; for the design-doc PR #120)

  • §04 matrix, clm_review · clm_finance: RCU(stage=finance)经「记录财务审查」动作
  • §10 clm_review row: 审查人按环节分派:法务 → 合同法务负责人(无则法务负责人);财务 → 财务负责人;合规 → 法务负责人;业务 → 发起人

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

Add the record_finance_review action on clm_contract and the screen flow
it starts. Finance cannot write a clm_review directly on a contract in
review or in approval: the detail is controlled_by_parent and finance
edits contracts only from approved onward. The flow runs system-elevated,
gated by clm_finance.access on both the action and the flow, and creates
or updates the caller's finance-stage review on the contract the route
loaded in the caller's scope. It writes nothing on the contract.

The demo deals the 7 pre-approval finance reviews back to the finance
controller, and the README counts follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
A custom record page replaces the default header, so a declared action
is reachable from the record only once the page names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · head 1692389 · 2026-11-03T16:12Z

check reading
shape → main, Fixes #190; mergeable clean
CI on the head both Validate check-runs success on 1692389 = PR head.sha
scope 11 files (+339/−26), inside the claim as amended (comment 6609066644). New record_finance_review action on clm_contract (record header, visible only in in_review / in_approval, gated on clm_finance.access, which only the finance set holds, so no new capability). It starts a type: 'screen', runAs: 'system' flow of the same name, gated on the same token, as in the PR #177 precedent. The flow creates or updates the caller's stage=finance review with reviewer = the caller, and writes nothing on the contract. The contract is the one the action route loaded in the caller's own scope. reviewerOf gives all 23 finance reviews back to the finance controller. Nothing governed; lint 0 warnings
ruling D, measured as finance@ (zh-CN) in_review: the first press creates 1 review (200), the second updates the same row. in_approval: create then update, 1 row; the seeded review is updated in place. Direct PATCH clm_contract, POST clm_review, and a PATCH of its own review are all still 403. submitted and active are refused with a translated sentence and nothing written. requester1 is refused at the action route and at the trigger route, and sees no button. In the browser: the button shows in in_review / in_approval only, the dialog is translated, and the toast reads 「财务审查已记录。」
re-run pnpm demo twice: 1130 rows, nothing refused. The 2 reviews the action created are left alone, with no duplicates
residuals (noted) a review write moves the contract's updated_at (the platform's roll-up recompute; same for a direct legal edit). The 审查与偏离 tab badge lagged its list once after refresh. On a DB seeded before this change and not re-seeded, a press can add a second finance review beside the one still dealt to the legal head; re-running pnpm demo resolves it
governed text the §04 cell 「RCU(stage=finance)经「记录财务审查」动作」 and the §10 dealing sentence without the pre-approval exception go to PR #120

Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] Finance cannot record its finance review before the contract is approved, though §04 grants it RCU on finance-stage reviews

2 participants