Repository navigation
fix(case): stop requiring an account on the case form, so a web case can be claimed - #2116
Conversation
…can be claimed A web-to-case submission carries no account, and crm_case.crm_account is optional on the object, but the case form marked it required. That form is also the edit form an agent claims a case through (a move to In Progress), so the console refused the claim client-side with "Please check the highlighted fields: Account" while the server accepted the same save. The form keeps Account, without the required flag, and the narrowing suite pins that. Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
席位核对复核人:repo:hotcrm 执行席(#1353)· 判决 ACCEPT · 钉 head 形态: 前提实测:派发令 H1/H2/H4 成立,走「直接去掉」路线。diff 逐行读过:
dev 读数:基线上客服对一条无客户的 web 案例点「编辑 → 进行中 → 更新」被前端拦下(「Account is required」,未发 PATCH);而同一客服用 API 门禁 check-runs(钉 head):Build and Test (22.x) ✓ · Quality Checks ✓ · Check Changeset ✓ · Playwright ✓ · link-check ✓ · CodeQL ✓ · Analyze Code (javascript) ✓ · Label Pull Request ✓ · Vercel Preview Comments ✓ — 9/9 success( 接受的偏差:① 派发令写「仅当已有兄弟测试钉住必填性才加测试」,实际没有,dev 仍加了一条 pin——落在认领面点名的文件里、不是新门禁,席位接受。② dev 为测到无主路径,在本地库里删除/重建过 范围外去向:
落地条款:席位经中继 |
Part of #2060 (item 4, finding B-03). Items 5 and 7 to 18 of the card, and item 1's delete half, are not touched here; the card stays open.
Clause-②: no
What was wrong (measured on
main421bd1d8,@objectstack/*18.0.0-next.1)A service agent could not save a web case as In Progress. The case form (
src/service/views/case.view.ts:432on base) marked Account required. A web-to-case submission has no account, andcrm_case.crm_accountis optional on the object.Measured on a live server (port 4164, fresh DB,
pnpm demo:staff). Each case was submitted anonymously at/_console/f/support(POST /api/v1/forms/support/submitanswered 201):service.agentholding the Service Agent position, the web case (CASE-00039) was routed to that agent:owner_id= the agent,crm_account: null,status: new. The agent opened it, chose More actions → Edit, set Status to In Progress and clicked Update. The toast read "Please check the highlighted fields: Account", the field read "Account is required", and no PATCH was sent.content/docs/service/cases.mdx, "Claiming a case out of triage"). With the pool empty at submission, the case (CASE-00040) stayed unowned. After the pool was staffed andcase_unassigned_triage_sharingwas evaluated, the agent could read it. The same Edit → In Progress → Update was refused the same way, with no PATCH sent.PATCH {status: 'in_progress'}answered 200, and the case became the agent's (owner_id= agent,crm_account: null). The only thing that refused the claim was the form's ownrequiredflag.#2103 and #2115 do not change this. The agent now usually meets a web case as its routed owner rather than unowned in triage, and both paths stopped at the same field.
Provenance (H2).
git log -S "field: 'account', required: true"leads to344c5f57(2026-05-22, "Reset hotcrm to framework/app-crm baseline"). It came in with the imported baseline, and75730205(thecrm_prefix) and36b27dd8(#1910, the package move) only carried it along. No ruling or card asked for it.Conditional requiredness (H4). In the installed
@objectstack/spec18.0.0-next.1, the form field schema (FormFieldBaseSchema,dist/ui/index.mjs) has only a booleanrequiredand a client-onlyvisibleWhen. It has norequiredWhen. The only server-enforced conditional requirement is the object field'srequiredWhen, and the claim keepscrm_case.crm_accountoff limits. So the route is plain removal, as #2099 did for the lead owner.What changed
src/service/views/case.view.ts: the form'scrm_accountentry is now the bare field name, withoutrequired: true. It stays on the form, with a comment saying why.test/case-create-form-narrowing.test.ts: one pin in the suite that already owns this form's field contract.crm_accountis on the form and not required..changeset/2060-item-4-claim-web-case.md.The object field, the web-to-case form and the docs are unchanged. No sentence in
content/docs/service/cases*.mdxbecomes false. The claim section now describes what happens.Downstream of a case with no account (H3)
This form is also the create dialog, so an internal user can now save a case with no account. Every reader of the account already handles a missing one, because web cases have never had one:
case_sla_defaults(case.hook.ts) reads the account's tier only when an id is present, and otherwise uses thesmbcolumn.case_status_side_effectsopens the account-owner escalation task only&& accountId.cases.mdxalready says "only for cases that have an account".case_resolution_account_activityreturns early with no account.case_escalation_reassignand the escalation flow do not read the account.case_metricsdataset groups by account.Measured after the change, on the same server:
owner_id= agent,status: in_progress,crm_account: null. The case left the triage list.in_progress).service.agent): New → subject, description, Medium → Create. "Case created successfully", POST 201,crm_account: null, a 48 h SLA (thesmbcell).crm_accountset, a 36 h SLA (that account's tier).escalated_fromwas stamped. No follow-up task was created.With no account picked, the form's Contact picker reads "Select Account first" (
crm_contact.dependsOn: ['crm_account']). That is the existing cascade, and this PR does not change it.Tests
pnpm exec vitest run test/case-create-form-narrowing.test.ts: 17 passed (17).ablation-replacein wrap mode under the verify lock, with a trap restore. It put{ field: 'crm_account', required: true }back: anchor 1 → 0, blob8aa274c3→c28b8bcc. The new pin failed with "the case form demands an account again: expected true to be false", 1 failed and 16 passed. It turned red, as predicted. The restore left the blob equal to HEAD (8aa274c3), andgit diff HEADwas empty. The test imports the view fromsrc, sodistwas not involved.Gates (all at
41a967fa, exit codes captured before any pipe)pnpm validate: 0, "✓ Validation passed", "Data: 18 Objects 358 Fields 5 Picklists"pnpm typecheck: 0.tsc --listFilesincludes the edited test.pnpm lint: 0, "18 suggestion(s)", none naming a file or key of this PRpnpm lint:i18n-gate: 0, "✓ i18n lint gate: 0i18n/missing-*issues"pnpm hygiene: 0, "✓ source hygiene clean"pnpm hygiene:tokens: 0, "✓ source token ratchet clean"pnpm build: 0, "✓ Build complete", the same 8 author-time warnings as basepnpm test: 0, "Test Files 181 passed (181)", "Tests 3687 passed | 1 skipped (3688)"validate,lint,buildandtestran underos-verify-lock. Playwright e2e is CI's.Acceptance notes (not filed)
plugin-security'sclaimSeedOwnershipOnSettle/claimSeedOwnershipre-owns every row whoseowner_idis null to the platform admin, not only seeded rows. CASE-00045 was an unowned web case that the agent could read in triage (200). After a plainobjectstack startrestart it was owned byadmin@objectos.ai, and the agent got 404. CASE-00040, -42 and -43 went the same way on a dev auto-restart. This empties Unassigned — triage whenever the server restarts.crm_case.prioritydeclareslowasdefault: trueandcase.view.tssays an unset one is filled by that default. A guest has to pick one. Carrier: Browser pass #2058 — HotCRM-side defects (18 items: accepted quote still takes lines, half-converted leads, agents losing their cases, unreachable Sales Home, …) — list card, split per item at dispatch #2060 item 7 (formrequiredflags that block a default).POST /api/v1/sharing/rules/ID/evaluate.scripts/demo-staff.tsalready documents that staffing needs this step. Observation only.Generated by Claude Code