@burdenoff/microfe-movethewheels 2026.1016.2 → 2026.1016.3

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 (31) hide show
  1. package/dist/components/freight-cost/FreightCommercialEditor.js +217 -0
  2. package/dist/components/freight-cost/FreightCommercialFactEditor.js +217 -0
  3. package/dist/components/freight-cost/FreightCommercialHistory.js +115 -0
  4. package/dist/components/freight-cost/FreightDraftForm.js +106 -91
  5. package/dist/components/freight-cost/FreightHistory.js +42 -40
  6. package/dist/generated/freight-cost-operations.d.ts +959 -0
  7. package/dist/hooks/useFreightRecoveryAction.js +20 -20
  8. package/dist/movethewheels/components/freight-cost/FreightCommercialEditor.d.ts +8 -0
  9. package/dist/movethewheels/components/freight-cost/FreightCommercialFactEditor.d.ts +8 -0
  10. package/dist/movethewheels/components/freight-cost/FreightCommercialHistory.d.ts +4 -0
  11. package/dist/movethewheels/components/freight-cost/FreightDraftForm.d.ts +4 -2
  12. package/dist/movethewheels/components/freight-cost/FreightHistory.d.ts +2 -2
  13. package/dist/movethewheels/utils/freightCommercial.d.ts +44 -0
  14. package/dist/pages/FreightEstimatePage.js +96 -92
  15. package/dist/src/generated/freight-cost-operations.js +495 -6
  16. package/dist/utils/freightAcknowledgement.js +6 -5
  17. package/dist/utils/freightCommercial.js +194 -0
  18. package/dist/utils/freightRecovery.js +22 -22
  19. package/package.json +4 -3
  20. package/src/generated/freight-cost-operations.ts +831 -1
  21. package/src/movethewheels/components/freight-cost/FreightCommercialEditor.tsx +236 -0
  22. package/src/movethewheels/components/freight-cost/FreightCommercialFactEditor.tsx +258 -0
  23. package/src/movethewheels/components/freight-cost/FreightCommercialHistory.tsx +90 -0
  24. package/src/movethewheels/components/freight-cost/FreightDraftForm.tsx +33 -4
  25. package/src/movethewheels/components/freight-cost/FreightHistory.tsx +4 -2
  26. package/src/movethewheels/hooks/useFreightRecoveryAction.ts +2 -1
  27. package/src/movethewheels/pages/FreightEstimatePage.tsx +14 -7
  28. package/src/movethewheels/utils/freightAcknowledgement.ts +3 -1
  29. package/src/movethewheels/utils/freightCommercial.ts +388 -0
  30. package/src/movethewheels/utils/freightRecovery.ts +6 -2
  31. package/src/operations/freight-cost/commercial.graphql +483 -0
@@ -5,6 +5,7 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
5
5
  import { matchesFreightAcknowledgement } from '../utils/freightAcknowledgement';
6
6
  import {
7
7
  SaveFreightCostsDocument,
8
+ SaveFreightCommercialCostsDocument,
8
9
  type SaveFreightCostsMutation,
9
10
  } from '../../generated/freight-cost-operations';
