@d19n/youfibre-odin-sdk 2.0.156 → 2.0.158
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.
- package/dist/wholesale-provider/change-reason-classifications.d.ts +73 -0
- package/dist/wholesale-provider/change-reason-classifications.js +178 -0
- package/dist/wholesale-provider/index.d.ts +3 -0
- package/dist/wholesale-provider/index.js +20 -1
- package/dist/wholesale-provider/install-stage-kci-spec.d.ts +26 -0
- package/dist/wholesale-provider/install-stage-kci-spec.js +102 -0
- package/dist/wholesale-provider/kci-effect-bridge.d.ts +21 -0
- package/dist/wholesale-provider/kci-effect-bridge.js +61 -4
- package/dist/wholesale-provider/test/kci-effect-bridge.spec.js +30 -1
- package/dist/wholesale-provider/types/fibrecafe.d.ts +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChangeReason enum classifications — the single source of truth for how
|
|
3
|
+
* the Install Fulfilment digest (and any future audit / detector) interprets
|
|
4
|
+
* `ChangeReasonRescheduleReason` and `ChangeReasonCancelReason` values.
|
|
5
|
+
*
|
|
6
|
+
* Why this lives in the SDK:
|
|
7
|
+
* The values in `entities-v2/ChangeReason.ts` are flat enums — they don't
|
|
8
|
+
* tell a consumer "which of these are tenant-side resolves vs supplier-side
|
|
9
|
+
* escalations vs data hygiene". Without a classification anchor, every
|
|
10
|
+
* downstream consumer (alerts, dashboards, remediation scripts) ends up
|
|
11
|
+
* hand-coding its own predicate.
|
|
12
|
+
*
|
|
13
|
+
* The authoring origin is the agent-os BP knowledge file
|
|
14
|
+
* `business-processes/change-reason-classifications.ts`; this is its
|
|
15
|
+
* published projection so both the agent-os tooling AND deployed services
|
|
16
|
+
* (youfibre-api-connect) consume one source. Keep the two in lockstep.
|
|
17
|
+
*
|
|
18
|
+
* String literals (not the SDK enum) are used so the sets stay decoupled
|
|
19
|
+
* from enum-shape churn; the `unclassified*` helpers below surface drift
|
|
20
|
+
* when a consumer passes `Object.values(ChangeReason*Reason)`.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Billing-blocked reschedules — YF moved the WO because the customer has no
|
|
24
|
+
* active direct-debit mandate. YF does NOT dispatch `amendOrder` to the
|
|
25
|
+
* supplier (held off until billing is set up), so these will NEVER receive
|
|
26
|
+
* an `AMENDED` KCI back. Action lever: chase the customer for the mandate.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BILLING_BLOCKED_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
29
|
+
/** Alias kept for backward compatibility — prefer the specific category above. */
|
|
30
|
+
export declare const TENANT_PENDING_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Customer follow-up reschedules — customer unavailable / unaware, so YF
|
|
33
|
+
* moved the appointment and must chase the customer to rebook.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Misselling-signal reschedules — customer indicates they don't need the
|
|
38
|
+
* service (likely a sales-time misselling artifact).
|
|
39
|
+
*/
|
|
40
|
+
export declare const MISSELLING_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
41
|
+
/** Reschedules whose root cause is a supplier-side issue. */
|
|
42
|
+
export declare const SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
43
|
+
/** Intra-today shuffle — `previousDate = today AND currentDate = today`. */
|
|
44
|
+
export declare const INTRA_TODAY_RESCHEDULE_REASONS: ReadonlySet<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Recontract side-effect cancellations — applying an existing-subscriber
|
|
47
|
+
* recontract cancels the PREVIOUS Order with `CancellationReason =
|
|
48
|
+
* CONTRACT_RENEWAL`. The install succeeded; the previous Order is retired.
|
|
49
|
+
* MUST be excluded from cancellation counts.
|
|
50
|
+
*/
|
|
51
|
+
export declare const RECONTRACT_SIDE_EFFECT_CANCEL_REASONS: ReadonlySet<string>;
|
|
52
|
+
/** Data-hygiene cancellations — excluded from "failed installs". */
|
|
53
|
+
export declare const DATA_HYGIENE_CANCEL_REASONS: ReadonlySet<string>;
|
|
54
|
+
/** Customer churn / decision cancellations — real fulfilment loss. */
|
|
55
|
+
export declare const CHURN_CANCEL_REASONS: ReadonlySet<string>;
|
|
56
|
+
/** Supplier-side cause cancellations — install blocked by supplier/network. */
|
|
57
|
+
export declare const SUPPLIER_SIDE_CAUSE_CANCEL_REASONS: ReadonlySet<string>;
|
|
58
|
+
/** Customer-side cause cancellations — premises / billing / engagement. */
|
|
59
|
+
export declare const CUSTOMER_SIDE_CAUSE_CANCEL_REASONS: ReadonlySet<string>;
|
|
60
|
+
/**
|
|
61
|
+
* A cancellation counts as a fulfilment failure iff its CancellationReason
|
|
62
|
+
* is NOT a recontract side-effect AND NOT a data-hygiene cleanup. Customer-
|
|
63
|
+
* and supplier-cause cancellations DO count — the customer didn't get
|
|
64
|
+
* serviced today. Unknown reason → conservative (counts as failure).
|
|
65
|
+
*/
|
|
66
|
+
export declare function isFulfilmentFailureCancel(cancelReason: string | undefined): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Returns enum values from `allKnownValues` not classified into any
|
|
69
|
+
* operational set. Callers pass `Object.values(ChangeReasonRescheduleReason)`
|
|
70
|
+
* so this stays free of a hard enum import while still enabling drift checks.
|
|
71
|
+
*/
|
|
72
|
+
export declare function unclassifiedRescheduleReasons(allKnownValues: readonly string[]): string[];
|
|
73
|
+
export declare function unclassifiedCancelReasons(allKnownValues: readonly string[]): string[];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ChangeReason enum classifications — the single source of truth for how
|
|
4
|
+
* the Install Fulfilment digest (and any future audit / detector) interprets
|
|
5
|
+
* `ChangeReasonRescheduleReason` and `ChangeReasonCancelReason` values.
|
|
6
|
+
*
|
|
7
|
+
* Why this lives in the SDK:
|
|
8
|
+
* The values in `entities-v2/ChangeReason.ts` are flat enums — they don't
|
|
9
|
+
* tell a consumer "which of these are tenant-side resolves vs supplier-side
|
|
10
|
+
* escalations vs data hygiene". Without a classification anchor, every
|
|
11
|
+
* downstream consumer (alerts, dashboards, remediation scripts) ends up
|
|
12
|
+
* hand-coding its own predicate.
|
|
13
|
+
*
|
|
14
|
+
* The authoring origin is the agent-os BP knowledge file
|
|
15
|
+
* `business-processes/change-reason-classifications.ts`; this is its
|
|
16
|
+
* published projection so both the agent-os tooling AND deployed services
|
|
17
|
+
* (youfibre-api-connect) consume one source. Keep the two in lockstep.
|
|
18
|
+
*
|
|
19
|
+
* String literals (not the SDK enum) are used so the sets stay decoupled
|
|
20
|
+
* from enum-shape churn; the `unclassified*` helpers below surface drift
|
|
21
|
+
* when a consumer passes `Object.values(ChangeReason*Reason)`.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.unclassifiedCancelReasons = exports.unclassifiedRescheduleReasons = exports.isFulfilmentFailureCancel = exports.CUSTOMER_SIDE_CAUSE_CANCEL_REASONS = exports.SUPPLIER_SIDE_CAUSE_CANCEL_REASONS = exports.CHURN_CANCEL_REASONS = exports.DATA_HYGIENE_CANCEL_REASONS = exports.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS = exports.INTRA_TODAY_RESCHEDULE_REASONS = exports.SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS = exports.MISSELLING_RESCHEDULE_REASONS = exports.CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS = exports.TENANT_PENDING_RESCHEDULE_REASONS = exports.BILLING_BLOCKED_RESCHEDULE_REASONS = void 0;
|
|
25
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
26
|
+
// RescheduleReason classification
|
|
27
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Billing-blocked reschedules — YF moved the WO because the customer has no
|
|
30
|
+
* active direct-debit mandate. YF does NOT dispatch `amendOrder` to the
|
|
31
|
+
* supplier (held off until billing is set up), so these will NEVER receive
|
|
32
|
+
* an `AMENDED` KCI back. Action lever: chase the customer for the mandate.
|
|
33
|
+
*/
|
|
34
|
+
exports.BILLING_BLOCKED_RESCHEDULE_REASONS = new Set([
|
|
35
|
+
'MISSING_MANDATE',
|
|
36
|
+
]);
|
|
37
|
+
/** Alias kept for backward compatibility — prefer the specific category above. */
|
|
38
|
+
exports.TENANT_PENDING_RESCHEDULE_REASONS = exports.BILLING_BLOCKED_RESCHEDULE_REASONS;
|
|
39
|
+
/**
|
|
40
|
+
* Customer follow-up reschedules — customer unavailable / unaware, so YF
|
|
41
|
+
* moved the appointment and must chase the customer to rebook.
|
|
42
|
+
*/
|
|
43
|
+
exports.CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS = new Set([
|
|
44
|
+
'PROPERTY_UNOCCUPIED',
|
|
45
|
+
'UNAWARE_OF_APPOINTMENT',
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Misselling-signal reschedules — customer indicates they don't need the
|
|
49
|
+
* service (likely a sales-time misselling artifact).
|
|
50
|
+
*/
|
|
51
|
+
exports.MISSELLING_RESCHEDULE_REASONS = new Set([
|
|
52
|
+
'NO_LONGER_REQUIRED',
|
|
53
|
+
'CANCELLATION_FOR_PERSONAL_REASONS',
|
|
54
|
+
]);
|
|
55
|
+
/** Reschedules whose root cause is a supplier-side issue. */
|
|
56
|
+
exports.SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS = new Set([
|
|
57
|
+
'AREA_NOT_RFS_SUPPLIER',
|
|
58
|
+
'ADDITIONAL_WORKS_REQUIRED_APPROVAL_NEEDED',
|
|
59
|
+
'SCHEDULE_CHANGED',
|
|
60
|
+
'SCHEDULE_CONTRACTOR_CHANGED',
|
|
61
|
+
'ENGINEER_DID_NOT_ATTEND',
|
|
62
|
+
'ENGINEER_UNABLE_TO_RESOLVE',
|
|
63
|
+
'ADVERSE_WEATHER_CONDITIONS',
|
|
64
|
+
'UNABLE_TO_ATTEND', // supplier-set slot the customer couldn't make
|
|
65
|
+
]);
|
|
66
|
+
/** Intra-today shuffle — `previousDate = today AND currentDate = today`. */
|
|
67
|
+
exports.INTRA_TODAY_RESCHEDULE_REASONS = new Set([
|
|
68
|
+
'TIME_BLOCK_CHANGED',
|
|
69
|
+
]);
|
|
70
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
71
|
+
// CancelReason classification
|
|
72
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Recontract side-effect cancellations — applying an existing-subscriber
|
|
75
|
+
* recontract cancels the PREVIOUS Order with `CancellationReason =
|
|
76
|
+
* CONTRACT_RENEWAL`. The install succeeded; the previous Order is retired.
|
|
77
|
+
* MUST be excluded from cancellation counts.
|
|
78
|
+
*/
|
|
79
|
+
exports.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS = new Set([
|
|
80
|
+
'CONTRACT_RENEWAL',
|
|
81
|
+
'CONTRACT_RENEWAL_ROLLBACK',
|
|
82
|
+
]);
|
|
83
|
+
/** Data-hygiene cancellations — excluded from "failed installs". */
|
|
84
|
+
exports.DATA_HYGIENE_CANCEL_REASONS = new Set([
|
|
85
|
+
'DUPLICATE_ORDER_OR_WORK_ORDER',
|
|
86
|
+
'REMEDIATION_RAISED_IN_ERROR',
|
|
87
|
+
'PREPULL_CANCELLATION_RAISED_IN_ERROR',
|
|
88
|
+
'REMEDIATION_NOT_REQUIRED',
|
|
89
|
+
'SERVICE_VISIT_NO_LONGER_REQUIRED',
|
|
90
|
+
'PTF_PROCESS_NOT_FOLLOWED',
|
|
91
|
+
'CHANGED_ASSIGNED_CONTRACTOR',
|
|
92
|
+
'PREPULL_CANCELLATION_ACCEPTING_INST_RISK',
|
|
93
|
+
]);
|
|
94
|
+
/** Customer churn / decision cancellations — real fulfilment loss. */
|
|
95
|
+
exports.CHURN_CANCEL_REASONS = new Set([
|
|
96
|
+
'CUSTOMER_CANCELLED',
|
|
97
|
+
'STAYING_WITH_EXISTING_PROVIDER',
|
|
98
|
+
'NO_LONGER_INTERESTED_IN_SERVICE',
|
|
99
|
+
'MULTIPLE_RESCHEDULE_NO_LONGER_WANTS_SERVICE',
|
|
100
|
+
'MOVING_PREMISES',
|
|
101
|
+
'CLAIMING_MISSELING',
|
|
102
|
+
'STILL_IN_CONTRACT',
|
|
103
|
+
'CANCELLATION_FOR_PERSONAL_REASONS',
|
|
104
|
+
]);
|
|
105
|
+
/** Supplier-side cause cancellations — install blocked by supplier/network. */
|
|
106
|
+
exports.SUPPLIER_SIDE_CAUSE_CANCEL_REASONS = new Set([
|
|
107
|
+
'AREA_NOT_RFS_SUPPLIER',
|
|
108
|
+
'MDU_NOT_RFS',
|
|
109
|
+
'CONNECTION_COSTS_UNVIABLE',
|
|
110
|
+
'NO_CLEAR_INSTALLATION_ROUTE',
|
|
111
|
+
'UNABLE_TO_GET_PTD_WAYLEAVE',
|
|
112
|
+
'RFS_QUARANTINED',
|
|
113
|
+
'RFS_REVERSED',
|
|
114
|
+
'ENGINEER_DID_NOT_ATTEND',
|
|
115
|
+
'ADDITIONAL_WORKS_REQUIRED_APPROVAL_NEEDED',
|
|
116
|
+
'ADVERSE_WEATHER_CONDITIONS',
|
|
117
|
+
]);
|
|
118
|
+
/** Customer-side cause cancellations — premises / billing / engagement. */
|
|
119
|
+
exports.CUSTOMER_SIDE_CAUSE_CANCEL_REASONS = new Set([
|
|
120
|
+
'MISSING_MANDATE',
|
|
121
|
+
'HEALTH_AND_SAFETY_CONCERN_CUSTOMER_PREMISE',
|
|
122
|
+
'H_S_CONCERN_CUSTOMER_PREMISE',
|
|
123
|
+
'UNHAPPY_WITH_EXTERNAL_INSTALLATION_ROUTE',
|
|
124
|
+
'UNHAPPY_WITH_INTERNAL_INSTALLATION_ROUTE',
|
|
125
|
+
'INCORRECT_ADDRESS_BOOKED',
|
|
126
|
+
'UNABLE_TO_ATTEND',
|
|
127
|
+
'CUSTOMER_REFUSED_REMEDIATION_WORK',
|
|
128
|
+
'REMEDIATION_REQUIRED_CUSTOMER_UNWILLING_TO_WAIT',
|
|
129
|
+
]);
|
|
130
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
131
|
+
// Convenience predicate
|
|
132
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* A cancellation counts as a fulfilment failure iff its CancellationReason
|
|
135
|
+
* is NOT a recontract side-effect AND NOT a data-hygiene cleanup. Customer-
|
|
136
|
+
* and supplier-cause cancellations DO count — the customer didn't get
|
|
137
|
+
* serviced today. Unknown reason → conservative (counts as failure).
|
|
138
|
+
*/
|
|
139
|
+
function isFulfilmentFailureCancel(cancelReason) {
|
|
140
|
+
if (!cancelReason)
|
|
141
|
+
return true;
|
|
142
|
+
if (exports.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS.has(cancelReason))
|
|
143
|
+
return false;
|
|
144
|
+
if (exports.DATA_HYGIENE_CANCEL_REASONS.has(cancelReason))
|
|
145
|
+
return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
exports.isFulfilmentFailureCancel = isFulfilmentFailureCancel;
|
|
149
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
150
|
+
// Coverage helpers — surface drift between the SDK enum and these sets
|
|
151
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
152
|
+
/**
|
|
153
|
+
* Returns enum values from `allKnownValues` not classified into any
|
|
154
|
+
* operational set. Callers pass `Object.values(ChangeReasonRescheduleReason)`
|
|
155
|
+
* so this stays free of a hard enum import while still enabling drift checks.
|
|
156
|
+
*/
|
|
157
|
+
function unclassifiedRescheduleReasons(allKnownValues) {
|
|
158
|
+
const classified = new Set([
|
|
159
|
+
...exports.TENANT_PENDING_RESCHEDULE_REASONS,
|
|
160
|
+
...exports.CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS,
|
|
161
|
+
...exports.MISSELLING_RESCHEDULE_REASONS,
|
|
162
|
+
...exports.SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS,
|
|
163
|
+
...exports.INTRA_TODAY_RESCHEDULE_REASONS,
|
|
164
|
+
]);
|
|
165
|
+
return allKnownValues.filter((v) => !classified.has(v));
|
|
166
|
+
}
|
|
167
|
+
exports.unclassifiedRescheduleReasons = unclassifiedRescheduleReasons;
|
|
168
|
+
function unclassifiedCancelReasons(allKnownValues) {
|
|
169
|
+
const classified = new Set([
|
|
170
|
+
...exports.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS,
|
|
171
|
+
...exports.DATA_HYGIENE_CANCEL_REASONS,
|
|
172
|
+
...exports.CHURN_CANCEL_REASONS,
|
|
173
|
+
...exports.SUPPLIER_SIDE_CAUSE_CANCEL_REASONS,
|
|
174
|
+
...exports.CUSTOMER_SIDE_CAUSE_CANCEL_REASONS,
|
|
175
|
+
]);
|
|
176
|
+
return allKnownValues.filter((v) => !classified.has(v));
|
|
177
|
+
}
|
|
178
|
+
exports.unclassifiedCancelReasons = unclassifiedCancelReasons;
|
|
@@ -31,5 +31,8 @@ export { NetomniaProvider, AggregateNotificationError } from './providers/netomn
|
|
|
31
31
|
export type { NetomniaProviderDeps } from './providers/netomnia-provider';
|
|
32
32
|
export { KCI_EFFECT_ROWS, decideKciDispatch, detectKciKind, } from './kci-effect-bridge';
|
|
33
33
|
export type { KciServiceOrderKind, YfKciSemanticOp, YfKciEffectKind, YfKciTargetEntity, YfKciEffect, KciEffectRow, KciDispatchDecision, } from './kci-effect-bridge';
|
|
34
|
+
export { INSTALL_STAGE_KCI_SOURCES, buildInstallStageReasonCodeSpecs, } from './install-stage-kci-spec';
|
|
35
|
+
export type { InstallStageKciSource, InstallStageReasonCodeSpec, } from './install-stage-kci-spec';
|
|
36
|
+
export { BILLING_BLOCKED_RESCHEDULE_REASONS, TENANT_PENDING_RESCHEDULE_REASONS, CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS, MISSELLING_RESCHEDULE_REASONS, SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS, INTRA_TODAY_RESCHEDULE_REASONS, RECONTRACT_SIDE_EFFECT_CANCEL_REASONS, DATA_HYGIENE_CANCEL_REASONS, CHURN_CANCEL_REASONS, SUPPLIER_SIDE_CAUSE_CANCEL_REASONS, CUSTOMER_SIDE_CAUSE_CANCEL_REASONS, isFulfilmentFailureCancel, unclassifiedRescheduleReasons, unclassifiedCancelReasons, } from './change-reason-classifications';
|
|
34
37
|
export type { VMO2OrderId, VMO2RequestId, VMO2AppointmentKey, VMO2PremiseId, VMO2OrderNotificationType, VMO2SMRNotificationType, VMO2NotificationEnvelope, VMO2OrderNotification, VMO2SMRNotification, VMO2InboundNotification, } from './types/vmo2';
|
|
35
38
|
export * from './types/fibrecafe';
|
|
@@ -41,7 +41,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
41
41
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
42
42
|
};
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.detectKciKind = exports.decideKciDispatch = exports.KCI_EFFECT_ROWS = exports.AggregateNotificationError = exports.NetomniaProvider = void 0;
|
|
44
|
+
exports.unclassifiedCancelReasons = exports.unclassifiedRescheduleReasons = exports.isFulfilmentFailureCancel = exports.CUSTOMER_SIDE_CAUSE_CANCEL_REASONS = exports.SUPPLIER_SIDE_CAUSE_CANCEL_REASONS = exports.CHURN_CANCEL_REASONS = exports.DATA_HYGIENE_CANCEL_REASONS = exports.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS = exports.INTRA_TODAY_RESCHEDULE_REASONS = exports.SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS = exports.MISSELLING_RESCHEDULE_REASONS = exports.CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS = exports.TENANT_PENDING_RESCHEDULE_REASONS = exports.BILLING_BLOCKED_RESCHEDULE_REASONS = exports.buildInstallStageReasonCodeSpecs = exports.INSTALL_STAGE_KCI_SOURCES = exports.detectKciKind = exports.decideKciDispatch = exports.KCI_EFFECT_ROWS = exports.AggregateNotificationError = exports.NetomniaProvider = void 0;
|
|
45
45
|
// ── Provider implementation ───────────────────────────────────────────
|
|
46
46
|
var netomnia_provider_1 = require("./providers/netomnia-provider");
|
|
47
47
|
Object.defineProperty(exports, "NetomniaProvider", { enumerable: true, get: function () { return netomnia_provider_1.NetomniaProvider; } });
|
|
@@ -51,6 +51,25 @@ var kci_effect_bridge_1 = require("./kci-effect-bridge");
|
|
|
51
51
|
Object.defineProperty(exports, "KCI_EFFECT_ROWS", { enumerable: true, get: function () { return kci_effect_bridge_1.KCI_EFFECT_ROWS; } });
|
|
52
52
|
Object.defineProperty(exports, "decideKciDispatch", { enumerable: true, get: function () { return kci_effect_bridge_1.decideKciDispatch; } });
|
|
53
53
|
Object.defineProperty(exports, "detectKciKind", { enumerable: true, get: function () { return kci_effect_bridge_1.detectKciKind; } });
|
|
54
|
+
// ── Install Fulfilment digest BP inputs (published projections) ───────
|
|
55
|
+
var install_stage_kci_spec_1 = require("./install-stage-kci-spec");
|
|
56
|
+
Object.defineProperty(exports, "INSTALL_STAGE_KCI_SOURCES", { enumerable: true, get: function () { return install_stage_kci_spec_1.INSTALL_STAGE_KCI_SOURCES; } });
|
|
57
|
+
Object.defineProperty(exports, "buildInstallStageReasonCodeSpecs", { enumerable: true, get: function () { return install_stage_kci_spec_1.buildInstallStageReasonCodeSpecs; } });
|
|
58
|
+
var change_reason_classifications_1 = require("./change-reason-classifications");
|
|
59
|
+
Object.defineProperty(exports, "BILLING_BLOCKED_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.BILLING_BLOCKED_RESCHEDULE_REASONS; } });
|
|
60
|
+
Object.defineProperty(exports, "TENANT_PENDING_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.TENANT_PENDING_RESCHEDULE_REASONS; } });
|
|
61
|
+
Object.defineProperty(exports, "CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.CUSTOMER_FOLLOWUP_RESCHEDULE_REASONS; } });
|
|
62
|
+
Object.defineProperty(exports, "MISSELLING_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.MISSELLING_RESCHEDULE_REASONS; } });
|
|
63
|
+
Object.defineProperty(exports, "SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.SUPPLIER_SIDE_CAUSE_RESCHEDULE_REASONS; } });
|
|
64
|
+
Object.defineProperty(exports, "INTRA_TODAY_RESCHEDULE_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.INTRA_TODAY_RESCHEDULE_REASONS; } });
|
|
65
|
+
Object.defineProperty(exports, "RECONTRACT_SIDE_EFFECT_CANCEL_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.RECONTRACT_SIDE_EFFECT_CANCEL_REASONS; } });
|
|
66
|
+
Object.defineProperty(exports, "DATA_HYGIENE_CANCEL_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.DATA_HYGIENE_CANCEL_REASONS; } });
|
|
67
|
+
Object.defineProperty(exports, "CHURN_CANCEL_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.CHURN_CANCEL_REASONS; } });
|
|
68
|
+
Object.defineProperty(exports, "SUPPLIER_SIDE_CAUSE_CANCEL_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.SUPPLIER_SIDE_CAUSE_CANCEL_REASONS; } });
|
|
69
|
+
Object.defineProperty(exports, "CUSTOMER_SIDE_CAUSE_CANCEL_REASONS", { enumerable: true, get: function () { return change_reason_classifications_1.CUSTOMER_SIDE_CAUSE_CANCEL_REASONS; } });
|
|
70
|
+
Object.defineProperty(exports, "isFulfilmentFailureCancel", { enumerable: true, get: function () { return change_reason_classifications_1.isFulfilmentFailureCancel; } });
|
|
71
|
+
Object.defineProperty(exports, "unclassifiedRescheduleReasons", { enumerable: true, get: function () { return change_reason_classifications_1.unclassifiedRescheduleReasons; } });
|
|
72
|
+
Object.defineProperty(exports, "unclassifiedCancelReasons", { enumerable: true, get: function () { return change_reason_classifications_1.unclassifiedCancelReasons; } });
|
|
54
73
|
// ── Reference types (re-exported — not on WholesaleProvider surface) ──
|
|
55
74
|
//
|
|
56
75
|
// FibreCafe wire-format types — useful for module-level DTO consolidation
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface InstallStageKciSource {
|
|
2
|
+
/** WorkOrder stage key (e.g. 'WorkOrderStageScheduled'). */
|
|
3
|
+
readonly stageKey: string;
|
|
4
|
+
/** Human-readable stage name (e.g. 'Scheduled'). */
|
|
5
|
+
readonly stageName: string;
|
|
6
|
+
/** Bridge row ids whose KCI drives entry into this stage. */
|
|
7
|
+
readonly kciSourceRowIds: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Frozen projection of INSTALL_PROCESS.stages (install.ts) — only stages
|
|
11
|
+
* with at least one kciSource-bearing entryAction. Verified against the
|
|
12
|
+
* live BP on 2026-05-20.
|
|
13
|
+
*/
|
|
14
|
+
export declare const INSTALL_STAGE_KCI_SOURCES: readonly InstallStageKciSource[];
|
|
15
|
+
export interface InstallStageReasonCodeSpec {
|
|
16
|
+
readonly stageKey: string;
|
|
17
|
+
readonly stageName: string;
|
|
18
|
+
/** Distinct supplier KCI reasonCodes that drive entry into this stage. */
|
|
19
|
+
readonly expectedReasonCodes: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve `INSTALL_STAGE_KCI_SOURCES` rowIds → distinct reasonCodes via
|
|
23
|
+
* `KCI_EFFECT_ROWS`. Stages whose rowIds resolve to no reasonCode are
|
|
24
|
+
* dropped (matches the digest's `reasonSet.size === 0` skip).
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildInstallStageReasonCodeSpecs(): InstallStageReasonCodeSpec[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildInstallStageReasonCodeSpecs = exports.INSTALL_STAGE_KCI_SOURCES = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* INSTALL work-order stage → expected supplier-KCI reasonCode spec.
|
|
6
|
+
*
|
|
7
|
+
* The Install Fulfilment digest's "KCI audit by stage" needs, per INSTALL
|
|
8
|
+
* WO stage, the set of supplier KCI `reasonCode`s that legitimately drive
|
|
9
|
+
* entry into that stage. That mapping is a projection of the typed BP
|
|
10
|
+
* template `business-processes/install.ts` (agent-os authoring origin):
|
|
11
|
+
* for each stage, its `entryActions[].kciSource.rowId` resolved against
|
|
12
|
+
* `KCI_EFFECT_ROWS` (the canonical bridge catalog, already in this SDK).
|
|
13
|
+
*
|
|
14
|
+
* `INSTALL_STAGE_KCI_SOURCES` below is the frozen projection of
|
|
15
|
+
* `INSTALL_PROCESS.stages[].entryActions[].kciSource.rowId`, extracted
|
|
16
|
+
* mechanically from install.ts. If the BP rewires a stage's kciSource,
|
|
17
|
+
* regenerate this array (it is the published copy, not a re-derivation).
|
|
18
|
+
*
|
|
19
|
+
* `buildInstallStageReasonCodeSpecs()` resolves the rowIds → reasonCodes via
|
|
20
|
+
* `KCI_EFFECT_ROWS` so consumers never hand-map rowId→reasonCode themselves.
|
|
21
|
+
*/
|
|
22
|
+
const kci_effect_bridge_1 = require("./kci-effect-bridge");
|
|
23
|
+
/**
|
|
24
|
+
* Frozen projection of INSTALL_PROCESS.stages (install.ts) — only stages
|
|
25
|
+
* with at least one kciSource-bearing entryAction. Verified against the
|
|
26
|
+
* live BP on 2026-05-20.
|
|
27
|
+
*/
|
|
28
|
+
exports.INSTALL_STAGE_KCI_SOURCES = [
|
|
29
|
+
{
|
|
30
|
+
stageKey: 'WorkOrderStageScheduled',
|
|
31
|
+
stageName: 'Scheduled',
|
|
32
|
+
kciSourceRowIds: ['provide-reappointed', 'provide-amended'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
stageKey: 'WorkOrderStageEnRoute',
|
|
36
|
+
stageName: 'En Route',
|
|
37
|
+
kciSourceRowIds: ['provide-update-install-en-route'],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
stageKey: 'WorkOrderStageInProgress',
|
|
41
|
+
stageName: 'In Progress',
|
|
42
|
+
kciSourceRowIds: ['provide-update-install-in-progress', 'provide-resolved'],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
stageKey: 'WorkOrderRemediationRequired',
|
|
46
|
+
stageName: 'Remediation Required',
|
|
47
|
+
kciSourceRowIds: ['provide-delay'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
stageKey: 'CustomerWorkOrdersStagePending',
|
|
51
|
+
stageName: 'Pending',
|
|
52
|
+
kciSourceRowIds: [
|
|
53
|
+
'provide-information-required',
|
|
54
|
+
'provide-contact-customer',
|
|
55
|
+
'provide-contact-reappoint',
|
|
56
|
+
'provide-reappoint',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
stageKey: 'WorkOrderStageDone',
|
|
61
|
+
stageName: 'Done',
|
|
62
|
+
kciSourceRowIds: ['provide-completed', 'provide-partial'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
stageKey: 'WorkOrderStageCancelled',
|
|
66
|
+
stageName: 'Cancelled',
|
|
67
|
+
kciSourceRowIds: [
|
|
68
|
+
'provide-cancelled',
|
|
69
|
+
'provide-failed',
|
|
70
|
+
'provide-rejected',
|
|
71
|
+
'provide-order-rejected',
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
/**
|
|
76
|
+
* Resolve `INSTALL_STAGE_KCI_SOURCES` rowIds → distinct reasonCodes via
|
|
77
|
+
* `KCI_EFFECT_ROWS`. Stages whose rowIds resolve to no reasonCode are
|
|
78
|
+
* dropped (matches the digest's `reasonSet.size === 0` skip).
|
|
79
|
+
*/
|
|
80
|
+
function buildInstallStageReasonCodeSpecs() {
|
|
81
|
+
const rowIdToReason = new Map();
|
|
82
|
+
for (const r of kci_effect_bridge_1.KCI_EFFECT_ROWS)
|
|
83
|
+
rowIdToReason.set(r.id, r.reasonCode);
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const stage of exports.INSTALL_STAGE_KCI_SOURCES) {
|
|
86
|
+
const reasonSet = new Set();
|
|
87
|
+
for (const rowId of stage.kciSourceRowIds) {
|
|
88
|
+
const rc = rowIdToReason.get(rowId);
|
|
89
|
+
if (rc)
|
|
90
|
+
reasonSet.add(rc);
|
|
91
|
+
}
|
|
92
|
+
if (reasonSet.size === 0)
|
|
93
|
+
continue;
|
|
94
|
+
out.push({
|
|
95
|
+
stageKey: stage.stageKey,
|
|
96
|
+
stageName: stage.stageName,
|
|
97
|
+
expectedReasonCodes: [...reasonSet],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
exports.buildInstallStageReasonCodeSpecs = buildInstallStageReasonCodeSpecs;
|
|
@@ -89,6 +89,20 @@ export interface YfKciEffect {
|
|
|
89
89
|
readonly timeBlockFrom: string;
|
|
90
90
|
/** JSONPath for the supplier-side appointment reservation reference. */
|
|
91
91
|
readonly supplierReferenceFrom: string;
|
|
92
|
+
/**
|
|
93
|
+
* When set, this dispatch is a *reschedule* (not a first-time booking):
|
|
94
|
+
* the dispatcher passes this value as the schedule-appointment
|
|
95
|
+
* `RescheduleReason`, which makes field-service update the existing
|
|
96
|
+
* ServiceAppointment in place AND record a `SA_RESCHEDULE` ChangeReason
|
|
97
|
+
* (Owner derived from the reason; supplier reasons → SUPPLIER).
|
|
98
|
+
*
|
|
99
|
+
* NOTE: `ChangeReasonRescheduleReason` does NOT map 1:1 to KCI types —
|
|
100
|
+
* the KCI only tells us *that* the supplier moved the slot, not the
|
|
101
|
+
* granular operational reason. We therefore stamp a single neutral
|
|
102
|
+
* supplier-owned value (`SCHEDULE_CHANGED`) for every supplier-driven
|
|
103
|
+
* KCI reschedule rather than inventing a per-KCI mapping.
|
|
104
|
+
*/
|
|
105
|
+
readonly rescheduleReason?: string;
|
|
92
106
|
};
|
|
93
107
|
readonly notes: string;
|
|
94
108
|
}
|
|
@@ -139,6 +153,13 @@ export declare type KciDispatchDecision = {
|
|
|
139
153
|
entityKey: YfKciTargetEntity;
|
|
140
154
|
properties: ReadonlyArray<string>;
|
|
141
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* Present only for reschedule rows. The dispatcher forwards it as the
|
|
158
|
+
* schedule-appointment `RescheduleReason`, triggering in-place SA update
|
|
159
|
+
* + `SA_RESCHEDULE` ChangeReason creation. Absent for first-booking-style
|
|
160
|
+
* schedule-appointment effects.
|
|
161
|
+
*/
|
|
162
|
+
rescheduleReason?: string;
|
|
142
163
|
} | {
|
|
143
164
|
kind: 'PROPERTY_WRITE';
|
|
144
165
|
row: KciEffectRow;
|
|
@@ -189,9 +189,16 @@ exports.KCI_EFFECT_ROWS = [
|
|
|
189
189
|
yfEffect: {
|
|
190
190
|
kind: 'WO_SCHEDULE_APPOINTMENT',
|
|
191
191
|
scheduleAppointment: {
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// NET now stamps the rebooked slot on the flat KCI envelope fields
|
|
193
|
+
// (appointmentDate / appointmentTimeBlock) rather than the nested
|
|
194
|
+
// provideServiceOrder.appointmentTimeslot. appointmentTimeBlock is
|
|
195
|
+
// already the AM/PM block — no WEEKDAY_ prefix to trim.
|
|
196
|
+
dateFrom: 'appointmentDate',
|
|
197
|
+
timeBlockFrom: 'appointmentTimeBlock',
|
|
194
198
|
supplierReferenceFrom: 'provideServiceOrder.appointmentReservationReference',
|
|
199
|
+
// REAPPOINTED is a supplier reschedule → update existing SA in place
|
|
200
|
+
// + record the SA_RESCHEDULE ChangeReason.
|
|
201
|
+
rescheduleReason: 'SCHEDULE_CHANGED',
|
|
195
202
|
},
|
|
196
203
|
// Co-effect: REAPPOINTED's dto also carries an updated
|
|
197
204
|
// `provideServiceOrder.committedDate` for the rebooked appointment.
|
|
@@ -321,6 +328,30 @@ exports.KCI_EFFECT_ROWS = [
|
|
|
321
328
|
},
|
|
322
329
|
logAgainstOrder: true,
|
|
323
330
|
},
|
|
331
|
+
{
|
|
332
|
+
// Supplier rescheduled the REMEDIATION visit. Move the REMEDIATION WO's
|
|
333
|
+
// appointment to the new slot and land it back in Scheduled, recording an
|
|
334
|
+
// SA_RESCHEDULE ChangeReason. Emitted on the parent INSTALL's PROVIDE
|
|
335
|
+
// order; tenantWorkOrderReference points at the REMEDIATION WO.
|
|
336
|
+
id: 'provide-update-remediation-rescheduled',
|
|
337
|
+
reasonCode: 'UPDATE',
|
|
338
|
+
serviceOrderKind: 'PROVIDE',
|
|
339
|
+
supplierCodes: ['NETOMNIA_REMEDIATION_WORK_ORDER_RESCHEDULED'],
|
|
340
|
+
yfEffect: {
|
|
341
|
+
kind: 'WO_SCHEDULE_APPOINTMENT',
|
|
342
|
+
scheduleAppointment: {
|
|
343
|
+
dateFrom: 'appointmentDate',
|
|
344
|
+
timeBlockFrom: 'appointmentTimeBlock',
|
|
345
|
+
supplierReferenceFrom: 'provideServiceOrder.appointmentReservationReference',
|
|
346
|
+
rescheduleReason: 'SCHEDULE_CHANGED',
|
|
347
|
+
},
|
|
348
|
+
notes: 'Supplier-confirmed REMEDIATION reschedule. Update the WO ' +
|
|
349
|
+
'ServiceAppointment Date/TimeBlock in place, land the WO in Scheduled, ' +
|
|
350
|
+
'and record the SA_RESCHEDULE ChangeReason. No Order stage / CommittedDate ' +
|
|
351
|
+
'effect (REMEDIATION is its own WO; parent INSTALL stage unchanged).',
|
|
352
|
+
},
|
|
353
|
+
logAgainstOrder: true,
|
|
354
|
+
},
|
|
324
355
|
// ═══════════════════════════════════════════════════════════════════
|
|
325
356
|
// REPAIR — SERVICE WO state changes (post-install fault / service visit)
|
|
326
357
|
//
|
|
@@ -365,6 +396,30 @@ exports.KCI_EFFECT_ROWS = [
|
|
|
365
396
|
},
|
|
366
397
|
logAgainstOrder: true,
|
|
367
398
|
},
|
|
399
|
+
{
|
|
400
|
+
// Supplier rescheduled the SERVICE (repair) visit. Move the SERVICE WO's
|
|
401
|
+
// appointment to the new slot and land it back in Scheduled (from any
|
|
402
|
+
// non-terminal stage, incl. EnRoute/InProgress), recording an
|
|
403
|
+
// SA_RESCHEDULE ChangeReason. Detected REPAIR via the
|
|
404
|
+
// NETOMNIA_SERVICE_WORK_ORDER_ prefix in detectKciKind.
|
|
405
|
+
id: 'repair-update-service-rescheduled',
|
|
406
|
+
reasonCode: 'UPDATE',
|
|
407
|
+
serviceOrderKind: 'REPAIR',
|
|
408
|
+
supplierCodes: ['NETOMNIA_SERVICE_WORK_ORDER_RESCHEDULED'],
|
|
409
|
+
yfEffect: {
|
|
410
|
+
kind: 'WO_SCHEDULE_APPOINTMENT',
|
|
411
|
+
scheduleAppointment: {
|
|
412
|
+
dateFrom: 'appointmentDate',
|
|
413
|
+
timeBlockFrom: 'appointmentTimeBlock',
|
|
414
|
+
supplierReferenceFrom: 'provideServiceOrder.appointmentReservationReference',
|
|
415
|
+
rescheduleReason: 'SCHEDULE_CHANGED',
|
|
416
|
+
},
|
|
417
|
+
notes: 'Supplier-confirmed SERVICE reschedule. Update the WO ServiceAppointment ' +
|
|
418
|
+
'Date/TimeBlock in place, land the WO in Scheduled, and record the ' +
|
|
419
|
+
'SA_RESCHEDULE ChangeReason. No Order stage effect (changesOrderStage:false).',
|
|
420
|
+
},
|
|
421
|
+
logAgainstOrder: true,
|
|
422
|
+
},
|
|
368
423
|
// ── PROVIDE — informational / no-action ──────────────────────────
|
|
369
424
|
// Generic UPDATE row — fallback for KCIs that don't carry a recognized
|
|
370
425
|
// supplierCode. Walker matches this only after rejecting all
|
|
@@ -469,7 +524,7 @@ exports.KCI_EFFECT_ROWS = [
|
|
|
469
524
|
yfEffect: {
|
|
470
525
|
kind: 'WO_FLOW_ACTION',
|
|
471
526
|
semanticOp: 'cancel',
|
|
472
|
-
notes: 'SERVICE WO cancelled by supplier — advance YF SERVICE WO Accepted/Scheduled/EnRoute/InProgress → Cancelled via CancelServiceWorkOrderFlowSupplier. Also the inbound confirmation when YF tenant-initiated the cancel via /service-
|
|
527
|
+
notes: 'SERVICE WO cancelled by supplier — advance YF SERVICE WO Accepted/Scheduled/EnRoute/InProgress → Cancelled via CancelServiceWorkOrderFlowSupplier. Also the inbound confirmation when YF tenant-initiated the cancel via /WholesaleGateway/v1.0/gateway/service-work-orders/cancel; YF WO usually already at Cancelled, action is idempotent on the WO stage.',
|
|
473
528
|
},
|
|
474
529
|
logAgainstOrder: true,
|
|
475
530
|
},
|
|
@@ -543,13 +598,15 @@ function decideKciDispatch(dto) {
|
|
|
543
598
|
if (!row.yfEffect.scheduleAppointment) {
|
|
544
599
|
throw new Error(`bridge invariant: WO_SCHEDULE_APPOINTMENT row ${row.id} missing scheduleAppointment`);
|
|
545
600
|
}
|
|
546
|
-
return Object.assign({ kind: 'WO_SCHEDULE_APPOINTMENT', row, dateFrom: row.yfEffect.scheduleAppointment.dateFrom, timeBlockFrom: row.yfEffect.scheduleAppointment.timeBlockFrom, supplierReferenceFrom: row.yfEffect.scheduleAppointment.supplierReferenceFrom }, (row.yfEffect.propertyWrite
|
|
601
|
+
return Object.assign(Object.assign({ kind: 'WO_SCHEDULE_APPOINTMENT', row, dateFrom: row.yfEffect.scheduleAppointment.dateFrom, timeBlockFrom: row.yfEffect.scheduleAppointment.timeBlockFrom, supplierReferenceFrom: row.yfEffect.scheduleAppointment.supplierReferenceFrom }, (row.yfEffect.propertyWrite
|
|
547
602
|
? {
|
|
548
603
|
propertyWrite: {
|
|
549
604
|
entityKey: row.yfEffect.propertyWrite.entityKey,
|
|
550
605
|
properties: row.yfEffect.propertyWrite.properties,
|
|
551
606
|
},
|
|
552
607
|
}
|
|
608
|
+
: {})), (row.yfEffect.scheduleAppointment.rescheduleReason
|
|
609
|
+
? { rescheduleReason: row.yfEffect.scheduleAppointment.rescheduleReason }
|
|
553
610
|
: {}));
|
|
554
611
|
case 'PROPERTY_WRITE':
|
|
555
612
|
if (!row.yfEffect.propertyWrite) {
|
|
@@ -140,10 +140,39 @@ function run() {
|
|
|
140
140
|
const decision = (0, kci_effect_bridge_1.decideKciDispatch)(makeProvideDto('RESUMED'));
|
|
141
141
|
assert('kind=LOG_KCI_ONLY', decision.kind === 'LOG_KCI_ONLY');
|
|
142
142
|
}
|
|
143
|
-
console.log('Test: REAPPOINTED → WO_SCHEDULE_APPOINTMENT (
|
|
143
|
+
console.log('Test: REAPPOINTED → WO_SCHEDULE_APPOINTMENT (reschedule: flat fields + rescheduleReason)');
|
|
144
144
|
{
|
|
145
145
|
const decision = (0, kci_effect_bridge_1.decideKciDispatch)(makeProvideDto('REAPPOINTED'));
|
|
146
146
|
assert('kind=WO_SCHEDULE_APPOINTMENT', decision.kind === 'WO_SCHEDULE_APPOINTMENT');
|
|
147
|
+
if (decision.kind === 'WO_SCHEDULE_APPOINTMENT') {
|
|
148
|
+
assert('dateFrom=appointmentDate', decision.dateFrom === 'appointmentDate');
|
|
149
|
+
assert('timeBlockFrom=appointmentTimeBlock', decision.timeBlockFrom === 'appointmentTimeBlock');
|
|
150
|
+
assert('rescheduleReason=SCHEDULE_CHANGED', decision.rescheduleReason === 'SCHEDULE_CHANGED');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log('Test: SERVICE WORK_ORDER_RESCHEDULED → WO_SCHEDULE_APPOINTMENT (REPAIR reschedule)');
|
|
154
|
+
{
|
|
155
|
+
const dto = makeProvideDto('UPDATE');
|
|
156
|
+
dto.supplierCodes = ['NETOMNIA_SERVICE_WORK_ORDER_RESCHEDULED'];
|
|
157
|
+
const decision = (0, kci_effect_bridge_1.decideKciDispatch)(dto);
|
|
158
|
+
assert('detectKciKind=REPAIR', (0, kci_effect_bridge_1.detectKciKind)(dto) === 'REPAIR');
|
|
159
|
+
assert('kind=WO_SCHEDULE_APPOINTMENT', decision.kind === 'WO_SCHEDULE_APPOINTMENT');
|
|
160
|
+
if (decision.kind === 'WO_SCHEDULE_APPOINTMENT') {
|
|
161
|
+
assert('row=repair-update-service-rescheduled', decision.row.id === 'repair-update-service-rescheduled');
|
|
162
|
+
assert('rescheduleReason=SCHEDULE_CHANGED', decision.rescheduleReason === 'SCHEDULE_CHANGED');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log('Test: REMEDIATION WORK_ORDER_RESCHEDULED → WO_SCHEDULE_APPOINTMENT (PROVIDE reschedule)');
|
|
166
|
+
{
|
|
167
|
+
const dto = makeProvideDto('UPDATE');
|
|
168
|
+
dto.supplierCodes = ['NETOMNIA_REMEDIATION_WORK_ORDER_RESCHEDULED'];
|
|
169
|
+
const decision = (0, kci_effect_bridge_1.decideKciDispatch)(dto);
|
|
170
|
+
assert('detectKciKind=PROVIDE', (0, kci_effect_bridge_1.detectKciKind)(dto) === 'PROVIDE');
|
|
171
|
+
assert('kind=WO_SCHEDULE_APPOINTMENT', decision.kind === 'WO_SCHEDULE_APPOINTMENT');
|
|
172
|
+
if (decision.kind === 'WO_SCHEDULE_APPOINTMENT') {
|
|
173
|
+
assert('row=provide-update-remediation-rescheduled', decision.row.id === 'provide-update-remediation-rescheduled');
|
|
174
|
+
assert('rescheduleReason=SCHEDULE_CHANGED', decision.rescheduleReason === 'SCHEDULE_CHANGED');
|
|
175
|
+
}
|
|
147
176
|
}
|
|
148
177
|
console.log('Test: WARNING → LOG_KCI_ONLY');
|
|
149
178
|
{
|
|
@@ -496,6 +496,15 @@ export interface FibreCafeKciDto {
|
|
|
496
496
|
* supplierCode discriminator needed).
|
|
497
497
|
*/
|
|
498
498
|
tenantWorkOrderReference?: string | null;
|
|
499
|
+
/**
|
|
500
|
+
* Rebooked appointment slot — set on REAPPOINTED and the SERVICE /
|
|
501
|
+
* REMEDIATION *_RESCHEDULED UPDATE KCIs. NET stamps these flat envelope
|
|
502
|
+
* fields directly (in preference to the nested
|
|
503
|
+
* provideServiceOrder.appointmentTimeslot). `appointmentDate` is the ISO
|
|
504
|
+
* appointment date; `appointmentTimeBlock` is the AM/PM block.
|
|
505
|
+
*/
|
|
506
|
+
appointmentDate?: string;
|
|
507
|
+
appointmentTimeBlock?: 'AM' | 'PM';
|
|
499
508
|
/** Full Odin WorkOrder record — sent for WO stage/appointment updates. */
|
|
500
509
|
workOrder?: OdinRecord<WorkOrderProperties>;
|
|
501
510
|
/** Partial Odin ServiceAppointment record — sent for WO stage/appointment updates. */
|