@burdenoff/microfe-movethewheels 2026.1016.2 → 2026.1016.4
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/README.md +10 -0
- package/dist/MoveTheWheelsRoot.js +5 -0
- package/dist/MoveTheWheelsRoutes.js +25 -21
- package/dist/components/ContainerEvidencePicker.js +55 -55
- package/dist/components/FleetAlertsPanel.js +2 -2
- package/dist/components/freight-cost/FreightCommercialEditor.js +217 -0
- package/dist/components/freight-cost/FreightCommercialFactEditor.js +217 -0
- package/dist/components/freight-cost/FreightCommercialHistory.js +115 -0
- package/dist/components/freight-cost/FreightDraftForm.js +106 -91
- package/dist/components/freight-cost/FreightHistory.js +42 -40
- package/dist/components/freight-invoice/InvoiceForm.js +366 -0
- package/dist/components/freight-invoice/InvoiceResults.js +240 -0
- package/dist/generated/consolidation-operations.d.ts +195 -265
- package/dist/generated/container-condition-operations.d.ts +124 -187
- package/dist/generated/container-operations.d.ts +59 -85
- package/dist/generated/empty-reuse-operations.d.ts +165 -271
- package/dist/generated/free-time-operations.d.ts +120 -184
- package/dist/generated/freight-cost-operations.d.ts +955 -165
- package/dist/generated/freight-invoice-operations.d.ts +1447 -0
- package/dist/generated/logistics-procedure-operations.d.ts +100 -156
- package/dist/generated/pending-pick-operations.d.ts +769 -1025
- package/dist/generated/shipment-status-authority-operations.d.ts +82 -103
- package/dist/generated/stock-binding-operations.d.ts +188 -260
- package/dist/generated/stock-observation-operations.d.ts +103 -138
- package/dist/generated/tracking-quality-operations.d.ts +27 -41
- package/dist/generated/trip-utilization-operations.d.ts +137 -185
- package/dist/generated/wspace-operations.d.ts +628 -2272
- package/dist/generated/wspace-types.d.ts +4 -23
- package/dist/hooks/useFreightRecoveryAction.js +20 -20
- package/dist/hooks/useInvoiceComparisonRecovery.js +118 -0
- package/dist/movethewheels/components/ContainerEvidencePicker.d.ts +2 -1
- package/dist/movethewheels/components/freight-cost/FreightCommercialEditor.d.ts +8 -0
- package/dist/movethewheels/components/freight-cost/FreightCommercialFactEditor.d.ts +8 -0
- package/dist/movethewheels/components/freight-cost/FreightCommercialHistory.d.ts +4 -0
- package/dist/movethewheels/components/freight-cost/FreightDraftForm.d.ts +4 -2
- package/dist/movethewheels/components/freight-cost/FreightHistory.d.ts +2 -2
- package/dist/movethewheels/components/freight-invoice/InvoiceForm.d.ts +19 -0
- package/dist/movethewheels/components/freight-invoice/InvoiceResults.d.ts +9 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +1 -1
- package/dist/movethewheels/hooks/useFleetIntelligence.d.ts +1 -1
- package/dist/movethewheels/hooks/useInvoiceComparisonRecovery.d.ts +22 -0
- package/dist/movethewheels/hooks/useProcedureSelection.d.ts +1 -3
- package/dist/movethewheels/pages/FreightInvoicePage.d.ts +10 -0
- package/dist/movethewheels/utils/driverLicenseExpiry.d.ts +6 -0
- package/dist/movethewheels/utils/freightCommercial.d.ts +44 -0
- package/dist/movethewheels/utils/invoiceComparison.d.ts +32 -0
- package/dist/pages/DriverDetailsPage.js +306 -282
- package/dist/pages/FreightEstimatePage.js +97 -92
- package/dist/pages/FreightInvoicePage.js +346 -0
- package/dist/src/generated/consolidation-operations.js +3961 -503
- package/dist/src/generated/container-condition-operations.js +2968 -206
- package/dist/src/generated/container-operations.js +997 -99
- package/dist/src/generated/empty-reuse-operations.js +4811 -747
- package/dist/src/generated/free-time-operations.js +2824 -233
- package/dist/src/generated/freight-cost-operations.js +4852 -323
- package/dist/src/generated/freight-invoice-operations.js +4894 -0
- package/dist/src/generated/logistics-procedure-operations.js +2707 -360
- package/dist/src/generated/pending-pick-operations.js +8244 -1196
- package/dist/src/generated/shipment-status-authority-operations.js +1157 -84
- package/dist/src/generated/stock-binding-operations.js +2779 -173
- package/dist/src/generated/stock-observation-operations.js +1274 -91
- package/dist/src/generated/tracking-quality-operations.js +407 -42
- package/dist/src/generated/trip-utilization-operations.js +1765 -138
- package/dist/src/generated/wspace-operations.js +428 -706
- package/dist/utils/driverLicenseExpiry.js +14 -0
- package/dist/utils/freightAcknowledgement.js +6 -5
- package/dist/utils/freightCommercial.js +194 -0
- package/dist/utils/freightRecovery.js +22 -22
- package/dist/utils/invoiceComparison.js +272 -0
- package/package.json +30 -29
- package/src/generated/consolidation-operations.ts +60 -559
- package/src/generated/container-condition-operations.ts +63 -265
- package/src/generated/container-operations.ts +37 -132
- package/src/generated/empty-reuse-operations.ts +83 -824
- package/src/generated/free-time-operations.ts +55 -284
- package/src/generated/freight-cost-operations.ts +373 -349
- package/src/generated/freight-invoice-operations.ts +830 -0
- package/src/generated/logistics-procedure-operations.ts +60 -414
- package/src/generated/pending-pick-operations.ts +48 -1239
- package/src/generated/shipment-status-authority-operations.ts +33 -113
- package/src/generated/stock-binding-operations.ts +29 -198
- package/src/generated/stock-observation-operations.ts +24 -111
- package/src/generated/tracking-quality-operations.ts +19 -57
- package/src/generated/trip-utilization-operations.ts +32 -166
- package/src/generated/wspace-operations.ts +284 -2906
- package/src/generated/wspace-types.ts +2 -8
- package/src/movethewheels/MoveTheWheelsRoot.tsx +6 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +2 -0
- package/src/movethewheels/components/ContainerEvidencePicker.tsx +12 -4
- package/src/movethewheels/components/FleetAlertsPanel.tsx +4 -2
- package/src/movethewheels/components/freight-cost/FreightCommercialEditor.tsx +236 -0
- package/src/movethewheels/components/freight-cost/FreightCommercialFactEditor.tsx +258 -0
- package/src/movethewheels/components/freight-cost/FreightCommercialHistory.tsx +90 -0
- package/src/movethewheels/components/freight-cost/FreightDraftForm.tsx +33 -4
- package/src/movethewheels/components/freight-cost/FreightHistory.tsx +4 -2
- package/src/movethewheels/components/freight-invoice/InvoiceForm.tsx +412 -0
- package/src/movethewheels/components/freight-invoice/InvoiceResults.tsx +162 -0
- package/src/movethewheels/hooks/useFleet.ts +1 -1
- package/src/movethewheels/hooks/useFleetIntelligence.ts +5 -1
- package/src/movethewheels/hooks/useFreightRecoveryAction.ts +2 -1
- package/src/movethewheels/hooks/useInvoiceComparisonRecovery.ts +227 -0
- package/src/movethewheels/pages/DriverDetailsPage.tsx +35 -9
- package/src/movethewheels/pages/FreightEstimatePage.tsx +15 -7
- package/src/movethewheels/pages/FreightInvoicePage.tsx +477 -0
- package/src/movethewheels/utils/driverLicenseExpiry.ts +25 -0
- package/src/movethewheels/utils/freightAcknowledgement.ts +3 -1
- package/src/movethewheels/utils/freightCommercial.ts +388 -0
- package/src/movethewheels/utils/freightRecovery.ts +6 -2
- package/src/movethewheels/utils/invoiceComparison.ts +520 -0
- package/src/operations/freight-cost/commercial.graphql +483 -0
- package/src/operations/freight-invoice/comparison.graphql +40 -0
- package/src/operations/wspace/freight-invoice-evidence.graphql +6 -0
- package/patches/braces@3.0.3.patch +0 -87
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MtwFreightCommercialCaptureInput,
|
|
3
|
+
MtwFreightCommercialLocationFactInput,
|
|
4
|
+
MtwFreightCommercialStatus,
|
|
5
|
+
MtwFreightCommercialProvenanceInput,
|
|
6
|
+
FreightCommercialLegsQuery,
|
|
7
|
+
} from '../../generated/freight-cost-operations';
|
|
8
|
+
|
|
9
|
+
export const commercialSlots = [
|
|
10
|
+
'origin',
|
|
11
|
+
'destination',
|
|
12
|
+
'carrier',
|
|
13
|
+
'service',
|
|
14
|
+
'cargo',
|
|
15
|
+
'weight',
|
|
16
|
+
'volume',
|
|
17
|
+
'equipment',
|
|
18
|
+
] as const;
|
|
19
|
+
export type CommercialSlot = (typeof commercialSlots)[number];
|
|
20
|
+
export type CommercialLeg = NonNullable<FreightCommercialLegsQuery['shipment']>['legs'][number];
|
|
21
|
+
export type CommercialCapture = MtwFreightCommercialCaptureInput;
|
|
22
|
+
export const unrecordedCommercialReason = 'Not recorded for this cost version';
|
|
23
|
+
export interface CommercialFactDraft {
|
|
24
|
+
status: MtwFreightCommercialStatus;
|
|
25
|
+
reason: string;
|
|
26
|
+
label: string;
|
|
27
|
+
reference: string;
|
|
28
|
+
description: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
jurisdiction: string;
|
|
31
|
+
amount: string;
|
|
32
|
+
unit: string;
|
|
33
|
+
zeroReason: string;
|
|
34
|
+
count: string;
|
|
35
|
+
sourceReference: string;
|
|
36
|
+
sourceRevision: string;
|
|
37
|
+
observedAt: string;
|
|
38
|
+
evidenceLinks: { fileId: string; versionId: string }[];
|
|
39
|
+
}
|
|
40
|
+
export type CommercialDraft = Record<CommercialSlot, CommercialFactDraft>;
|
|
41
|
+
export interface CommercialEditorState {
|
|
42
|
+
facts: CommercialDraft;
|
|
43
|
+
selectedLegs: CommercialCapture['selectedLegs'];
|
|
44
|
+
reviewed: string | null;
|
|
45
|
+
}
|
|
46
|
+
export function newCommercialEditor(): CommercialEditorState {
|
|
47
|
+
return { facts: newCommercialDraft(), selectedLegs: [], reviewed: null };
|
|
48
|
+
}
|
|
49
|
+
export function newCommercialFact(
|
|
50
|
+
status: MtwFreightCommercialStatus = 'UNKNOWN'
|
|
51
|
+
): CommercialFactDraft {
|
|
52
|
+
return {
|
|
53
|
+
status,
|
|
54
|
+
reason: status === 'UNKNOWN' ? unrecordedCommercialReason : '',
|
|
55
|
+
label: '',
|
|
56
|
+
reference: '',
|
|
57
|
+
description: '',
|
|
58
|
+
kind: '',
|
|
59
|
+
jurisdiction: '',
|
|
60
|
+
amount: '',
|
|
61
|
+
unit: '',
|
|
62
|
+
zeroReason: '',
|
|
63
|
+
count: '',
|
|
64
|
+
sourceReference: '',
|
|
65
|
+
sourceRevision: '',
|
|
66
|
+
observedAt: '',
|
|
67
|
+
evidenceLinks: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function newCommercialDraft(): CommercialDraft {
|
|
71
|
+
return {
|
|
72
|
+
origin: newCommercialFact(),
|
|
73
|
+
destination: newCommercialFact(),
|
|
74
|
+
carrier: newCommercialFact(),
|
|
75
|
+
service: newCommercialFact(),
|
|
76
|
+
cargo: newCommercialFact(),
|
|
77
|
+
weight: newCommercialFact(),
|
|
78
|
+
volume: newCommercialFact(),
|
|
79
|
+
equipment: newCommercialFact(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function text(value: string, limit: number, label: string): string {
|
|
83
|
+
const result = value.trim();
|
|
84
|
+
if (!result || result.length > limit) throw new Error(`${label}: enter 1–${limit} characters.`);
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
function instant(value: string): string {
|
|
88
|
+
if (
|
|
89
|
+
!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(value) ||
|
|
90
|
+
new Date(value).toISOString() !== value ||
|
|
91
|
+
Date.parse(value) > Date.now()
|
|
92
|
+
)
|
|
93
|
+
throw new Error('Use a valid observed time no later than now.');
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function decimal(value: string, zeroReason: string) {
|
|
97
|
+
if (!/^(0|[1-9][0-9]{0,17})(\.[0-9]{0,5}[1-9])?$/.test(value))
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Use a canonical nonnegative decimal: up to 18 integer and 6 fractional digits, no exponent or trailing fractional zero.'
|
|
100
|
+
);
|
|
101
|
+
if (value === '0') return text(zeroReason, 255, 'Recorded zero reason');
|
|
102
|
+
if (zeroReason.trim()) throw new Error('A zero reason belongs only to an amount of zero.');
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function provenance(
|
|
106
|
+
row: CommercialFactDraft,
|
|
107
|
+
evidence: { fileId: string; versionId: string }[]
|
|
108
|
+
): MtwFreightCommercialProvenanceInput {
|
|
109
|
+
const links = row.evidenceLinks;
|
|
110
|
+
if (
|
|
111
|
+
links.length < 1 ||
|
|
112
|
+
links.length > 2 ||
|
|
113
|
+
new Set(links.map((link) => JSON.stringify([link.fileId, link.versionId]))).size !==
|
|
114
|
+
links.length ||
|
|
115
|
+
links.some(
|
|
116
|
+
(link) =>
|
|
117
|
+
!evidence.some((item) => item.fileId === link.fileId && item.versionId === link.versionId)
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
throw new Error(
|
|
121
|
+
'Choose 1–2 distinct exact evidence versions already selected for this cost record.'
|
|
122
|
+
);
|
|
123
|
+
return {
|
|
124
|
+
sourceReference: text(row.sourceReference, 255, 'Fact source'),
|
|
125
|
+
sourceRevision: text(row.sourceRevision, 255, 'Fact source revision'),
|
|
126
|
+
observedAt: row.observedAt ? instant(row.observedAt) : null,
|
|
127
|
+
evidenceLinks: [...links]
|
|
128
|
+
.sort((a, b) =>
|
|
129
|
+
JSON.stringify([a.fileId, a.versionId]) < JSON.stringify([b.fileId, b.versionId])
|
|
130
|
+
? -1
|
|
131
|
+
: JSON.stringify([a.fileId, a.versionId]) > JSON.stringify([b.fileId, b.versionId])
|
|
132
|
+
? 1
|
|
133
|
+
: 0
|
|
134
|
+
)
|
|
135
|
+
.map((link) => ({ ...link })),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** No source values are inferred from the owner snapshot or other fact slots. */
|
|
139
|
+
export function commercialCaptureInput(
|
|
140
|
+
draft: CommercialDraft,
|
|
141
|
+
pins: CommercialCapture['selectedLegs'],
|
|
142
|
+
evidence: { fileId: string; versionId: string }[]
|
|
143
|
+
): CommercialCapture {
|
|
144
|
+
const common = (key: CommercialSlot) => {
|
|
145
|
+
const row = draft[key];
|
|
146
|
+
if (row.status === 'UNKNOWN')
|
|
147
|
+
return {
|
|
148
|
+
status: row.status,
|
|
149
|
+
value: null,
|
|
150
|
+
reason: text(row.reason, 255, key),
|
|
151
|
+
provenance: null,
|
|
152
|
+
};
|
|
153
|
+
if (row.status !== 'SOURCED' && row.status !== 'NOT_APPLICABLE')
|
|
154
|
+
throw new Error('Choose a supported commercial fact status.');
|
|
155
|
+
return {
|
|
156
|
+
status: row.status,
|
|
157
|
+
value: null,
|
|
158
|
+
reason: row.status === 'NOT_APPLICABLE' ? text(row.reason, 255, key) : null,
|
|
159
|
+
provenance: provenance(row, evidence),
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const location = (key: 'origin' | 'destination'): MtwFreightCommercialLocationFactInput => {
|
|
163
|
+
const base = common(key),
|
|
164
|
+
row = draft[key];
|
|
165
|
+
if (base.status !== 'SOURCED') return base;
|
|
166
|
+
if (
|
|
167
|
+
row.kind !== 'ADDRESS_LABEL' &&
|
|
168
|
+
row.kind !== 'PORT_LABEL' &&
|
|
169
|
+
row.kind !== 'TERMINAL_LABEL' &&
|
|
170
|
+
row.kind !== 'POSTCODE_LABEL' &&
|
|
171
|
+
row.kind !== 'CUSTOM_LABEL'
|
|
172
|
+
)
|
|
173
|
+
throw new Error('Choose a location label kind.');
|
|
174
|
+
return {
|
|
175
|
+
...base,
|
|
176
|
+
value: {
|
|
177
|
+
kind: row.kind,
|
|
178
|
+
label: text(row.label, 255, key),
|
|
179
|
+
jurisdictionLabel: row.jurisdiction.trim()
|
|
180
|
+
? text(row.jurisdiction, 120, 'Jurisdiction label')
|
|
181
|
+
: null,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const weight = common('weight'),
|
|
186
|
+
volume = common('volume'),
|
|
187
|
+
carrier = common('carrier'),
|
|
188
|
+
service = common('service'),
|
|
189
|
+
cargo = common('cargo'),
|
|
190
|
+
equipment = common('equipment');
|
|
191
|
+
const w = draft.weight,
|
|
192
|
+
v = draft.volume;
|
|
193
|
+
if (w.status === 'SOURCED' && w.unit !== 'KG' && w.unit !== 'TONNE' && w.unit !== 'LB')
|
|
194
|
+
throw new Error('Choose a recorded weight unit.');
|
|
195
|
+
if (v.status === 'SOURCED' && v.unit !== 'M3' && v.unit !== 'FT3')
|
|
196
|
+
throw new Error('Choose a recorded volume unit.');
|
|
197
|
+
if (
|
|
198
|
+
draft.equipment.status === 'SOURCED' &&
|
|
199
|
+
(!/^[1-9][0-9]{0,5}$/.test(draft.equipment.count) || Number(draft.equipment.count) > 100000)
|
|
200
|
+
)
|
|
201
|
+
throw new Error('Equipment count must be an integer from 1 to 100000.');
|
|
202
|
+
if (pins.length > 16 || new Set(pins.map((pin) => pin.id)).size !== pins.length)
|
|
203
|
+
throw new Error('Select at most 16 distinct reviewed legs.');
|
|
204
|
+
const selectedLegs = pins
|
|
205
|
+
.map((pin) => ({ id: text(pin.id, 200, 'Leg ID'), expectedUpdatedAt: pin.expectedUpdatedAt }))
|
|
206
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
207
|
+
const result: CommercialCapture = {
|
|
208
|
+
schemaVersion: 1,
|
|
209
|
+
selectedLegs,
|
|
210
|
+
origin: location('origin'),
|
|
211
|
+
destination: location('destination'),
|
|
212
|
+
carrier:
|
|
213
|
+
carrier.status === 'SOURCED'
|
|
214
|
+
? {
|
|
215
|
+
...carrier,
|
|
216
|
+
value: {
|
|
217
|
+
reference: text(draft.carrier.reference, 120, 'Carrier reference'),
|
|
218
|
+
label: text(draft.carrier.label, 255, 'Carrier label'),
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
: carrier,
|
|
222
|
+
service:
|
|
223
|
+
service.status === 'SOURCED'
|
|
224
|
+
? { ...service, value: text(draft.service.label, 120, 'Service') }
|
|
225
|
+
: service,
|
|
226
|
+
cargo:
|
|
227
|
+
cargo.status === 'SOURCED'
|
|
228
|
+
? {
|
|
229
|
+
...cargo,
|
|
230
|
+
value: {
|
|
231
|
+
classificationLabel: text(draft.cargo.label, 120, 'Cargo classification'),
|
|
232
|
+
description: text(draft.cargo.description, 500, 'Cargo description'),
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
: cargo,
|
|
236
|
+
weight:
|
|
237
|
+
weight.status === 'SOURCED'
|
|
238
|
+
? {
|
|
239
|
+
...weight,
|
|
240
|
+
value: {
|
|
241
|
+
amount: w.amount,
|
|
242
|
+
unit: w.unit === 'KG' ? 'KG' : w.unit === 'TONNE' ? 'TONNE' : 'LB',
|
|
243
|
+
zeroReason: decimal(w.amount, w.zeroReason),
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
: weight,
|
|
247
|
+
volume:
|
|
248
|
+
volume.status === 'SOURCED'
|
|
249
|
+
? {
|
|
250
|
+
...volume,
|
|
251
|
+
value: {
|
|
252
|
+
amount: v.amount,
|
|
253
|
+
unit: v.unit === 'M3' ? 'M3' : 'FT3',
|
|
254
|
+
zeroReason: decimal(v.amount, v.zeroReason),
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
: volume,
|
|
258
|
+
equipment:
|
|
259
|
+
equipment.status === 'SOURCED'
|
|
260
|
+
? {
|
|
261
|
+
...equipment,
|
|
262
|
+
value: {
|
|
263
|
+
typeLabel: text(draft.equipment.label, 120, 'Equipment type'),
|
|
264
|
+
count: Number(draft.equipment.count),
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
: equipment,
|
|
268
|
+
};
|
|
269
|
+
if (new TextEncoder().encode(JSON.stringify(result)).byteLength > 32768)
|
|
270
|
+
throw new Error('Commercial capture exceeds 32768 bytes. Shorten facts before saving.');
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function commercialPinsMatch(
|
|
275
|
+
pins: CommercialCapture['selectedLegs'],
|
|
276
|
+
legs: CommercialLeg[],
|
|
277
|
+
shipmentId: string
|
|
278
|
+
): boolean {
|
|
279
|
+
return (
|
|
280
|
+
pins.length <= 16 &&
|
|
281
|
+
new Set(pins.map((pin) => pin.id)).size === pins.length &&
|
|
282
|
+
pins.every((pin) =>
|
|
283
|
+
legs.some(
|
|
284
|
+
(leg) =>
|
|
285
|
+
leg.id === pin.id &&
|
|
286
|
+
leg.shipmentId === shipmentId &&
|
|
287
|
+
leg.updatedAt === pin.expectedUpdatedAt
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Compare typed GraphQL output with the canonical input without trusting __typename or key order. */
|
|
294
|
+
export function commercialCanonical(value: unknown): string {
|
|
295
|
+
function ordered(v: unknown): unknown {
|
|
296
|
+
if (Array.isArray(v)) return v.map(ordered);
|
|
297
|
+
if (v && typeof v === 'object')
|
|
298
|
+
return Object.fromEntries(
|
|
299
|
+
Object.entries(v)
|
|
300
|
+
.filter(([key]) => key !== '__typename')
|
|
301
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
302
|
+
.map(([key, item]) => [key, ordered(item)])
|
|
303
|
+
);
|
|
304
|
+
return v;
|
|
305
|
+
}
|
|
306
|
+
return JSON.stringify(ordered(value));
|
|
307
|
+
}
|
|
308
|
+
export function matchesCommercialCapture(
|
|
309
|
+
receipt: unknown,
|
|
310
|
+
input: Record<string, unknown>
|
|
311
|
+
): boolean {
|
|
312
|
+
if (
|
|
313
|
+
!receipt ||
|
|
314
|
+
typeof receipt !== 'object' ||
|
|
315
|
+
!('commercialCapture' in receipt) ||
|
|
316
|
+
!receipt.commercialCapture ||
|
|
317
|
+
typeof receipt.commercialCapture !== 'object'
|
|
318
|
+
)
|
|
319
|
+
return false;
|
|
320
|
+
const capture = receipt.commercialCapture;
|
|
321
|
+
if (
|
|
322
|
+
!input.commercialCapture ||
|
|
323
|
+
typeof input.commercialCapture !== 'object' ||
|
|
324
|
+
!('selectedLegs' in input.commercialCapture) ||
|
|
325
|
+
!Array.isArray(input.commercialCapture.selectedLegs) ||
|
|
326
|
+
!('ownerShipment' in capture) ||
|
|
327
|
+
!capture.ownerShipment ||
|
|
328
|
+
typeof capture.ownerShipment !== 'object' ||
|
|
329
|
+
!('shipmentId' in capture.ownerShipment) ||
|
|
330
|
+
capture.ownerShipment.shipmentId !== input.shipmentId ||
|
|
331
|
+
!('updatedAt' in capture.ownerShipment) ||
|
|
332
|
+
capture.ownerShipment.updatedAt !== input.expectedShipmentUpdatedAt ||
|
|
333
|
+
!('primaryMode' in capture.ownerShipment) ||
|
|
334
|
+
typeof capture.ownerShipment.primaryMode !== 'string' ||
|
|
335
|
+
!['AIR', 'OCEAN', 'RAIL', 'ROAD'].includes(capture.ownerShipment.primaryMode) ||
|
|
336
|
+
!('ownerLegs' in capture) ||
|
|
337
|
+
!Array.isArray(capture.ownerLegs)
|
|
338
|
+
)
|
|
339
|
+
return false;
|
|
340
|
+
const declared = Object.fromEntries(
|
|
341
|
+
Object.entries(capture).filter(([key]) => key !== 'ownerShipment' && key !== 'ownerLegs')
|
|
342
|
+
);
|
|
343
|
+
if (commercialCanonical(declared) !== commercialCanonical(input.commercialCapture)) return false;
|
|
344
|
+
const pins = input.commercialCapture.selectedLegs;
|
|
345
|
+
const ownerLegs = capture.ownerLegs;
|
|
346
|
+
return (
|
|
347
|
+
ownerLegs.length === pins.length &&
|
|
348
|
+
pins.every(
|
|
349
|
+
(pin) =>
|
|
350
|
+
pin &&
|
|
351
|
+
typeof pin === 'object' &&
|
|
352
|
+
'id' in pin &&
|
|
353
|
+
'expectedUpdatedAt' in pin &&
|
|
354
|
+
ownerLegs.some(
|
|
355
|
+
(leg: unknown) =>
|
|
356
|
+
leg &&
|
|
357
|
+
typeof leg === 'object' &&
|
|
358
|
+
'id' in leg &&
|
|
359
|
+
leg.id === pin.id &&
|
|
360
|
+
'updatedAt' in leg &&
|
|
361
|
+
leg.updatedAt === pin.expectedUpdatedAt &&
|
|
362
|
+
'legNumber' in leg &&
|
|
363
|
+
typeof leg.legNumber === 'number' &&
|
|
364
|
+
Number.isInteger(leg.legNumber) &&
|
|
365
|
+
leg.legNumber > 0 &&
|
|
366
|
+
'mode' in leg &&
|
|
367
|
+
typeof leg.mode === 'string' &&
|
|
368
|
+
['AIR', 'OCEAN', 'RAIL', 'ROAD'].includes(leg.mode) &&
|
|
369
|
+
['carrierId', 'carrierName'].every(
|
|
370
|
+
(key) =>
|
|
371
|
+
key in leg &&
|
|
372
|
+
(Reflect.get(leg, key) === null || typeof Reflect.get(leg, key) === 'string')
|
|
373
|
+
) &&
|
|
374
|
+
['weightKg', 'volumeCbm', 'containerCount'].every(
|
|
375
|
+
(key) =>
|
|
376
|
+
key in leg &&
|
|
377
|
+
(Reflect.get(leg, key) === null ||
|
|
378
|
+
(typeof Reflect.get(leg, key) === 'number' &&
|
|
379
|
+
Number.isFinite(Reflect.get(leg, key))))
|
|
380
|
+
) &&
|
|
381
|
+
'containerType' in leg &&
|
|
382
|
+
(leg.containerType === null ||
|
|
383
|
+
(typeof leg.containerType === 'string' &&
|
|
384
|
+
['FCL_20FT', 'FCL_40FT', 'FCL_40FT_HC', 'LCL'].includes(leg.containerType)))
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { print } from 'graphql';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
SaveFreightCostsDocument,
|
|
4
|
+
SaveFreightCommercialCostsDocument,
|
|
5
|
+
} from '../../generated/freight-cost-operations';
|
|
3
6
|
import type { ReuseRecovery } from './reuseRecovery';
|
|
4
7
|
export const FREIGHT_RECOVERY_STORAGE = 'movethewheels.freight.unresolved.v1';
|
|
5
8
|
export function decodeFreightRecovery(raw: string | null): ReuseRecovery | null {
|
|
@@ -14,7 +17,8 @@ export function decodeFreightRecovery(raw: string | null): ReuseRecovery | null
|
|
|
14
17
|
!('scope' in value) ||
|
|
15
18
|
!('input' in value) ||
|
|
16
19
|
!('operation' in value) ||
|
|
17
|
-
value.operation !== print(SaveFreightCostsDocument)
|
|
20
|
+
(value.operation !== print(SaveFreightCostsDocument) &&
|
|
21
|
+
value.operation !== print(SaveFreightCommercialCostsDocument)) ||
|
|
18
22
|
!('key' in value) ||
|
|
19
23
|
typeof value.key !== 'string' ||
|
|
20
24
|
!value.key ||
|