Repository navigation
clm_contract.department is a fixed option list — make it a lookup to the platform's sys_business_unit, as #20 ruled for our_entity - #128
Merged
Conversation
…it (kind department) The requesting department was an eight-option select: organisation structure written as schema, the shape #20's ruling C took away from our_entity. It is now a lookup to the platform's sys_business_unit filtered to active department-kind units, with the field name kept (business_unit is a platform reserved word) and still optional. The demo seeds seven departments under the US parent in the same upsert as the three signing entities, and every contract names its department by code. The intake asks for the department in the launch dialog with the other pickers, the dataset groups by the unit's name, and the legal workbench's filter reads its options from the units. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
…orkbench filter reaches the column Measured on 18.1.0: a widget's runtime filter key is resolved through a dimension of the same name first. With a dimension called `department` over `department.name`, the legal workbench's department filter, which applies the business unit's id, compiled to `department.name = <id>` and every widget read zero. Named like `contract_type_name`, the dimension still groups by the department's name, and the filter reaches the `department` column. Also states on the field that the picker is the only check on a unit's kind. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
Contributor
Author
席位核对 — ACCEPT · head
|
| check | reading |
|---|---|
| shape | draft → main, Fixes #109; assignee zhuangjianguo; mergeable clean against main ae13f57 |
| CI on the head | both Validate check-runs success on f65f9be = PR head.sha |
| scope | 22 files (+225/−122), all in the claim's surface: department becomes Field.lookup('sys_business_unit') with kind = department / active lookupFilters, optional; seven departments seeded under ARC-US by code; the picker moves from the core screen to the launch dialog; contract_metrics groups by department_name over department.name; the legal workbench filter reads its options from sys_business_unit; option entries leave both bundles. Nothing governed |
| premises | ① 18.1.0's sys_business_unit.kind has department — holds; ② consumers enumerated (seat grep on the head: page, view, dashboard, dataset, flow, actions, seed — all re-pointed); ③ §03 wording goes to the seat's governed PR #120 (pushed as 952a079) |
| measured | seed 823 → 830 (+7 departments), boot signatures identical; dataset counts and names unchanged in en and zh-CN; as a requester persona the picker offers exactly the 7 active departments and the record page names the choice; F12 renewal carries the department |
| seat calls on the open questions | write-side kind check → B, as its own card (refuse a non-department kind, do not judge active, so a retired department never blocks a renewal); a 管理 nav row → no, Setup → Business Units is the home; non-demo old rows → no remap, as #118 did |
| not in this PR | a dashboard global filter on a key that is also a dimension name is lowered to that dimension (department.name = <id>), against the spec's documented resolution against the dataset object — the seat files it upstream; README's demo row count (now 830) → the #95 remainder |
Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).
Generated by Claude Code
zhuangjianguo
marked this pull request as ready for review
October 25, 2026 15:23
This was referenced Oct 25, 2026
Draft
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 #109
clm_contract.departmentwas an eight-optionField.select(sales · procurement · legal · finance · operations · people · it · other). That is organisation structure written as schema, the shape #20's ruling C (comment 6396457129, step 5) took away fromour_entity. This PR makes it a lookup to the platform'ssys_business_unit, filtered to active department-kind units. The field name staysdepartment, becausebusiness_unitis a platform reserved word. The departments are seeded as data, the same way #118 seeded the signing entities.Premises re-checked first (card's three)
@objectstack/platform-objects18.1.0sys_business_unitdeclareskindas a required select with the optionscompany · division · department · office · cost_center, defaultdepartment. So no app-side object is needed, and none was added.departmentover the tree) and re-pointed. See the table below. The §13 Q2 note is kept on the field: department-level sharing is a customer overlay.src/sharing/**has no reference todepartment.What changed
The field (
src/objects/contract.object.ts).department: Field.lookup('sys_business_unit', …)withlookupFilterskind eq departmentandactive eq true. It uses theeqspelling thatour_entityuses;lookupFilters[].operatoris an enum in whichequalsis refused. The description now points to Setup → Business Units.Optional, as it was. No
requiredand nostorage.notNull, for three reasons:our_entityis required because a contract cannot be signed without a signing entity. The requesting department is reporting data.The seed (
src/data/business-unit.seed.ts,plan.ts,strings.ts, both locale bundles):code) also writes seven departments ofkind: department. The companies come first, so a department's parent resolves.ARC-SALES·ARC-PROC·ARC-LEGAL·ARC-FIN·ARC-OPS·ARC-PEOPLE·ARC-IT. Names in en: Sales · Procurement · Legal · Finance · Operations · People & HR · IT. Names in zh: 销售部 · 采购部 · 法务部 · 财务部 · 运营部 · 人力资源部 · 信息技术部.ARC-US. A requesting department is a group function, and it is a different axis from the entity that signs. Entities follow governing law; departments follow contract type. If Operations sat under the EU subsidiary, a New York lease would be requested by a European department.otheroption has no row. "Other" is not a unit of the organisation, and a contract without a department leaves the field empty.ContractTypePlan.departmentnow holds a department code, and each contract names its department by code. The type-to-department mapping did not change, and neither did any count.Every consumer:
contract-intake.flow.ts(F1)screen_core. It is collected in the launch dialog with the other three pickers. Thedepartmentvariable stays an input. The header now names four pickers.contract.actions.ts(launch_contract)departmentchanged from a hidden headless text param to a field-backed picker param,required: false, withhelpText.contract-backfill.actions.ts(executed_upload){ field: 'department' }), so it inherits the lookup and the filter. Only the comment changed.executed-upload.flow.ts,renewal-notice.flow.ts(F12, and theRENEWAL_DRAFT_FIELDSthat F13 shares),expiration-sweep.flow.tscontract.view.ts,contract_detail.page.tscontract.dataset.tsincludegainsdepartment. The dimension is nowdepartment_nameoverdepartment.name. The rename is explained in Acceptance notes.legal.dashboard.tstype: 'lookup'withoptionsFromsys_business_unit(id/name,kind = department). The eight static options are gone. Retired departments stay in its list, because their contracts are still on the board.clm.app.tsoptionsblocks offields.departmentare removed. Thescreen_core.fields.departmententries are removed. Thelaunch_contractparam getshelpText. The dataset dimension key is renamed todepartment_name. The help text names Setup → Business Units.src/sharing/**,src/mappings/**,src/skills/**, other datasets and dashboardsdepartment.Evidence: real boots on 18.1.0, clean
.objectstack/dataeachSeed counts (
pnpm demo):ae13f57:{"inserted":823,"updated":0,"skipped":0,"errored":404,"referencesDropped":0}{"inserted":830,"updated":0,"skipped":0,"errored":404,"referencesDropped":0}The +7 rows are the seven departments. The 404 errors are the same unresolved persona names on both sides. The de-duplicated WARN/ERROR/⚠/✗ signatures (timestamps, ids and counters stripped) are identical: 247 lines on each side,
diffexit 0.degraded,no such table,failed to loadandFailed to expandeach match 0 lines in both logs.pnpm demo:zhon a clean database: inserted 830, errored 404. It writes the seven 部 names.Readback (admin,
GET /api/v1/data/sys_business_unitandclm_contract?top=500&populate=department):parent = ARC-US,active: trueandorganization_idstamped.Datasets render names, not ids.
POST /api/v1/analytics/dataset/queryoncontract_metrics, grouped by department:department): Legal 34 · Operations 6 · People / HR 6 · Procurement 18 · Sales 56.department_name): Legal 34 · Operations 6 · People & HR 6 · Procurement 18 · Sales 56. The SQL isSELECT "department"."name" … LEFT JOIN "sys_business_unit" "department" ON "clm_contract"."department" = "department"."id".A real requester persona, created the README way.
POST /api/v1/auth/admin/create-userBusiness Requester 1→ 200 (mustChangePassword: true). Then, signed in as the persona,POST /api/v1/auth/change-password→ 200./opt/pw-browsers/chromium). The blocks after them say admin where they were admin.Launch dialog (sidebar Launch a Contract):
Contract type*,Counterparty,Our signing entity*,Requesting department. The help text reads "The department launching this contract."GET /api/v1/data/sys_business_unit?populate=parent_business_unit_id,manager_user_id&top=50&filter=["and",["kind","=","department"],["active","=",true]]→ 200.Launch with a department picked:
Readback as the requester:
NDA-2026-0021draft, owner Business Requester 1,department→{code: ARC-LEGAL, name: Legal, kind: department}, entity ARC-EU.Record page as the requester:
REQUESTING DEPARTMENT Legal.SUP-2026-0012) readsREQUESTING DEPARTMENT Procurementin en and发起部门 Procurementin zh-CN.Contract form, Edit, as the requester:
POST /api/v1/batch→ 200 and the toast "Contract updated successfully".Browser console on every page: only the pre-login
get-session401 and one resource 404, plus the three pre-existingrecord.statuspredicate warnings on the record header. No new error.Contract Register (admin): the "Requesting Department" column reads e.g.
AMD-2026-0001 | Legal.Legal workbench filter (admin,
/dashboard/legal_workbench):Requesting Department: All.POST /api/v1/analytics/dataset/querywith the inline dataset{object: "sys_business_unit", dimensions: id, name}and the runtime filter{"kind":"department"}. They are All · Probe Retired Department · Procurement · Finance · Sales · Legal · Operations · IT · People & HR. No office and no company.runtimeFilter {"$and":[{"status":"submitted"},{"department":"…ARC-LEGAL id…"}]}. The board then reads Awaiting Intake 1 · In Review 3 · Approved This Month 3, which matches the Legal status counts (Submitted 1 · In Review 3 · Approved 3).F12 renewal. As admin,
POST /api/v1/actions/clm_contract/start_renewalonLSE-2026-0002(department ARC-OPS) → 200. The new draft "Renewal — Lease Agreement — Corvus Security Services" carriesdepartmentARC-OPS Operations.Write side, no rule (measured, admin):
PATCH clm_contract/ID {department: ARC-EU company id}→ 200, and the company is stored (restored afterwards).PATCH {department: "sales"}→ 400VALIDATION_FAILED,fields[0] = {field: "department", code: "reference_not_found"}, "no sys_business_unit record has id "sales"".So a dangling id is already refused by the engine, and a unit of the wrong kind is not. See Acceptance notes.
Existing databases (select string → lookup id)
These were measured on the database the
ae13f57boot left behind, which holds"department":"sales"and similar strings. The new code was then run against it.os migrate planreports no change onclm_contract. With the new metadata it gives0 change(s): 0 safe, 0 needs-confirm, 0 destructive. The control, the same command withsrcatae13f57on the same database, gives the identical plan. Both list the same 7 platform tables to create (sys_automation_run…), which are unrelated to this change. The column is text on both sides, so schema drift sees nothing.pnpm dev, no seed) emits nothing about it. Old rows read back raw:GET …?populate=department→"department":"sales". The dataset groups all 120 undernull. An edit that leavesdepartmentalone still saves (PATCH {summary}→ 200).pnpm demoon that database gave{"inserted":7,"updated":120,"skipped":703,…}. The upsert re-asserts every contract'sdepartmentby code, and the dataset reads Legal 34 · Operations 6 · People & HR 6 · Procurement 18 · Sales 56 again.NOT MEASURED
RENEWAL_DRAFT_FIELDScopy as the F12 path measured above.execute_contractcapability (records persona), and I did not attempt it. Itsdepartmentparam was already field-backed and is unchanged.launch_contractdoor with a department. I did not run it. The param now goes through the field-backed declaration.clm_requester, which grants read onsys_business_unit(A requester cannot launch a contract — clm_requester has no read on sys_business_unit, and our_entity is required #126).Gates (head
f65f9be, each exit captured to a file before any pipe)Acceptance notes
clm_contractrow, replacedepartment select(种子)withdepartment lookup sys_business_unit(发起部门,kind = department,可选,种子). The §04 sentence that callsdepartment"合同上的冗余标量" stays true, and so does §13 Q2.department_name(measured on 18.1.0). A widget'sruntimeFilterkey is resolved through a dimension of the same name first.departmentoverdepartment.name, the workbench filter's id compiled toWHERE "department"."name" = $1, and every widget read 0.WHERE department = $1.GlobalFilterSchema.fielddocuments the key as resolving "against the object behind each bound widget's dataset (dataset.object), not against that dataset's declareddimensions". So this is reported to the seat as a possible spec/runtime seam.contract_type_nameprecedent. No widget selected the olddepartmentdimension.active, and that would refuse an F12/F13 renewal of a contract whose department has been retired since. For an optional reporting field that is a product decision. The report onclm_contract.departmentis a fixed option list — make it a lookup to the platform'ssys_business_unit, as #20 ruled forour_entity#109 gives the options.pnpm demoloads 820 rows. It has been 823 since our_entity is a lookup to the platform's sys_business_unit (kind company), seeded as data #118 and is 830 now. The README is outside this card's claimed surface unless it names the option list, so it is left for the seat.PROBE-RETIRED,PROBE-OFFICE) and the contracts launched and renewed were written to throwaway local databases only.🤖 Generated with Claude Code
https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn