@openlup/core 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.
- package/CHANGELOG.md +20 -343
- package/README.md +9 -1
- package/dist/fulfillment/currentFulfillmentParcel.d.ts +16 -0
- package/dist/fulfillment/currentFulfillmentParcel.js +70 -0
- package/dist/fulfillment/index.d.ts +16 -0
- package/dist/fulfillment/index.js +8 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.d.ts +14 -0
- package/dist/fulfillment/normalizedFulfillmentFacts.js +18 -0
- package/dist/fulfillment/statusMap.d.ts +220 -0
- package/dist/fulfillment/statusMap.js +214 -0
- package/dist/fulfillment/trackingProgress.d.ts +4 -0
- package/dist/fulfillment/trackingProgress.js +42 -0
- package/dist/fulfillment/types.d.ts +15 -0
- package/dist/fulfillment/types.js +1 -0
- package/package.json +1 -1
- package/release-gates.json +7 -3
- package/src/fulfillment/currentFulfillmentParcel.ts +94 -0
- package/src/fulfillment/index.ts +20 -0
- package/src/fulfillment/normalizedFulfillmentFacts.ts +52 -0
- package/src/fulfillment/statusMap.ts +253 -0
- package/src/fulfillment/trackingProgress.ts +49 -0
- package/src/fulfillment/types.ts +12 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseNormalizedFulfillmentFact,
|
|
3
|
+
parseTransitionConflict,
|
|
4
|
+
type CompatibleNormalizedFulfillmentFact as CoreCompatibleNormalizedFulfillmentFact,
|
|
5
|
+
type FulfillmentTransitionSnapshot as CoreFulfillmentTransitionSnapshot,
|
|
6
|
+
type NormalizedFulfillmentFact as CoreNormalizedFulfillmentFact,
|
|
7
|
+
type NormalizedFulfillmentFactV0 as CoreNormalizedFulfillmentFactV0,
|
|
8
|
+
type NormalizedFulfillmentFactV1 as CoreNormalizedFulfillmentFactV1,
|
|
9
|
+
type TransitionConflict as CoreTransitionConflict,
|
|
10
|
+
} from "./facts.js";
|
|
11
|
+
import {
|
|
12
|
+
CANONICAL_LOCAL_FULFILLMENT_STATUSES,
|
|
13
|
+
} from "./statusMap.js";
|
|
14
|
+
import type { CanonicalLocalFulfillmentStatus } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/** Application binding: core evidence narrowed to the single Layer B status canon. */
|
|
17
|
+
export type CanonicalNormalizedFulfillmentFact =
|
|
18
|
+
CoreNormalizedFulfillmentFact<CanonicalLocalFulfillmentStatus>;
|
|
19
|
+
export type CanonicalNormalizedFulfillmentFactV1 =
|
|
20
|
+
CoreNormalizedFulfillmentFactV1<CanonicalLocalFulfillmentStatus>;
|
|
21
|
+
export type CanonicalNormalizedFulfillmentFactV0 =
|
|
22
|
+
CoreNormalizedFulfillmentFactV0<CanonicalLocalFulfillmentStatus>;
|
|
23
|
+
export type CanonicalCompatibleNormalizedFulfillmentFact =
|
|
24
|
+
CoreCompatibleNormalizedFulfillmentFact<CanonicalLocalFulfillmentStatus>;
|
|
25
|
+
export type CanonicalFulfillmentTransitionSnapshot =
|
|
26
|
+
CoreFulfillmentTransitionSnapshot<CanonicalLocalFulfillmentStatus>;
|
|
27
|
+
export type CanonicalTransitionConflict =
|
|
28
|
+
CoreTransitionConflict<CanonicalLocalFulfillmentStatus>;
|
|
29
|
+
|
|
30
|
+
const CANONICAL_STATUSES = new Set<string>(CANONICAL_LOCAL_FULFILLMENT_STATUSES);
|
|
31
|
+
|
|
32
|
+
export function isCanonicalLocalFulfillmentStatus(
|
|
33
|
+
value: string,
|
|
34
|
+
): value is CanonicalLocalFulfillmentStatus {
|
|
35
|
+
return CANONICAL_STATUSES.has(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Strict unknown-input parser bound to the existing status canon. */
|
|
39
|
+
export function parseFulfillmentFact(
|
|
40
|
+
value: unknown,
|
|
41
|
+
): CanonicalNormalizedFulfillmentFact {
|
|
42
|
+
return parseNormalizedFulfillmentFact(value, {
|
|
43
|
+
isCanonicalStatus: isCanonicalLocalFulfillmentStatus,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Strict conflict parser bound to the same status canon. */
|
|
48
|
+
export function parseFulfillmentTransitionConflict(value: unknown): CanonicalTransitionConflict {
|
|
49
|
+
return parseTransitionConflict(value, {
|
|
50
|
+
isCanonicalStatus: isCanonicalLocalFulfillmentStatus,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { CustomerFulfillmentStep } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface FulfillmentStatusStage {
|
|
4
|
+
/** Layer C — the provider-neutral customer step. */
|
|
5
|
+
customerStep: CustomerFulfillmentStep;
|
|
6
|
+
/** Precedence when several signals disagree — highest rank wins. */
|
|
7
|
+
rank: number;
|
|
8
|
+
/** Linear 5-step bar position (paid0 → delivered4); off-track states pinned. */
|
|
9
|
+
phaseIndex: number;
|
|
10
|
+
/** Whether this step sits on the normal paid→delivered track. */
|
|
11
|
+
onTrack: boolean;
|
|
12
|
+
/** i18n key for the short label (progress bar + cards). */
|
|
13
|
+
stepLabelKey: string;
|
|
14
|
+
/** Polish milestone label emitted server-side into the order-detail timeline. */
|
|
15
|
+
timelineLabel: string;
|
|
16
|
+
/** Key under `account:dashboard.panels.orders.trackingPhase.*` (start tile). */
|
|
17
|
+
trackingPhaseKey: string;
|
|
18
|
+
/** Short display label contributed with the status map. */
|
|
19
|
+
label: { pl: string; en: string };
|
|
20
|
+
/** Layer B `local_status` / durable FSM / order-level statuses resolving here. */
|
|
21
|
+
localStatus: readonly string[];
|
|
22
|
+
omsFulfillmentStatus: string[];
|
|
23
|
+
orderStatus: string[];
|
|
24
|
+
/** Extra provider/synonym tokens that also resolve here (for token→step). */
|
|
25
|
+
aliasTokens: string[];
|
|
26
|
+
/** Contributed effect metadata, pinned by the package test's projection digest. */
|
|
27
|
+
effects: { stock: string; handover: boolean; customerEmail: string | false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FulfillmentStatusMap {
|
|
31
|
+
version: number; comment: string;
|
|
32
|
+
stages: readonly FulfillmentStatusStage[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const FULFILLMENT_STATUS_MAP = {
|
|
36
|
+
version: 1,
|
|
37
|
+
// Contributed metadata below is retained verbatim. The package-local test pins
|
|
38
|
+
// the projection digest, token round-trips and helpers; it does not check host
|
|
39
|
+
// provider dictionaries or i18n resources.
|
|
40
|
+
comment:
|
|
41
|
+
"SINGLE SOURCE OF TRUTH for fulfillment status across all layers. Layer C (customerStep) is the spine. Providers (Layer A) map ONLY to local_status (Layer B) in their own dictionary; the UI consumes only Layer C. statusMap.test.ts keeps every layer consistent.",
|
|
42
|
+
stages: [
|
|
43
|
+
{
|
|
44
|
+
customerStep: "paid",
|
|
45
|
+
rank: 1,
|
|
46
|
+
phaseIndex: 0,
|
|
47
|
+
onTrack: true,
|
|
48
|
+
stepLabelKey: "account:dashboard.ordersV2.steps.paid",
|
|
49
|
+
timelineLabel: "Oplacone",
|
|
50
|
+
trackingPhaseKey: "paid",
|
|
51
|
+
label: { pl: "Opłacone", en: "Paid" },
|
|
52
|
+
localStatus: [],
|
|
53
|
+
omsFulfillmentStatus: ["created"],
|
|
54
|
+
orderStatus: ["paid", "fulfillment_pending"],
|
|
55
|
+
aliasTokens: [],
|
|
56
|
+
effects: { stock: "reserved", handover: false, customerEmail: false },
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
customerStep: "accepted",
|
|
60
|
+
rank: 2,
|
|
61
|
+
phaseIndex: 1,
|
|
62
|
+
onTrack: true,
|
|
63
|
+
stepLabelKey: "account:dashboard.ordersV2.steps.accepted",
|
|
64
|
+
timelineLabel: "Przyjete do realizacji",
|
|
65
|
+
trackingPhaseKey: "accepted",
|
|
66
|
+
label: { pl: "Przyjęte", en: "Accepted" },
|
|
67
|
+
localStatus: ["provider_received"],
|
|
68
|
+
omsFulfillmentStatus: ["label_pending", "label_created"],
|
|
69
|
+
orderStatus: [],
|
|
70
|
+
aliasTokens: ["new", "accepted", "processing", "provider_attempt_recorded"],
|
|
71
|
+
effects: { stock: "reserved", handover: false, customerEmail: false },
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
customerStep: "packing",
|
|
75
|
+
rank: 3,
|
|
76
|
+
phaseIndex: 2,
|
|
77
|
+
onTrack: true,
|
|
78
|
+
stepLabelKey: "account:dashboard.ordersV2.steps.packing",
|
|
79
|
+
timelineLabel: "Przygotowane do wysylki",
|
|
80
|
+
trackingPhaseKey: "preparing",
|
|
81
|
+
label: { pl: "Przygotowanie", en: "Preparing" },
|
|
82
|
+
localStatus: ["picking", "packed"],
|
|
83
|
+
omsFulfillmentStatus: ["packed"],
|
|
84
|
+
orderStatus: [],
|
|
85
|
+
aliasTokens: ["picked", "in_fulfillment", "ready_for_packing", "awaiting_courier", "ready_for_pickup"],
|
|
86
|
+
effects: { stock: "consumed", handover: false, customerEmail: false },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
customerStep: "transit",
|
|
90
|
+
rank: 4,
|
|
91
|
+
phaseIndex: 3,
|
|
92
|
+
onTrack: true,
|
|
93
|
+
stepLabelKey: "account:dashboard.ordersV2.steps.transit",
|
|
94
|
+
timelineLabel: "W drodze",
|
|
95
|
+
trackingPhaseKey: "in_transit",
|
|
96
|
+
label: { pl: "W drodze", en: "In transit" },
|
|
97
|
+
localStatus: ["in_transit"],
|
|
98
|
+
omsFulfillmentStatus: ["handed_over", "in_transit"],
|
|
99
|
+
orderStatus: [],
|
|
100
|
+
aliasTokens: ["shipped", "shipping", "out_for_delivery", "dispatched"],
|
|
101
|
+
effects: { stock: "consumed", handover: true, customerEmail: "shipment.dispatched" },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
customerStep: "delivered",
|
|
105
|
+
rank: 7,
|
|
106
|
+
phaseIndex: 4,
|
|
107
|
+
onTrack: true,
|
|
108
|
+
stepLabelKey: "account:dashboard.ordersV2.steps.delivered",
|
|
109
|
+
timelineLabel: "Dostarczono",
|
|
110
|
+
trackingPhaseKey: "delivered",
|
|
111
|
+
label: { pl: "Dostarczone", en: "Delivered" },
|
|
112
|
+
localStatus: ["delivered"],
|
|
113
|
+
omsFulfillmentStatus: ["delivered"],
|
|
114
|
+
orderStatus: ["fulfilled"],
|
|
115
|
+
aliasTokens: ["delivery_delivered"],
|
|
116
|
+
effects: { stock: "consumed", handover: true, customerEmail: false },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
customerStep: "exception",
|
|
120
|
+
rank: 5,
|
|
121
|
+
phaseIndex: 1,
|
|
122
|
+
onTrack: false,
|
|
123
|
+
stepLabelKey: "account:dashboard.ordersV2.status.exception",
|
|
124
|
+
timelineLabel: "Wymaga sprawdzenia",
|
|
125
|
+
trackingPhaseKey: "exception",
|
|
126
|
+
label: { pl: "Wymaga sprawdzenia", en: "Needs review" },
|
|
127
|
+
localStatus: ["exception"],
|
|
128
|
+
omsFulfillmentStatus: ["exception"],
|
|
129
|
+
orderStatus: [],
|
|
130
|
+
aliasTokens: ["shipping_failed", "failed", "suspended", "returned_to_sender"],
|
|
131
|
+
effects: { stock: "held", handover: false, customerEmail: "shipment.exception (provider exception before handoff)" },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
customerStep: "cancelled",
|
|
135
|
+
rank: 6,
|
|
136
|
+
phaseIndex: 0,
|
|
137
|
+
onTrack: false,
|
|
138
|
+
stepLabelKey: "account:dashboard.ordersV2.status.cancelled",
|
|
139
|
+
timelineLabel: "Anulowano",
|
|
140
|
+
trackingPhaseKey: "cancelled",
|
|
141
|
+
label: { pl: "Anulowano", en: "Cancelled" },
|
|
142
|
+
localStatus: ["cancelled"],
|
|
143
|
+
omsFulfillmentStatus: ["cancelled"],
|
|
144
|
+
orderStatus: ["cancelled", "refunded"],
|
|
145
|
+
aliasTokens: ["canceled"],
|
|
146
|
+
effects: { stock: "released", handover: false, customerEmail: false },
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
} as const satisfies FulfillmentStatusMap;
|
|
150
|
+
|
|
151
|
+
type CanonicalLocalFulfillmentStatus = import("./types.js").CanonicalLocalFulfillmentStatus;
|
|
152
|
+
export const CANONICAL_LOCAL_FULFILLMENT_STATUSES: readonly CanonicalLocalFulfillmentStatus[] =
|
|
153
|
+
FULFILLMENT_STATUS_MAP.stages.flatMap((stage) => stage.localStatus);
|
|
154
|
+
// Off-track steps render separately and are absent from the progress-bar spine.
|
|
155
|
+
export const CUSTOMER_STEP_SEQUENCE: CustomerFulfillmentStep[] = ["paid", "accepted", "packing", "transit", "delivered"];
|
|
156
|
+
|
|
157
|
+
// Provider strings arrive in varied spelling ("IN_FULFILLMENT", "in-fulfillment",
|
|
158
|
+
// "Label Created") — compare on a canonical token so casing/separator drift never
|
|
159
|
+
// silently changes the mapping.
|
|
160
|
+
export function canonicalStatusToken(value: string | null | undefined): string | null {
|
|
161
|
+
const token = value?.trim().toLowerCase().replace(/[\s_-]+/g, "_");
|
|
162
|
+
return token || null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const STAGE_BY_STEP: Record<CustomerFulfillmentStep, FulfillmentStatusStage> = Object.fromEntries(
|
|
166
|
+
FULFILLMENT_STATUS_MAP.stages.map((stage) => [stage.customerStep, stage]),
|
|
167
|
+
) as unknown as Record<CustomerFulfillmentStep, FulfillmentStatusStage>;
|
|
168
|
+
|
|
169
|
+
// token → customerStep, derived from the map (localStatus + FSM + orderStatus +
|
|
170
|
+
// aliasTokens). The package-local fulfillment/statusMap.test.ts witness checks
|
|
171
|
+
// that each contributed token round-trips to its declared customer step.
|
|
172
|
+
const STEP_LOOKUP: Record<string, CustomerFulfillmentStep> = (() => {
|
|
173
|
+
const table: Record<string, CustomerFulfillmentStep> = {};
|
|
174
|
+
for (const stage of FULFILLMENT_STATUS_MAP.stages) {
|
|
175
|
+
for (const token of [
|
|
176
|
+
...stage.localStatus,
|
|
177
|
+
...stage.omsFulfillmentStatus,
|
|
178
|
+
...stage.orderStatus,
|
|
179
|
+
...stage.aliasTokens,
|
|
180
|
+
]) {
|
|
181
|
+
const canon = canonicalStatusToken(token);
|
|
182
|
+
if (canon) table[canon] = stage.customerStep;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return table;
|
|
186
|
+
})();
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resolve a single raw token (a Layer B `local_status`, an FSM value, an order
|
|
190
|
+
* status, or a provider/synonym) to a customer step. Exact map lookup first, then
|
|
191
|
+
* a conservative substring fallback so an unknown FUTURE token never regresses to
|
|
192
|
+
* a misleading default — it returns null and the caller decides (generic label /
|
|
193
|
+
* "paid" floor). Never guesses a more-advanced state than the token implies.
|
|
194
|
+
*/
|
|
195
|
+
export function stepForToken(token: string | null | undefined): CustomerFulfillmentStep | null {
|
|
196
|
+
const canon = canonicalStatusToken(token);
|
|
197
|
+
if (!canon) return null;
|
|
198
|
+
const exact = STEP_LOOKUP[canon];
|
|
199
|
+
if (exact) return exact;
|
|
200
|
+
if (/deliver/.test(canon)) return "delivered";
|
|
201
|
+
if (/cancel/.test(canon)) return "cancelled";
|
|
202
|
+
if (/exception|fail/.test(canon)) return "exception";
|
|
203
|
+
if (/transit|dispatch|shipp|out_for/.test(canon)) return "transit";
|
|
204
|
+
if (/pack|pick|fulfil/.test(canon)) return "packing";
|
|
205
|
+
if (/label|accept|received|processing/.test(canon) || canon === "new") return "accepted";
|
|
206
|
+
if (/paid|created/.test(canon)) return "paid";
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The single customer step for an order, from all its real signals (order status,
|
|
212
|
+
* durable fulfillment status, latest provider evidence). The most-advanced real
|
|
213
|
+
* signal wins by rank — so the internal `label_created` (set at OUR dispatch) can
|
|
214
|
+
* never overstate a box the provider has not started, and a real `picking`/
|
|
215
|
+
* `in_transit` is never held back by a stale internal state. Floors at "paid".
|
|
216
|
+
*/
|
|
217
|
+
export function customerStepFromSignals(tokens: Array<string | null | undefined>): CustomerFulfillmentStep {
|
|
218
|
+
let best: CustomerFulfillmentStep | null = null;
|
|
219
|
+
let bestRank = -1;
|
|
220
|
+
for (const token of tokens) {
|
|
221
|
+
const step = stepForToken(token);
|
|
222
|
+
if (!step) continue;
|
|
223
|
+
const rank = STAGE_BY_STEP[step].rank;
|
|
224
|
+
if (rank > bestRank) {
|
|
225
|
+
bestRank = rank;
|
|
226
|
+
best = step;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return best ?? "paid";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function phaseIndexForStep(step: CustomerFulfillmentStep): number {
|
|
233
|
+
return STAGE_BY_STEP[step].phaseIndex;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** i18n key for a step's short label (progress bar + cards). */
|
|
237
|
+
export function stepLabelKey(step: CustomerFulfillmentStep): string {
|
|
238
|
+
return STAGE_BY_STEP[step].stepLabelKey;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Server-emitted Polish milestone label for the order-detail timeline. */
|
|
242
|
+
export function timelineLabelForStep(step: CustomerFulfillmentStep): string {
|
|
243
|
+
return STAGE_BY_STEP[step].timelineLabel;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** `panels.orders.trackingPhase.*` key for a step (start-page tile). */
|
|
247
|
+
export function trackingPhaseKeyForStep(step: CustomerFulfillmentStep): string {
|
|
248
|
+
return STAGE_BY_STEP[step].trackingPhaseKey;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function stepUsesTruckIcon(step: CustomerFulfillmentStep): boolean {
|
|
252
|
+
return step === "transit" || step === "delivered";
|
|
253
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FulfillmentProgressInput, FulfillmentProgressPresentation } from './types.js';
|
|
2
|
+
import { CUSTOMER_STEP_SEQUENCE, phaseIndexForStep, stepForToken } from './statusMap.js';
|
|
3
|
+
import type { CustomerFulfillmentStep } from './types.js';
|
|
4
|
+
/** The host resolves its existing step; only actual recorded milestones complete. */
|
|
5
|
+
export function fulfillmentProgressPresentation(order: FulfillmentProgressInput, step: CustomerFulfillmentStep): FulfillmentProgressPresentation | null {
|
|
6
|
+
const status = order.status.toLowerCase();
|
|
7
|
+
const rawPayment = order.paymentStatus?.toLowerCase() ?? "";
|
|
8
|
+
const payment = rawPayment === "paid" ? "succeeded" : rawPayment;
|
|
9
|
+
const paid = payment === "succeeded";
|
|
10
|
+
const terminal = status === "cancelled" || status === "refunded";
|
|
11
|
+
const orderMilestone = stepForToken(status);
|
|
12
|
+
const recordedOrderMilestone = orderMilestone !== null && orderMilestone !== "paid"
|
|
13
|
+
&& CUSTOMER_STEP_SEQUENCE.includes(orderMilestone as typeof CUSTOMER_STEP_SEQUENCE[number]);
|
|
14
|
+
const parcelEvidence = recordedOrderMilestone || Boolean(order.fulfillmentStatus)
|
|
15
|
+
|| Boolean(order.customerFulfillmentStep && step !== "paid" && !["draft", "failed", "expired"].includes(status))
|
|
16
|
+
|| order.trackingTimeline.some((event) => {
|
|
17
|
+
const observed = stepForToken(event.eventType);
|
|
18
|
+
return observed !== null && observed !== "paid";
|
|
19
|
+
});
|
|
20
|
+
const advancedParcel = !terminal && parcelEvidence && step !== "paid";
|
|
21
|
+
return progressPresentation(order, step, paid, terminal, advancedParcel);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function progressPresentation(
|
|
25
|
+
order: FulfillmentProgressInput,
|
|
26
|
+
step: CustomerFulfillmentStep,
|
|
27
|
+
paid: boolean,
|
|
28
|
+
terminal: boolean,
|
|
29
|
+
advancedParcel: boolean,
|
|
30
|
+
): FulfillmentProgressPresentation | null {
|
|
31
|
+
if (terminal || ["draft", "expired"].includes(order.status.toLowerCase())
|
|
32
|
+
|| step === "exception" || step === "cancelled" || !paid && !advancedParcel) return null;
|
|
33
|
+
const completed = new Set<CustomerFulfillmentStep>(paid ? ["paid"] : []);
|
|
34
|
+
const record = (token: string | null | undefined) => {
|
|
35
|
+
const observed = stepForToken(token);
|
|
36
|
+
// No historical inference from an index, and no payment proof from parcel tokens.
|
|
37
|
+
if (observed && observed !== "paid" && CUSTOMER_STEP_SEQUENCE.includes(observed as typeof CUSTOMER_STEP_SEQUENCE[number])
|
|
38
|
+
&& phaseIndexForStep(observed) <= phaseIndexForStep(step)) completed.add(observed);
|
|
39
|
+
};
|
|
40
|
+
if (advancedParcel) {
|
|
41
|
+
record(order.fulfillmentStatus);
|
|
42
|
+
record(step);
|
|
43
|
+
order.trackingTimeline.forEach((event) => record(event.eventType));
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
completedSteps: [...completed],
|
|
47
|
+
currentStep: advancedParcel && step !== "delivered" ? step : null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CanonicalLocalFulfillmentStatus = "provider_received" | "picking" | "packed" | "in_transit" | "delivered" | "exception" | "cancelled";
|
|
2
|
+
export type CustomerFulfillmentStep = "paid" | "accepted" | "packing" | "transit" | "delivered" | "exception" | "cancelled";
|
|
3
|
+
|
|
4
|
+
export interface FulfillmentProgressInput {
|
|
5
|
+
status: string; paymentStatus?: string | null; fulfillmentStatus?: string | null;
|
|
6
|
+
customerFulfillmentStep?: CustomerFulfillmentStep | null;
|
|
7
|
+
trackingTimeline: { eventType: string }[];
|
|
8
|
+
}
|
|
9
|
+
export interface FulfillmentProgressPresentation {
|
|
10
|
+
completedSteps: CustomerFulfillmentStep[];
|
|
11
|
+
currentStep: CustomerFulfillmentStep | null;
|
|
12
|
+
}
|