Skip to content

contracts.mdx 还剩四处与实现不符:五条幽灵列表视图、三个幽灵选项、无实现的「已激活不能删除」、教销售代表做他无权做的事 #832

Description

@yinlianghui

来源:#826(PR #831)实施过程中的顺带发现。#826 的裁定面是同页的三处(到期扫描第 3 条、续约提醒的一键操作、记录字段表的四个幽灵字段),本条是该页其余四处,均与那三处无因果关系,也都不由 #831 的改动导致,故单独立单。基线 origin/main = 4c12791(#823 已在其中)。

文件面:content/docs/revenue/contracts.mdx + zh-Hans / zh-Hant(三语同款)。

与 #752 / #757 / #758 / #765 / #792 / #796 / #805 / #826 同一类:页面声称的东西在 src/ 下不存在。四处可在一遍里改完(同页、同三个文件)。

1. 「标准列表视图」五条全是幽灵(:115-121)

页面写:

- **My Active Contracts** — `owner_id = me AND status = Activated`.
- **Expiring in 60 Days** — `end_date <= TODAY + 60 AND status = Activated`.
- **Up for Renewal** — within renewal-notice window.
- **Expired This Month** — `status = Expired AND last_modified IN this month`.
- **Pending Activation** — `status IN [Draft, In Approval]`.

src/views/contract.view.ts 里一条都没有。真实的四个视图是:

name type label 语言包
all_contracts grid All Contracts 全部合同(zh-CN.ts:562)
renewal_calendar calendar Renewal Calendar 续约日历
contract_gantt gantt Contract Terms 合同条款
contract_timeline timeline Contract Timeline 合同时间线

而且四者是同一份数据的四种可视化(appearance.allowedVisualizations + tabs[]),不是五个按条件过滤的列表——读者按页面去找「我的活跃合同」这一栏,找到的是一个没有任何过滤的全量网格。与 #792 那批 sales-list-view-rosters / opportunities-docs-view-roster 同型,只是落在合同上。

2. 计费周期与付款条款各有幽灵选项(:44-62)

  • 计费周期:页面列 One-Time / Monthly / Quarterly / Annual / Custom。contract.object.ts:131-140 的 billing_frequency 只有 monthly(default) / quarterly / annually / one_time —— 没有 Custom。
  • 付款条款:页面列 Net 15 / Net 30 / Net 45 / Net 60 / Net 90 / Due on Receipt / Prepaid。PAYMENT_TERMS_OPTIONS(src/objects/_picklists.ts:83-89)只有 net_15 / net_30(default) / net_60 / net_90 / due_on_receipt —— 没有 Net 45,也没有 Prepaid。

这一组是「在表单上找不到」型:读者按页面去选 Net 45 或 Prepaid,下拉里根本没有。注意 PAYMENT_TERMS_OPTIONS 是与 Quote 共用的规范集(#490),所以同一处失真可能也落在 quotes 页,改前顺带核一下。

3. 「内置规则」小节:一条无实现,两条真规则没写(:99-103)

页面写:

- **End date must be after start date** — basic sanity check.
- **Contract value must be zero or positive** — NDAs can be $0.
- **Activated contracts cannot be deleted** — change status to Terminated instead.
  • 第 1 条成立(validations[].end_after_start)。
  • 第 2 条勉强成立,但它不是 validation rule,是 contract_value 的 min: 0 字段约束。
  • 第 3 条没有任何实现:contract.object.ts / contract.hook.ts 里没有 beforeDelete,没有任何按 status 拦删除的校验。删除权限完全由 profile 决定,且与状态无关:system-admin allowDelete: true(连已激活的也能删),sales-manager 与 sales-rep 都是 allowDelete: false(连草稿也删不掉)。也就是说这句话对管理员是假的安全感、对其他人是多余的限制说明。

同时这一节漏掉了合同上真正会拦住保存的三条规则,而它们恰恰是用户最容易撞上的:

  • contract_validation hook(contract.hook.ts:19-73):end_date 与 start_date + contract_term_months 相差超过 1 个月直接抛错;
  • 同一 hook:合同激活后不允许把 end_date 往前改("Cannot shrink end_date … after activation");
  • contract_status_progression 状态机(contract.object.ts:257-275):draft 不能直接跳 activated,expired / terminated 都是终态。

4. 「给销售代表的提示」教销售代表做他无权做的事(:129-132)

页面写:「当报价被接受、你创建合同时,商机链接会被预填——保留它;它驱动售后报表。」

两处都不成立:

  • src/profiles/sales-rep.profile.ts:52 上 crm_contract 是 allowCreate: false, allowEdit: false —— 销售代表既不能建也不能改合同(sales-manager 也是 allowEdit: true 但 allowDelete: false;只有 admin 能建能改能删)。这条提示整段的前提就不成立。
  • 没有任何地方预填这个链接:crm_contract.crm_opportunity 只有 dependsOn: ['crm_account'](把选择器按客户过滤),没有 defaultValue;grep -rn "crm_contract" src/actions/ src/flows/ 除两条合同 flow 外只有 demo-bootstrap 的一行导航配置——没有任何 action 或 flow 从报价/商机创建合同。

顺带:同页「共享」小节写「合同所有者 读取并编辑自己的合同」,对销售代表同样不成立(allowEdit: false)——记录级共享放开了也过不了对象级的 CRUD 门。这一句要不要一起改,看维护者判断(它属于 #549 的邻域)。

影响

第 1、2 条是「在界面上找不到」型,读者照做会卡住;第 4 条更重一点,是「教读者去做一件权限层直接拒绝的事」,而且提示语气是肯定的("保留它;它驱动售后报表");第 3 条第三句属于「以为有护栏」——管理员照着它以为已激活的合同删不掉,实际 admin 一键就能删。

修法建议

以 src/views/contract.view.ts、src/objects/contract.object.ts、src/objects/_picklists.ts、src/objects/contract.hook.ts、src/profiles/*.profile.ts 为事实来源改这四处(三语)。列表视图一节建议按 #792 在 sales-list-view-rosters 里的做法重写成「一份数据 + 四种可视化」。不为散文加钉子测试。

⛔ 不要改 src/** —— 要不要真加 Custom 计费周期 / Net 45 / Prepaid、要不要给已激活合同加删除护栏、要不要放开销售代表建合同的权限,都是产品决定;若确定要做,另开实现单。

Refs #826 #805 #792 #796 #549

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 5, 2026
  2. self-assigned this
    on Aug 6, 2026
  3. yinlianghui commented on Aug 6, 2026

    @yinlianghui
    CollaboratorAuthor

    [PM 认领 · R21] session_01VHrPAGEgFDoHjphqYG4BMa · 分支 claude/issue-832-contracts-residual-four · 文件面:content/docs/revenue/contracts.mdx ×3 + changeset

    裁定与边界:

    1. 四处按 issue 修法建议执行,事实来源锁定 contract.view.ts / contract.object.ts / _picklists.ts / contract.hook.ts / profiles/*.profile.ts(全部在最新 main 上重测):列表视图节按 同一缺陷类(残余):leads.mdx / contacts.mdx 的「记录存储的内容」区块表仍与对象 fieldGroups 不符,含幽灵字段 tags #792 先例重写为「一份数据 + 四种可视化」;幽灵选项(Custom / Net 45 / Prepaid)删除,选项集按包与 _picklists 写实;「内置规则」节删无实现的第 3 条、把 min:0 归位为字段约束、补上真正会拦保存的三条(hook 双规则 + 状态机);「给销售代表的提示」按 profile 实况改写(sales-rep 不能建不能改合同,无任何预填链路)。
    2. 「共享」小节那句(合同所有者编辑自己的合同)不改——issue 自己标了属 Sharing coverage gap: territory/team-shared accounts show quote, task and contract related lists the rep cannot read #549 邻域、交维护者判断;报回即可。
    3. PAYMENT_TERMS_OPTIONS 与 Quote 共用:顺带核 quotes 页是否同批失真——只核不改,有发现先对既有单去重再报回。
    4. ⚠️ 基线漂移:issue 基线 4c12791,其后 docs(contracts): 按到期/续约 flow 与 contract.object.ts 重写三语剩余三处失真 (#826) #831(同页三处)已合并——逐条重定位重核,若某处已被顺手修掉如实报。
    5. ⛔ 不动 src/**(加选项/加护栏/放权限均产品决策);不加守卫钉散文;三语同步;zh 字段/选项名按语言包(docs(sales): 让 zh 页的线索状态名、到期日期、直属上级与区块名跟随语言包 (#801) #825 先例)。

    ⛔ 不升级 @objectstack/*;不动 releases/;changeset 站内路径反引号;控制字节自扫;JSON 报告按标准 schema。


    Generated by Claude Code

  4. yinlianghui commented on Aug 6, 2026

    @yinlianghui
    CollaboratorAuthor

    [PM 验收] PR #874 ACCEPT — 已转 ready 并挂 auto-merge(session_01VHrPAGEgFDoHjphqYG4BMa)

    复核结论:

    越界发现处置:#871(quotes 页接受链路多写两步)入队 pm:queue;#872(「手动终止」两句假护栏 + 追加的「共享」句)入队 pm:queue,同页优先排;#873(共用词表的理由未被实现——hook 不复制 payment_terms)记 finding;#802/#714 去重命中处理正确。


    Generated by Claude Code

  5. added and removed on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions