|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +import { defineFlow } from '@objectstack/spec'; |
| 4 | + |
| 5 | +/** |
| 6 | + * #3508 dogfood: one node per RECORD-BACKED approver kind. |
| 7 | + * |
| 8 | + * The designer's Approvers → Value control used to query the metadata registry |
| 9 | + * (`GET /api/v1/meta/:type`) for every kind. `user` / `team` / `department` / |
| 10 | + * `position` are DATA rows in the platform's directory objects, not registry |
| 11 | + * entries, so candidates came back empty and the control degraded to a free-text |
| 12 | + * box — the author could only hand-type a userid. `APPROVER_VALUE_BINDINGS` |
| 13 | + * now publishes the data contract per kind (`xRef.sources`), and the designer |
| 14 | + * renders a real record lookup off it. |
| 15 | + * |
| 16 | + * This flow is the authoring specimen for that surface: every kind whose control |
| 17 | + * changed gets a node, so opening it in the flow designer exercises each one. |
| 18 | + * The existing approval flows in `./index.ts` already cover `position` heavily |
| 19 | + * (it is the kind showcase actually routes on) and `./dynamic-approval.flow.ts` |
| 20 | + * covers `org_membership_level` + `expression`; the kinds below are the ones no |
| 21 | + * other flow declares. |
| 22 | + * |
| 23 | + * ## Why `draft` |
| 24 | + * |
| 25 | + * Deliberate, not an oversight. The record-backed kinds point at rows a fresh |
| 26 | + * boot does not have: `sys_user` holds only the seeded admin, `sys_team` is |
| 27 | + * empty (the seeded `Team` rows are the DEMO `showcase_team` object), and both |
| 28 | + * unit membership (`sys_business_unit_member`) and position assignment |
| 29 | + * (`sys_user_position`) are runtime admin actions by design — see the seed |
| 30 | + * notes in `src/data/seed/index.ts`. An `active` flow over them would resolve |
| 31 | + * to nobody, which is precisely the silent dead slot #3508 exists to remove. |
| 32 | + * Shipping that as a demo would re-teach the bug. `draft` keeps it out of the |
| 33 | + * runtime while still loading in the designer, which is the surface under test. |
| 34 | + * |
| 35 | + * `queue` is absent on purpose: #3536 made it non-authorable |
| 36 | + * (`NON_AUTHORABLE_APPROVER_TYPES` → `xEnumDeprecated`) because the engine has |
| 37 | + * no queue branch, so the designer no longer offers it. |
| 38 | + */ |
| 39 | +export const ApproverBindingsFlow = defineFlow({ |
| 40 | + name: 'showcase_approver_bindings', |
| 41 | + label: 'Approver Binding Specimens (#3508)', |
| 42 | + description: |
| 43 | + 'Authoring specimen: one approval node per record-backed approver kind, so the designer ' |
| 44 | + + 'renders each Value control. Draft on purpose — a fresh org has no rows for these kinds.', |
| 45 | + type: 'autolaunched', |
| 46 | + status: 'draft', |
| 47 | + nodes: [ |
| 48 | + { |
| 49 | + id: 'start', |
| 50 | + type: 'start', |
| 51 | + label: 'On Field Zoo Updated', |
| 52 | + config: { |
| 53 | + objectName: 'showcase_field_zoo', |
| 54 | + triggerType: 'record-after-update', |
| 55 | + }, |
| 56 | + }, |
| 57 | + { |
| 58 | + id: 'by_user', |
| 59 | + type: 'approval', |
| 60 | + label: 'Named user', |
| 61 | + config: { |
| 62 | + // `sys_user` row id. The most portable-hostile choice there is — an id |
| 63 | + // does not survive an environment migration and dies with the person — |
| 64 | + // which is why the designer's Type dropdown lists it LAST. |
| 65 | + approvers: [{ type: 'user', value: '' }], |
| 66 | + behavior: 'first_response', |
| 67 | + }, |
| 68 | + }, |
| 69 | + { |
| 70 | + id: 'by_team', |
| 71 | + type: 'approval', |
| 72 | + label: 'Team members', |
| 73 | + config: { |
| 74 | + // `sys_team` row id; the engine expands it through `sys_team_member`. |
| 75 | + approvers: [{ type: 'team', value: '' }], |
| 76 | + behavior: 'first_response', |
| 77 | + }, |
| 78 | + }, |
| 79 | + { |
| 80 | + id: 'by_department', |
| 81 | + type: 'approval', |
| 82 | + label: 'Department members', |
| 83 | + config: { |
| 84 | + // `sys_business_unit` row id — NOT `sys_department`. The seeded org tree |
| 85 | + // (bu_acme / bu_field_ops / bu_west_coast / …) is what this picker lists. |
| 86 | + approvers: [{ type: 'department', value: '' }], |
| 87 | + behavior: 'first_response', |
| 88 | + }, |
| 89 | + }, |
| 90 | + { |
| 91 | + id: 'by_position', |
| 92 | + type: 'approval', |
| 93 | + label: 'Position holders', |
| 94 | + config: { |
| 95 | + // Commits the position's machine NAME, not its row id: the engine routes |
| 96 | + // on `sys_user_position.position`, and a machine name is what stays |
| 97 | + // valid across environments. `manager` is a showcase position. |
| 98 | + approvers: [{ type: 'position', value: 'manager' }], |
| 99 | + behavior: 'first_response', |
| 100 | + }, |
| 101 | + }, |
| 102 | + { |
| 103 | + id: 'by_manager', |
| 104 | + type: 'approval', |
| 105 | + label: "Submitter's manager", |
| 106 | + config: { |
| 107 | + // Resolved at run time from the submitter — there is no value to author, |
| 108 | + // so the designer disables the cell instead of offering a free-text box. |
| 109 | + approvers: [{ type: 'manager' }], |
| 110 | + behavior: 'first_response', |
| 111 | + }, |
| 112 | + }, |
| 113 | + { |
| 114 | + id: 'by_field', |
| 115 | + type: 'approval', |
| 116 | + label: 'User in a trigger field', |
| 117 | + config: { |
| 118 | + // Value is a FIELD NAME on the trigger object, not a record reference — |
| 119 | + // so this one keeps the object-field selector rather than a record |
| 120 | + // lookup. `f_user` is Field Zoo's single-value `sys_user` reference. |
| 121 | + approvers: [{ type: 'field', value: 'f_user' }], |
| 122 | + behavior: 'first_response', |
| 123 | + }, |
| 124 | + }, |
| 125 | + { id: 'approved', type: 'end', label: 'Approved' }, |
| 126 | + { id: 'rejected', type: 'end', label: 'Rejected' }, |
| 127 | + ], |
| 128 | + edges: [ |
| 129 | + { id: 'e1', source: 'start', target: 'by_user' }, |
| 130 | + { id: 'e2', source: 'by_user', target: 'by_team', label: 'approve' }, |
| 131 | + { id: 'e3', source: 'by_team', target: 'by_department', label: 'approve' }, |
| 132 | + { id: 'e4', source: 'by_department', target: 'by_position', label: 'approve' }, |
| 133 | + { id: 'e5', source: 'by_position', target: 'by_manager', label: 'approve' }, |
| 134 | + { id: 'e6', source: 'by_manager', target: 'by_field', label: 'approve' }, |
| 135 | + { id: 'e7', source: 'by_field', target: 'approved', label: 'approve' }, |
| 136 | + { id: 'e8', source: 'by_user', target: 'rejected', label: 'reject' }, |
| 137 | + { id: 'e9', source: 'by_team', target: 'rejected', label: 'reject' }, |
| 138 | + { id: 'e10', source: 'by_department', target: 'rejected', label: 'reject' }, |
| 139 | + { id: 'e11', source: 'by_position', target: 'rejected', label: 'reject' }, |
| 140 | + { id: 'e12', source: 'by_manager', target: 'rejected', label: 'reject' }, |
| 141 | + { id: 'e13', source: 'by_field', target: 'rejected', label: 'reject' }, |
| 142 | + ], |
| 143 | +}); |
0 commit comments