@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.
Files changed (113) hide show
  1. package/README.md +10 -0
  2. package/dist/MoveTheWheelsRoot.js +5 -0
  3. package/dist/MoveTheWheelsRoutes.js +25 -21
  4. package/dist/components/ContainerEvidencePicker.js +55 -55
  5. package/dist/components/FleetAlertsPanel.js +2 -2
  6. package/dist/components/freight-cost/FreightCommercialEditor.js +217 -0
  7. package/dist/components/freight-cost/FreightCommercialFactEditor.js +217 -0
  8. package/dist/components/freight-cost/FreightCommercialHistory.js +115 -0
  9. package/dist/components/freight-cost/FreightDraftForm.js +106 -91
  10. package/dist/components/freight-cost/FreightHistory.js +42 -40
  11. package/dist/components/freight-invoice/InvoiceForm.js +366 -0
  12. package/dist/components/freight-invoice/InvoiceResults.js +240 -0
  13. package/dist/generated/consolidation-operations.d.ts +195 -265
  14. package/dist/generated/container-condition-operations.d.ts +124 -187
  15. package/dist/generated/container-operations.d.ts +59 -85
  16. package/dist/generated/empty-reuse-operations.d.ts +165 -271
  17. package/dist/generated/free-time-operations.d.ts +120 -184
  18. package/dist/generated/freight-cost-operations.d.ts +955 -165
  19. package/dist/generated/freight-invoice-operations.d.ts +1447 -0
  20. package/dist/generated/logistics-procedure-operations.d.ts +100 -156
  21. package/dist/generated/pending-pick-operations.d.ts +769 -1025
  22. package/dist/generated/shipment-status-authority-operations.d.ts +82 -103
  23. package/dist/generated/stock-binding-operations.d.ts +188 -260
  24. package/dist/generated/stock-observation-operations.d.ts +103 -138
  25. package/dist/generated/tracking-quality-operations.d.ts +27 -41
  26. package/dist/generated/trip-utilization-operations.d.ts +137 -185
  27. package/dist/generated/wspace-operations.d.ts +628 -2272
  28. package/dist/generated/wspace-types.d.ts +4 -23
  29. package/dist/hooks/useFreightRecoveryAction.js +20 -20
  30. package/dist/hooks/useInvoiceComparisonRecovery.js +118 -0
  31. package/dist/movethewheels/components/ContainerEvidencePicker.d.ts +2 -1
  32. package/dist/movethewheels/components/freight-cost/FreightCommercialEditor.d.ts +8 -0
  33. package/dist/movethewheels/components/freight-cost/FreightCommercialFactEditor.d.ts +8 -0
  34. package/dist/movethewheels/components/freight-cost/FreightCommercialHistory.d.ts +4 -0
  35. package/dist/movethewheels/components/freight-cost/FreightDraftForm.d.ts +4 -2
  36. package/dist/movethewheels/components/freight-cost/FreightHistory.d.ts +2 -2
  37. package/dist/movethewheels/components/freight-invoice/InvoiceForm.d.ts +19 -0
  38. package/dist/movethewheels/components/freight-invoice/InvoiceResults.d.ts +9 -0
  39. package/dist/movethewheels/hooks/useFleet.d.ts +1 -1
  40. package/dist/movethewheels/hooks/useFleetIntelligence.d.ts +1 -1
  41. package/dist/movethewheels/hooks/useInvoiceComparisonRecovery.d.ts +22 -0
  42. package/dist/movethewheels/hooks/useProcedureSelection.d.ts +1 -3
  43. package/dist/movethewheels/pages/FreightInvoicePage.d.ts +10 -0
  44. package/dist/movethewheels/utils/driverLicenseExpiry.d.ts +6 -0
  45. package/dist/movethewheels/utils/freightCommercial.d.ts +44 -0
  46. package/dist/movethewheels/utils/invoiceComparison.d.ts +32 -0
  47. package/dist/pages/DriverDetailsPage.js +306 -282
  48. package/dist/pages/FreightEstimatePage.js +97 -92
  49. package/dist/pages/FreightInvoicePage.js +346 -0
  50. package/dist/src/generated/consolidation-operations.js +3961 -503
  51. package/dist/src/generated/container-condition-operations.js +2968 -206
  52. package/dist/src/generated/container-operations.js +997 -99
  53. package/dist/src/generated/empty-reuse-operations.js +4811 -747
  54. package/dist/src/generated/free-time-operations.js +2824 -233
  55. package/dist/src/generated/freight-cost-operations.js +4852 -323
  56. package/dist/src/generated/freight-invoice-operations.js +4894 -0
  57. package/dist/src/generated/logistics-procedure-operations.js +2707 -360
  58. package/dist/src/generated/pending-pick-operations.js +8244 -1196
  59. package/dist/src/generated/shipment-status-authority-operations.js +1157 -84
  60. package/dist/src/generated/stock-binding-operations.js +2779 -173
  61. package/dist/src/generated/stock-observation-operations.js +1274 -91
  62. package/dist/src/generated/tracking-quality-operations.js +407 -42
  63. package/dist/src/generated/trip-utilization-operations.js +1765 -138
  64. package/dist/src/generated/wspace-operations.js +428 -706
  65. package/dist/utils/driverLicenseExpiry.js +14 -0
  66. package/dist/utils/freightAcknowledgement.js +6 -5
  67. package/dist/utils/freightCommercial.js +194 -0
  68. package/dist/utils/freightRecovery.js +22 -22
  69. package/dist/utils/invoiceComparison.js +272 -0
  70. package/package.json +30 -29
  71. package/src/generated/consolidation-operations.ts +60 -559
  72. package/src/generated/container-condition-operations.ts +63 -265
  73. package/src/generated/container-operations.ts +37 -132
  74. package/src/generated/empty-reuse-operations.ts +83 -824
  75. package/src/generated/free-time-operations.ts +55 -284
  76. package/src/generated/freight-cost-operations.ts +373 -349
  77. package/src/generated/freight-invoice-operations.ts +830 -0
  78. package/src/generated/logistics-procedure-operations.ts +60 -414
  79. package/src/generated/pending-pick-operations.ts +48 -1239
  80. package/src/generated/shipment-status-authority-operations.ts +33 -113
  81. package/src/generated/stock-binding-operations.ts +29 -198
  82. package/src/generated/stock-observation-operations.ts +24 -111
  83. package/src/generated/tracking-quality-operations.ts +19 -57
  84. package/src/generated/trip-utilization-operations.ts +32 -166
  85. package/src/generated/wspace-operations.ts +284 -2906
  86. package/src/generated/wspace-types.ts +2 -8
  87. package/src/movethewheels/MoveTheWheelsRoot.tsx +6 -0
  88. package/src/movethewheels/MoveTheWheelsRoutes.tsx +2 -0
  89. package/src/movethewheels/components/ContainerEvidencePicker.tsx +12 -4
  90. package/src/movethewheels/components/FleetAlertsPanel.tsx +4 -2
  91. package/src/movethewheels/components/freight-cost/FreightCommercialEditor.tsx +236 -0
  92. package/src/movethewheels/components/freight-cost/FreightCommercialFactEditor.tsx +258 -0
  93. package/src/movethewheels/components/freight-cost/FreightCommercialHistory.tsx +90 -0
  94. package/src/movethewheels/components/freight-cost/FreightDraftForm.tsx +33 -4
  95. package/src/movethewheels/components/freight-cost/FreightHistory.tsx +4 -2
  96. package/src/movethewheels/components/freight-invoice/InvoiceForm.tsx +412 -0
  97. package/src/movethewheels/components/freight-invoice/InvoiceResults.tsx +162 -0
  98. package/src/movethewheels/hooks/useFleet.ts +1 -1
  99. package/src/movethewheels/hooks/useFleetIntelligence.ts +5 -1
  100. package/src/movethewheels/hooks/useFreightRecoveryAction.ts +2 -1
  101. package/src/movethewheels/hooks/useInvoiceComparisonRecovery.ts +227 -0
  102. package/src/movethewheels/pages/DriverDetailsPage.tsx +35 -9
  103. package/src/movethewheels/pages/FreightEstimatePage.tsx +15 -7
  104. package/src/movethewheels/pages/FreightInvoicePage.tsx +477 -0
  105. package/src/movethewheels/utils/driverLicenseExpiry.ts +25 -0
  106. package/src/movethewheels/utils/freightAcknowledgement.ts +3 -1
  107. package/src/movethewheels/utils/freightCommercial.ts +388 -0
  108. package/src/movethewheels/utils/freightRecovery.ts +6 -2
  109. package/src/movethewheels/utils/invoiceComparison.ts +520 -0
  110. package/src/operations/freight-cost/commercial.graphql +483 -0
  111. package/src/operations/freight-invoice/comparison.graphql +40 -0
  112. package/src/operations/wspace/freight-invoice-evidence.graphql +6 -0
  113. 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 { SaveFreightCostsDocument } from '../../generated/freight-cost-operations';
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 ||