@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,162 @@
1
+ import type {
2
+ InvoiceReceiptFragment,
3
+ InvoiceStructuralPreviewQuery,
4
+ } from '../../../generated/freight-invoice-operations';
5
+ import { freightLabel } from '../../utils/freightDraft';
6
+ import { invoiceMoney, invoiceRowDifference } from '../../utils/invoiceComparison';
7
+ import { reuseCard } from '../ReuseControls';
8
+
9
+ type Preview = InvoiceStructuralPreviewQuery['previewMtwFreightInvoice'];
10
+ export function InvoiceResults({ value }: { value: Preview | InvoiceReceiptFragment }) {
11
+ const { estimate, outcome } = value;
12
+ return (
13
+ <section className={reuseCard} aria-label="Invoice comparison results">
14
+ <h2 className="text-lg font-semibold">Recorded-fact comparison</h2>
15
+ <p>
16
+ Exact {estimate.kind.toLowerCase()} {estimate.estimateReference}, version {estimate.version}
17
+ . Source {estimate.sourceReference} / {estimate.sourceRevision}.
18
+ </p>
19
+ <p>
20
+ Quote date {estimate.quotedAt ?? 'Unknown'}; source validity{' '}
21
+ {estimate.validFrom ?? 'Unknown'} to {estimate.validUntil ?? 'Unknown'}. Acceptance is not
22
+ established by this comparison.
23
+ </p>
24
+ {'recordable' in value && (
25
+ <p role="status">
26
+ Structural preview only.{' '}
27
+ {value.recordable
28
+ ? 'Source structure is recordable; current permission and Files eligibility are checked when recording.'
29
+ : `Not recordable: ${value.blockers.map(freightLabel).join(', ')}.`}
30
+ </p>
31
+ )}
32
+ <p className="font-medium">
33
+ {outcome.allLinesMatched
34
+ ? 'All explicitly compared rows and expected coverage agree under this method.'
35
+ : 'Differences or unknown coverage remain.'}
36
+ </p>
37
+ <p>
38
+ This is not an all-in cost, contracted-rate audit, liability finding, invoice approval or
39
+ payment.
40
+ </p>
41
+ {outcome.rows.map((row, index) => (
42
+ <article className="min-w-0 space-y-2 rounded border p-3" key={row.invoiceLineId}>
43
+ <h3 className="font-medium">
44
+ Row {index + 1}:{' '}
45
+ {value.facts.rows.find((source) => source.id === row.invoiceLineId)?.description ??
46
+ 'Reported charge'}
47
+ </h3>
48
+ <p>
49
+ {row.estimateLineId
50
+ ? 'Mapped to the selected estimate row.'
51
+ : 'No estimate row mapped.'}
52
+ </p>
53
+ <p>{row.flags.map(freightLabel).join(' · ')}</p>
54
+ {row.reasons.length > 0 && (
55
+ <p>Unknown reasons: {row.reasons.map(freightLabel).join(', ')}</p>
56
+ )}
57
+ <p>Comparable amount difference: {invoiceRowDifference(value, row.invoiceLineId)}</p>
58
+ </article>
59
+ ))}
60
+ {outcome.missingExpectedLineIds.map((id) => (
61
+ <p key={id}>Missing expected line: {id}. An unknown value is not treated as zero.</p>
62
+ ))}
63
+ {outcome.unknownExpectedCoverage.map((row) => (
64
+ <p key={row.estimateLineId}>
65
+ Unknown expected-charge coverage: {row.estimateLineId}. This is not a proven missing
66
+ charge.
67
+ </p>
68
+ ))}
69
+ <section className="space-y-2">
70
+ <h3 className="font-semibold">Comparable pairs only</h3>
71
+ {outcome.comparableSubtotals.length === 0 && <p>No bounded comparable subtotal.</p>}
72
+ {outcome.comparableSubtotals.map((group) => (
73
+ <p key={JSON.stringify([group.currency, group.payerReference])}>
74
+ Payer{' '}
75
+ {value.facts.rows.find((row) => row.payerReference === group.payerReference)
76
+ ?.payerLabel ?? group.payerReference}
77
+ : estimate {invoiceMoney(group.estimateMinor, group.currency)}, invoice{' '}
78
+ {invoiceMoney(group.invoiceMinor, group.currency)}, difference{' '}
79
+ {invoiceMoney(group.deltaMinor, group.currency)}. Included rows{' '}
80
+ {group.invoiceLineIds
81
+ .map(
82
+ (id) =>
83
+ value.facts.rows.find((row) => row.id === id)?.description ?? 'Reported charge'
84
+ )
85
+ .join(', ')}
86
+ . Other rows are omitted.
87
+ </p>
88
+ ))}
89
+ </section>
90
+ {(
91
+ [
92
+ ['Declared estimate subtotals', value.estimateDeclaredSubtotals],
93
+ ['Declared invoice subtotals', value.invoiceDeclaredSubtotals],
94
+ ] as const
95
+ ).map(([label, groups]) => (
96
+ <section key={label} className="space-y-2">
97
+ <h3 className="font-semibold">{label}</h3>
98
+ {groups.map((group, index) => (
99
+ <p key={JSON.stringify([group.currency, group.payerReference, index])}>
100
+ Payer {group.payerLabel ?? 'Unknown'} ({group.payerReference ?? 'Unknown reference'});{' '}
101
+ known {invoiceMoney(group.knownAmountMinor, group.currency)}; total{' '}
102
+ {invoiceMoney(group.totalAmountMinor, group.currency)}.
103
+ </p>
104
+ ))}
105
+ </section>
106
+ ))}
107
+ </section>
108
+ );
109
+ }
110
+ export function InvoiceHistoryRecord({ record }: { record: InvoiceReceiptFragment }) {
111
+ return (
112
+ <section className="min-w-0 space-y-4 break-words">
113
+ <h2 className="text-lg font-semibold">Immutable comparison revision {record.revision}</h2>
114
+ <p>
115
+ Invoice {record.facts.invoiceReference}; supplier{' '}
116
+ {record.facts.supplierLabel ?? 'Unknown label'} ({record.facts.supplierReference}). Source{' '}
117
+ {record.facts.sourceReference} / {record.facts.sourceRevision}; invoice date{' '}
118
+ {record.facts.invoiceDate ?? 'Unknown'}.
119
+ </p>
120
+ <p>
121
+ Recorded {record.recordedAt} by {record.createdBy}, {freightLabel(record.actorProvenance)};
122
+ on behalf of {record.onBehalfOf ?? 'Not declared'}. Method {record.methodVersion}.
123
+ </p>
124
+ {record.correctionReason && (
125
+ <p>
126
+ Correction reason: {record.correctionReason}; predecessor {record.previousComparisonId}.
127
+ </p>
128
+ )}
129
+ <details>
130
+ <summary className="min-h-11 cursor-pointer font-medium">
131
+ Original reported rows and exact evidence
132
+ </summary>
133
+ <div className="space-y-3">
134
+ {record.facts.rows.map((row) => (
135
+ <article key={row.id} className="rounded border p-3">
136
+ <h3>
137
+ {row.description} ({row.id})
138
+ </h3>
139
+ <p>
140
+ Mapping {row.estimateLineId ?? 'Unmatched'}; amount{' '}
141
+ {invoiceMoney(row.amountMinor, row.currency)}; payer {row.payerLabel ?? 'Unknown'} (
142
+ {row.payerReference ?? 'Unknown reference'}).
143
+ </p>
144
+ <p>
145
+ Quantity {row.quantity ?? 'Unknown'} {row.unit ?? 'unknown unit'}; supplied rate{' '}
146
+ {row.unitRateMajor ?? 'Unknown'}. Invoice amount was not recalculated.
147
+ </p>
148
+ {row.zeroReason && <p>Zero reason: {row.zeroReason}</p>}
149
+ </article>
150
+ ))}
151
+ {record.evidence.map((e) => (
152
+ <p className="break-all" key={e.versionId}>
153
+ File {e.fileId}, exact version {e.versionId}; SHA256 {e.sha256Hash}; checked{' '}
154
+ {e.checkedAt}. Point-in-time eligibility only.
155
+ </p>
156
+ ))}
157
+ </div>
158
+ </details>
159
+ <InvoiceResults value={record} />
160
+ </section>
161
+ );
162
+ }
@@ -42,7 +42,7 @@ export interface CreateDriverInput {
42
42
  phone?: string;
43
43
  licenseNumber?: string;
44
44
  licenseClass?: string;
45
- licenseExpiry?: string;
45
+ licenseExpiry?: string | null;
46
46
  /** DriverStatus enum value, e.g. `AVAILABLE`, `OFF_DUTY`. */
47
47
  status?: string;
48
48
  vehicleId?: string | null;
@@ -11,7 +11,11 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
11
11
  import { executeGraphQL } from '../utils/graphqlClient';
12
12
 
13
13
  export type FleetAlertType =
14
- 'LICENSE_EXPIRED' | 'LICENSE_EXPIRING' | 'MAINTENANCE_OVERDUE' | 'MAINTENANCE_DUE_SOON';
14
+ | 'LICENSE_UNKNOWN'
15
+ | 'LICENSE_EXPIRED'
16
+ | 'LICENSE_EXPIRING'
17
+ | 'MAINTENANCE_OVERDUE'
18
+ | 'MAINTENANCE_DUE_SOON';
15
19
  export type FleetAlertSeverity = 'CRITICAL' | 'WARNING';
16
20
  export type FleetAlertSubjectType = 'VEHICLE' | 'DRIVER';
17
21
 
@@ -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);
@@ -0,0 +1,227 @@
1
+ import { useLayoutEffect, useRef, useState } from 'react';
2
+ import { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';
3
+ import {
4
+ InvoiceComparisonExactDocument,
5
+ RecordInvoiceComparisonDocument,
6
+ RecoverInvoiceComparisonDocument,
7
+ type CompareMtwFreightInvoiceInput,
8
+ type InvoiceComparisonExactQuery,
9
+ type InvoiceEstimatePinFragment,
10
+ type InvoiceReceiptFragment,
11
+ type RecordInvoiceComparisonMutation,
12
+ type RecoverInvoiceComparisonQuery,
13
+ } from '../../generated/freight-invoice-operations';
14
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
15
+ import { resolveGraphqlUrl } from '../utils/graphqlClient';
16
+ import {
17
+ INVOICE_RECOVERY_STORAGE,
18
+ decodeInvoiceIntent,
19
+ encodeInvoiceIntent,
20
+ invoiceCanonical,
21
+ invoiceRecordInput,
22
+ matchesInvoiceReceipt,
23
+ sameInvoiceScope,
24
+ type InvoiceIntent,
25
+ type InvoiceScope,
26
+ } from '../utils/invoiceComparison';
27
+ import { useConditionApi } from './useContainerCondition';
28
+
29
+ function retained() {
30
+ try {
31
+ const raw = sessionStorage.getItem(INVOICE_RECOVERY_STORAGE),
32
+ intent = decodeInvoiceIntent(raw);
33
+ return { intent, blocked: raw !== null && intent === null };
34
+ } catch {
35
+ return { intent: null, blocked: true };
36
+ }
37
+ }
38
+ /** Canonical visible context only; never decode credentials or claim the full server context. */
39
+ export function useInvoiceComparisonScope(canRead: boolean): InvoiceScope | null {
40
+ const owner = useMoveTheWheels(),
41
+ active = useActiveContext();
42
+ if (
43
+ !canRead ||
44
+ !owner.currentUser?.id ||
45
+ !owner.workspaceId ||
46
+ active.workspaceId !== owner.workspaceId ||
47
+ !active.organizationId ||
48
+ (owner.tenantId && active.tenantId !== owner.tenantId)
49
+ )
50
+ return null;
51
+ return {
52
+ actorId: owner.currentUser.id,
53
+ workspaceId: owner.workspaceId,
54
+ tenantId: active.tenantId,
55
+ organizationId: active.organizationId,
56
+ projectId: active.projectId,
57
+ gateway: resolveGraphqlUrl(owner.apiGatewayUrl),
58
+ };
59
+ }
60
+ /** One original business intent. UNKNOWN permits reads only, never a retry/new key or automatic resend. */
61
+ export function useInvoiceComparisonRecovery(
62
+ scope: InvoiceScope | null,
63
+ allowsWrite: () => boolean
64
+ ) {
65
+ const request = useConditionApi('SINGLE_ATTEMPT'),
66
+ [initial] = useState(retained);
67
+ const [pending, setPending] = useState<InvoiceIntent | null>(initial.intent),
68
+ pendingRef = useRef(pending);
69
+ const [blocked, setBlocked] = useState(initial.blocked),
70
+ running = useRef(false);
71
+ const [busy, setBusy] = useState(false),
72
+ [revision, setRevision] = useState(0);
73
+ const scopeKey = invoiceCanonical(scope),
74
+ currentScope = useRef(scope);
75
+ const [feedback, setFeedback] = useState<{
76
+ scopeKey: string;
77
+ message: string;
78
+ error: boolean;
79
+ } | null>(null);
80
+ useLayoutEffect(() => {
81
+ currentScope.current = scope;
82
+ return () => {
83
+ currentScope.current = null;
84
+ };
85
+ }, [scopeKey]);
86
+ const tell = (message: string, error = true) => setFeedback({ scopeKey, message, error });
87
+ const confirm = (receipt: InvoiceReceiptFragment | null | undefined, intent: InvoiceIntent) => {
88
+ if (
89
+ !receipt ||
90
+ !matchesInvoiceReceipt(receipt, intent) ||
91
+ !sameInvoiceScope(intent.scope, currentScope.current)
92
+ )
93
+ throw new Error('UNCONFIRMED');
94
+ const saved = decodeInvoiceIntent(sessionStorage.getItem(INVOICE_RECOVERY_STORAGE));
95
+ if (!saved || invoiceCanonical(saved) !== invoiceCanonical(intent))
96
+ throw new Error('RETAINED_INTENT_CHANGED');
97
+ sessionStorage.removeItem(INVOICE_RECOVERY_STORAGE);
98
+ pendingRef.current = null;
99
+ setPending(null);
100
+ setBlocked(false);
101
+ setRevision((v) => v + 1);
102
+ tell('The exact immutable comparison receipt is confirmed.', false);
103
+ return receipt;
104
+ };
105
+ const run = async (
106
+ supplied: Omit<CompareMtwFreightInvoiceInput, 'idempotencyKey'>,
107
+ estimate: InvoiceEstimatePinFragment
108
+ ) => {
109
+ if (running.current || pendingRef.current || blocked) {
110
+ tell('Resolve the original request before starting another.');
111
+ return null;
112
+ }
113
+ if (!scope || !sameInvoiceScope(scope, currentScope.current) || !allowsWrite()) {
114
+ tell(
115
+ 'Current shipment update permission and workspace context are required. Nothing was sent.'
116
+ );
117
+ return null;
118
+ }
119
+ let intent: InvoiceIntent;
120
+ try {
121
+ if (sessionStorage.getItem(INVOICE_RECOVERY_STORAGE) !== null) throw new Error('EXISTING');
122
+ intent = {
123
+ version: 1,
124
+ scope: structuredClone(scope),
125
+ input: invoiceRecordInput({ ...supplied, idempotencyKey: crypto.randomUUID() }),
126
+ estimate: structuredClone(estimate),
127
+ };
128
+ sessionStorage.setItem(INVOICE_RECOVERY_STORAGE, encodeInvoiceIntent(intent));
129
+ pendingRef.current = intent;
130
+ setPending(intent);
131
+ } catch {
132
+ tell(
133
+ 'The original request could not be retained safely. Nothing was sent; reload and inspect any retained request.'
134
+ );
135
+ return null;
136
+ }
137
+ running.current = true;
138
+ setBusy(true);
139
+ setFeedback(null);
140
+ try {
141
+ // No await occurs between this final scope/permission check and the one product dispatch.
142
+ if (!sameInvoiceScope(intent.scope, currentScope.current) || !allowsWrite()) {
143
+ const saved = decodeInvoiceIntent(sessionStorage.getItem(INVOICE_RECOVERY_STORAGE));
144
+ if (saved && invoiceCanonical(saved) === invoiceCanonical(intent)) {
145
+ sessionStorage.removeItem(INVOICE_RECOVERY_STORAGE);
146
+ pendingRef.current = null;
147
+ setPending(null);
148
+ }
149
+ tell('Context or permission changed before dispatch. Nothing was sent.');
150
+ return null;
151
+ }
152
+ const result = await request<RecordInvoiceComparisonMutation>(
153
+ RecordInvoiceComparisonDocument,
154
+ { input: intent.input }
155
+ );
156
+ return confirm(result.compareMtwFreightInvoice, intent);
157
+ } catch {
158
+ tell(
159
+ 'Save outcome is UNKNOWN. Keep the original intent and use read-only recovery; do not resend it.'
160
+ );
161
+ return null;
162
+ } finally {
163
+ running.current = false;
164
+ setBusy(false);
165
+ }
166
+ };
167
+ const reconcile = async () => {
168
+ const intent = pendingRef.current;
169
+ if (running.current || !intent) return null;
170
+ if (!sameInvoiceScope(intent.scope, currentScope.current)) {
171
+ tell(
172
+ 'Return to the original account, organization, workspace and project to inspect its request.'
173
+ );
174
+ return null;
175
+ }
176
+ running.current = true;
177
+ setBusy(true);
178
+ setFeedback(null);
179
+ try {
180
+ const found = await request<RecoverInvoiceComparisonQuery>(RecoverInvoiceComparisonDocument, {
181
+ shipmentId: intent.input.shipmentId,
182
+ requestKey: intent.input.idempotencyKey,
183
+ });
184
+ const receipt = found.mtwFreightInvoiceComparisonByRequestKey;
185
+ if (!receipt) {
186
+ tell(
187
+ 'No original-context receipt was returned. This does not prove no effect; the request stays UNKNOWN and cannot be resent.'
188
+ );
189
+ return null;
190
+ }
191
+ if (!matchesInvoiceReceipt(receipt, intent)) throw new Error('MISMATCH');
192
+ if (!sameInvoiceScope(intent.scope, currentScope.current)) throw new Error('CONTEXT_CHANGED');
193
+ const exact = await request<InvoiceComparisonExactQuery>(InvoiceComparisonExactDocument, {
194
+ shipmentId: intent.input.shipmentId,
195
+ id: receipt.id,
196
+ });
197
+ if (
198
+ !matchesInvoiceReceipt(exact.mtwFreightInvoiceComparison, intent) ||
199
+ exact.mtwFreightInvoiceComparison.requestFingerprint !== receipt.requestFingerprint ||
200
+ invoiceCanonical(exact.mtwFreightInvoiceComparison) !== invoiceCanonical(receipt)
201
+ )
202
+ throw new Error('MISMATCH');
203
+ return confirm(exact.mtwFreightInvoiceComparison, intent);
204
+ } catch {
205
+ tell(
206
+ 'Read-only recovery could not confirm the exact original receipt. Keep the intent; no save was sent.'
207
+ );
208
+ return null;
209
+ } finally {
210
+ running.current = false;
211
+ setBusy(false);
212
+ }
213
+ };
214
+ return {
215
+ run,
216
+ reconcile,
217
+ busy,
218
+ pending,
219
+ blocked,
220
+ uncertain: pending !== null || blocked,
221
+ revision,
222
+ scopeKey,
223
+ canReconcile: !!pending && sameInvoiceScope(pending.scope, scope),
224
+ feedback: feedback?.scopeKey === scopeKey ? feedback : null,
225
+ refresh: () => setRevision((v) => v + 1),
226
+ };
227
+ }
@@ -61,6 +61,11 @@ import { useDebouncedValue } from '../hooks/useDebouncedValue';
61
61
  import { useRefetchOnLeaveEdit } from '../hooks/useRefetchOnLeaveEdit';
62
62
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
63
63
  import { formatCurrency, formatDate } from '../utils/formatters';
64
+ import {
65
+ driverLicenseExpiryInput,
66
+ driverLicenseExpiryUpdate,
67
+ parseDriverLicenseExpiry,
68
+ } from '../utils/driverLicenseExpiry';
64
69
  import { joinPath } from '../utils/navigation';
65
70
  import { FormError } from '../components/FormError';
66
71
  import { DocumentsCard } from '../components/DocumentsCard';
@@ -163,7 +168,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
163
168
  phone: driver.phone || '',
164
169
  licenseNumber: driver.licenseNumber || '',
165
170
  licenseClass: driver.licenseType || '',
166
- licenseExpiry: toDateInput(driver.licenseExpiry),
171
+ licenseExpiry: driverLicenseExpiryInput(driver.licenseExpiry),
167
172
  status: driver.status,
168
173
  vehicleId: driver.assignedVehicle || '',
169
174
  vehicleIds: driver.vehicleIds,
@@ -186,7 +191,9 @@ function NewDriverForm({ editId }: { editId?: string }) {
186
191
  phone: form.phone.trim() || undefined,
187
192
  licenseNumber: form.licenseNumber.trim() || undefined,
188
193
  licenseClass: form.licenseClass.trim() || undefined,
189
- licenseExpiry: fromDateInput(form.licenseExpiry),
194
+ licenseExpiry: isEditing
195
+ ? driverLicenseExpiryUpdate(form.licenseExpiry, driver?.licenseExpiry)
196
+ : parseDriverLicenseExpiry(form.licenseExpiry),
190
197
  status: toApiDriverStatus(form.status),
191
198
  vehicleId: form.vehicleId || null,
192
199
  vehicleIds: form.vehicleIds,
@@ -417,14 +424,33 @@ function NewDriverForm({ editId }: { editId?: string }) {
417
424
  placeholder="Class A"
418
425
  />
419
426
  </div>
420
- <div className="space-y-2">
421
- <Label htmlFor="licenseExpiry">License Expiry</Label>
427
+ <div className="space-y-2 md:col-span-3">
428
+ <div className="flex items-center justify-between gap-2">
429
+ <Label htmlFor="licenseExpiry">Licence expiry (UTC)</Label>
430
+ {form.licenseExpiry && (
431
+ <Button
432
+ type="button"
433
+ variant="ghost"
434
+ size="sm"
435
+ aria-label="Clear recorded licence expiry"
436
+ onClick={() => setForm((prev) => ({ ...prev, licenseExpiry: '' }))}
437
+ >
438
+ Clear
439
+ </Button>
440
+ )}
441
+ </div>
422
442
  <Input
423
443
  id="licenseExpiry"
424
- type="date"
444
+ type="datetime-local"
445
+ step="0.001"
446
+ aria-describedby="licenseExpiryHelp"
425
447
  value={form.licenseExpiry}
426
448
  onChange={(e) => setForm((prev) => ({ ...prev, licenseExpiry: e.target.value }))}
427
449
  />
450
+ <p id="licenseExpiryHelp" className="text-xs text-text-secondary">
451
+ Enter the recorded UTC time. Blank means expiry not recorded; clearing removes it.
452
+ A recorded expiry does not certify legal driving eligibility.
453
+ </p>
428
454
  </div>
429
455
  </div>
430
456
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -765,8 +791,8 @@ export default function DriverDetailsPage() {
765
791
  }
766
792
  >
767
793
  <PagePurpose icon={<Users size={18} />}>
768
- A driver&apos;s full profile — licensing, assigned vehicle, documents and HR record. Use it
769
- to confirm someone is road-ready and compliant before assigning them an order or route.
794
+ Review the recorded licensing, assigned vehicle, documents and HR facts before assignment.
795
+ These records do not certify legal driving eligibility or duty-hours availability.
770
796
  </PagePurpose>
771
797
 
772
798
  <NextSteps
@@ -822,9 +848,9 @@ export default function DriverDetailsPage() {
822
848
  {driver.licenseNumber || '—'}
823
849
  {driver.licenseType ? ` - ${driver.licenseType}` : ''}
824
850
  </p>
825
- <p className="text-sm text-text-secondary">
851
+ <p className="text-sm text-text-secondary break-all">
826
852
  {driver.licenseExpiry
827
- ? `Expires: ${formatDate(driver.licenseExpiry)}`
853
+ ? `Recorded expiry (UTC): ${driver.licenseExpiry.toISOString()}`
828
854
  : 'Expiry not recorded'}
829
855
  </p>
830
856
  </div>
@@ -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
  }
@@ -328,6 +335,7 @@ function FreightWorkbench({ actions }: { actions: ReturnType<typeof useFreightRe
328
335
  key={row.id}
329
336
  type="button"
330
337
  variant="outline"
338
+ className="h-auto w-full max-w-full justify-start whitespace-normal break-all text-left"
331
339
  onClick={() => setSelected(row.id)}
332
340
  >
333
341
  {row.facts.title} / {row.estimateReference} / version {row.version} —{' '}