Skip to content

refactor(contract): move crm_contract and its items from revenue into the app package - #2323

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-tw05ny
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-tw05ny

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread
Fixes #2310

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:合同对象 crm_contract 连同它的 hook、视图、三条流程(到期、续约、激活后交财务)、payment_terms 选项列表、合同种子数据、两封合同通知邮件模板和侧边栏的「合同」入口,从 revenue 模块搬进 HotCRM 本体(app.objectstack.hotcrm)。对象名、字段、记录都不变。
  • 为什么改:执行 2026-10-26 的裁决(拆包计划第 10、11 条)——每个附加包只依赖本体一层;O2C、PSA 都挂在合同上,合同留在 revenue 就会出现第二层依赖。
  • 风险与回滚:只换归属包,不改字段和数据;已安装租户一次升级完成。侧边栏「销售」组里「合同」现在排在「销售业绩」之后、「报价」之前。回滚即还原本 PR。
  • 先看:objectstack.composition.ts(种子与 hook、流程的注册)、src/revenue/index.ts(revenue 不再注册合同)、src/sales/apps/crm.app.ts(合同入口改为本体自己的导航项)。
  • 两分钟验证:pnpm dev 后打开「销售 → 合同」,列表有 4 条演示合同;把一张「已呈送」报价改为「已接受」,合同列表多出一条草稿合同,付款条件随报价带过来。
  • 需要你做的:无,席位落地。

What changes

