@openlup/platform 0.21.0 → 0.22.0

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 (263) hide show
  1. package/CHANGELOG.md +210 -47
  2. package/README.md +27 -4
  3. package/dist/composition/composeBundle.d.ts +7 -0
  4. package/dist/composition/composeBundle.js +2 -0
  5. package/dist/composition/fulfillmentContribution.d.ts +21 -0
  6. package/dist/composition/fulfillmentContribution.js +19 -0
  7. package/dist/composition/index.d.ts +1 -0
  8. package/dist/composition/index.js +1 -0
  9. package/dist/fulfillment/adapterConformance.d.ts +3 -0
  10. package/dist/fulfillment/adapterConformance.js +223 -0
  11. package/dist/fulfillment/bindings.d.ts +3 -0
  12. package/dist/fulfillment/bindings.js +29 -0
  13. package/dist/fulfillment/commerceFulfillmentHandlers.d.ts +29 -0
  14. package/dist/fulfillment/commerceFulfillmentHandlers.js +184 -0
  15. package/dist/fulfillment/commerceFulfillmentReadModel.d.ts +86 -0
  16. package/dist/fulfillment/commerceFulfillmentReadModel.js +131 -0
  17. package/dist/fulfillment/contracts/alignment.d.ts +25 -0
  18. package/dist/fulfillment/contracts/alignment.js +1 -0
  19. package/dist/fulfillment/contracts/bindings.d.ts +51 -0
  20. package/dist/fulfillment/contracts/bindings.js +1 -0
  21. package/dist/fulfillment/contracts/capabilities.d.ts +29 -0
  22. package/dist/fulfillment/contracts/capabilities.js +1 -0
  23. package/dist/fulfillment/contracts/conformance.d.ts +93 -0
  24. package/dist/fulfillment/contracts/conformance.js +1 -0
  25. package/dist/fulfillment/contracts/customerAlignment.d.ts +6 -0
  26. package/dist/fulfillment/contracts/customerAlignment.js +1 -0
  27. package/dist/fulfillment/contracts/dispatch.d.ts +105 -0
  28. package/dist/fulfillment/contracts/dispatch.js +1 -0
  29. package/dist/fulfillment/contracts/index.d.ts +18 -0
  30. package/dist/fulfillment/contracts/index.js +1 -0
  31. package/dist/fulfillment/contracts/label.d.ts +35 -0
  32. package/dist/fulfillment/contracts/label.js +1 -0
  33. package/dist/fulfillment/contracts/nativeUnsupported.d.ts +6 -0
  34. package/dist/fulfillment/contracts/nativeUnsupported.js +1 -0
  35. package/dist/fulfillment/contracts/observation.d.ts +52 -0
  36. package/dist/fulfillment/contracts/observation.js +1 -0
  37. package/dist/fulfillment/contracts/operator.d.ts +64 -0
  38. package/dist/fulfillment/contracts/operator.js +1 -0
  39. package/dist/fulfillment/contracts/read.d.ts +110 -0
  40. package/dist/fulfillment/contracts/read.js +1 -0
  41. package/dist/fulfillment/contracts/readback.d.ts +38 -0
  42. package/dist/fulfillment/contracts/readback.js +1 -0
  43. package/dist/fulfillment/contracts/replacement.d.ts +34 -0
  44. package/dist/fulfillment/contracts/replacement.js +1 -0
  45. package/dist/fulfillment/contracts/store.d.ts +86 -0
  46. package/dist/fulfillment/contracts/store.js +1 -0
  47. package/dist/fulfillment/contracts/sync.d.ts +170 -0
  48. package/dist/fulfillment/contracts/sync.js +1 -0
  49. package/dist/fulfillment/currentFulfillmentParcel.d.ts +2 -0
  50. package/dist/fulfillment/currentFulfillmentParcel.js +1 -0
  51. package/dist/fulfillment/delayReadModel.d.ts +11 -0
  52. package/dist/fulfillment/delayReadModel.js +45 -0
  53. package/dist/fulfillment/deliveryAlignmentReadModel.d.ts +4 -0
  54. package/dist/fulfillment/deliveryAlignmentReadModel.js +12 -0
  55. package/dist/fulfillment/dispatchProjection.d.ts +7 -0
  56. package/dist/fulfillment/dispatchProjection.js +18 -0
  57. package/dist/fulfillment/fulfillmentObservationEffects.d.ts +66 -0
  58. package/dist/fulfillment/fulfillmentObservationEffects.js +36 -0
  59. package/dist/fulfillment/handlerSchemas.d.ts +647 -0
  60. package/dist/fulfillment/handlerSchemas.js +213 -0
  61. package/dist/fulfillment/handlerSupport.d.ts +33 -0
  62. package/dist/fulfillment/handlerSupport.js +25 -0
  63. package/dist/fulfillment/index.d.ts +37 -0
  64. package/dist/fulfillment/index.js +37 -0
  65. package/dist/fulfillment/labelLifecycle.d.ts +24 -0
  66. package/dist/fulfillment/labelLifecycle.js +9 -0
  67. package/dist/fulfillment/lowStockEvidenceHandler.d.ts +36 -0
  68. package/dist/fulfillment/lowStockEvidenceHandler.js +46 -0
  69. package/dist/fulfillment/migrations.d.ts +27 -0
  70. package/dist/fulfillment/migrations.js +8 -0
  71. package/dist/fulfillment/nativeUnsupported.d.ts +10 -0
  72. package/dist/fulfillment/nativeUnsupported.js +18 -0
  73. package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +2 -0
  74. package/dist/fulfillment/normalizedFulfillmentFacts.js +1 -0
  75. package/dist/fulfillment/observationProjection.d.ts +11 -0
  76. package/dist/fulfillment/observationProjection.js +37 -0
  77. package/dist/fulfillment/omnipack/capabilities.d.ts +3 -0
  78. package/dist/fulfillment/omnipack/capabilities.js +16 -0
  79. package/dist/fulfillment/omnipack/client.d.ts +37 -0
  80. package/dist/fulfillment/omnipack/client.js +162 -0
  81. package/dist/fulfillment/omnipack/index.d.ts +52 -0
  82. package/dist/fulfillment/omnipack/index.js +349 -0
  83. package/dist/fulfillment/omnipack/mappers.d.ts +73 -0
  84. package/dist/fulfillment/omnipack/mappers.js +177 -0
  85. package/dist/fulfillment/omnipack/observation.d.ts +21 -0
  86. package/dist/fulfillment/omnipack/observation.js +57 -0
  87. package/dist/fulfillment/omnipack/omnipackDispatchPayload.d.ts +36 -0
  88. package/dist/fulfillment/omnipack/omnipackDispatchPayload.js +63 -0
  89. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.d.ts +83 -0
  90. package/dist/fulfillment/omnipack/omnipackOutboundOrderPayload.js +113 -0
  91. package/dist/fulfillment/omnipack/omnipackProductPackPayload.d.ts +11 -0
  92. package/dist/fulfillment/omnipack/omnipackProductPackPayload.js +38 -0
  93. package/dist/fulfillment/omnipack/omnipackProductPayload.d.ts +18 -0
  94. package/dist/fulfillment/omnipack/omnipackProductPayload.js +51 -0
  95. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.d.ts +1 -0
  96. package/dist/fulfillment/omnipack/omnipackReconciliationPayload.js +3 -0
  97. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.d.ts +4 -0
  98. package/dist/fulfillment/omnipack/omnipackStatusVocabulary.js +87 -0
  99. package/dist/fulfillment/omnipack/omnipackWebhookPayload.d.ts +9 -0
  100. package/dist/fulfillment/omnipack/omnipackWebhookPayload.js +76 -0
  101. package/dist/fulfillment/omnipack/productSyncMappers.d.ts +18 -0
  102. package/dist/fulfillment/omnipack/productSyncMappers.js +43 -0
  103. package/dist/fulfillment/omnipack/providerError.d.ts +25 -0
  104. package/dist/fulfillment/omnipack/providerError.js +47 -0
  105. package/dist/fulfillment/omnipack/trackingReferences.d.ts +16 -0
  106. package/dist/fulfillment/omnipack/trackingReferences.js +52 -0
  107. package/dist/fulfillment/paidFulfillmentRecovery.d.ts +67 -0
  108. package/dist/fulfillment/paidFulfillmentRecovery.js +154 -0
  109. package/dist/fulfillment/paidHandoff.d.ts +27 -0
  110. package/dist/fulfillment/paidHandoff.js +86 -0
  111. package/dist/fulfillment/paidHandoffPorts.d.ts +24 -0
  112. package/dist/fulfillment/paidHandoffPorts.js +3 -0
  113. package/dist/fulfillment/postgresDeliveryAlignment.d.ts +4 -0
  114. package/dist/fulfillment/postgresDeliveryAlignment.js +12 -0
  115. package/dist/fulfillment/postgresDispatchStore.d.ts +16 -0
  116. package/dist/fulfillment/postgresDispatchStore.js +44 -0
  117. package/dist/fulfillment/postgresEvidenceReadPort.d.ts +25 -0
  118. package/dist/fulfillment/postgresEvidenceReadPort.js +45 -0
  119. package/dist/fulfillment/postgresPaidFulfillmentRecovery.d.ts +25 -0
  120. package/dist/fulfillment/postgresPaidFulfillmentRecovery.js +26 -0
  121. package/dist/fulfillment/postgresPaidHandoffStore.d.ts +4 -0
  122. package/dist/fulfillment/postgresPaidHandoffStore.js +10 -0
  123. package/dist/fulfillment/postgresShipmentSpine.d.ts +63 -0
  124. package/dist/fulfillment/postgresShipmentSpine.js +232 -0
  125. package/dist/fulfillment/postgresStockSyncPort.d.ts +8 -0
  126. package/dist/fulfillment/postgresStockSyncPort.js +63 -0
  127. package/dist/fulfillment/productReconciliationEvidenceHandler.d.ts +36 -0
  128. package/dist/fulfillment/productReconciliationEvidenceHandler.js +50 -0
  129. package/dist/fulfillment/providerCapabilities.d.ts +9 -0
  130. package/dist/fulfillment/providerCapabilities.js +17 -0
  131. package/dist/fulfillment/providerCommandLedger.d.ts +143 -0
  132. package/dist/fulfillment/providerCommandLedger.js +14 -0
  133. package/dist/fulfillment/providerDispatchAcceptance.d.ts +26 -0
  134. package/dist/fulfillment/providerDispatchAcceptance.js +23 -0
  135. package/dist/fulfillment/providerDispatchError.d.ts +6 -0
  136. package/dist/fulfillment/providerDispatchError.js +5 -0
  137. package/dist/fulfillment/providerDispatchWorker.d.ts +31 -0
  138. package/dist/fulfillment/providerDispatchWorker.js +172 -0
  139. package/dist/fulfillment/providerProductSyncWorker.d.ts +28 -0
  140. package/dist/fulfillment/providerProductSyncWorker.js +122 -0
  141. package/dist/fulfillment/providerReconciliationError.d.ts +9 -0
  142. package/dist/fulfillment/providerReconciliationError.js +17 -0
  143. package/dist/fulfillment/providerReconciliationTerminalSkip.d.ts +2 -0
  144. package/dist/fulfillment/providerReconciliationTerminalSkip.js +6 -0
  145. package/dist/fulfillment/providerReconciliationTimestamp.d.ts +1 -0
  146. package/dist/fulfillment/providerReconciliationTimestamp.js +27 -0
  147. package/dist/fulfillment/providerReconciliationWorker.d.ts +82 -0
  148. package/dist/fulfillment/providerReconciliationWorker.js +151 -0
  149. package/dist/fulfillment/providerStockAvailability.d.ts +13 -0
  150. package/dist/fulfillment/providerStockAvailability.js +51 -0
  151. package/dist/fulfillment/providerStockSyncWorker.d.ts +37 -0
  152. package/dist/fulfillment/providerStockSyncWorker.js +186 -0
  153. package/dist/fulfillment/providerTrackingReferences.d.ts +15 -0
  154. package/dist/fulfillment/providerTrackingReferences.js +23 -0
  155. package/dist/fulfillment/replacementRefusals.d.ts +5 -0
  156. package/dist/fulfillment/replacementRefusals.js +38 -0
  157. package/dist/fulfillment/replacementShipmentHandler.d.ts +5 -0
  158. package/dist/fulfillment/replacementShipmentHandler.js +46 -0
  159. package/dist/fulfillment/schemaCapabilities.d.ts +13 -0
  160. package/dist/fulfillment/schemaCapabilities.js +7 -0
  161. package/dist/fulfillment/shipmentsOverviewHandler.d.ts +16 -0
  162. package/dist/fulfillment/shipmentsOverviewHandler.js +54 -0
  163. package/dist/fulfillment/statusMap.d.ts +2 -0
  164. package/dist/fulfillment/statusMap.js +1 -0
  165. package/dist/fulfillment/trackingProgress.d.ts +1 -0
  166. package/dist/fulfillment/trackingProgress.js +1 -0
  167. package/dist/fulfillment/verifiedIngress.d.ts +52 -0
  168. package/dist/fulfillment/verifiedIngress.js +66 -0
  169. package/dist/payments/stripe/index.d.ts +25 -0
  170. package/dist/payments/stripe/index.js +25 -0
  171. package/dist/payments/tpay/index.d.ts +30 -0
  172. package/dist/payments/tpay/index.js +30 -0
  173. package/package.json +17 -2
  174. package/release-gates.json +44 -3
  175. package/sql/20260808210000_fulfillment_shipment_rail.sql +810 -0
  176. package/sql/20260819150000_fulfillment_shipment_sequence.sql +98 -0
  177. package/sql/20261022080000_fulfillment_command_ledger.sql +786 -0
  178. package/sql/20261022090000_fulfillment_delivery_alignment.sql +1106 -0
  179. package/src/composition/composeBundle.ts +4 -0
  180. package/src/composition/fulfillmentContribution.ts +28 -0
  181. package/src/composition/index.ts +1 -0
  182. package/src/fulfillment/adapterConformance.ts +195 -0
  183. package/src/fulfillment/bindings.ts +26 -0
  184. package/src/fulfillment/commerceFulfillmentHandlers.ts +263 -0
  185. package/src/fulfillment/commerceFulfillmentReadModel.ts +257 -0
  186. package/src/fulfillment/contracts/alignment.ts +13 -0
  187. package/src/fulfillment/contracts/bindings.ts +30 -0
  188. package/src/fulfillment/contracts/capabilities.ts +15 -0
  189. package/src/fulfillment/contracts/conformance.ts +61 -0
  190. package/src/fulfillment/contracts/customerAlignment.ts +4 -0
  191. package/src/fulfillment/contracts/dispatch.ts +58 -0
  192. package/src/fulfillment/contracts/index.ts +18 -0
  193. package/src/fulfillment/contracts/label.ts +9 -0
  194. package/src/fulfillment/contracts/nativeUnsupported.ts +6 -0
  195. package/src/fulfillment/contracts/observation.ts +18 -0
  196. package/src/fulfillment/contracts/operator.ts +31 -0
  197. package/src/fulfillment/contracts/read.ts +42 -0
  198. package/src/fulfillment/contracts/readback.ts +11 -0
  199. package/src/fulfillment/contracts/replacement.ts +15 -0
  200. package/src/fulfillment/contracts/store.ts +26 -0
  201. package/src/fulfillment/contracts/sync.ts +61 -0
  202. package/src/fulfillment/currentFulfillmentParcel.ts +2 -0
  203. package/src/fulfillment/delayReadModel.ts +60 -0
  204. package/src/fulfillment/deliveryAlignmentReadModel.ts +15 -0
  205. package/src/fulfillment/dispatchProjection.ts +20 -0
  206. package/src/fulfillment/fulfillmentObservationEffects.ts +86 -0
  207. package/src/fulfillment/handlerSchemas.ts +255 -0
  208. package/src/fulfillment/handlerSupport.ts +25 -0
  209. package/src/fulfillment/index.ts +38 -0
  210. package/src/fulfillment/labelLifecycle.ts +11 -0
  211. package/src/fulfillment/lowStockEvidenceHandler.ts +60 -0
  212. package/src/fulfillment/migrations.ts +8 -0
  213. package/src/fulfillment/nativeUnsupported.ts +23 -0
  214. package/src/fulfillment/normalizedFulfillmentFacts.ts +2 -0
  215. package/src/fulfillment/observationProjection.ts +41 -0
  216. package/src/fulfillment/omnipack/capabilities.ts +18 -0
  217. package/src/fulfillment/omnipack/client.ts +258 -0
  218. package/src/fulfillment/omnipack/index.ts +312 -0
  219. package/src/fulfillment/omnipack/mappers.ts +261 -0
  220. package/src/fulfillment/omnipack/observation.ts +61 -0
  221. package/src/fulfillment/omnipack/omnipackDispatchPayload.ts +93 -0
  222. package/src/fulfillment/omnipack/omnipackOutboundOrderPayload.ts +194 -0
  223. package/src/fulfillment/omnipack/omnipackProductPackPayload.ts +47 -0
  224. package/src/fulfillment/omnipack/omnipackProductPayload.ts +67 -0
  225. package/src/fulfillment/omnipack/omnipackReconciliationPayload.ts +7 -0
  226. package/src/fulfillment/omnipack/omnipackStatusVocabulary.ts +103 -0
  227. package/src/fulfillment/omnipack/omnipackWebhookPayload.ts +73 -0
  228. package/src/fulfillment/omnipack/productSyncMappers.ts +74 -0
  229. package/src/fulfillment/omnipack/providerError.ts +64 -0
  230. package/src/fulfillment/omnipack/trackingReferences.ts +77 -0
  231. package/src/fulfillment/paidFulfillmentRecovery.ts +252 -0
  232. package/src/fulfillment/paidHandoff.ts +122 -0
  233. package/src/fulfillment/paidHandoffPorts.ts +21 -0
  234. package/src/fulfillment/postgresDeliveryAlignment.ts +14 -0
  235. package/src/fulfillment/postgresDispatchStore.ts +55 -0
  236. package/src/fulfillment/postgresEvidenceReadPort.ts +59 -0
  237. package/src/fulfillment/postgresPaidFulfillmentRecovery.ts +29 -0
  238. package/src/fulfillment/postgresPaidHandoffStore.ts +11 -0
  239. package/src/fulfillment/postgresShipmentSpine.ts +321 -0
  240. package/src/fulfillment/postgresStockSyncPort.ts +65 -0
  241. package/src/fulfillment/productReconciliationEvidenceHandler.ts +64 -0
  242. package/src/fulfillment/providerCapabilities.ts +18 -0
  243. package/src/fulfillment/providerCommandLedger.ts +53 -0
  244. package/src/fulfillment/providerDispatchAcceptance.ts +31 -0
  245. package/src/fulfillment/providerDispatchError.ts +7 -0
  246. package/src/fulfillment/providerDispatchWorker.ts +131 -0
  247. package/src/fulfillment/providerProductSyncWorker.ts +154 -0
  248. package/src/fulfillment/providerReconciliationError.ts +20 -0
  249. package/src/fulfillment/providerReconciliationTerminalSkip.ts +8 -0
  250. package/src/fulfillment/providerReconciliationTimestamp.ts +34 -0
  251. package/src/fulfillment/providerReconciliationWorker.ts +150 -0
  252. package/src/fulfillment/providerStockAvailability.ts +60 -0
  253. package/src/fulfillment/providerStockSyncWorker.ts +222 -0
  254. package/src/fulfillment/providerTrackingReferences.ts +31 -0
  255. package/src/fulfillment/replacementRefusals.ts +43 -0
  256. package/src/fulfillment/replacementShipmentHandler.ts +47 -0
  257. package/src/fulfillment/schemaCapabilities.ts +7 -0
  258. package/src/fulfillment/shipmentsOverviewHandler.ts +73 -0
  259. package/src/fulfillment/statusMap.ts +2 -0
  260. package/src/fulfillment/trackingProgress.ts +1 -0
  261. package/src/fulfillment/verifiedIngress.ts +80 -0
  262. package/src/payments/stripe/index.ts +30 -0
  263. package/src/payments/tpay/index.ts +36 -0
