Skip to content

docs(leads): only web-to-lead reaches the round-robin; an import makes the importer the owner (#2360) - #2361

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-xpo8c3
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-xpo8c3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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

维护者速读

  • 改了什么:用户手册「线索——分配与转化」页(中英文)不再说 CSV 导入和 API 采集的线索会轮询分给销售代表。现在只有网页转线索进入轮询;界面创建、用本人登录或 API 密钥走 API、文件导入的线索都归录入的人,导入的归执行导入的人,并链接到管理员手册的「导入数据」页。另把 task.hook.ts 头注释里的「警告」改成「拒绝」。只改文字,不改任何行为。
  • 为什么改:在 18.2.0 dev server 上逐个入口实测:只有匿名网页表单提交的线索没有负责人、会被 lead_auto_assign 分给负载最轻的代表;其余入口在钩子运行前已被平台盖上写入者为负责人。用户手册和管理员手册因此互相矛盾。提醒晚于截止日期的任务会被 400 拒绝,不是警告。
  • 风险与回滚:风险极低,两页文档加一行注释;回滚即 revert。
  • 先看:src/docs/crm_sales_leads.md 第 32–42 行;src/docs/crm_sales_leads.zh.md 第 21–23 行。
  • 两分钟验证:npx vitest run test/docs-drift.test.ts test/docs-books.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

  • src/docs/crm_sales_leads.md and .zh.md: the round-robin sentence names one door, web-to-lead, because it is the only door measured to leave a lead ownerless. A new paragraph says that a lead a person puts in keeps that person as owner (UI, API under a sign-in or an API key, file import), that an import never reaches the round-robin, and it links ./crm_admin_data_import.md, which batch 2 wrote to the same measurement. The en and zh pages say the same thing.
  • src/sales/objects/task.hook.ts, header comment only: "Warns when reminder_date is after due_date" now reads "Refuses (VALIDATION_FAILED / 400) …", which is what the handler at :140 does.
  • .changeset/leads-help-import-owner.md: a patch changeset, since the manual ships in the artifact.
  • No change to the import, the assignment or the task behaviour. docs-drift does not pin the lead sentence (it pins the hot-lead threshold on this page), so no test changes.

Tests

Every probe ran on the shipped artifact (dist/objectstack.json from this branch's pnpm build, served by objectstack dev -a on ObjectStack 18.2.0) after pnpm demo:staff, so the rep pool is na.rep and eu.rep (both holders of sales_rep, each owning 0 open leads). Neither the dev admin nor sales.manager is in the pool, so an owner of admin or manager cannot be a round-robin pick.

Door named on the old page Probe Owner stored Reaches lead_auto_assign?
web-to-lead anonymous POST /api/v1/forms/contact-us/submit → 201 eu.rep (a pool member), lead_source: web, created_by null yes
CSV import, admin POST /api/v1/data/crm_lead/import, mappingName: crm_lead_import, blank "Lead Owner Email" → 200, created 1 dev admin (the importer) no
CSV import, member the same as sales.manager / as na.rep → 200, created 1 each the importer each time no
JSON import, admin, no owner column POST …/crm_lead/import, format: json → 200 dev admin no
API capture, user session POST /api/v1/data/crm_lead as admin / as sales.manager, no owner → 201 the caller each time no
API capture, API key key minted by sales.manager at POST /api/v1/keys, then POST /api/v1/data/crm_lead with x-api-key → 201 sales.manager (the key holder) no
API capture as a system write no public door makes one: a session and an API key both carry a user (rows above). The in-process seed door is the only user-less write, and test/hooks-runtime.test.ts › lead_auto_assign › "assigns an ownerless lead to the rep with the fewest open leads" pins that it is routed n/a on the dev server yes, but not reachable by a user or integration

The PM mechanism assumption holds: an admin's import of a blank-owner lead row makes the importer the owner.

Item 2, on the same server, as sales.manager: POST /api/v1/data/crm_task with due_date: 2026-11-10, reminder_date: 2026-11-12T09:00:00Z → 400 VALIDATION_FAILED, "Reminder (2026-11-12T09:00:00Z) is after the due date (2026-11-10); reminders should fire before the deadline." Control, reminder on 2026-11-08 → 201.

Acceptance notes

  • "API capture" is dropped from the round-robin sentence rather than reworded to "a system write": no door a user or an integration can reach writes without a user, so naming it would describe nothing a reader can do.
  • The link to crm_admin_data_import points into the Admin Manual, which only system_admin reads. A sales rep who follows it gets the platform's doc access refusal (read on 18.2.0 in In-app docs, batch 1: a Help group at the end of the HotCRM menu, three books, and the four package docs split into nine with Chinese variants #2327), so the prose calls it "the administrator's Importing Data page" and says enough on its own (blank owner ⇒ the importer; reassign afterwards).
  • Out of scope, reported, not fixed: content/docs/administration/setup.mdx:68 and its .zh-Hans.mdx:68 / .zh-Hant.mdx:70 siblings make the same claim ("An ownerless lead — web-to-lead, CSV import, API capture — is assigned on insert …"). That is the product site, not this card's file surface.

Verification

  • pnpm verify on head 976deca (validate, typecheck, lint --strict, hygiene, token ratchet, build, vitest run), exit 0: "Validation passed", lint 0 errors (19 suggestions, as on main), "source hygiene clean", "source token ratchet clean", Test Files 242 passed (242), Tests 4606 passed, 1 skipped (4607), 653 s. That run includes test/docs-drift.test.ts, test/docs-books.test.ts and test/hooks-runtime.test.ts (the system-write case cited above).
  • Probe scripts and their raw output are kept in the implementing session's scratchpad; the readings are the table above.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAo4oRPhkffQUg8HR7n

…port makes the importer the owner

The Leads page said CSV import and API capture leave a lead ownerless and so
reach lead_auto_assign. Measured on 18.2.0: an import (CSV with the shipped
mapping, or JSON) and an API create under a session or API key are stamped
with the caller as owner; only the anonymous web-to-lead form reaches the
round-robin. The page now says so and links the Admin Manual's import page.

task.hook.ts's header said the reminder check warns; it refuses with
VALIDATION_FAILED / 400.

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

vercel Bot commented Oct 28, 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 28, 2026 7:41am UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces backend Server-side behaviour — hooks, flows, actions labels Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-28T07:43Z · head 976deca
Implemented-by: card thread session session_01J8VVAo4oRPhkffQUg8HR7n (report 6463509093 on #2360). Reviewed-by: the seat, against the 4-file diff and hotcrm origin/main 0312803.

  • Shape:

    • The PR is a draft against main, assigned to huangyiirene. Its body reads Fixes #2360.
    • No governed path is touched.
    • The diff stays within claim 6463088688: the Leads page in en and zh, one header-comment line in task.hook.ts, and a changeset. docs-drift did not pin the sentence.
    • git merge-tree against 0312803 is clean.
  • Ruling held: the text follows the code, and no behaviour changes. Each door was measured on the shipped 18.2.0 artifact with a two-rep pool:

    • web-to-lead: an anonymous POST /forms/contact-us/submit returned 201, and the round-robin assigned the lead to a pool rep. This is the only door the page now names.
    • file import: a blank-owner import (mappingName crm_lead_import) as admin, sales manager or rep each made the importer the owner. JSON import did the same.
    • API create: a create through the session or an API key (minted by the sales manager) made the caller the owner.
    • system write: no public door makes one. hooks-runtime pins the seed-door routing.

    The import sentence links ./crm_admin_data_import.md. The zh page says the same thing as the en page.

  • Item 2: the header now says the reminder check refuses with VALIDATION_FAILED / 400. A reminder after the due date returned 400, and the control returned 201.

  • Mechanism assumption 1 holds: an admin's blank-owner import makes the importer the owner.

  • Gates:

    • pnpm verify exits 0 at 976deca: 242 files, 4606 passed, 1 skipped.
    • CI on 976deca: 8 of 9 check runs are success, link-check included. Build and Test (22.x) is still running.
  • Out of scope, carded by the seat: content/docs/administration/setup.mdx:68 (and the zh-Hans and zh-Hant pages) repeats "web-to-lead, CSV import, API capture" as ownerless doors.

  • Deviation noted: b36c51c carries a model-named trailer. History is not rewritten.

  • Landing: non-governed. The seat flips the PR to ready and arms auto-merge in this stroke.


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 documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants