Repository navigation
docs(contracts): 以 contract.hook.ts 为唯一事实来源重写三语「合同激活时」小节 (#805) - #823
Merged
Merged
Conversation
….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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Aug 5, 2026
yinlianghui
marked this pull request as ready for review
August 5, 2026 22:16
This was referenced Aug 5, 2026
activities.mdx 三语把「合同结束前 60 天的续约提醒」当成重复任务的用例:既不是 60 天(按合同自己的 renewal_notice_days,默认 30),也不是重复任务
#883
Closed
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #805
Contracts 页「On contract activation」列了四步,激活时真正跑的
contract_on_activation(src/objects/contract.hook.ts:77-126)只做两件事,而且其中一件页面从没提过。前提复核(逐条,基线 origin/main = 6f6ed9a)
customer_since是幽灵字段grep -rn customer_since src/零命中;crm_account上没有任何字段记录「何时成为客户」contract-expiration/contract-renewal两条 scheduled flow,notify收件人都是负责人;激活链路上没有任何外发邮件,也没有「(configurable)」所指的配置面signed_date盖戳文档没写:104-109),已补写为第 1 条content/docs/revenue/contracts.mdx是对的;认领评论里的content/docs/sales/contracts.mdx是笔误,该路径不存在,实际改的是revenue/下三语三个文件改了什么
「合同激活时」小节(三语) 按 hook 重写成两条 + 三段说明:
!input.signed_date && !previous?.signed_date,:104)。所以「激活的同一次编辑里自己填了签署日期」会被保留,这一句对用户是有用的行为。删掉的三条按 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):控制字符自扫(hygiene 不覆盖
content/,#818 在案):Generated by Claude Code