10
11
  import {
@@ -18,7 +19,7 @@ import {
18
19
  encodeFreightRecovery,
19
20
  FREIGHT_RECOVERY_STORAGE,
20
21
  } from '../utils/freightRecovery';
21
- const documents = [SaveFreightCostsDocument];
22
+ const documents = [SaveFreightCostsDocument, SaveFreightCommercialCostsDocument];
22
23
  function loadRecovery(): { attempt: ReuseRecovery | null; blocked: boolean } {
23
24
  try {
24
25
  const raw = sessionStorage.getItem(FREIGHT_RECOVERY_STORAGE);
@@ -13,16 +13,18 @@ import {
13
13
  FreightShipmentsDocument,
14
14
  FreightShipmentDocument,
15
15
  FreightHistoryDocument,
16
- FreightVersionDocument,
16
+ FreightCommercialVersionDocument,
17
17
  PreviewFreightCostsDocument,
18
18
  SaveFreightCostsDocument,
19
+ SaveFreightCommercialCostsDocument,
19
20
  type FreightShipmentsQuery,
20
21
  type FreightShipmentQuery,
21
22
  type FreightHistoryQuery,
22
- type FreightVersionQuery,
23
+ type FreightCommercialVersionQuery,
23
24
  type MtwFreightFactsInput,
24
25
  type PreviewFreightCostsQuery,
25
26
  } from '../../generated/freight-cost-operations';
27
+ import type { CommercialCapture } from '../utils/freightCommercial';
26
28
  import type { SelectedEvidence } from '../components/ContainerEvidencePicker';
27
29
  export default function FreightEstimatePage() {
28
30
  const actions = useFreightRecoveryAction();
@@ -93,8 +95,8 @@ function FreightWorkbench({ actions }: { actions: ReturnType<typeof useFreightRe
93
95
  : null,
94
96
  actions.revision
95
97
  );
96
- const exact = useConditionQuery<FreightVersionQuery>(
97
- FreightVersionDocument,
98
+ const exact = useConditionQuery<FreightCommercialVersionQuery>(
99
+ FreightCommercialVersionDocument,
98
100
  historyTarget && selected ? { shipmentId: historyTarget.shipmentId, id: selected } : null,
99
101
  actions.revision
100
102
  );
@@ -112,7 +114,11 @@ function FreightWorkbench({ actions }: { actions: ReturnType<typeof useFreightRe
112
114
  })) ?? [];
113
115
  if (shipment && !options.some((row) => row.id === shipment.id))
114
116
  options.unshift({ id: shipment.id, label: `${shipment.label} — selected outside this page` });
115
- const save = async (facts: MtwFreightFactsInput, evidence: SelectedEvidence[]) => {
117
+ const save = async (
118
+ facts: MtwFreightFactsInput,
119
+ evidence: SelectedEvidence[],
120
+ commercialCapture?: CommercialCapture
121
+ ) => {
116
122
  if (preflight.current || paused) return;
117
123
  if (!draft) throw new Error('Review the shipment and current cost history first.');
118
124
  preflight.current = true;
@@ -139,8 +145,8 @@ function FreightWorkbench({ actions }: { actions: ReturnType<typeof useFreightRe
139
145
  evidence.map(({ fileId, versionId }) => ({ fileId, versionId }))
140
146
  );
141
147
  const result = await actions.run(
142
- SaveFreightCostsDocument,
143
- input,
148
+ commercialCapture ? SaveFreightCommercialCostsDocument : SaveFreightCostsDocument,
149
+ commercialCapture ? { ...input, commercialCapture } : input,
144
150
  'Sourced cost version recorded.'
145
151
  );
146
152
  if (result && active.current) {
@@ -250,6 +256,7 @@ function FreightWorkbench({ actions }: { actions: ReturnType<typeof useFreightRe
250
256
  {draft && !changed && !mismatch && (
251
257
  <FreightDraftForm
252
258
  key={draft.generation}
259
+ shipmentId={draft.shipmentId}
253
260
  disabled={
254
261
  paused || current.loading || latest.loading || !!current.error || !!latest.error
255
262
  }
@@ -1,3 +1,4 @@
1
+ import { matchesCommercialCapture } from './freightCommercial';
1
2
  /** Validate the minimum immutable receipt identity before resolving a retained request. */
2
3
  export function matchesFreightAcknowledgement(
3
4
  result: unknown,
@@ -26,6 +27,7 @@ export function matchesFreightAcknowledgement(
26
27
  input.expectedVersion >= 0 &&
27
28
  receipt.version === input.expectedVersion + 1 &&
28
29
  'previousVersionId' in receipt &&
29
- receipt.previousVersionId === input.expectedPreviousVersionId
30
+ receipt.previousVersionId === input.expectedPreviousVersionId &&
31
+ (input.commercialCapture == null || matchesCommercialCapture(receipt, input))
30
32
  );
31
33
  }
@@ -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 ||