@d19n/youfibre-odin-sdk 2.0.340 → 2.0.341

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@ export interface SendTlosNinjaCaseEmailMessage extends OdinRecordCreatedEvent<Se
23
23
  * Properties for SendTlosNinjaCaseEmail action
24
24
  *
25
25
  * @property Required fields: 1
26
- * @property Optional fields: 7
26
+ * @property Optional fields: 9
27
27
  */
28
28
  export interface SendTlosNinjaCaseEmailProperties {
29
29
  /**
@@ -85,6 +85,20 @@ export interface SendTlosNinjaCaseEmailProperties {
85
85
  * @type {TEXT}
86
86
  */
87
87
  MessageId?: string | null;
88
+ /**
89
+ * Title
90
+ *
91
+ * Used for title
92
+ * @type {TEXT}
93
+ */
94
+ Title?: string | null;
95
+ /**
96
+ * Subject
97
+ *
98
+ * Data field for Email records. Used by Communications team.
99
+ * @type {TEXT}
100
+ */
101
+ Subject?: string | null;
88
102
  }
89
103
  /**
90
104
  * SendTlosNinjaCaseEmail
@@ -355,9 +355,11 @@ export declare enum CaseProblem {
355
355
  * - `TLOS_APP` - TLOS App
356
356
  * - `TLOS_PORTAL` - TLOS Portal
357
357
  * - `TRUSTPILOT_SERVICE_REVIEW` - Trustpilot review
358
+ * - `TWITTER_X_DM` - Twitter/X DM
359
+ * - `TWITTER_X_MENTION` - Twitter/X Mention
358
360
  * - `WEB_CHAT` - Web chat
359
361
  * - `WEB_FORM_BUSINESS` - Web form - Business
360
- * - `WHATSAPP` - Whatsapp
362
+ * - ... and 1 more
361
363
  */
362
364
  export declare enum CaseChannel {
363
365
  CALL = "CALL",
@@ -371,6 +373,8 @@ export declare enum CaseChannel {
371
373
  TLOS_APP = "TLOS_APP",
372
374
  TLOS_PORTAL = "TLOS_PORTAL",
373
375
  TRUSTPILOT_REVIEW = "TRUSTPILOT_SERVICE_REVIEW",
376
+ TWITTER_X_DM = "TWITTER_X_DM",
377
+ TWITTER_X_MENTION = "TWITTER_X_MENTION",
374
378
  WEB_CHAT = "WEB_CHAT",
375
379
  WEB_FORM_BUSINESS = "WEB_FORM_BUSINESS",
376
380
  WHATSAPP = "WHATSAPP"
@@ -347,9 +347,11 @@ var CaseProblem;
347
347
  * - `TLOS_APP` - TLOS App
348
348
  * - `TLOS_PORTAL` - TLOS Portal
349
349
  * - `TRUSTPILOT_SERVICE_REVIEW` - Trustpilot review
350
+ * - `TWITTER_X_DM` - Twitter/X DM
351
+ * - `TWITTER_X_MENTION` - Twitter/X Mention
350
352
  * - `WEB_CHAT` - Web chat
351
353
  * - `WEB_FORM_BUSINESS` - Web form - Business
352
- * - `WHATSAPP` - Whatsapp
354
+ * - ... and 1 more
353
355
  */
354
356
  var CaseChannel;
355
357
  (function (CaseChannel) {
@@ -364,6 +366,8 @@ var CaseChannel;
364
366
  CaseChannel["TLOS_APP"] = "TLOS_APP";
365
367
  CaseChannel["TLOS_PORTAL"] = "TLOS_PORTAL";
366
368
  CaseChannel["TRUSTPILOT_REVIEW"] = "TRUSTPILOT_SERVICE_REVIEW";
369
+ CaseChannel["TWITTER_X_DM"] = "TWITTER_X_DM";
370
+ CaseChannel["TWITTER_X_MENTION"] = "TWITTER_X_MENTION";
367
371
  CaseChannel["WEB_CHAT"] = "WEB_CHAT";
368
372
  CaseChannel["WEB_FORM_BUSINESS"] = "WEB_FORM_BUSINESS";
369
373
  CaseChannel["WHATSAPP"] = "WHATSAPP";
@@ -130,6 +130,29 @@ exports.ORDER_PROCESS_PROJECTION = {
130
130
  {
131
131
  kind: 'related-record',
132
132
  id: 'supply-must-have-active-progressing-install',
133
+ /**
134
+ * NOT START ORDERS. A START order takes over a line that is already
135
+ * provisioned, so no engineer attends: the supplier cancels its INSTALL
136
+ * and says so with
137
+ * `NETOMNIA_INSTALL_WORK_ORDER_CANCELLED_ENGINEER_NOT_REQUIRED`. A
138
+ * Cancelled INSTALL is therefore the normal, correct state for these
139
+ * orders — they activate on the COMPLETED-OrderKci gate instead.
140
+ *
141
+ * Without this scope the dep fires BLOCKER on every healthy START order
142
+ * and the Cancelled-INSTALL clause below prescribes `cancel-parent`,
143
+ * which would deny a customer a service that is physically ready.
144
+ * Measured in production 2026-09-16: 98 of the 122 orders off-track on
145
+ * this dep were START, 58 of them with a COMPLETED confirmation.
146
+ *
147
+ * `notEq` is plain `!==` in the engine, so a NULL SupplierOrderType —
148
+ * the ordinary NEW-provide case — still matches and stays in scope.
149
+ *
150
+ * Ported from the BP, which has declared this since 2026-08-13:
151
+ * agent-os/knowledge/business-processes/order.ts:740
152
+ */
153
+ parentFilter: [
154
+ { column: 'SupplierOrderType', op: 'notEq', value: 'START' },
155
+ ],
133
156
  relation: {
134
157
  entity: 'FieldServiceModule:WorkOrder',
135
158
  filter: [
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * ORDER projection — scope of `supply-must-have-active-progressing-install`.
5
+ *
6
+ * A START order takes over a line that is already provisioned, so no engineer
7
+ * attends: the supplier cancels its INSTALL and says so with
8
+ * `NETOMNIA_INSTALL_WORK_ORDER_CANCELLED_ENGINEER_NOT_REQUIRED`. A Cancelled
9
+ * INSTALL is therefore the normal, correct state for these orders — they
10
+ * activate on the COMPLETED-OrderKci gate instead.
11
+ *
12
+ * The BP has excluded START from this rule since 2026-08-13
13
+ * (agent-os/knowledge/business-processes/order.ts:740). The projection is a
14
+ * hand-maintained copy and never received it, so the engine kept scoring healthy
15
+ * START orders BLOCKER and prescribing `cancel-parent` — which would deny a
16
+ * customer a service that is physically ready. Production 2026-09-16: 98 of the
17
+ * 122 orders off-track on this dep were START, 58 with a COMPLETED confirmation.
18
+ *
19
+ * These tests pin the scope so the drift cannot return silently.
20
+ *
21
+ * Run: npm run test:process-model
22
+ */
23
+ const order_process_projection_1 = require("./order-process.projection");
24
+ const process_validator_1 = require("./process-validator");
25
+ const DEP_ID = 'supply-must-have-active-progressing-install';
26
+ const WO_ENTITY = 'FieldServiceModule:WorkOrder';
27
+ let passed = 0;
28
+ let failed = 0;
29
+ const failures = [];
30
+ function assert(label, cond, hint) {
31
+ if (cond) {
32
+ passed += 1;
33
+ console.log(` PASS ${label}`);
34
+ }
35
+ else {
36
+ failed += 1;
37
+ const msg = ` FAIL ${label}${hint ? ' — ' + hint : ''}`;
38
+ failures.push(msg);
39
+ console.log(msg);
40
+ }
41
+ }
42
+ /** A Supply order, optionally carrying a SupplierOrderType discriminator. */
43
+ function supplyOrder(supplierOrderType) {
44
+ return {
45
+ id: 'ord-1',
46
+ recordNumber: 'OR0000001',
47
+ stage: { key: 'OrderStageSupply', name: 'Supply' },
48
+ createdAt: new Date(Date.now() - 3 * 86400000).toISOString(),
49
+ properties: supplierOrderType === undefined
50
+ ? { Provider: 'NETOMNIA' }
51
+ : { Provider: 'NETOMNIA', SupplierOrderType: supplierOrderType },
52
+ };
53
+ }
54
+ const CANCELLED_INSTALL = {
55
+ id: 'wo-1',
56
+ recordNumber: 'WO-1',
57
+ type: 'INSTALL',
58
+ stage: { key: 'WorkOrderStageCancelled', name: 'Cancelled' },
59
+ };
60
+ const SCHEDULED_INSTALL = {
61
+ id: 'wo-2',
62
+ recordNumber: 'WO-2',
63
+ type: 'INSTALL',
64
+ stage: { key: 'WorkOrderStageScheduled', name: 'Scheduled' },
65
+ };
66
+ function score(order, workOrders) {
67
+ return (0, process_validator_1.validateAgainstProcess)({
68
+ record: order,
69
+ process: order_process_projection_1.ORDER_PROCESS_PROJECTION,
70
+ relations: { [WO_ENTITY]: workOrders },
71
+ });
72
+ }
73
+ function verdictFor(report, depId) {
74
+ return report.verdicts.find((v) => v.depId === depId);
75
+ }
76
+ function run() {
77
+ var _a, _b, _c, _d, _e;
78
+ console.log(`\n${DEP_ID} — START orders are out of scope\n`);
79
+ {
80
+ const v = verdictFor(score(supplyOrder('START'), [CANCELLED_INSTALL]), DEP_ID);
81
+ assert('START + Cancelled INSTALL scores correct (parentFilter excludes it)', (v === null || v === void 0 ? void 0 : v.status) === 'correct', `got ${v === null || v === void 0 ? void 0 : v.status} / ${(_b = (_a = v === null || v === void 0 ? void 0 : v.prescribedAction) === null || _a === void 0 ? void 0 : _a.kind) !== null && _b !== void 0 ? _b : 'no action'}`);
82
+ }
83
+ {
84
+ const v = verdictFor(score(supplyOrder('NEW'), [CANCELLED_INSTALL]), DEP_ID);
85
+ assert('NEW + Cancelled INSTALL still scores variance', (v === null || v === void 0 ? void 0 : v.status) === 'variance', `got ${v === null || v === void 0 ? void 0 : v.status}`);
86
+ assert('NEW + Cancelled INSTALL still prescribes cancel-parent', ((_c = v === null || v === void 0 ? void 0 : v.prescribedAction) === null || _c === void 0 ? void 0 : _c.kind) === 'cancel-parent', `got ${(_d = v === null || v === void 0 ? void 0 : v.prescribedAction) === null || _d === void 0 ? void 0 : _d.kind}`);
87
+ }
88
+ {
89
+ // `notEq` is plain `!==`, so the ordinary NEW-provide case (no discriminator
90
+ // written) must stay fully in scope. This is the rule's blast radius.
91
+ const v = verdictFor(score(supplyOrder(undefined), [CANCELLED_INSTALL]), DEP_ID);
92
+ assert('absent SupplierOrderType stays in scope (variance)', (v === null || v === void 0 ? void 0 : v.status) === 'variance', `got ${v === null || v === void 0 ? void 0 : v.status}`);
93
+ }
94
+ {
95
+ const v = verdictFor(score(supplyOrder('START'), [SCHEDULED_INSTALL]), DEP_ID);
96
+ assert('START + Scheduled INSTALL scores correct', (v === null || v === void 0 ? void 0 : v.status) === 'correct', `got ${v === null || v === void 0 ? void 0 : v.status}`);
97
+ }
98
+ {
99
+ const v = verdictFor(score(supplyOrder('NEW'), [SCHEDULED_INSTALL]), DEP_ID);
100
+ assert('NEW + Scheduled INSTALL scores correct', (v === null || v === void 0 ? void 0 : v.status) === 'correct', `got ${v === null || v === void 0 ? void 0 : v.status}`);
101
+ }
102
+ {
103
+ // The projection must carry the BP's filter verbatim, not merely behave
104
+ // correctly for the cases above.
105
+ const supply = order_process_projection_1.ORDER_PROCESS_PROJECTION.stages.find((s) => s.key === 'OrderStageSupply');
106
+ const dep = (_e = supply === null || supply === void 0 ? void 0 : supply.dependencies) === null || _e === void 0 ? void 0 : _e.find((d) => d.id === DEP_ID);
107
+ const pf = dep && 'parentFilter' in dep ? dep.parentFilter : undefined;
108
+ assert('dep declares parentFilter SupplierOrderType notEq START', (pf === null || pf === void 0 ? void 0 : pf.length) === 1 &&
109
+ pf[0].column === 'SupplierOrderType' &&
110
+ pf[0].op === 'notEq' &&
111
+ pf[0].value === 'START', `got ${JSON.stringify(pf)}`);
112
+ }
113
+ console.log('\n────────────────────────────────────────');
114
+ console.log(`Tests: ${passed} passed, ${failed} failed`);
115
+ if (failed > 0) {
116
+ console.log('');
117
+ failures.forEach((f) => console.log(f));
118
+ process.exit(1);
119
+ }
120
+ }
121
+ run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d19n/youfibre-odin-sdk",
3
- "version": "2.0.340",
3
+ "version": "2.0.341",
4
4
  "description": "",
5
5
  "author": "@d19n",
6
6
  "license": "UNLICENSED",
@@ -42,12 +42,17 @@
42
42
  "ts-node": "^10.9.2",
43
43
  "typescript": "4.8.4"
44
44
  },
45
+ "overrides": {
46
+ "uuid": "^11.1.1",
47
+ "diff": "^4.0.4"
48
+ },
45
49
  "dependencies": {
46
50
  "@d19n/odin-sdk-generator": "^3.0.244",
47
51
  "@d19n/odin-types": "^3.6.8"
48
52
  },
49
53
  "scripts": {
50
54
  "test:wholesale-provider": "npx --yes tsx src/wholesale-provider/test/netomnia-provider.spec.ts && npx --yes tsx src/wholesale-provider/test/kci-effect-bridge.spec.ts",
55
+ "test:process-model": "npx --yes tsx src/process-model/order-process.projection.spec.ts",
51
56
  "release:beta": "echo Local publish disabled. Use GitHub Actions workflow_dispatch on .github/workflows/release.yml with target=beta && exit 1",
52
57
  "release:prod": "echo Local publish disabled. Use GitHub Actions workflow_dispatch on .github/workflows/release.yml with target=prod && exit 1"
53
58
  },