@tokenoftrust/storefront-runner 2.4.51 → 2.4.53
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/apps/storefront/src/lib/admin/orderClient.ts +45 -11
- package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +10 -53
- package/apps/storefront/src/lib/admin/services/orders.ts +524 -87
- package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +1159 -33
- package/apps/storefront/src/lib/admin/viewmodels/orders.ts +37 -1
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +20 -5
- package/apps/storefront/src/pages/api/admin/orders.ts +8 -12
- package/package.json +1 -1
|
@@ -17,7 +17,12 @@ import type {
|
|
|
17
17
|
CheckoutOrderDetailResponse,
|
|
18
18
|
CheckoutOrderListFilters,
|
|
19
19
|
CheckoutOrderListResponse,
|
|
20
|
+
CommerceActor,
|
|
21
|
+
CommerceRole,
|
|
22
|
+
CommerceScope,
|
|
23
|
+
CommerceStage,
|
|
20
24
|
} from "@tot/public-runtime";
|
|
25
|
+
import { readOwnerAddress } from "@tot/public-runtime";
|
|
21
26
|
import type { CustomizationEnv } from "@tot/public-runtime";
|
|
22
27
|
|
|
23
28
|
/** Fetch shape we depend on — the platform `fetch`, injectable for tests. */
|
|
@@ -75,24 +80,53 @@ export function orderApiBaseUrl(env: CustomizationEnv): string {
|
|
|
75
80
|
: "https://foxycart-test.tokenoftrust.com";
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Map an HTTP status to a stable code. The body is scanned for a known code
|
|
85
|
+
* only. It is never copied into the error — upstream bodies can carry an
|
|
86
|
+
* address or a customer identifier.
|
|
87
|
+
*/
|
|
79
88
|
function toError(status: number, body: string): OrderApiError {
|
|
80
|
-
|
|
81
|
-
if (status === 400) return new OrderApiError("missing_app_domain", 400, detail);
|
|
89
|
+
if (status === 400) return new OrderApiError("missing_app_domain", 400);
|
|
82
90
|
if (status === 401) {
|
|
83
|
-
|
|
84
|
-
const code: OrderApiErrorCode = /not_provisioned/.test(body)
|
|
91
|
+
const code: OrderApiErrorCode = body.includes("not_provisioned")
|
|
85
92
|
? "not_provisioned"
|
|
86
|
-
:
|
|
93
|
+
: body.includes("unknown_tenant")
|
|
87
94
|
? "unknown_tenant"
|
|
88
|
-
:
|
|
95
|
+
: body.includes("missing_bearer")
|
|
89
96
|
? "missing_bearer"
|
|
90
97
|
: "invalid_partner_key";
|
|
91
|
-
return new OrderApiError(code, 401
|
|
98
|
+
return new OrderApiError(code, 401);
|
|
92
99
|
}
|
|
93
|
-
if (status === 404) return new OrderApiError("order_not_found", 404
|
|
94
|
-
if (status >= 500) return new OrderApiError("bad_gateway", 502
|
|
95
|
-
return new OrderApiError("unexpected", status
|
|
100
|
+
if (status === 404) return new OrderApiError("order_not_found", 404);
|
|
101
|
+
if (status >= 500) return new OrderApiError("bad_gateway", 502);
|
|
102
|
+
return new OrderApiError("unexpected", status);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Live customization is publication. Every other env is preview. */
|
|
106
|
+
export function commerceStageForCustomization(env: CustomizationEnv): CommerceStage {
|
|
107
|
+
return env === "live" ? "publication" : "preview";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Owner reads private order fields. An admitted member is a caller. Everyone else is denied. */
|
|
111
|
+
export function commerceActorForAdmin(input: {
|
|
112
|
+
tenantId: string;
|
|
113
|
+
stage: CommerceStage;
|
|
114
|
+
owner: boolean;
|
|
115
|
+
admitted: boolean;
|
|
116
|
+
}): CommerceActor {
|
|
117
|
+
const role: CommerceRole = !input.admitted ? "denied" : input.owner ? "owner" : "caller";
|
|
118
|
+
return { role, tenantId: input.tenantId, stage: input.stage };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Drop the address unless this actor may read it for this tenant and stage. */
|
|
122
|
+
export function projectCheckoutOrder<T extends { shippingAddress?: unknown }>(
|
|
123
|
+
order: T,
|
|
124
|
+
actor: CommerceActor,
|
|
125
|
+
resource: CommerceScope,
|
|
126
|
+
): T {
|
|
127
|
+
const read = readOwnerAddress(actor, resource, () => order.shippingAddress ?? null);
|
|
128
|
+
if (read.ok) return order;
|
|
129
|
+
return { ...order, shippingAddress: null };
|
|
96
130
|
}
|
|
97
131
|
|
|
98
132
|
/** Build the list querystring: appDomain (required) + any provided filters. */
|
|
@@ -1,62 +1,19 @@
|
|
|
1
1
|
import type { AdminTenantEnvelope } from "../../envelope";
|
|
2
2
|
import { memoizeByEnvelope } from "../../memoizeByEnvelope";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
FulfillmentViewModel,
|
|
9
|
-
ShipmentRow,
|
|
3
|
+
import {
|
|
4
|
+
defaultWorkspaceQuery,
|
|
5
|
+
initialWorkspaceState,
|
|
6
|
+
projectWorkspace,
|
|
7
|
+
type FulfillmentViewModel,
|
|
10
8
|
} from "../../viewmodels/fulfillment";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{ key: "packed", label: "Packed" },
|
|
17
|
-
{ key: "label_needed", label: "Label needed" },
|
|
18
|
-
{ key: "shipped", label: "Shipped" },
|
|
19
|
-
{ key: "exceptions", label: "Exceptions" },
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const CARRIERS = ["USPS", "UPS", "FedEx"];
|
|
23
|
-
const DESTINATIONS = ["Minneapolis, MN", "Portland, OR", "Austin, TX", "Columbus, OH", "Denver, CO"];
|
|
10
|
+
/** Initial test-adapter snapshot. Not a seeded queue and not a live order feed. */
|
|
11
|
+
export function readTestAdapterSnapshot(targetTenant: string): FulfillmentViewModel {
|
|
12
|
+
return projectWorkspace(initialWorkspaceState(targetTenant), defaultWorkspaceQuery());
|
|
13
|
+
}
|
|
24
14
|
|
|
25
15
|
function computeFulfillmentViewModel(envelope: AdminTenantEnvelope): FulfillmentViewModel {
|
|
26
|
-
|
|
27
|
-
const rng = seededRandom(`fulfillment:${targetTenant}`);
|
|
28
|
-
|
|
29
|
-
const shipments: ShipmentRow[] = QUEUE_DEFS.flatMap((def) =>
|
|
30
|
-
Array.from({ length: randomInt(rng, 1, 3) }, () => {
|
|
31
|
-
const labelLocked = def.key === "label_needed" || (def.key === "holds" && randomBool(rng, 0.6));
|
|
32
|
-
return {
|
|
33
|
-
orderId: `#${2_200_000_000 + Math.floor(rng() * 90_000_000)}`,
|
|
34
|
-
queue: def.key,
|
|
35
|
-
carrier: pick(rng, CARRIERS),
|
|
36
|
-
destination: pick(rng, DESTINATIONS),
|
|
37
|
-
slaAgeHours: randomInt(rng, 1, 48),
|
|
38
|
-
labelLocked,
|
|
39
|
-
labelLockReason: labelLocked ? pick(rng, ["Shipping restriction on destination", "Payment not yet captured", "Age verification pending"]) : null,
|
|
40
|
-
carrierReady: !labelLocked && randomBool(rng, 0.9),
|
|
41
|
-
destinationReady: randomBool(rng, 0.85),
|
|
42
|
-
};
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const queues: FulfillmentQueueSummary[] = QUEUE_DEFS.map((def) => ({
|
|
47
|
-
key: def.key,
|
|
48
|
-
label: def.label,
|
|
49
|
-
count: shipments.filter((s) => s.queue === def.key).length,
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
const exceptionShipments = shipments.filter((s) => s.queue === "exceptions" || s.labelLocked);
|
|
53
|
-
const assistDrafts: FulfillmentAssistDraft[] = exceptionShipments.map((s) => ({
|
|
54
|
-
orderId: s.orderId,
|
|
55
|
-
kind: "blocker_explanation",
|
|
56
|
-
summary: s.labelLockReason ? `${s.labelLockReason} — resolve to unlock the label for ${s.orderId}.` : `Exception queue: ${s.orderId} needs manual review.`,
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
return { targetTenant, queues, shipments, assistDrafts };
|
|
16
|
+
return readTestAdapterSnapshot(envelope.targetTenant);
|
|
60
17
|
}
|
|
61
18
|
|
|
62
19
|
export const getFulfillmentViewModel = memoizeByEnvelope(computeFulfillmentViewModel);
|