Skip to content

docs(contracts): 以 contract.hook.ts 为唯一事实来源重写三语「合同激活时」小节 (#805) - #823

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-805-contract-activation-docs
Aug 5, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-805-contract-activation-docs

Conversation

@yinlianghui

Copy link
Copy Markdown
Contributor

Fixes #805

Contracts 页「On contract activation」列了四步,激活时真正跑的 contract_on_activation(src/objects/contract.hook.ts:77-126)只做两件事,而且其中一件页面从没提过。

前提复核(逐条,基线 origin/main = 6f6ed9a)

issue 说法 复核结果
第 1 条 Prospect → Customer 成立 成立,但条件比文档宽(见下)
第 2 条 customer_since 是幽灵字段 成立。grep -rn customer_since src/ 零命中;crm_account 上没有任何字段记录「何时成为客户」
第 3 条欢迎邮件无实现 成立。合同域只有 contract-expiration / contract-renewal 两条 scheduled flow,notify 收件人都是负责人;激活链路上没有任何外发邮件,也没有「(configurable)」所指的配置面
第 4 条给客户负责人 notify 无实现 成立。hook 里没有 notify 节点;两条 flow 的 notify 挂在到期与续约链路上
signed_date 盖戳文档没写 成立(:104-109),已补写为第 1 条
文件路径 issue 正文写的 content/docs/revenue/contracts.mdx 是对的;认领评论里的 content/docs/sales/contracts.mdx 是笔误,该路径不存在,实际改的是 revenue/ 下三语三个文件

改了什么

「合同激活时」小节(三语) 按 hook 重写成两条 + 三段说明:

  1. 签署日期盖在合同上(不是客户上)——写当天日期,且仅当这次更新和已存记录都没有签署日期时才盖(!input.signed_date && !previous?.signed_date,:104)。所以「激活的同一次编辑里自己填了签署日期」会被保留,这一句对用户是有用的行为。
  2. 账户类型 → Customer。

删掉的三条按 issue 裁定不在 src/** 补实现——要不要真发欢迎邮件、要不要加 customer_since,是产品决策,本 PR 只让文档停止声称它已经发生。

比 issue 更严一处,请复核。 issue 说第 1 条「成立」,但 hook 的门是 account.type !== 'customer'(:113),而 crm_account.type 有四个值(prospect / customer / partner / former)。只写「如果账户曾是潜在客户」会漏掉:合作伙伴账户和前客户账户同样会被静默改成正式客户——这对合作伙伴账户的负责人是意料之外的。既然这一句本来就在重写范围内,就按代码的真实条件写全了。

另外补了两条决定「到底会不会发生」的机制,都直接读自声明:hook 是 async: true / onError: 'log'(:82-83),失败只记日志不回滚;hook 是 afterUpdate 且有 previous?.status === 'activated' 门(:80、:88),所以创建时就已经是 Activated 的合同(数据导入、种子数据、一次性写整条记录的集成)两次写入都不触发,其账户停留在潜在客户——这是「automatically」在这一支不成立的地方,同 #796 里「无负责人则整条链路不触发」同型。

同页一致性(#794 先例,已披露)

「给管理员的提示」原文是「合同激活流程和到期扫描可以定制」。激活是 object hook 不是 flow,管理员照这句去自动化页的 flow 列表里找是找不到的(它本来就不在那张表里)。这句因本次重写而失真,一并改成指名 src/objects/contract.hook.ts,并保留指向到期扫描 / 续约提醒的指针——那两条确实是定时 flow。三语同步。

同页其余小节(到期扫描、续约提醒、记录字段表)也有与实现不符之处,但不由本次改动导致,按越界纪律另立单,未在本 PR 修改。

不加钉子测试(散文)。仅文档,无 metadata / 行为 / 字段变更。

验证

四件套全绿(flock /tmp/os-heavy-verify.lock,NODE_OPTIONS=--max-old-space-size=4096,vitest --maxWorkers=2):

pnpm validate  → ✓ Validation passed (2191ms)     exit=0
pnpm typecheck → tsc --noEmit                     exit=0
pnpm build     → ✓ Build complete (2106ms)        exit=0
pnpm test      → Test Files 64 passed (64)
                 Tests 1560 passed | 1 skipped    exit=0
pnpm lint      → 13 warning(s), 14 suggestion(s)  exit=0(均为既有)
pnpm hygiene   → ✓ source hygiene clean(只扫 src/test/e2e/scripts 243 个文件)

控制字符自扫(hygiene 不覆盖 content/,#818 在案):

grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' content/docs/revenue/contracts*.mdx .changeset/contracts-docs-activation-against-the-hook.md
→ 零命中

Generated by Claude Code

….ts (#805)

The Contracts page's "On contract activation" list promised four things and the
hook that runs on activation does two, neither of which was the first item's
match. `contract_on_activation` (src/objects/contract.hook.ts:77-126) stamps
`signed_date` on the CONTRACT when neither the update nor the stored record
carries one (:104-109), and sets the account's `type` to `customer` when the
account is not already one (:111-119). That is the whole of it.

Of the four documented steps, three have no implementation anywhere:

- "Customer Since date stamped on the account if blank" — `customer_since` is a
  ghost field. `grep -rn customer_since src/` is empty, and `crm_account`
  declares no field of any name for when an account became a customer, so the
  cohort analysis a reader would build on it has nothing to read.
- "Welcome email sent to the primary contact (configurable)" — the contract
  domain ships two flows, `contract-expiration` and `contract-renewal`, both
  scheduled and both notifying the OWNER. No outbound customer mail is sent at
  activation and there is no configuration surface behind "(configurable)". Same
  promise, same shape as #796 on the Contacts page.
- "Notification to the account owner" — the hook raises no notification at all.
  The two contract `notify` nodes hang off the expiration and renewal sweeps.

The signed-date stamp, which does happen, was the one step the page never
mentioned; it is now step 1, with the condition stated from the code — set
Signed Date in the same edit that activates and your value is kept.

The surviving step is stated more tightly than "if it was a Prospect". The gate
is `account.type !== 'customer'`, so a *Partner* and a *Former Customer* are
promoted exactly as a *Prospect* is; documenting only the Prospect case hid a
promotion that a partner-account owner would not expect. Two mechanics that
decide whether anything happens are written down too: the hook is `async: true`
with `onError: 'log'` (:82-83), so failures are logged rather than raised, and it
is an `afterUpdate` hook (:80) with a `previous?.status === 'activated'` guard
(:88) — a contract created already *Activated* by an import, by seed data or by
an integration writing the record in one shot triggers neither write, and its
account stays a *Prospect*.

Same-page consistency (the #794 precedent): the admin tip called activation a
"flow" and sent the reader to the flow list to customize it. It is an object
hook and does not appear there, so the tip now names the file and keeps the
pointer for the expiration and renewal sweeps, which really are scheduled flows.

Documentation only — no metadata, behaviour or field changes. Whether HotCRM
should send a welcome email or carry a `customer_since` field is a product
decision and is deliberately left open.

Fixes #805

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

Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 5, 2026 10:13pm

Request Review

@yinlianghui
yinlianghui marked this pull request as ready for review August 5, 2026 22:16
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 0ac226e Aug 5, 2026
9 checks passed
This was referenced Aug 5, 2026
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…ctstack-ai#824) (objectstack-ai#841)

`crm_account` 上不存在 `customer_since`(`grep -rn customer_since src/` 零命中),
也没有任何其它名字的字段记录客户何时成为客户。合同激活(`contract.hook.ts:104-119`)
在**合同**上盖 `signed_date`、把 `account.type` 改成 `customer`,不写日期到客户上。
objectstack-ai#805 / PR objectstack-ai#823 已改掉 contracts 页,同一条声明还剩 revenue/index 与 sales/accounts
两页(各三语),本次按同一口径改写,不留幽灵引用。

revenue/index 的每日续约提醒按 `contract-renewal.flow.ts` 写实:唯一的 notify 节点
收件人只有 `{currentContract.owner_id}`(合同负责人,inbox + email),账户所有者与
账户上的续约负责人都收不到;同句顺带写上通知前先建的续约任务,并把「例如结束前 60 天」
换成合同自己的 `renewal_notice_days`(defaultValue: 30)。

仅文档,未动 `src/**`。要不要真加 `customer_since` 是产品决定,不由文档倒推。


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

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…-ai#840) (objectstack-ai#848)

Customer Since 幽灵字段的最后 6 处,全在 ai-copilot 两页,换了一种更难
被搜到的拼法:en 页写 `customer-since`(带连字符)、zh 页意译成「成为
客户的日期」,所以前三轮按 `Customer Since` 的关键字搜索都没扫到。

以两个 skill 源码为唯一事实来源改写这两条 bullet:

- customer_360 `get_record` 账户,故「关系快照」写实为客户分层 + 客户
  负责人(都是 account.object.ts 上的真实字段);
- case_triage 的评判权重首项是「customer tier and contract value」,
  从未提到跟了多久,故「客户画像」写实为客户分层 + 合同金额。

两处都按 objectstack-ai#823 / objectstack-ai#824 已定口径明说账户上没有这个日期,并给读者留下真实
去处(合同的签署日期 / 开始日期),没有默默删掉这个名字。

zh 两页的 tier 字段名按语言包拼写(objectstack-ai#825 先例):客户分层 / 客戶分層。

仅文档:src/** 与 content/docs/releases/ 未动。


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

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…bjectstack-ai#834) (objectstack-ai#864)

The "Email templates" section closed with a claim about this app that this app
does not implement:

  Built-in templates cover lead-routing, opportunity wins, case
  acknowledgments, contract activations, renewal reminders.

Measured on a fresh `pnpm build`: `dist/objectstack.json` has no email-template
collection among its 21 top-level keys, `grep` finds no `*.email-template.*`
file under `src/`, and the artifact does not contain the string
`email_template` once. So an admin who went to Setup looking for a "Renewal
Reminder" template to reword found nothing to edit, and the wording they were
after was somewhere else entirely: the notifications behind those business
events are `notify` node config inside the flows this same page tabulates
(16 of the 24 shipped flows carry one), with the subject and body written
inline.

The page was also contradicting itself across two sections — the sentence under
the flow table has said "notifications inside flows are delivered by the
`notify` node" all along. The replacement echoes it instead of adding a second
answer.

Contract activation is called out separately because it is not a flow: it runs
as an object hook (`src/objects/contract.hook.ts`) which sends no message at
all (objectstack-ai#805/objectstack-ai#823 measured the same thing from the contracts page), while the
contract mail that does go out belongs to Contract Auto-Expiration and Contract
Renewal Reminder.

Scoped to what this repo can prove — that HotCRM authors no templates. It makes
no claim about the platform's template surface (the objectstack-ai#800 trap): Setup → Email
Templates stays the place to author one, and the section's other four bullets
describe that platform capability unchanged.

Three locales, one sentence each. Documentation only.

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

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants