@d19n/youfibre-odin-sdk 2.0.251 → 2.0.253

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.
@@ -22,17 +22,17 @@ export interface UpdateCallAnalyticsMessage extends OdinRecordUpdatedEvent<Updat
22
22
  * Properties for UpdateCallAnalytics action
23
23
  *
24
24
  * @property Required fields: 3
25
- * @property Optional fields: 1
25
+ * @property Optional fields: 2
26
26
  */
27
27
  export interface UpdateCallAnalyticsProperties {
28
28
  /**
29
29
  * TimeInProgress
30
30
  *
31
31
  * TimeInProgress (Communications - Call)
32
- * @type {TEXT}
32
+ * @type {NUMBER}
33
33
  * @required
34
34
  */
35
- TimeInProgress: string;
35
+ TimeInProgress: number;
36
36
  /**
37
37
  * CustomerHoldTotalSec
38
38
  *
@@ -49,6 +49,13 @@ export interface UpdateCallAnalyticsProperties {
49
49
  * @required
50
50
  */
51
51
  AgentHoldTotalSec: string;
52
+ /**
53
+ * TimeInProgressFormatted
54
+ *
55
+ * TimeInProgressFormatted
56
+ * @type {TEXT}
57
+ */
58
+ TimeInProgressFormatted?: string | null;
52
59
  /**
53
60
  * Time in IVR
54
61
  *
@@ -22,7 +22,7 @@ export interface UpdateCallOnInboundCallAnsweredMessage extends OdinRecordUpdate
22
22
  * Properties for UpdateCallOnInboundCallAnswered action
23
23
  *
24
24
  * @property Required fields: 4
25
- * @property Optional fields: 4
25
+ * @property Optional fields: 5
26
26
  */
27
27
  export interface UpdateCallOnInboundCallAnsweredProperties {
28
28
  /**
@@ -53,10 +53,17 @@ export interface UpdateCallOnInboundCallAnsweredProperties {
53
53
  * QueueTime
54
54
  *
55
55
  * Seconds spent in queue (Communications - Call)
56
- * @type {TEXT}
56
+ * @type {NUMBER}
57
57
  * @required
58
58
  */
59
- QueueTime: string;
59
+ QueueTime: number;
60
+ /**
61
+ * QueueTimeFormatted
62
+ *
63
+ * QueueTimeFormatted
64
+ * @type {TEXT}
65
+ */
66
+ QueueTimeFormatted?: string | null;
60
67
  /**
61
68
  * RingingDuration
62
69
  *
@@ -22,7 +22,7 @@ export interface UpdateCallOnNumberCallStatusMessage extends OdinRecordUpdatedEv
22
22
  * Properties for UpdateCallOnNumberCallStatus action
23
23
  *
24
24
  * @property Required fields: 5
25
- * @property Optional fields: 0
25
+ * @property Optional fields: 1
26
26
  */
27
27
  export interface UpdateCallOnNumberCallStatusProperties {
28
28
  /**
@@ -61,10 +61,17 @@ export interface UpdateCallOnNumberCallStatusProperties {
61
61
  * QueueTime
62
62
  *
63
63
  * Seconds spent in queue (Communications - Call)
64
- * @type {TEXT}
64
+ * @type {NUMBER}
65
65
  * @required
66
66
  */
67
- QueueTime: string;
67
+ QueueTime: number;
68
+ /**
69
+ * QueueTimeFormatted
70
+ *
71
+ * QueueTimeFormatted
72
+ * @type {TEXT}
73
+ */
74
+ QueueTimeFormatted?: string | null;
68
75
  }
69
76
  /**
70
77
  * UpdateCallOnNumberCallStatus
@@ -224,6 +224,10 @@ export declare enum CallPropertyKeys {
224
224
  RecordingDurationFormatted = "RecordingDurationFormatted",
225
225
  /** Tracks the agent side call status. Used by Communications team. */
226
226
  AgentCallStatus = "AgentCallStatus",
227
+ /** TimeInProgressFormatted */
228
+ TimeInProgressFormatted = "TimeInProgressFormatted",
229
+ /** QueueTimeFormatted */
230
+ QueueTimeFormatted = "QueueTimeFormatted",
227
231
  /** Data field for Call records. Used by Communications team. */
228
232
  EndTime = "EndTime",
229
233
  /** RecordingDuration (Communications - Call) */
@@ -354,9 +358,9 @@ export interface CallProperties {
354
358
  WorkOrderId: string;
355
359
  /** Seconds spent in queue (Communications - Call)
356
360
  *
357
- * @type {TEXT}
361
+ * @type {NUMBER}
358
362
  */
359
- QueueTime: string;
363
+ QueueTime: number;
360
364
  /** TransferCallId (Communications - Call)
361
365
  *
362
366
  * @type {UUID}
@@ -389,9 +393,9 @@ export interface CallProperties {
389
393
  TransferCount: number;
390
394
  /** TimeInProgress (Communications - Call)
391
395
  *
392
- * @type {TEXT}
396
+ * @type {NUMBER}
393
397
  */
394
- TimeInProgress: string;
398
+ TimeInProgress: number;
395
399
  /** OutboundDialCampaignId
396
400
  *
397
401
  * @type {LOOKUP}
@@ -460,6 +464,16 @@ export interface CallProperties {
460
464
  * @type {TEXT}
461
465
  */
462
466
  AgentCallStatus: string;
467
+ /** TimeInProgressFormatted
468
+ *
469
+ * @type {TEXT}
470
+ */
471
+ TimeInProgressFormatted: string;
472
+ /** QueueTimeFormatted
473
+ *
474
+ * @type {TEXT}
475
+ */
476
+ QueueTimeFormatted: string;
463
477
  /** Data field for Call records. Used by Communications team.
464
478
  *
465
479
  * @type {DATE_TIME}
@@ -224,6 +224,10 @@ var CallPropertyKeys;
224
224
  CallPropertyKeys["RecordingDurationFormatted"] = "RecordingDurationFormatted";
225
225
  /** Tracks the agent side call status. Used by Communications team. */
226
226
  CallPropertyKeys["AgentCallStatus"] = "AgentCallStatus";
227
+ /** TimeInProgressFormatted */
228
+ CallPropertyKeys["TimeInProgressFormatted"] = "TimeInProgressFormatted";
229
+ /** QueueTimeFormatted */
230
+ CallPropertyKeys["QueueTimeFormatted"] = "QueueTimeFormatted";
227
231
  /** Data field for Call records. Used by Communications team. */
228
232
  CallPropertyKeys["EndTime"] = "EndTime";
229
233
  /** RecordingDuration (Communications - Call) */
@@ -120,6 +120,16 @@ export interface KciEffectRow {
120
120
  * matching row (with supplierCodes) over the generic fallback (no
121
121
  * supplierCodes). */
122
122
  readonly supplierCodes?: ReadonlyArray<string>;
123
+ /** Optional envelope guard — when set, the row only matches if `when(dto)`
124
+ * returns true. A second discriminator axis alongside `supplierCodes`, for
125
+ * reasonCodes whose YF effect depends on an envelope FIELD rather than a
126
+ * supplier code. Used to split the ambiguous `RESUMED` KCI: the same
127
+ * reasonCode is emitted both for a resume-from-hold (no new slot → log only)
128
+ * and for a reappointment-out-of-remediation (carries a rebooked
129
+ * `appointmentDate` → schedule the WO). The walker prefers a matching
130
+ * guarded row over the generic (no-discriminator) fallback, mirroring the
131
+ * `supplierCodes` precedence. */
132
+ readonly when?: (dto: FibreCafeKciDto) => boolean;
123
133
  readonly yfEffect: YfKciEffect;
124
134
  /** Always true per rule #4. Field is explicit so future rows that
125
135
  * document an intentional skip can mark it false. */
@@ -143,18 +143,56 @@ exports.KCI_EFFECT_ROWS = [
143
143
  },
144
144
  logAgainstOrder: true,
145
145
  },
146
+ {
147
+ // T-20260508-011: NET emits RESUMED (not REAPPOINTED) for a rebook OUT OF
148
+ // RemediationRequired, carrying the rebooked slot on the flat envelope
149
+ // (`appointmentDate` / `appointmentTimeBlock`). `appointmentDate` is only
150
+ // populated on rebooked-slot events (see FibreCafeKciDto.appointmentDate
151
+ // doc) — a plain resume-from-hold RESUMED carries no new slot — so its
152
+ // presence uniquely marks the reappointment case. Route it through the
153
+ // same bundled effect as REAPPOINTED: WO → Scheduled + set appointment
154
+ // date + stamp supplier reservation reference + write Order.CommittedDate.
155
+ // Without this, the reschedule was logged only and the YF WO stayed stuck
156
+ // in RemediationRequired while NET moved to Scheduled (WO2758769 /
157
+ // WO2760529, 2026-07-23).
158
+ id: 'provide-resumed-reappoint',
159
+ reasonCode: 'RESUMED',
160
+ serviceOrderKind: 'PROVIDE',
161
+ when: (dto) => Boolean(dto.appointmentDate),
162
+ yfEffect: {
163
+ kind: 'WO_SCHEDULE_APPOINTMENT',
164
+ scheduleAppointment: {
165
+ dateFrom: 'appointmentDate',
166
+ timeBlockFrom: 'appointmentTimeBlock',
167
+ supplierReferenceFrom: 'provideServiceOrder.appointmentReservationReference',
168
+ rescheduleReason: 'SUPPLIER_RESCHEDULED_APPOINTMENT',
169
+ },
170
+ propertyWrite: {
171
+ entityKey: 'OrderModule:Order',
172
+ properties: ['CommittedDate'],
173
+ },
174
+ notes: 'Reappointment-out-of-remediation delivered via RESUMED (not ' +
175
+ 'REAPPOINTED). Same bundled effect as REAPPOINTED: WO → Scheduled + ' +
176
+ 'appointment date + supplier reservation reference + new ' +
177
+ 'Order.CommittedDate. Guarded on appointmentDate presence so only the ' +
178
+ 'rebook variant of RESUMED is scheduled; bare RESUMED falls through to ' +
179
+ 'provide-resumed (log only).',
180
+ },
181
+ logAgainstOrder: true,
182
+ },
146
183
  {
147
184
  id: 'provide-resumed',
148
185
  reasonCode: 'RESUMED',
149
186
  serviceOrderKind: 'PROVIDE',
150
187
  yfEffect: {
151
188
  kind: 'LOG_KCI_ONLY',
152
- notes: "RESUMED is NET-side ambiguous (emitted for both Held→InProgress and " +
153
- "the invalid-in-YF Pending→InProgress). Per Frank: Pending→InProgress " +
154
- "is never a valid YF transition (Pending exits to Scheduled only). " +
155
- "RESOLVED already covers the RemediationRequired→InProgress case. " +
156
- "Log only until T-20260508-011 has NET emit REAPPOINTED for the rebook " +
157
- "case instead of RESUMED.",
189
+ notes: "Bare RESUMED (no rebooked appointmentDate) is NET-side ambiguous " +
190
+ "(emitted for both Held→InProgress and the invalid-in-YF " +
191
+ "Pending→InProgress). Per Frank: Pending→InProgress is never a valid " +
192
+ "YF transition (Pending exits to Scheduled only). RESOLVED already " +
193
+ "covers the RemediationRequired→InProgress case. The rebook variant " +
194
+ "(RESUMED carrying appointmentDate) is handled by " +
195
+ "provide-resumed-reappoint above; this row is the log-only fallback.",
158
196
  },
159
197
  logAgainstOrder: true,
160
198
  },
@@ -618,9 +656,16 @@ function decideKciDispatch(dto) {
618
656
  // walker resolves each variant to its specific WO target action.
619
657
  const candidates = exports.KCI_EFFECT_ROWS.filter((r) => r.reasonCode === dto.reasonCode && r.serviceOrderKind === kind);
620
658
  const dtoSupplierCodes = (_a = dto.supplierCodes) !== null && _a !== void 0 ? _a : [];
621
- const specificMatch = candidates.find((r) => r.supplierCodes !== undefined &&
622
- r.supplierCodes.some((sc) => dtoSupplierCodes.includes(sc)));
623
- const genericMatch = candidates.find((r) => r.supplierCodes === undefined);
659
+ // A row is eligible only if BOTH discriminators it declares pass: the
660
+ // supplierCode gate (intersects the KCI's codes) and the envelope `when`
661
+ // guard. Undeclared gates pass by default.
662
+ const eligible = candidates.filter((r) => (r.supplierCodes === undefined ||
663
+ r.supplierCodes.some((sc) => dtoSupplierCodes.includes(sc))) &&
664
+ (r.when === undefined || r.when(dto)));
665
+ // Prefer a discriminated row (carries a supplierCode or a `when` guard) over
666
+ // the fully-generic fallback for the same reasonCode.
667
+ const specificMatch = eligible.find((r) => r.supplierCodes !== undefined || r.when !== undefined);
668
+ const genericMatch = eligible.find((r) => r.supplierCodes === undefined && r.when === undefined);
624
669
  const row = specificMatch !== null && specificMatch !== void 0 ? specificMatch : genericMatch;
625
670
  if (!row) {
626
671
  return { kind: 'NO_MATCH', reasonCode: dto.reasonCode, serviceOrderKind: kind };
@@ -48,6 +48,7 @@ function makeProvideDto(reasonCode) {
48
48
  };
49
49
  }
50
50
  function run() {
51
+ var _a;
51
52
  console.log('KCI effect bridge — walker tests\n');
52
53
  // ── 1. detectKciKind correctly classifies ──────────────────────────
53
54
  console.log('Test: detectKciKind on PROVIDE');
@@ -135,10 +136,29 @@ function run() {
135
136
  assert("semanticOp=move-to-remediation", decision.semanticOp === 'move-to-remediation');
136
137
  }
137
138
  }
138
- console.log('Test: RESUMED → LOG_KCI_ONLY (NET-side ambiguous; Pending→InProgress is invalid in YF)');
139
+ console.log('Test: bare RESUMED (no appointmentDate) → LOG_KCI_ONLY (ambiguous resume-from-hold)');
139
140
  {
140
141
  const decision = (0, kci_effect_bridge_1.decideKciDispatch)(makeProvideDto('RESUMED'));
141
142
  assert('kind=LOG_KCI_ONLY', decision.kind === 'LOG_KCI_ONLY');
143
+ if (decision.kind === 'LOG_KCI_ONLY') {
144
+ assert('row=provide-resumed', decision.row.id === 'provide-resumed');
145
+ }
146
+ }
147
+ console.log('Test: RESUMED WITH appointmentDate → WO_SCHEDULE_APPOINTMENT (reappoint-out-of-remediation; T-20260508-011)');
148
+ {
149
+ const dto = makeProvideDto('RESUMED');
150
+ dto.appointmentDate = '2026-07-23';
151
+ dto.appointmentTimeBlock = 'AM';
152
+ const decision = (0, kci_effect_bridge_1.decideKciDispatch)(dto);
153
+ assert('kind=WO_SCHEDULE_APPOINTMENT', decision.kind === 'WO_SCHEDULE_APPOINTMENT');
154
+ if (decision.kind === 'WO_SCHEDULE_APPOINTMENT') {
155
+ assert('row=provide-resumed-reappoint', decision.row.id === 'provide-resumed-reappoint');
156
+ assert('dateFrom=appointmentDate', decision.dateFrom === 'appointmentDate');
157
+ assert('timeBlockFrom=appointmentTimeBlock', decision.timeBlockFrom === 'appointmentTimeBlock');
158
+ assert('rescheduleReason=SUPPLIER_RESCHEDULED_APPOINTMENT', decision.rescheduleReason === 'SUPPLIER_RESCHEDULED_APPOINTMENT');
159
+ assert('co-effect propertyWrite CommittedDate', ((_a = decision.propertyWrite) === null || _a === void 0 ? void 0 : _a.entityKey) === 'OrderModule:Order' &&
160
+ decision.propertyWrite.properties.includes('CommittedDate'));
161
+ }
142
162
  }
143
163
  console.log('Test: REAPPOINTED → WO_SCHEDULE_APPOINTMENT (reschedule: flat fields + rescheduleReason)');
144
164
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d19n/youfibre-odin-sdk",
3
- "version": "2.0.251",
3
+ "version": "2.0.253",
4
4
  "description": "",
5
5
  "author": "@d19n",
6
6
  "license": "UNLICENSED",