Skip to content

feat(actions): record pages offer only the actions a record can still take; quote and case status buttons (#2366) - #2374

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-v1z1xv
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-v1z1xv

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread
Fixes #2366
Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Each row of the card, with its verdict:

# Row Verdict Where
1 Converted lead offers Edit Fixed crm_lead.userActions.edit.visibleWhen reads is_converted. The lock in lead.hook.ts is unchanged.
2 Settled quote offers Edit and a line-item New Fixed crm_quote.userActions.edit and crm_quote_line_item.userActions.create (the related list evaluates the host quote) hide both on Accepted, Rejected and Expired. Hiding Edit also removes the page's double-click inline editing (measured). quote_workflow's freeze now covers Rejected too, with status left writable so Revise is the way out.
3 A new quote may start Accepted/Rejected/Expired Fixed The three options carry visibleWhen: has(record.created_at): absent on the New form, present on every server insert (measured, seeds included). quote_workflow refuses a user's create at any of them, VALIDATION_FAILED / 400, with a userMessage. System writes (seed, import) still store them.
4 Hook-maintained campaign counters are editable Fixed actual_revenue and the six num_* fields on crm_campaign, and has_responded / response_date on crm_campaign_member, are readonly. campaign_metrics_refresh now runs runAs: 'system' like its three siblings, so its write is not stripped.
5 Agent sees Update Stage / New on a campaign's opportunities tab Already fixed / by grant Update Stage is already gone (#2338 item 4). New is the crm_user floor: /auth/me/permissions for service.agent lists crm_opportunity allowCreate: true, and a REST create answers 201. Changing that is a change to who may do what, out of scope.
6 Generate Quote while the deal is locked for approval Not changed (premise) #2015 ruled that quoting under approval drafts the quote and keeps the stage, pinned in test/flow-quote.test.ts. The console already greys Edit on the locked deal. Hiding Generate Quote there needs a ruling.
7 Edit inline for the agent Already fixed / platform The agent may edit their own opportunities (crm_user floor), and the platform's editInline default is on. Any remaining gating is objectstack#27355 row 6.
8 Reopen Case Fixed reopen_case: a declarative update to in_progress, offered on Resolved and Closed to callers who may edit cases (the grant Escalate and Close read). Listed in case_detail_page's header.
9 Quote lifecycle actions Fixed submit_quote_for_review, mark_quote_presented, accept_quote (with confirmText), reject_quote, revise_quote, each a declarative update of status alone, offered only on the statuses its move leaves from and only to callers who may edit quotes. → expired stays the sweep's. The allowed transitions are unchanged.
10 Option kind on quote, campaign-member and knowledge-article status Fixed Kinds declared. crm_campaign.status is included as the same defect class.
  • Translations: the five quote actions and reopen_case in en, zh-CN, es-ES and ja-JP.
  • In-app manual:
    • Quotes (crm_revenue_quotes): the buttons, how a quote starts, and settled quotes.
    • Cases (crm_service_cases): Reopen Case and what it does to the SLA.
    • Leads (crm_sales_leads): a converted lead has no Edit.
    • Campaigns (crm_marketing_campaigns): the numbers and the member's response fields are read-only.
    • Each page in en and zh.
  • Docs site: content/docs/sales/quotes.* (en, zh-Hans, zh-Hant): a Rejected quote is revised with Revise and is frozen until then.
  • Counts: README and docs/STATUS.md state 38 actions (pnpm validate).
  • Changeset (minor).

Tests

  • test/quote-settled-actions.test.ts (new, 25):
    • Both userActions predicates and the option predicates are evaluated on the platform's CEL engine, settled and open statuses, plus the first render.
    • A rep's REST create at each settled status is refused with the exact userMessage, and nothing is stored.
    • Controls: open statuses and a system seed of a settled quote still store.
    • A Rejected quote refuses field edits and a new line, still takes Internal Notes, and can be moved to Draft and revised there.
  • test/quote-lifecycle-actions.test.ts (new, 18):
    • Every action's offered statuses are read off its visible and checked against quote_status_progression. Every declared move but → expired has an action.
    • The offer follows /auth/me/permissions: a rep is offered them; a marketing user and a service agent are offered none.
    • A rep walks a quote Draft → In Review → Presented → Accepted, and Presented → Rejected → Draft, through the action door. Another rep gets RECORD_NOT_FOUND / 404 and nothing moves.
  • test/case-reopen-action.test.ts (new, 7):
    • Offered on Resolved and Closed to an agent only, not to a sales rep, and not before the record loads.
    • The run as the owning agent stores in_progress with is_closed false.
    • sla_due_date is kept, and is_sla_violated stays true.
  • test/campaign-metric-fields-readonly.test.ts (new, 14):
    • Every metric field and both member fields are readonly, and all four refresh hooks run elevated.
    • A marketer's write of num_sent, num_responses and actual_revenue is dropped while the rest of the same write lands.
    • A marketer's has_responded / response_date is dropped.
    • Mark Responded still stamps both, and the campaign counts it.
    • The marketer's status move recomputes a knocked-off count.
  • Updated:
    • test/status-option-kinds.test.ts: four new objects.
    • test/converted-lead-guard.test.ts: the Edit predicate.
    • test/refusal-labels.test.ts: the Rejected label and sentence.
    • test/refusal-envelope.test.ts: 35 → 36 call sites.
    • test/accepted-quote-frees-contact.test.ts: the born-without-contact case is now born Presented, since a user can no longer create a quote as Accepted.

Acceptance notes

  • Browser (dev server, 18.2.0):
    • Quote buttons per status, and the Submit toast naming the quote.
    • An accepted quote with no Edit, no line New and no double-click editing.
    • The New form without the three settled options; the Edit form still lists them.
    • Campaign counters disabled on the edit form.
    • Reopen Case on a resolved case, with its toast.
    • The record path drawing Rejected and Expired as lost.
    • Not measured in the browser: row 1, because the dev seed has no converted lead. The predicate is pinned on the CEL engine, and userActions.edit behaved the same way on quotes.
  • Line items on a settled quote keep their per-row Edit and Delete in the related list. A row action evaluates against the line, which does not carry its quote's status, and the server refuses those writes as before. Reported as a platform finding.
  • Reopen and the close stamps: closed_date and resolution_time_hours keep the first close when a reopened case is closed again. This is pre-existing and also reachable through Edit, so it is not changed here and is in the report.
  • zh-CN: a hook refusal carries no translation key on 18.2.0, so the new create refusal shows in English in zh-CN, as every hook refusal does (objectstack#26170).

Verification

pnpm verify at 9599f1f
pnpm verify (validate && typecheck && lint --strict && hygiene && hygiene:tokens && build && test)
  HotCRM v6.0.0 · Data: 20 Objects 375 Fields · UI: ... 38 Actions · Logic: 33 Flows
  build: 8 author-time warnings, all pre-existing approval-approvers-may-resolve-empty / hierarchy-security
 Test Files  246 passed (246)
      Tests  4703 passed | 1 skipped (4704)

b4e6e01 only adds a 30 s budget to two describes in test/campaign-metric-fields-readonly.test.ts (14/14 green, tsc --noEmit clean). CI runs coverage and e2e.

Ablations (on the committed tree; exact-anchor replace, anchor count 1 → 0, restored with git checkout, blob hash equal to HEAD after each)
A1 settled-create refusal disabled (false && …)      quote-settled-actions: 3 red (REST create accepted/rejected/expired)
A2 'rejected' dropped from the quote freeze           quote-settled-actions: 1 red (field edit on a Rejected quote)
A3 readonly dropped from num_sent                     campaign-metric-fields-readonly: 2 red (declaration, marketer write dropped)
A4 runAs dropped from campaign_metrics_refresh        campaign-metric-fields-readonly: 2 red (declaration, status move recomputes)
A5 current_user.can dropped from reopen_case.visible  case-reopen-action: 1 red (withheld from a sales rep)

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAmPx3rficEnw9LvZUQ

…d case lifecycle actions (#2366) [WIP]

Converted lead and settled quote offer no Edit; a settled quote offers no
line-item New; a quote cannot be created at a settled status; campaign
metric counts and member response fields are read-only; quote lifecycle
actions and Reopen Case; option kind on quote, campaign, campaign member and
knowledge article status. Tests and docs follow.

Claude-Session: https://claude.ai/code/session_01J8VVAmPx3rficEnw9LvZUQ

Co-Authored-By: Claude <noreply@anthropic.com>
… counter changes; manual pages (#2366)

- New tests: campaign-metric-fields-readonly, case-reopen-action,
  quote-lifecycle-actions, quote-settled-actions; status-option-kinds and
  converted-lead-guard extended; refusal-envelope 36 sites; refusal-labels
  Rejected; accepted-quote-frees-contact born-presented.
- Case detail header lists reopen_case.
- In-app manual (en + zh): quotes, cases, leads, campaigns; docs-site
  quotes page (en, zh-Hans, zh-Hant): Rejected is revised with Revise.
- README / STATUS: 38 actions. Changeset.

Claude-Session: https://claude.ai/code/session_01J8VVAmPx3rficEnw9LvZUQ
Co-Authored-By: Claude <noreply@anthropic.com>
…t on refresh hooks (#2366)

Claude-Session: https://claude.ai/code/session_01J8VVAmPx3rficEnw9LvZUQ
Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 29, 2026 3:52am UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces backend Server-side behaviour — hooks, flows, actions labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-29T03:54Z · head b4e6e01
Implemented-by: card thread session session_01J8VVAmPx3rficEnw9LvZUQ (report 6487444975 on #2366). Reviewed-by: the seat, against the 46-file diff and hotcrm origin/main 44d2d90.


Generated by Claude Code

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

Labels

backend Server-side behaviour — hooks, flows, actions ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants