Repository navigation
fix(lead): a refused lead conversion ends refused instead of toasting success - #2114
Conversation
The three conversion refusals (a confirmed duplicate, a conversion approval
that is pending or rejected, a company with no match key) were message-only
waitForInput screens that resumed into the plain end. Submit on the dialog
therefore completed the run, and the console toasted convert_lead's
"Lead converted successfully!" over a lead that had not been converted
(measured in the console on all three paths).
Each refusal is now an end node with outcome 'refused' and a message, the
shape refuse_nonpositive_amount and quote_generation's refuse_held use: the
run ends refused, is never resumed, and the console shows the message with
Close only. An end node has no heading, so each message opens with the
words the screen's title carried ("Conversion refused") and the body is
unchanged. The edges e26 / e30 / e33 (refusal to end) are retired and their
ids stay vacant. The translations move from the screens' title and
description to refusals.NODE.message in zh-CN, es-ES and ja-JP, same text.
The suites that pinned the three screens now pin the refused outcome and an
empty store together.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
席位核对复核人:repo:hotcrm 执行席(#1353)· 判决 ACCEPT · 钉 head 形态: 前提实测:diff 逐行读过。 门禁 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,mergeable_state 接受的偏差:① 「文案不变」按对话框里原有的字读:end 节点没有标题,拒绝对话框标题是动作名,所以 message 以原标题开头,否则「Conversion refused」会从对话框消失、 范围外去向:拒绝对话框标题用动作名、暂停屏用流程名——控制台的取舍,与 落地条款:席位经中继 |
Part of #2060 (item 2c only, comment 6181884832). The card stays open for its other items.
Clause-②: no
What was wrong
Convert Lead refuses three kinds of lead before its form opens:
refuse_confirmed_duplicate);refuse_unapproved);refuse_no_match_key).Each refusal was a field-less
waitForInputscreen that resumed into the plainend(e26/e30/e33). So the dialog offered Submit, and Submit completed the run. The console then toastedconvert_lead's "Lead converted successfully!" over a lead it had not converted.Measured on base
abc16634(@objectstack/*18.0.0-next.1). The server wasobjectstack dev -p 4162 --fresh, driven as the dev admin by Playwright on/opt/pw-browsers/chromium, read with DOM probes:refuse_confirmed_duplicaterefuse_unapproved(Rejected; the stale-page path, see Acceptance notes)refuse_no_match_key(match key NULL)At the HTTP door,
POST /api/v1/automation/lead_conversion/triggeranswered 200 on all three, withstatus: paused, arunId, and the refusal as thescreen.The change
endnodes. Each istype: 'end'withoutcome: 'refused'and amessage(specEndConfigSchema). This is the shaperefuse_nonpositive_amount(item 2b) andquote_generation'srefuse_heldalready use. The run endsrefused, is never resumed, and the console shows the message with Close only. When each one refuses is unchanged: the decision edges into them (e25,e28,e32) are untouched.endnode has no heading: the spec's guidance is that "a runner showsmessagealone under the flow's label". Each message is therefore the screen's title, a sentence break, then its description, for example "Conversion refused. This lead's Duplicate Status is Confirmed: …". No word is added, dropped or changed. The console titles the dialog with the action's label (Convert Lead), so without the joined title the words "Conversion refused" would have disappeared from the dialog.e26,e30ande33are retired, and their ids stay vacant under the file's edge-id rule. A refusingendhas no outgoing edge.flows.lead_conversion.screens.NODE.title/.descriptiontoflows.lead_conversion.refusals.NODE.message. Title and description are joined the same way: 。 in zh-CN and ja-JP, a period in es-ES. Noscreens.refuse_*key remains in any pack.refuse_nonpositive_amount's comment said it was "not the message-only screen the three refusals above use". It is reworded. Its config and edges are untouched, and so isdecision_amount.Measured after the change
These readings are from head
e0ecf3c8, built withpnpm buildand served byobjectstack dev -p 4162 --fresh.Convert Lead/Conversion refused. …(the full message), and its buttons are["Close","Close"](the corner X and the footer button).转化线索/转化被拒绝。…, the buttons are["关闭","关闭"], there is no toast, and nothing is written.Accept-Languageset to en-US, es-ES, ja-JP and zh-CN, 12 runs in all.status: refused. None carried arunId, ascreenor asuccessMessage.refusalMessagecame back in the requested language, openingConversion refused./Conversión rechazada./変換は拒否されました。/转化被拒绝。.screen_1. Resuming it with an amount of 25000 converted it, with an account and an opportunity linked.Tests
The three suites that pinned the screens now pin the refused outcome and the store in one object:
success: true,status: 'refused',runIdundefined, noscreen;The changes by file:
test/lead-duplicate-visibility.test.ts: the three confirmed-survivor cases now pin that outcome. The "says why" case readsrefusalMessageand keeps every earlier wording assertion: "refused", "Duplicate Status is Confirmed", "reviewer", the Duplicate Management group label, "surviving record", no record id, and no override hatch.test/account-name-normalized-match.test.ts: the key-less lead is refused. The earlier store assertions are unchanged.test/lead-conversion-approval-gate.test.ts: the Pending and Rejected cases keep the static check thate28targetsrefuse_unapproved, and now assert that no edge leaves it. Each case also runs the conversion as the rep and pins the refused outcome: 0 accounts, 0 contacts, lead qualified and not converted.test/flow-conversion.test.ts: one comment that said item 2b's was the only refusal endingrefused.Readings:
Tests 7 failed | 150 passed (157). Each refusal came backpaused, with arunIdand the refusalscreen. The approval-gate cases went red on their static half (e30leavesrefuse_unapproved).e0ecf3c8: the base flow and the three base packs were written on disk, with the blobs checked. The result wasTests 7 failed | 150 passed (157), the same shape.e0ecf3c8: onlyrefuse_unapprovedlost itsoutcomeandmessage, which makes it a refusal that completes. Its three pack entries were removed too, usingablation-replacewith--absentverified. The result wasTests 2 failed | 155 passed (157): the approval-gate runtime half went red onstatuswhile its static half held.git checkout HEAD, proven by blob == HEAD for all four files and an emptygit diff HEAD.i18n-references,lint-i18n-gate,flow-condition-totality,flow-decision-authorityandflow-quote. Result:Tests 659 passed (659).Local gates on
e0ecf3c8, each exit code captured on its own:pnpm validate0 (✓ Validation passed)pnpm typecheck0pnpm lint0 (18 suggestion(s), 0 errors)pnpm lint:i18n-gate0 (✓ 0i18n/missing-*issues)pnpm hygiene0 (✓ source hygiene clean)pnpm hygiene:tokens0 (✓ source token ratchet clean;src/salesbusiness semantics ~57,926 of ~88,500)pnpm build0 (✓ Build complete)pnpm test, run under the verify lock:VERDICT command-exit 0,Test Files 180 passed (180),Tests 3679 passed | 1 skipped (3680)Playwright e2e is left to CI.
Acceptance notes
refuse_unapprovedin the console.convert_lead'svisiblepredicate hides the button on a Pending or Rejected lead, so in the console this refusal is reached only from a stale page: the verdict changes while the rep has the lead open. I measured that path by writingrejectedas the system after the page loaded. A REST caller or the AI tool reaches it directly. This is by design, not a defect.refuse_held.content/docs/sales/leads*.mdx,lead-approval*.mdxordocs/MAINTENANCE.mdbecame false. Convert still opens a refusal that says "Conversion refused" instead of the conversion form.Generated by Claude Code