Skip to content

Commit a599cc1

Browse files
test(qa-scenarios): add the order_to_cash mid-tier scenario card (#30706)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D92W26tBPQgYaQxn6AjhUbM)_ Fixes #30477 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:新增「订单到收款」中档场景卡(8 个对象:员工、客户、产品、销售订单、订单明细、发票、收款、跟进活动;折扣审批加逾期催收自动化;3 个角色其中 2 个按行限定;1 张应收账龄报表;120 张订单跨 7–10 月;10 道问数、6 个写操作、5 个 agent 动作),并配一份只属于这张卡的真值测试。 - **为什么改**:落实 cloud#3600 九个场景中的订单到收款场景,让 AI 建应用、问数、代办写操作都有一张能用代码判分的销售到财务闭环卡。 - **风险与回滚**:只加测试数据和测试,所在包私有、不发布,运行时零改动,也不碰任何共享文件;回滚即删两个新文件。 - **先看**:① `docs/qa/scenarios/order_to_cash.json` 的 `run.questions` 与 `run.operations`;② `packages/qa/scenarios/test/order-to-cash.test.ts` 的 `EXPECTED_TRUTHS` 与 `EXPECTED_PERSONA_TRUTHS`;③ 同文件的种子一致性用例(发票、收款、账龄与催收规则)。 - **两分钟验证**:`pnpm --filter @objectstack/scenarios test`,预期 5 个测试文件、70 个用例全绿。 - **需要你做的**:无,席位落地。 ## What changes - `docs/qa/scenarios/order_to_cash.json` (new): the 订单到收款 order-to-cash mid-tier card. - Eight objects: `employee`, `account`, `product`, `sales_order`, `order_line`, `invoice`, `payment` and `activity` (the account's follow-up stream, its own records with lookups to the account, the invoice and the owner, never typed into the account). - The flows: an order with a line discounted above 10% is routed to the owner's sales manager (`approver`, `approved_on`), and an invoice 15 days past due and not fully paid gets a collection call for the AR clerk. - Three personas: `sales_rep` (e03, own accounts; no payment object, no product cost) and `sales_manager` (e01, team east; no payment bank reference) carry a row scope; `ar_clerk` (e09) sees everything but product cost. - Seed: 120 orders dated July to October 2026, plus 240 lines, 95 invoices, 72 payments and 64 activities. The arithmetic rule that generated it is stated in the card's `$comment`, so every truth can be worked by hand. Receivables are as of `asOf` 2026-10-31: one account has paid nothing, one pays half on the due date, one pays 20 days late. - Run half: 10 questions, 6 operations and 5 agent actions (one confirm-gated); `iterate` adds credit notes. - `packages/qa/scenarios/test/order-to-cash.test.ts` (new): this card's own test file. - It pins all 10 computed truths and the 8 per-persona truths against hand-worked values, with the derivations written beside the pins. - It asserts the mid-tier floor, and checks the seed against its own rule: line amounts, order totals, approval routing, one invoice per fulfilled order, the payment roll-up, balance, status and aging bucket, and the collection calls. - It checks that each write's declared outcome agrees with the seed (the targeted rows' state, the totals, the open balance the overpayment exceeds). - It shows that a wrong seed cell and a wrong truth spec in a fixture copy go red against those pins. ## Tests `pnpm --filter @objectstack/scenarios test`: 5 files, 70 tests passed (64 before this card, 6 new). `pnpm --filter @objectstack/scenarios typecheck`: exit 0, after building the closure with `pnpm --filter '@objectstack/scenarios^...' build`. The pinned values were worked from the seed rule by a script that implements the business rule directly (it never reads the card's truth specs or calls the loader), then matched against the loader's computed truths: every pin agreed on the first run. The ranking, aging, subscription and per-persona derivations are written out by hand beside their pins. ## Acceptance notes - **File name.** The card is `order_to_cash.json`, not `order-to-cash.json` as the issue spells it. A card's `id` must be a snake_case machine name and equal the file's basename, so a hyphenated basename cannot load. The test file follows the merged sibling's spelling, `order-to-cash.test.ts`. - **No shared file touched.** The travel-expense card's PR made `test/library.test.ts` assert only the three control cards and moved per-card pins to each card's own file, so this PR edits neither `library.test.ts` nor the README. Sibling cards no longer conflict with it. - **README spelling.** `docs/qa/scenarios/README.md` "Adding a card" step 3 names the pin file `test/ID.test.ts`, while its model and this card use a hyphenated basename. An observation only; the README is unchanged here. - **No schema field for some floor items.** The approval flow, the automation, "declarative preferred" and a report as distinct from a dashboard have no field in the schema. The card states them in `prompt.detailed` (zh and en) and in `capabilities`, seeds the approval on `sales_order` (`approver`, `approved_on`) and the automation's collection calls as `activity` rows, and declares the report under `expect.dashboards`. Nothing in the loader grades them. - **Seven operation kinds, six operations.** `order_from_text` covers two of them: entry from free text with lines, and the multi-step chain (create the order, add two lines, submit it, route it to the manager). The workflow step is `approve_discount`; its progress query is the question `so_0120_waiting_on`. - **Two writes on SO-0110.** `rep_approves_own_discount` (refused) is listed before `approve_discount` (applied). A refused write changes nothing, so both outcomes hold whether a runner grades each write against the seed or applies the writes in order. - **Created rows matched by `where`.** A `where` cannot name the new order's id, so `order_from_text` locates the order by account and today's date and each line by product, quantity and discount; no seeded row matches any of them. The lines are therefore not tied to the new order in the post-state. - **Stored aging bucket.** `invoice.aging_bucket` is a formula on today in the built app; the seed stores its value as of `asOf`, and the test holds every bucket to the due date. - **Capability tags.** `approvals.dynamic-approver-routing` and `automation.time-relative-trigger` are checklist items; the other tags follow the existing cards' wording. The loader checks the area half only. - **Changeset.** `@objectstack/scenarios` is `private: true` and nothing else moves, so this PR carries `skip-changeset`, as the travel-expense card's PR did. - **Unreachable references.** The domain is designed from the card text and general business knowledge alone; objectstack-ai/cloud#3567 was not read. ## Verification <details> <summary>scenarios test + typecheck (head e44efca)</summary> ``` Test Files 5 passed (5) Tests 70 passed (70) > tsc --noEmit os-verify-lock: VERDICT command-exit 0 ``` </details> <details> <summary>dispatch-gates --commands (50 commands, local, head e44efca)</summary> All 50 derived commands exit 0 on this head (the merge of `main` at e44efca), and on the pre-merge head 5d751a9 before it. The `--ran` reconciliation reads: 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero because every line recorded its exit code. On the first run two of them exited 3 (PREREQUISITE NOT MET) and exited 0 once the prerequisite was met: - `check-plugin-teardown-shape.mjs --self-test`, after fetching its pinned positive-control commit into the shallow clone; - `check:dual-build-cjs-loads`, after a full `turbo run build` (119 of 119 tasks). After the merge of `main` (which moved `packages/spec`): `pnpm --filter @objectstack/spec build` then `check:generated` reported all 14 generated artifacts up to date. Changed lines: 1379 (+1379 / -0, 2 files), under the 3000 threshold. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4d1Exmm9uiWTdqYBhqM)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent aed9663 commit a599cc1

2 files changed

Lines changed: 1379 additions & 0 deletions

File tree

0 commit comments

Comments
 (0)