@@ -0,0 +1,13 @@
1
+ import type { FulfillmentStockSyncPort, LocalStock } from "./contracts/index.js";
2
+ type StockLine = Awaited<ReturnType<FulfillmentStockSyncPort["readLocalInventoryStock"]>>[number];
3
+ export declare function aggregateAtpCompatibleStock(lines: readonly StockLine[], now: Date): Map<string, LocalStock>;
4
+ /** Cap the host-local movement window at UTC today, preserving donor overlap. */
5
+ export declare function stockMovementWindow(cursor: {
6
+ lastMovementOccurredAt: string | null;
7
+ lastStockSyncedAt: string | null;
8
+ } | null, now: Date, timeZone: string): {
9
+ startLocalDate: string;
10
+ endLocalDate: string;
11
+ };
12
+ export declare function emptyLocal(sku: string): LocalStock;
13
+ export {};
@@ -0,0 +1,51 @@
1
+ export function aggregateAtpCompatibleStock(lines, now) {
2
+ const requestedAt = now.toISOString();
3
+ const balances = new Map();
4
+ for (const line of lines.filter((item) => isAtpUsable(item, requestedAt))) {
5
+ const current = balances.get(line.skuRef) ?? emptyLocal(line.skuRef);
6
+ current.onHand += line.quantity.onHand;
7
+ current.reserved += line.quantity.reserved;
8
+ current.unavailable += line.quantity.unavailable;
9
+ current.safetyStock += line.quantity.safetyStock;
10
+ balances.set(line.skuRef, current);
11
+ }
12
+ return balances;
13
+ }
14
+ /** Cap the host-local movement window at UTC today, preserving donor overlap. */
15
+ export function stockMovementWindow(cursor, now, timeZone) {
16
+ const startAt = new Date(cursor?.lastMovementOccurredAt ?? cursor?.lastStockSyncedAt ?? now.toISOString());
17
+ const safeStartAt = Number.isNaN(startAt.getTime()) ? now : startAt;
18
+ const endLocalDate = minDate(localDate(now, timeZone), utcDate(now));
19
+ return {
20
+ startLocalDate: minDate(localDate(safeStartAt, timeZone), endLocalDate),
21
+ endLocalDate,
22
+ };
23
+ }
24
+ function utcDate(date) {
25
+ return date.toISOString().slice(0, 10);
26
+ }
27
+ function minDate(a, b) {
28
+ return a <= b ? a : b;
29
+ }
30
+ function isAtpUsable(line, requestedAt) {
31
+ if (!line.fulfillable || line.locationStatus !== "active")
32
+ return false;
33
+ if (line.lotStatus && line.lotStatus !== "available")
34
+ return false;
35
+ if (line.expiresAt && Date.parse(line.expiresAt) <= Date.parse(requestedAt))
36
+ return false;
37
+ return true;
38
+ }
39
+ export function emptyLocal(sku) {
40
+ return { skuRef: sku, onHand: 0, reserved: 0, unavailable: 0, safetyStock: 0 };
41
+ }
42
+ function localDate(date, timeZone) {
43
+ const parts = new Intl.DateTimeFormat("en", {
44
+ timeZone,
45
+ year: "numeric",
46
+ month: "2-digit",
47
+ day: "2-digit",
48
+ }).formatToParts(date);
49
+ const byType = new Map(parts.map((part) => [part.type, part.value]));
50
+ return `${byType.get("year")}-${byType.get("month")}-${byType.get("day")}`;
51
+ }
@@ -0,0 +1,37 @@
1
+ import type { FulfillmentStockSyncPort, FulfillmentStockSyncProvider } from "./contracts/index.js";
2
+ type Threshold = Parameters<FulfillmentStockSyncPort["resolveLowStockEvidence"]>[0]["activeThresholdKinds"][number];
3
+ interface StockSyncResult {
4
+ ok: boolean;
5
+ checked: number;
6
+ updated: number;
7
+ mismatches: number;
8
+ reservationCoverage: number;
9
+ unclassified: number;
10
+ lowStock: number;
11
+ movements: number;
12
+ replayed: number;
13
+ failures: number;
14
+ providerCalls: number;
15
+ readBacks: number;
16
+ skipped: boolean;
17
+ syncRunId: string;
18
+ lastMovementOccurredAt: string | null;
19
+ reason?: string;
20
+ }
21
+ export declare function runFulfillmentStockSyncWorker(input: {
22
+ port: FulfillmentStockSyncPort;
23
+ provider: FulfillmentStockSyncProvider;
24
+ movementBatchSize: number;
25
+ staleSyncThresholdHours?: number;
26
+ now?: () => Date;
27
+ compatibility: {
28
+ syncRunId(now: Date): string;
29
+ staleEvidenceSku: string;
30
+ timeZone: string;
31
+ currentKey(run: string, sku: string): string;
32
+ snapshotKey(run: string, sku: string): string;
33
+ lowStockKey(sku: string, threshold: Threshold): string;
34
+ staleKey(lastSyncedAt: string): string;
35
+ };
36
+ }): Promise<StockSyncResult>;
37
+ export {};
@@ -0,0 +1,186 @@
1
+ import { aggregateAtpCompatibleStock, emptyLocal, stockMovementWindow } from "./providerStockAvailability.js";
2
+ export async function runFulfillmentStockSyncWorker(input) {
3
+ const now = input.now?.() ?? new Date();
4
+ const staleThresholdHours = input.staleSyncThresholdHours && input.staleSyncThresholdHours > 0
5
+ ? input.staleSyncThresholdHours
6
+ : 6;
7
+ const syncRunId = input.compatibility.syncRunId(now);
8
+ const result = {
9
+ ok: true,
10
+ checked: 0,
11
+ updated: 0,
12
+ mismatches: 0,
13
+ reservationCoverage: 0,
14
+ unclassified: 0,
15
+ lowStock: 0,
16
+ movements: 0,
17
+ replayed: 0,
18
+ failures: 0,
19
+ providerCalls: 0,
20
+ readBacks: 0,
21
+ skipped: false,
22
+ syncRunId,
23
+ lastMovementOccurredAt: null,
24
+ };
25
+ const previousCursor = await input.port.readCursor();
26
+ // Stale-sync alert: if the last successful sync aged past the threshold, open a stale_sync
27
+ // low-stock evidence row (evidence/warn — never blocks sales); a healthy run resolves it
28
+ // below. A never-synced state (no prior cursor) is not "stale".
29
+ const lastSyncedMs = previousCursor?.lastStockSyncedAt ? Date.parse(previousCursor.lastStockSyncedAt) : null;
30
+ const staleGapHours = lastSyncedMs !== null && !Number.isNaN(lastSyncedMs) ? (now.getTime() - lastSyncedMs) / 3_600_000 : null;
31
+ const syncIsStale = staleGapHours !== null && staleGapHours >= staleThresholdHours;
32
+ if (syncIsStale) {
33
+ await input.port.recordLowStockEvidence({
34
+ requestKey: input.compatibility.staleKey(previousCursor.lastStockSyncedAt),
35
+ skuRef: input.compatibility.staleEvidenceSku,
36
+ thresholdKind: "stale_sync",
37
+ severity: "warning",
38
+ providerForSaleQuantity: 0,
39
+ localAvailableQuantity: 0,
40
+ syncRunId,
41
+ });
42
+ }
43
+ await input.port.recordCursor({
44
+ status: "running",
45
+ lastStockSyncedAt: null,
46
+ lastMovementOccurredAt: null,
47
+ continuation: { nextRef: null, startLocalDate: null, endLocalDate: null },
48
+ coverageGapSkus: [], refusal: null,
49
+ });
50
+ const [localStock, providerStock] = await Promise.all([
51
+ input.port.readLocalInventoryStock(now.toISOString()),
52
+ input.provider.getStock(),
53
+ ]);
54
+ result.providerCalls += 1;
55
+ const stockItems = providerStock.filter((item) => item.skuRef.trim());
56
+ const skus = [...new Set(stockItems.map((item) => item.skuRef))];
57
+ const [inventoryClasses, activeReservations] = await Promise.all([
58
+ input.port.readSkuInventoryClasses(skus),
59
+ input.port.readActiveReservations(skus, now.toISOString()),
60
+ ]);
61
+ const movements = await input.provider.getStockMovements({ ...stockMovementWindow(previousCursor, now, input.compatibility.timeZone), size: input.movementBatchSize });
62
+ result.providerCalls += 1;
63
+ result.movements = movements.length;
64
+ result.lastMovementOccurredAt = latestOccurredAt(movements);
65
+ const localBySku = aggregateAtpCompatibleStock(localStock, now);
66
+ const coverageCandidates = [];
67
+ const currentCoverageGapSkus = new Set();
68
+ const packagingSkus = new Set();
69
+ const previousCoverageGapSkus = previousCursor?.status === "succeeded"
70
+ ? readStringSet(previousCursor.coverageGapSkus)
71
+ : new Set();
72
+ for (const stock of stockItems) {
73
+ try {
74
+ const local = localBySku.get(stock.skuRef) ?? emptyLocal(stock.skuRef);
75
+ const inventoryClass = inventoryClasses.get(stock.skuRef) ?? null;
76
+ const activeReserved = activeReservations.get(stock.skuRef) ?? 0;
77
+ const staleAfter = new Date(now.getTime() + staleThresholdHours * 3_600_000).toISOString();
78
+ const current = await input.port.recordProviderStockCurrent({
79
+ requestKey: input.compatibility.currentKey(syncRunId, stock.skuRef),
80
+ snapshot: stock, inventoryClass, lastSyncedAt: now.toISOString(), staleAfter,
81
+ });
82
+ const snapshot = await input.port.recordStockSnapshot({
83
+ requestKey: input.compatibility.snapshotKey(syncRunId, stock.skuRef),
84
+ snapshot: stock, local, mismatchKind: "none", inventoryClass,
85
+ });
86
+ result.checked += 1;
87
+ result.updated += (snapshot.replayed ? 0 : 1) + (current.replayed ? 0 : 1);
88
+ if (snapshot.replayed)
89
+ result.replayed += 1;
90
+ if (current.replayed)
91
+ result.replayed += 1;
92
+ if (snapshot.readBack)
93
+ result.readBacks += 1;
94
+ if (current.readBack)
95
+ result.readBacks += 1;
96
+ if (inventoryClass === null)
97
+ result.unclassified += 1;
98
+ if (inventoryClass === "packaging")
99
+ packagingSkus.add(stock.skuRef);
100
+ if (inventoryClass === "sellable") {
101
+ coverageCandidates.push({ stock, local, activeReserved });
102
+ if (activeReserved > stock.forSaleQuantity)
103
+ currentCoverageGapSkus.add(stock.skuRef);
104
+ }
105
+ }
106
+ catch (error) {
107
+ result.ok = false;
108
+ result.failures += 1;
109
+ result.reason = result.reason ?? "stock_sync_operation_failed";
110
+ }
111
+ }
112
+ if (result.ok) {
113
+ for (const { stock, local, activeReserved } of coverageCandidates) {
114
+ const localAvailable = availableQuantity(local);
115
+ const confirmedCoverageGap = currentCoverageGapSkus.has(stock.skuRef) && previousCoverageGapSkus.has(stock.skuRef);
116
+ const lowStockSignals = lowStockSignalsFor(stock, local, activeReserved, confirmedCoverageGap);
117
+ const activeThresholdKinds = lowStockSignals.map((signal) => signal.thresholdKind);
118
+ if (currentCoverageGapSkus.has(stock.skuRef) && !activeThresholdKinds.includes("reservation_coverage")) {
119
+ activeThresholdKinds.push("reservation_coverage");
120
+ }
121
+ for (const signal of lowStockSignals) {
122
+ const evidence = await input.port.recordLowStockEvidence({
123
+ requestKey: input.compatibility.lowStockKey(stock.skuRef, signal.thresholdKind),
124
+ skuRef: stock.skuRef, thresholdKind: signal.thresholdKind, severity: signal.severity,
125
+ providerForSaleQuantity: stock.forSaleQuantity, localAvailableQuantity: localAvailable, syncRunId,
126
+ });
127
+ if (evidence.replayed)
128
+ result.replayed += 1;
129
+ result.lowStock += evidence.replayed ? 0 : 1;
130
+ if (signal.thresholdKind === "reservation_coverage")
131
+ result.reservationCoverage += 1;
132
+ }
133
+ await input.port.resolveLowStockEvidence({
134
+ skuRef: stock.skuRef,
135
+ activeThresholdKinds,
136
+ syncRunId,
137
+ });
138
+ }
139
+ for (const sku of packagingSkus) {
140
+ await input.port.resolveLowStockEvidence({
141
+ skuRef: sku,
142
+ activeThresholdKinds: [],
143
+ syncRunId,
144
+ });
145
+ }
146
+ }
147
+ await input.port.recordCursor({
148
+ status: result.ok ? "succeeded" : "failed",
149
+ lastStockSyncedAt: result.ok ? now.toISOString() : null,
150
+ lastMovementOccurredAt: result.lastMovementOccurredAt,
151
+ continuation: { nextRef: null, startLocalDate: null, endLocalDate: null },
152
+ coverageGapSkus: result.ok ? [...currentCoverageGapSkus].sort() : [],
153
+ refusal: result.ok ? null : { reason: "io_failure", phase: "sync", retryable: true },
154
+ });
155
+ // Recovery: a healthy run (succeeded, not itself stale) closes any open stale_sync row.
156
+ if (result.ok && !syncIsStale) {
157
+ await input.port.resolveLowStockEvidence({
158
+ skuRef: input.compatibility.staleEvidenceSku,
159
+ activeThresholdKinds: [],
160
+ syncRunId,
161
+ });
162
+ }
163
+ return result;
164
+ }
165
+ function lowStockSignalsFor(stock, local, activeReserved, confirmedCoverageGap) {
166
+ const signals = [];
167
+ const remainingAfterReservations = Math.max(0, stock.forSaleQuantity - activeReserved);
168
+ if (local.safetyStock > 0 && remainingAfterReservations <= local.safetyStock) {
169
+ signals.push({ thresholdKind: "safety_stock", severity: remainingAfterReservations <= 0 ? "critical" : "warning" });
170
+ }
171
+ if (confirmedCoverageGap)
172
+ signals.push({ thresholdKind: "reservation_coverage", severity: "critical" });
173
+ return signals;
174
+ }
175
+ function readStringSet(value) {
176
+ return new Set(Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : []);
177
+ }
178
+ function availableQuantity(balance) {
179
+ return Math.max(0, balance.onHand - balance.reserved - balance.unavailable - balance.safetyStock);
180
+ }
181
+ function latestOccurredAt(movements) {
182
+ return movements
183
+ .map((movement) => movement.occurredAt)
184
+ .filter((value) => Boolean(value))
185
+ .sort((a, b) => Date.parse(b) - Date.parse(a))[0] ?? null;
186
+ }
@@ -0,0 +1,15 @@
1
+ import type { FulfillmentTrackingReference } from "./contracts/index.js";
2
+ export declare function trackingReferenceComplete(ref: FulfillmentTrackingReference): boolean;
3
+ /** Provider evidence wins as a unit; private carrier/URL codecs stay injected. */
4
+ export declare function enrichTrackingReference(ref: FulfillmentTrackingReference, deliveryCarrier: {
5
+ carrierRef: string | null;
6
+ serviceRef: string | null;
7
+ } | null, codecs: {
8
+ normalizeCarrier(input: {
9
+ carrierRef: string | null;
10
+ serviceRef: string | null;
11
+ }): string | null;
12
+ publicUrl(carrierRef: string | null, trackingNumber: string): string | null;
13
+ }): FulfillmentTrackingReference;
14
+ /** First occurrence wins, including its selection, after trimming the number. */
15
+ export declare function uniqueTrackingReferences(refs: readonly FulfillmentTrackingReference[]): FulfillmentTrackingReference[];
@@ -0,0 +1,23 @@
1
+ export function trackingReferenceComplete(ref) {
2
+ return Boolean(ref.carrierRef && ref.publicUrl && ref.serviceRef);
3
+ }
4
+ /** Provider evidence wins as a unit; private carrier/URL codecs stay injected. */
5
+ export function enrichTrackingReference(ref, deliveryCarrier, codecs) {
6
+ const providerCarrier = ref.carrierRef ?? codecs.normalizeCarrier(ref);
7
+ const carrierRef = providerCarrier ?? codecs.normalizeCarrier({ carrierRef: deliveryCarrier?.carrierRef ?? null, serviceRef: deliveryCarrier?.serviceRef ?? null });
8
+ return { trackingNumber: ref.trackingNumber, publicUrl: ref.publicUrl ?? codecs.publicUrl(carrierRef, ref.trackingNumber),
9
+ carrierRef, serviceRef: ref.serviceRef ?? (providerCarrier ? null : deliveryCarrier?.serviceRef ?? null) };
10
+ }
11
+ /** First occurrence wins, including its selection, after trimming the number. */
12
+ export function uniqueTrackingReferences(refs) {
13
+ const seen = new Set();
14
+ const result = [];
15
+ for (const ref of refs) {
16
+ const trackingNumber = ref.trackingNumber.trim();
17
+ if (!trackingNumber || seen.has(trackingNumber))
18
+ continue;
19
+ seen.add(trackingNumber);
20
+ result.push({ trackingNumber, publicUrl: ref.publicUrl ?? null, carrierRef: ref.carrierRef ?? null, serviceRef: ref.serviceRef ?? null });
21
+ }
22
+ return result;
23
+ }
@@ -0,0 +1,5 @@
1
+ export declare const FULFILLMENT_REPLACEMENT_COMMAND_REFUSALS: readonly ["invalid_input", "idempotency_conflict", "order_not_found", "order_not_replaceable", "missing_client", "missing_shipping_address", "shipping_address_not_found", "payment_not_succeeded", "subscription_cycle_not_paid", "no_parcel_to_replace", "undispatched_replacement_exists", "missing_order_items", "missing_catalog_sku", "missing_inventory_reservation", "blocked_by_payment_not_succeeded_hold", "blocked_by_inventory_review_hold", "blocked_by_risk_review_hold", "blocked_by_address_review_hold", "blocked_by_manual_support_hold"];
2
+ export declare const FULFILLMENT_REPLACEMENT_RESERVATION_REFUSALS: readonly ["inventory_external_provider_insufficient_available_stock", "inventory_external_provider_stock_stale", "inventory_external_provider_stock_missing", "inventory_external_provider_sku_not_found", "inventory_external_provider_location_missing", "inventory_reservation_insufficient_available_stock", "inventory_reservation_allocation_incomplete", "inventory_reservation_payment_status_not_reservable", "inventory_reservation_invalid_quantity"];
3
+ export declare const UNSPECIFIED_FULFILLMENT_REPLACEMENT_REFUSAL = "unspecified_refusal";
4
+ export declare const FULFILLMENT_REPLACEMENT_REFUSALS: readonly ["invalid_input", "idempotency_conflict", "order_not_found", "order_not_replaceable", "missing_client", "missing_shipping_address", "shipping_address_not_found", "payment_not_succeeded", "subscription_cycle_not_paid", "no_parcel_to_replace", "undispatched_replacement_exists", "missing_order_items", "missing_catalog_sku", "missing_inventory_reservation", "blocked_by_payment_not_succeeded_hold", "blocked_by_inventory_review_hold", "blocked_by_risk_review_hold", "blocked_by_address_review_hold", "blocked_by_manual_support_hold", "inventory_external_provider_insufficient_available_stock", "inventory_external_provider_stock_stale", "inventory_external_provider_stock_missing", "inventory_external_provider_sku_not_found", "inventory_external_provider_location_missing", "inventory_reservation_insufficient_available_stock", "inventory_reservation_allocation_incomplete", "inventory_reservation_payment_status_not_reservable", "inventory_reservation_invalid_quantity", "unspecified_refusal"];
5
+ export type FulfillmentReplacementRefusal = (typeof FULFILLMENT_REPLACEMENT_REFUSALS)[number];
@@ -0,0 +1,38 @@
1
+ export const FULFILLMENT_REPLACEMENT_COMMAND_REFUSALS = [
2
+ "invalid_input",
3
+ "idempotency_conflict",
4
+ "order_not_found",
5
+ "order_not_replaceable",
6
+ "missing_client",
7
+ "missing_shipping_address",
8
+ "shipping_address_not_found",
9
+ "payment_not_succeeded",
10
+ "subscription_cycle_not_paid",
11
+ "no_parcel_to_replace",
12
+ "undispatched_replacement_exists",
13
+ "missing_order_items",
14
+ "missing_catalog_sku",
15
+ "missing_inventory_reservation",
16
+ "blocked_by_payment_not_succeeded_hold",
17
+ "blocked_by_inventory_review_hold",
18
+ "blocked_by_risk_review_hold",
19
+ "blocked_by_address_review_hold",
20
+ "blocked_by_manual_support_hold",
21
+ ];
22
+ export const FULFILLMENT_REPLACEMENT_RESERVATION_REFUSALS = [
23
+ "inventory_external_provider_insufficient_available_stock",
24
+ "inventory_external_provider_stock_stale",
25
+ "inventory_external_provider_stock_missing",
26
+ "inventory_external_provider_sku_not_found",
27
+ "inventory_external_provider_location_missing",
28
+ "inventory_reservation_insufficient_available_stock",
29
+ "inventory_reservation_allocation_incomplete",
30
+ "inventory_reservation_payment_status_not_reservable",
31
+ "inventory_reservation_invalid_quantity",
32
+ ];
33
+ export const UNSPECIFIED_FULFILLMENT_REPLACEMENT_REFUSAL = "unspecified_refusal";
34
+ export const FULFILLMENT_REPLACEMENT_REFUSALS = [
35
+ ...FULFILLMENT_REPLACEMENT_COMMAND_REFUSALS,
36
+ ...FULFILLMENT_REPLACEMENT_RESERVATION_REFUSALS,
37
+ UNSPECIFIED_FULFILLMENT_REPLACEMENT_REFUSAL,
38
+ ];
@@ -0,0 +1,5 @@
1
+ import { type BaseDeps, type FulfillmentHandlerRequest } from './handlerSupport.js';
2
+ import type { FulfillmentReplacementPort } from './contracts/replacement.js';
3
+ export declare function createAdminCommerceOrderRequestReplacementShipmentHandler<Response>({ authorizeAdmin, respond, omsPort, }: BaseDeps<Response> & {
4
+ omsPort: FulfillmentReplacementPort;
5
+ }): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
@@ -0,0 +1,46 @@
1
+ import { adminCommerceOrderRequestReplacementShipmentRequestSchema, adminCommerceOrderRequestReplacementShipmentResponseSchema } from './handlerSchemas.js';
2
+ import { authorize, CommerceFulfillmentConflictError, isNativeUnsupported } from './handlerSupport.js';
3
+ export function createAdminCommerceOrderRequestReplacementShipmentHandler({ authorizeAdmin, respond, omsPort, }) {
4
+ const { sendMethodNotAllowed } = respond;
5
+ return async function handler(req, res) {
6
+ if (req.method !== "POST")
7
+ return sendMethodNotAllowed(res, ["POST"]);
8
+ const auth = await authorize(req, res, authorizeAdmin, respond);
9
+ if (!auth)
10
+ return;
11
+ const request = parseOmsRequest(res, respond, adminCommerceOrderRequestReplacementShipmentRequestSchema, req.body ?? {}, "Invalid admin commerce replacement shipment request");
12
+ if (request === undefined)
13
+ return;
14
+ await sendOmsMutationResponse(res, respond, () => omsPort.requestReplacementShipment({ ...request, actorUserId: auth.userId }), adminCommerceOrderRequestReplacementShipmentResponseSchema, "Admin commerce replacement shipment returned invalid response", "Admin commerce replacement shipment failed");
15
+ };
16
+ }
17
+ function parseOmsRequest(res, respond, schema, input, message) {
18
+ const request = schema.safeParse(input);
19
+ if (!request.success) {
20
+ respond.sendBffError(res, 'BAD_REQUEST', message, { details: request.error.flatten() });
21
+ return undefined;
22
+ }
23
+ return request.data;
24
+ }
25
+ async function sendOmsMutationResponse(res, respond, action, schema, invalidMessage, failureMessage) {
26
+ try {
27
+ const result = await action();
28
+ if (isNativeUnsupported(result)) {
29
+ respond.sendBffError(res, 'UPSTREAM_UNAVAILABLE', 'Native fulfillment operation unsupported', { details: result });
30
+ return;
31
+ }
32
+ const response = schema.safeParse(result);
33
+ if (!response.success) {
34
+ respond.sendBffError(res, 'INVALID_RESPONSE', invalidMessage);
35
+ return;
36
+ }
37
+ respond.sendBffSuccess(res, response.data);
38
+ }
39
+ catch (error) {
40
+ if (error instanceof CommerceFulfillmentConflictError) {
41
+ respond.sendBffError(res, 'CONFLICT', error.message, { details: error.details });
42
+ return;
43
+ }
44
+ respond.sendBffError(res, 'UPSTREAM_UNAVAILABLE', failureMessage);
45
+ }
46
+ }
@@ -0,0 +1,13 @@
1
+ /** Sparse projections require the full canonical graph and a trusted native binding. */
2
+ export declare const FULFILLMENT_NATIVE_CAPABILITIES: {
3
+ readonly shipment: "supported";
4
+ readonly commandFence: "supported";
5
+ readonly deliveryAlignment: "supported";
6
+ readonly stockEvidence: "supported";
7
+ readonly observationPersistence: "supported";
8
+ readonly localInventoryBalances: "host_required";
9
+ readonly dispatchContactPreparation: "host_required";
10
+ readonly productPacks: "unsupported";
11
+ readonly providerAccess: "unsupported";
12
+ readonly automaticReplacementConfirmation: "unsupported";
13
+ };
@@ -0,0 +1,7 @@
1
+ /** Sparse projections require the full canonical graph and a trusted native binding. */
2
+ export const FULFILLMENT_NATIVE_CAPABILITIES = {
3
+ shipment: 'supported', commandFence: 'supported', deliveryAlignment: 'supported',
4
+ stockEvidence: 'supported', observationPersistence: 'supported',
5
+ localInventoryBalances: 'host_required', dispatchContactPreparation: 'host_required',
6
+ productPacks: 'unsupported', providerAccess: 'unsupported', automaticReplacementConfirmation: 'unsupported',
7
+ };
@@ -0,0 +1,16 @@
1
+ import { type FulfillmentHandlerRequest, type FulfillmentHandlerResponder } from './handlerSupport.js';
2
+ import type { FulfillmentShipmentsOverviewPort, FulfillmentShipmentsOverviewResponse } from './contracts/read.js';
3
+ export interface FulfillmentShipmentsOverviewHandlerDeps<Response> {
4
+ respond: FulfillmentHandlerResponder<Response>;
5
+ overviewPort: FulfillmentShipmentsOverviewPort;
6
+ responseSchema: {
7
+ safeParse(input: unknown): {
8
+ success: true;
9
+ data: FulfillmentShipmentsOverviewResponse;
10
+ } | {
11
+ success: false;
12
+ };
13
+ };
14
+ authorizeAdmin: (req: FulfillmentHandlerRequest) => Promise<boolean>;
15
+ }
16
+ export declare function createFulfillmentShipmentsOverviewHandler<Response>({ respond, overviewPort, responseSchema: adminShipmentsOverviewResponseSchema, authorizeAdmin, }: FulfillmentShipmentsOverviewHandlerDeps<Response>): (req: FulfillmentHandlerRequest, res: Response) => Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { isNativeUnsupported } from './handlerSupport.js';
2
+ import { z } from 'zod';
3
+ const adminShipmentsOverviewRequestSchema = z.object({
4
+ shippedFilter: z.enum(['today', '7d', '30d', 'all']).default('7d'),
5
+ });
6
+ export function createFulfillmentShipmentsOverviewHandler({ respond, overviewPort, responseSchema: adminShipmentsOverviewResponseSchema, authorizeAdmin, }) {
7
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = respond;
8
+ return async function handler(req, res) {
9
+ if (req.method !== "GET") {
10
+ sendMethodNotAllowed(res, ["GET"]);
11
+ return;
12
+ }
13
+ let authorized = false;
14
+ try {
15
+ authorized = await authorizeAdmin(req);
16
+ }
17
+ catch {
18
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
19
+ return;
20
+ }
21
+ if (!authorized) {
22
+ sendBffError(res, "UNAUTHORIZED", "Admin session required");
23
+ return;
24
+ }
25
+ const request = adminShipmentsOverviewRequestSchema.safeParse({
26
+ shippedFilter: scalarQuery(req.query.shippedFilter),
27
+ });
28
+ if (!request.success) {
29
+ sendBffError(res, "BAD_REQUEST", "Invalid shipments overview request", {
30
+ details: request.error.flatten(),
31
+ });
32
+ return;
33
+ }
34
+ try {
35
+ const result = await overviewPort.getShipmentsOverview(request.data);
36
+ if (isNativeUnsupported(result)) {
37
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Native fulfillment operation unsupported", { details: result });
38
+ return;
39
+ }
40
+ const response = adminShipmentsOverviewResponseSchema.safeParse(result);
41
+ if (!response.success) {
42
+ sendBffError(res, "INVALID_RESPONSE", "Shipments overview returned invalid response");
43
+ return;
44
+ }
45
+ sendBffSuccess(res, response.data);
46
+ }
47
+ catch {
48
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Shipments overview read failed");
49
+ }
50
+ };
51
+ }
52
+ function scalarQuery(value) {
53
+ return Array.isArray(value) ? value[0] : value;
54
+ }
@@ -0,0 +1,2 @@
1
+ export { FULFILLMENT_STATUS_MAP, CANONICAL_LOCAL_FULFILLMENT_STATUSES, CUSTOMER_STEP_SEQUENCE, canonicalStatusToken, stepForToken, customerStepFromSignals, phaseIndexForStep, stepLabelKey, timelineLabelForStep, trackingPhaseKeyForStep, stepUsesTruckIcon } from "@openlup/core/fulfillment";
2
+ export type { FulfillmentStatusStage, FulfillmentStatusMap } from "@openlup/core/fulfillment";
@@ -0,0 +1 @@
1
+ export { FULFILLMENT_STATUS_MAP, CANONICAL_LOCAL_FULFILLMENT_STATUSES, CUSTOMER_STEP_SEQUENCE, canonicalStatusToken, stepForToken, customerStepFromSignals, phaseIndexForStep, stepLabelKey, timelineLabelForStep, trackingPhaseKeyForStep, stepUsesTruckIcon } from "@openlup/core/fulfillment";
@@ -0,0 +1 @@
1
+ export { fulfillmentProgressPresentation } from "@openlup/core/fulfillment";
@@ -0,0 +1 @@
1
+ export { fulfillmentProgressPresentation } from "@openlup/core/fulfillment";
@@ -0,0 +1,52 @@
1
+ import type { FulfillmentObservation, FulfillmentObservationPort, FulfillmentRefusal } from "./contracts/index.js";
2
+ import { applyFulfillmentObservationEffects } from "./fulfillmentObservationEffects.js";
3
+ type EffectInput = Parameters<typeof applyFulfillmentObservationEffects<FulfillmentObservation["fact"]["canonicalStatus"], "in_transit" | "delivered">>[0];
4
+ type DispatchRef = {
5
+ orderRef: string;
6
+ fulfillmentRef: string;
7
+ dispatchRef: string;
8
+ externalRef: string | null;
9
+ };
10
+ /** Adapter verification owns auth-before-parse; the host owns durable replay. */
11
+ export declare function verifyAndNormalizeFulfillmentObservation(input: {
12
+ enabled: boolean;
13
+ provider: FulfillmentObservationPort;
14
+ verification: Parameters<FulfillmentObservationPort["verifyAndNormalize"]>[0];
15
+ localPersistence: {
16
+ /** G17: atomic insert by eventKey; retries keep the first stored firstReceivedAt. */
17
+ recordObservation(observation: FulfillmentObservation): Promise<{
18
+ replayed: boolean;
19
+ }>;
20
+ /** Idempotent; only called after completion or a classified superseding conflict. */
21
+ markProcessed(eventKey: string, outcome: {
22
+ status: "processed" | "ignored";
23
+ refusal?: FulfillmentRefusal;
24
+ }): Promise<void>;
25
+ };
26
+ lifecycle: {
27
+ findDispatchRef(observation: FulfillmentObservation): Promise<DispatchRef | null>;
28
+ acknowledgeDispatchAcceptance(ref: DispatchRef, observation: FulfillmentObservation): Promise<void>;
29
+ recordStatusEvidence(ref: DispatchRef, observation: FulfillmentObservation): Promise<{
30
+ replayed: boolean;
31
+ }>;
32
+ effects: EffectInput["port"];
33
+ keys: {
34
+ stock(ref: string): string;
35
+ handoff(ref: string): string;
36
+ tracking(eventKey: string, number: string): string;
37
+ };
38
+ issueAccountingInvoice?(ref: string): Promise<void>;
39
+ classifyFailure(error: unknown): "stock_contention" | "state_conflict" | "other";
40
+ annotateStockContention?(eventKey: string, fulfillmentRef: string): Promise<void>;
41
+ };
42
+ }): Promise<{
43
+ kind: "refused";
44
+ refusal: FulfillmentRefusal;
45
+ } | {
46
+ kind: "processed" | "ignored" | "superseded";
47
+ observation: FulfillmentObservation;
48
+ replayed: boolean;
49
+ trackingRefs: number;
50
+ trackingReadBacks: number;
51
+ }>;
52
+ export {};