module-split-plan.md item 11 (ruled 2026-10-26, decision text in #2309): crm_contract moves from app.objectstack.hotcrm.revenue to app.objectstack.hotcrm. Object name unchanged (ADR-0129).

Item From To
object, hook src/revenue/objects/contract.{object,hook}.ts src/sales/objects/
list views src/revenue/views/contract.view.ts src/sales/views/
contract_renewal, contract_expiration, billing_handoff_contract_activated src/revenue/flows/ src/sales/flows/ (imports now from their own directory)
payment_terms picklist src/revenue/picklists/ (directory retired) src/sales/picklists/; crm_quote.payment_terms now references it along the revenue -> app edge
contract seed family src/revenue/data/revenue.seed.ts src/sales/data/contract.seed.ts; replay order in CrmSeedData unchanged
crm.contract_expired, crm.contract_renewal email templates (4 locales) src/revenue/email-templates/ src/sales/email-templates/; revenue keeps crm.quote_created
nav_contract revenue navigationContributions the app's own group_sales children
Contracts guide (en, zh-Hans, zh-Hant) content/docs/revenue/contracts* content/docs/sales/contracts*, both meta*.json and every link

Registration: appObjects, appPicklists, appViews pick the new exports up from the sales barrels by export name; contractHook joins appHooks after contactHook (source-file-name order); the three flows join appFlows in the order revenue registered them. createHotCrmRevenueStack registers four objects, no picklist, one flow (quote_expiration), and keeps requires: ['automation', 'triggers'] (measured: dropping triggers fails pnpm validate on quote_expiration's schedule trigger).

Two items the card did not list moved because the repo's own rules require it, not by choice:

  • the contract seed family: test/package-ownership.test.ts's assignment sweep reports a seed family registered by a package that does not own its object;
  • the two contract email templates and the Contracts nav entry: AGENTS.md layout rule 3 (an item lives with the object it is authored against). Left in revenue, the app's own flows would name revenue's templates and the Contracts entry would vanish with revenue, which is the second dependency level item 10 rules out.

quote_accepted_contract_draft stays in src/revenue/objects/quote.hook.ts; it now writes along the revenue -> app edge (R1 accepted). The activated-contract counts in account.hook.ts / contact.hook.ts are now reads of the app package's own object; the inventory's hand-edit log records them for #2311's ledger.

Changeset: .changeset/2310-contract-in-app-package.md (minor: the contract's owning package and one sidebar position change).

Tests

  • test/package-ownership.test.ts: revenue owns four objects, the app nine; a new case pins crm_contract and payment_terms in the app package, no module picklist and no module nav entry on crm_contract.
  • test/docs-sales-index-navigation.test.ts: authored Sales children end with nav_contract; the served group ends with nav_quote, nav_product.
  • test/saas-composition.test.ts, test/territory-seed-coverage.test.ts, test/seed-replay-user-edits.test.ts: seed family moved and enrolled.
  • Path-only updates (imports and doc pointers) in 12 further suites and test/helpers/composed-stack.ts.
  • README headline: the sales package's business semantics now measure 64,038 tokens (gate reading), above the ~58k banner's 5% band, so the banner reads ~64k and test/docs-readme-token-figures.test.ts's docstring row is restated from it. The growth is the contract's object, hook and flows arriving in src/sales; every ceiling still holds.

Acceptance notes

  • Sidebar order is a consequence, not a choice: an app's own entries precede module contributions, so Sales now reads Leads, Accounts, Contacts, Opportunities, Sales Performance, Contracts, Quotes, Products. The sales index and the quick tour (three locales) say so.
  • The revenue docs index no longer lists Contracts under "What's included"; its lifecycle section still links the guide, as it already linked Quotes in the Sales section.
  • Not changed, and why: AGENTS.md (tree line and the src/revenue/objects/contract.object.ts example) is a governed path, so this PR leaves it; the stale lines are reported to the seat. docs/architecture/module-split-plan.md decision 5's revenue object list is amended by item 11 in docs: record the 2026-10-26 split decisions and file REQ-0008 and REQ-0009 #2309. Dated planning documents (docs/architecture/psa-module-plan.md, order-to-cash-module-plan.md, docs/requirements/0002, 0007) keep the paths they were written against.
  • docs/architecture/module-split-inventory.json: five files[].path rows re-pointed, figures left as measured, entry added to $hand_edits.
  • Merged origin/main at 1f3f888 (chore(deps): upgrade the ObjectStack platform to 18.2.0, translate what it opens, and update the five drifted tests #2322, ObjectStack 18.2.0) before opening; pnpm verify re-ran on the merged head. No new label class: the moved templates carry all four locales and nav_contract keeps its existing translations.

Verification

Browser, Chromium at /opt/pw-browsers/chromium, pnpm dev on this branch (18.2.0 merged), fresh DB, dev admin, 1440x900. Screenshots are in the project thread (sidebar, contract list, record, activated, drafted).

Check Result
Sidebar, Sales group ... Sales Performance, Contracts, Quotes, Products
Contract list 4 seeded contracts (CTR-0001 to CTR-0004)
Record page, edit form Payment Terms offers Net 15/30/60/90 and Due on Receipt (the moved picklist)
Activate CTR-0002 (In Approval -> Activated, edit form) PATCH 200; signed_date stamped 2026-10-27 by contract_on_activation; outbox row crm.contract.activated enqueued by send_contract_activated_handoff
Accept QTE-0003 (Presented -> Accepted) CTR-0005 drafted: Draft, Globex, contact Sarah Johnson, opportunity Globex Manufacturing Suite (now closed_won), payment_terms net_60 carried from the quote
Contact delete guard DELETE crm_contact for John Smith and Robert Wilson: 409 DELETE_RESTRICTED, "referenced by 1 active contract"
GET /api/v1/packages crm_contract under app.objectstack.hotcrm (9 objects); revenue holds 4
Boot log only the scheduled-work policy line (8 flows unbound by deployment policy, contract_renewal and contract_expiration among them, as on main)
pnpm verify on 7c1385f (shared verify lock)
os-verify-lock: VERDICT command-exit 0 · held the lock 942s (15m42s) · waited 0s
validate        ✓ Validation passed
typecheck       tsc --noEmit, no output
lint            os lint --strict, 16 suggestions, no error
hygiene         ✓ source hygiene clean
hygiene:tokens  ✓ source token ratchet clean (src/sales business semantics ~64,038 of ~88,500)
build           ✓ Build complete; dist/objectstack.json: crm_contract and payment_terms in app.objectstack.hotcrm
test            Test Files 224 passed (224) · Tests 4381 passed, 1 skipped (4382)

Generated by Claude Code

…he app package

The object, its hook, views, the three contract flows, the payment_terms
picklist, the contract seed family, the two contract notification
templates and the Contracts navigation entry now live in src/sales/ and
register with app.objectstack.hotcrm (module-split-plan item 11). The
object name is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAmYwnaiFvuRyfAwh1e
… the move

The guide moves from content/docs/revenue/ to content/docs/sales/ in all
three languages with its navigation entry and every link. The ownership
test, the Sales navigation pins, the seed-family roster and the
module-split inventory read the new owner; a changeset records the move.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAmYwnaiFvuRyfAwh1e
…adline figure

Contracts is the app's own Sales entry now, so it precedes the revenue
module's Quotes and Products in all three quick-tour pages. The sales
package's business semantics grew by the contract's object, hook and
flows: the gate measures 64,038, so the README banner reads ~64k.

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

vercel Bot commented Oct 27, 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 27, 2026 9:21am UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces configuration Build and app configuration files backend Server-side behaviour — hooks, flows, actions labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Upgrade-path reading for #2310 (review round 1). The section below is written to be appended to this PR's Verification section; os-dev.md has the dev write the PR body once, at create, so the seat applies it.

Upgrade path: main 1f3f888 to this branch 7c1385f, same database

One worktree, one persistent dev database (.objectstack/data/objectstack.db, SQLite file, not in-memory). Booted pnpm dev on origin/main 1f3f888 (18.2.0) on an empty file, let it seed, then stopped the server, checked out 7c1385f in the same worktree (pnpm install --frozen-lockfile, same lockfile) and booted again on the same file. Chromium at /opt/pw-browsers/chromium, dev admin.

Reading main 1f3f888 this branch 7c1385f, same DB
Boot OK, seeded 4 contracts OK. No error line; no ownership, duplicate-registration or migration warning. Warnings are the 8 approver advisories and the metadata plugin-superseded line main prints too; no [Seeder] line
GET /api/v1/packages, owner of crm_contract app.objectstack.hotcrm.revenue (5 objects); app package 8 objects app.objectstack.hotcrm (9 objects); revenue 4 objects, crm_contract absent
Contract rows 4 (CTR-0001 to CTR-0004) the same 4 ids, no duplicates (contract seed is mode: 'ignore')
Contract activated on main: CTR-0002, id iLXU4ZK41Y_RDSbo PATCH status=activated 200; signed_date stamped; outbox crm.contract.activated for CTR-0002 same id opens on its record page (console, /crm_contract/record/iLXU4ZK41Y_RDSbo) and saves through the edit form (Activated to Terminated: PATCH 200, toast "Contract CTR-0002 updated")
Status change fires the flows after the upgrade n/a CTR-0004 (-XimCs6LatNMFCVY, seeded on main) Draft to In Approval to Activated through the edit form: both PATCH 200; contract_on_activation stamped signed_date; new sys_http_delivery row send_contract_activated_handoff / crm.contract.activated for CTR-0004

What carries the move: on this deployment shape the package registry is built from the artifact at boot, not stored. sys_packages, sys_metadata and sys_metadata_activation hold 0 rows in the database after the main boot, so there is no persisted owner row for the upgrade to rewrite, and the data table crm_contract is untouched by the owner change. Not measured: a marketplace or cloud install that persists the package registry; that path is #2271's first measurement (module-split-plan item 13).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-27T12:54Z · head 7c1385f2e5
Implemented-by: card thread session session_01J8VVAmYwnaiFvuRyfAwh1e (report comment 6438183975 on #2310; upgrade readings comment 6438313348 on this PR) · Reviewed-by: the seat, against the PR's 105-file list and hotcrm origin/main 1f3f888

  • Shape: draft, base main, assignee huangyiirene, Fixes #2310 and no other closing keyword. No governed path (AGENTS.md, CLAUDE.md, .claude/**), so no Tier H fork.
  • Ruling held: crm_contract, its hook, views, the three contract flows and payment_terms register with app.objectstack.hotcrm. createHotCrmRevenueStack keeps four objects, no picklist and quote_expiration alone, with dependencies still the single app edge. The object name, fields and records are unchanged (contract.object.ts moved byte-identical).
  • Surface beyond the claim, accepted: each item is forced by a repo rule or by a fact the move made false. That covers the contract seed family (the package-ownership sweep), the two contract email templates in four locales and nav_contract (layout rule 3; left in revenue they would be app → revenue hard edges), the comments and path pointers in docs (three locales), the README tree and token headline (gate reading 64,038, band 60,800–67,200), and 13 test files. The sidebar order change (Contracts before Quotes) is the platform's rule that an app's own entries precede contributions; the changeset states it.
  • Upgrade path, measured (comment 6438313348): the same SQLite file was booted and seeded on main 1f3f888, then booted on 7c1385f. The boot was clean. /api/v1/packages moves crm_contract from revenue (5 → 4) to the app (9), and the same four contract ids appear with no duplicate seeds. An activated-on-main contract opens and saves (PATCH 200), and an activation on the branch stamps signed_date and enqueues crm.contract.activated. Not measured: a persisted-registry install (marketplace or cloud); the dev shape holds 0 sys_packages / sys_metadata rows. So the changeset's "an installed tenant takes the move in this one upgrade" is measured for the self-hosted shape only. QA: complete browser pass of HotCRM on @objectstack/* 18.2.0 — all 161 feature-inventory rows, Setup and Studio, in six tracks #2320's pass and the next release's aftercare re-read it on a registry install.
  • Gates: local pnpm verify under the lock at 7c1385f: VERDICT command-exit 0, 224/224 files, 4381 passed, 1 skipped, lint --strict 16 suggestions and no error. Check-runs on 7c1385f2e5 at review: CodeQL, Typecheck and Build, Analyze Code (javascript), Quality Checks, Playwright, Check Changeset, link-check, Label Pull Request and Vercel Preview Comments are all success; Build and Test (22.x) success too.
  • Open question answered: option A. The two false AGENTS.md lines (the src/ tree and the rule-2 example) get their own governed docs-only draft from the seat after this lands, and docs: record the 2026-10-26 split decisions and file REQ-0008 and REQ-0009 #2309 is untouched.
  • Out-of-scope finding filed: Billing hand-off for an activated contract sends signed_date null when the activation stamps it: contract_on_activation writes the date in a separate async afterUpdate, after the flow has read the record #2325, "billing hand-off sends signed_date null when the activation stamps it" (class a, p2, Blocked-by: #2310 because its files move here). The other three findings are carried by docs: record the 2026-10-26 split decisions and file REQ-0008 and REQ-0009 #2309 or by the seat's AGENTS.md draft, or are dated plan docs left as written.
  • Landing: this is a non-governed PR, so the seat flips it ready and arms auto-merge on this head. Hard-edge ledger: list every app → module reference with its destination, measured on a booted stack with each module absent, before revenue leaves the artifact (module-split-plan item 13 (c)) #2311 returns to pm:queue when it merges.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 12:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 5eb2f0d Oct 27, 2026
11 checks passed
huangyiirene pushed a commit that referenced this pull request Oct 27, 2026
Bring in #2323, which moves crm_contract into the app package. Conflicts
resolved additively: the revenue module keeps the price book objects,
hooks, seed and nav item and drops the contract; the Sales and revenue
docs, quick tour, composition seed order and ownership tests carry both.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAjytvJfaG4gci9s7at
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 ci/cd CI plumbing and the verification pipeline configuration Build and app configuration files documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants