@open-mercato/core 0.4.9-develop-ce96cffe00 → 0.4.9-develop-5d884303ad
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/dist/modules/auth/lib/setup-app.js +17 -1
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +23 -12
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/shipping_carriers/api/cancel/route.js +5 -1
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/points/route.js +59 -0
- package/dist/modules/shipping_carriers/api/points/route.js.map +7 -0
- package/dist/modules/shipping_carriers/api/providers/route.js +38 -0
- package/dist/modules/shipping_carriers/api/providers/route.js.map +7 -0
- package/dist/modules/shipping_carriers/backend/shipping-carriers/create/page.js +90 -0
- package/dist/modules/shipping_carriers/backend/shipping-carriers/create/page.js.map +7 -0
- package/dist/modules/shipping_carriers/backend/shipping-carriers/create/page.meta.js +8 -0
- package/dist/modules/shipping_carriers/backend/shipping-carriers/create/page.meta.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +17 -2
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/AddressFields.js +76 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/AddressFields.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +243 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfirmStep.js +134 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfirmStep.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.js +70 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ProviderStep.js +32 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ProviderStep.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/WizardNav.js +37 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/WizardNav.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +92 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +212 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/types.js +1 -0
- package/dist/modules/shipping_carriers/lib/shipment-wizard/types.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipping-service.js +36 -3
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js +7 -0
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
- package/package.json +7 -4
- package/src/modules/auth/lib/setup-app.ts +22 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -9
- package/src/modules/shipping_carriers/api/cancel/route.ts +5 -1
- package/src/modules/shipping_carriers/api/points/route.ts +57 -0
- package/src/modules/shipping_carriers/api/providers/route.ts +35 -0
- package/src/modules/shipping_carriers/backend/shipping-carriers/create/page.meta.ts +4 -0
- package/src/modules/shipping_carriers/backend/shipping-carriers/create/page.tsx +93 -0
- package/src/modules/shipping_carriers/data/validators.ts +15 -0
- package/src/modules/shipping_carriers/i18n/de.json +66 -0
- package/src/modules/shipping_carriers/i18n/en.json +66 -0
- package/src/modules/shipping_carriers/i18n/es.json +66 -0
- package/src/modules/shipping_carriers/i18n/pl.json +66 -0
- package/src/modules/shipping_carriers/lib/adapter.ts +20 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/AddressFields.tsx +72 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +343 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfirmStep.tsx +213 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/PackageEditor.tsx +82 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ProviderStep.tsx +54 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/WizardNav.tsx +46 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +153 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +312 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/types.ts +76 -0
- package/src/modules/shipping_carriers/lib/shipping-service.ts +53 -3
- package/src/modules/shipping_carriers/lib/status-sync.ts +7 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { match } from "ts-pattern";
|
|
5
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
+
const STEP_ORDER = ["provider", "configure", "confirm"];
|
|
7
|
+
const WizardNav = (props) => {
|
|
8
|
+
const { step, onNavigate } = props;
|
|
9
|
+
const t = useT();
|
|
10
|
+
const stepIndex = STEP_ORDER.indexOf(step);
|
|
11
|
+
const stepLabels = {
|
|
12
|
+
provider: t("shipping_carriers.create.step.provider", "Select carrier"),
|
|
13
|
+
configure: t("shipping_carriers.create.step.configure", "Configure shipment"),
|
|
14
|
+
confirm: t("shipping_carriers.create.step.confirm", "Select service & confirm")
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: STEP_ORDER.map((stepId, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
17
|
+
index > 0 ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u203A" }) : null,
|
|
18
|
+
/* @__PURE__ */ jsxs(
|
|
19
|
+
"span",
|
|
20
|
+
{
|
|
21
|
+
className: match({ isCurrent: stepId === step, isPast: index < stepIndex }).with({ isCurrent: true }, () => "font-medium text-foreground").with({ isPast: true }, () => "cursor-pointer hover:text-foreground").otherwise(() => ""),
|
|
22
|
+
onClick: () => {
|
|
23
|
+
if (index < stepIndex) onNavigate(stepId);
|
|
24
|
+
},
|
|
25
|
+
children: [
|
|
26
|
+
index + 1,
|
|
27
|
+
". ",
|
|
28
|
+
stepLabels[stepId]
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
] }, stepId)) });
|
|
33
|
+
};
|
|
34
|
+
export {
|
|
35
|
+
WizardNav
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=WizardNav.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/shipping_carriers/lib/shipment-wizard/components/WizardNav.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { match } from 'ts-pattern'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { WizardStep } from '../types'\n\nconst STEP_ORDER: WizardStep[] = ['provider', 'configure', 'confirm']\n\nexport type WizardNavProps = {\n step: WizardStep\n onNavigate: (step: WizardStep) => void\n}\n\nexport const WizardNav = (props: WizardNavProps) => {\n const { step, onNavigate } = props\n const t = useT()\n const stepIndex = STEP_ORDER.indexOf(step)\n\n const stepLabels: Record<WizardStep, string> = {\n provider: t('shipping_carriers.create.step.provider', 'Select carrier'),\n configure: t('shipping_carriers.create.step.configure', 'Configure shipment'),\n confirm: t('shipping_carriers.create.step.confirm', 'Select service & confirm'),\n }\n\n return (\n <nav className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n {STEP_ORDER.map((stepId, index) => (\n <React.Fragment key={stepId}>\n {index > 0 ? <span aria-hidden>\u203A</span> : null}\n <span\n className={match({ isCurrent: stepId === step, isPast: index < stepIndex })\n .with({ isCurrent: true }, () => 'font-medium text-foreground')\n .with({ isPast: true }, () => 'cursor-pointer hover:text-foreground')\n .otherwise(() => '')}\n onClick={() => {\n if (index < stepIndex) onNavigate(stepId)\n }}\n >\n {index + 1}. {stepLabels[stepId]}\n </span>\n </React.Fragment>\n ))}\n </nav>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA6BuB,cACb,YADa;AA3BvB,YAAY,WAAW;AACvB,SAAS,aAAa;AACtB,SAAS,YAAY;AAGrB,MAAM,aAA2B,CAAC,YAAY,aAAa,SAAS;AAO7D,MAAM,YAAY,CAAC,UAA0B;AAClD,QAAM,EAAE,MAAM,WAAW,IAAI;AAC7B,QAAM,IAAI,KAAK;AACf,QAAM,YAAY,WAAW,QAAQ,IAAI;AAEzC,QAAM,aAAyC;AAAA,IAC7C,UAAU,EAAE,0CAA0C,gBAAgB;AAAA,IACtE,WAAW,EAAE,2CAA2C,oBAAoB;AAAA,IAC5E,SAAS,EAAE,yCAAyC,0BAA0B;AAAA,EAChF;AAEA,SACE,oBAAC,SAAI,WAAU,yDACZ,qBAAW,IAAI,CAAC,QAAQ,UACvB,qBAAC,MAAM,UAAN,EACE;AAAA,YAAQ,IAAI,oBAAC,UAAK,eAAW,MAAC,oBAAC,IAAU;AAAA,IAC1C;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,MAAM,EAAE,WAAW,WAAW,MAAM,QAAQ,QAAQ,UAAU,CAAC,EACvE,KAAK,EAAE,WAAW,KAAK,GAAG,MAAM,6BAA6B,EAC7D,KAAK,EAAE,QAAQ,KAAK,GAAG,MAAM,sCAAsC,EACnE,UAAU,MAAM,EAAE;AAAA,QACrB,SAAS,MAAM;AACb,cAAI,QAAQ,UAAW,YAAW,MAAM;AAAA,QAC1C;AAAA,QAEC;AAAA,kBAAQ;AAAA,UAAE;AAAA,UAAG,WAAW,MAAM;AAAA;AAAA;AAAA,IACjC;AAAA,OAZmB,MAarB,CACD,GACH;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { match, P } from "ts-pattern";
|
|
2
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
3
|
+
const fetchProviders = async () => {
|
|
4
|
+
const call = await apiCall(
|
|
5
|
+
"/api/shipping-carriers/providers",
|
|
6
|
+
void 0,
|
|
7
|
+
{ fallback: { providers: [] } }
|
|
8
|
+
);
|
|
9
|
+
return match(call).with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({
|
|
10
|
+
ok: true,
|
|
11
|
+
providers: result.providers
|
|
12
|
+
})).otherwise(() => ({ ok: false, error: "Failed to load shipping providers." }));
|
|
13
|
+
};
|
|
14
|
+
const fetchOrderAddresses = async (orderId) => {
|
|
15
|
+
const call = await apiCall(
|
|
16
|
+
`/api/sales/document-addresses?documentId=${orderId}&documentKind=order&pageSize=50`,
|
|
17
|
+
void 0,
|
|
18
|
+
{ fallback: { items: [] } }
|
|
19
|
+
);
|
|
20
|
+
return match(call).with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({
|
|
21
|
+
ok: true,
|
|
22
|
+
items: result.items
|
|
23
|
+
})).otherwise(() => ({ ok: false }));
|
|
24
|
+
};
|
|
25
|
+
const fetchRates = async (params) => {
|
|
26
|
+
const { providerKey, origin, destination, packages, receiverPhone, receiverEmail } = params;
|
|
27
|
+
const call = await apiCall(
|
|
28
|
+
"/api/shipping-carriers/rates",
|
|
29
|
+
{
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
providerKey,
|
|
34
|
+
origin,
|
|
35
|
+
destination,
|
|
36
|
+
packages,
|
|
37
|
+
...receiverPhone !== void 0 ? { receiverPhone } : {},
|
|
38
|
+
...receiverEmail !== void 0 ? { receiverEmail } : {}
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
{ fallback: { rates: [] } }
|
|
42
|
+
);
|
|
43
|
+
return match(call).with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({
|
|
44
|
+
ok: true,
|
|
45
|
+
rates: result.rates
|
|
46
|
+
})).otherwise(({ result }) => ({
|
|
47
|
+
ok: false,
|
|
48
|
+
error: result?.error ?? "Failed to fetch shipping rates."
|
|
49
|
+
}));
|
|
50
|
+
};
|
|
51
|
+
const createShipment = async (params) => {
|
|
52
|
+
const call = await apiCall(
|
|
53
|
+
"/api/shipping-carriers/shipments",
|
|
54
|
+
{
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify(params)
|
|
58
|
+
},
|
|
59
|
+
{ fallback: null }
|
|
60
|
+
);
|
|
61
|
+
return match(call).with({ ok: true }, () => ({ ok: true })).otherwise(({ result }) => ({
|
|
62
|
+
ok: false,
|
|
63
|
+
error: result?.error ?? "Failed to create shipment."
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
const fetchDropOffPoints = async (params) => {
|
|
67
|
+
const url = new URL("/api/shipping-carriers/points", "http://placeholder");
|
|
68
|
+
url.searchParams.set("providerKey", params.providerKey);
|
|
69
|
+
if (params.query) url.searchParams.set("query", params.query);
|
|
70
|
+
if (params.type) url.searchParams.set("type", params.type);
|
|
71
|
+
if (params.postCode) url.searchParams.set("postCode", params.postCode);
|
|
72
|
+
const call = await apiCall(
|
|
73
|
+
`${url.pathname}${url.search}`,
|
|
74
|
+
void 0,
|
|
75
|
+
{ fallback: { points: [] } }
|
|
76
|
+
);
|
|
77
|
+
return match(call).with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({
|
|
78
|
+
ok: true,
|
|
79
|
+
points: result.points
|
|
80
|
+
})).otherwise(({ result }) => ({
|
|
81
|
+
ok: false,
|
|
82
|
+
error: result?.error ?? "Failed to fetch drop-off points."
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
export {
|
|
86
|
+
createShipment,
|
|
87
|
+
fetchDropOffPoints,
|
|
88
|
+
fetchOrderAddresses,
|
|
89
|
+
fetchProviders,
|
|
90
|
+
fetchRates
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=shipmentApi.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts"],
|
|
4
|
+
"sourcesContent": ["import { match, P } from 'ts-pattern'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { Provider, Address, PackageDimension, ShippingRate, DocumentAddress, DropOffPoint } from '../types'\n\nexport type FetchProvidersResult =\n | { ok: true; providers: Provider[] }\n | { ok: false; error: string }\n\nexport type FetchOrderAddressesResult =\n | { ok: true; items: DocumentAddress[] }\n | { ok: false }\n\nexport type FetchRatesParams = {\n providerKey: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n receiverPhone?: string\n receiverEmail?: string\n}\n\nexport type FetchRatesResult =\n | { ok: true; rates: ShippingRate[] }\n | { ok: false; error: string }\n\nexport type CreateShipmentParams = {\n providerKey: string\n orderId: string\n origin: Address\n destination: Address\n packages: PackageDimension[]\n serviceCode: string\n labelFormat: string\n senderPhone?: string\n senderEmail?: string\n receiverPhone?: string\n receiverEmail?: string\n targetPoint?: string\n c2cSendingMethod?: string\n}\n\nexport type CreateShipmentResult =\n | { ok: true }\n | { ok: false; error: string }\n\nexport type FetchDropOffPointsParams = {\n providerKey: string\n query?: string\n type?: string\n postCode?: string\n}\n\nexport type FetchDropOffPointsResult =\n | { ok: true; points: DropOffPoint[] }\n | { ok: false; error: string }\n\nexport const fetchProviders = async (): Promise<FetchProvidersResult> => {\n const call = await apiCall<{ providers: Provider[] }>(\n '/api/shipping-carriers/providers',\n undefined,\n { fallback: { providers: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n providers: result.providers,\n }))\n .otherwise(() => ({ ok: false as const, error: 'Failed to load shipping providers.' }))\n}\n\nexport const fetchOrderAddresses = async (orderId: string): Promise<FetchOrderAddressesResult> => {\n const call = await apiCall<{ items: DocumentAddress[] }>(\n `/api/sales/document-addresses?documentId=${orderId}&documentKind=order&pageSize=50`,\n undefined,\n { fallback: { items: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n items: result.items,\n }))\n .otherwise(() => ({ ok: false as const }))\n}\n\nexport const fetchRates = async (params: FetchRatesParams): Promise<FetchRatesResult> => {\n const { providerKey, origin, destination, packages, receiverPhone, receiverEmail } = params\n const call = await apiCall<{ rates: ShippingRate[] }>(\n '/api/shipping-carriers/rates',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n providerKey,\n origin,\n destination,\n packages,\n ...(receiverPhone !== undefined ? { receiverPhone } : {}),\n ...(receiverEmail !== undefined ? { receiverEmail } : {}),\n }),\n },\n { fallback: { rates: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n rates: result.rates,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch shipping rates.',\n }))\n}\n\nexport const createShipment = async (params: CreateShipmentParams): Promise<CreateShipmentResult> => {\n const call = await apiCall(\n '/api/shipping-carriers/shipments',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(params),\n },\n { fallback: null },\n )\n return match(call)\n .with({ ok: true }, () => ({ ok: true as const }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to create shipment.',\n }))\n}\n\nexport const fetchDropOffPoints = async (params: FetchDropOffPointsParams): Promise<FetchDropOffPointsResult> => {\n const url = new URL('/api/shipping-carriers/points', 'http://placeholder')\n url.searchParams.set('providerKey', params.providerKey)\n if (params.query) url.searchParams.set('query', params.query)\n if (params.type) url.searchParams.set('type', params.type)\n if (params.postCode) url.searchParams.set('postCode', params.postCode)\n\n const call = await apiCall<{ points: DropOffPoint[] }>(\n `${url.pathname}${url.search}`,\n undefined,\n { fallback: { points: [] } },\n )\n return match(call)\n .with({ ok: true, result: P.not(P.nullish) }, ({ result }) => ({\n ok: true as const,\n points: result.points,\n }))\n .otherwise(({ result }) => ({\n ok: false as const,\n error: (result as { error?: string } | null)?.error ?? 'Failed to fetch drop-off points.',\n }))\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,OAAO,SAAS;AACzB,SAAS,eAAe;AAuDjB,MAAM,iBAAiB,YAA2C;AACvE,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE;AAAA,EAChC;AACA,SAAO,MAAM,IAAI,EACd,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,OAAO;AAAA,IAC7D,IAAI;AAAA,IACJ,WAAW,OAAO;AAAA,EACpB,EAAE,EACD,UAAU,OAAO,EAAE,IAAI,OAAgB,OAAO,qCAAqC,EAAE;AAC1F;AAEO,MAAM,sBAAsB,OAAO,YAAwD;AAChG,QAAM,OAAO,MAAM;AAAA,IACjB,4CAA4C,OAAO;AAAA,IACnD;AAAA,IACA,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,EAC5B;AACA,SAAO,MAAM,IAAI,EACd,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,OAAO;AAAA,IAC7D,IAAI;AAAA,IACJ,OAAO,OAAO;AAAA,EAChB,EAAE,EACD,UAAU,OAAO,EAAE,IAAI,MAAe,EAAE;AAC7C;AAEO,MAAM,aAAa,OAAO,WAAwD;AACvF,QAAM,EAAE,aAAa,QAAQ,aAAa,UAAU,eAAe,cAAc,IAAI;AACrF,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,QACvD,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,IACA,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,EAC5B;AACA,SAAO,MAAM,IAAI,EACd,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,OAAO;AAAA,IAC7D,IAAI;AAAA,IACJ,OAAO,OAAO;AAAA,EAChB,EAAE,EACD,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,IAC1B,IAAI;AAAA,IACJ,OAAQ,QAAsC,SAAS;AAAA,EACzD,EAAE;AACN;AAEO,MAAM,iBAAiB,OAAO,WAAgE;AACnG,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,MACE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7B;AAAA,IACA,EAAE,UAAU,KAAK;AAAA,EACnB;AACA,SAAO,MAAM,IAAI,EACd,KAAK,EAAE,IAAI,KAAK,GAAG,OAAO,EAAE,IAAI,KAAc,EAAE,EAChD,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,IAC1B,IAAI;AAAA,IACJ,OAAQ,QAAsC,SAAS;AAAA,EACzD,EAAE;AACN;AAEO,MAAM,qBAAqB,OAAO,WAAwE;AAC/G,QAAM,MAAM,IAAI,IAAI,iCAAiC,oBAAoB;AACzE,MAAI,aAAa,IAAI,eAAe,OAAO,WAAW;AACtD,MAAI,OAAO,MAAO,KAAI,aAAa,IAAI,SAAS,OAAO,KAAK;AAC5D,MAAI,OAAO,KAAM,KAAI,aAAa,IAAI,QAAQ,OAAO,IAAI;AACzD,MAAI,OAAO,SAAU,KAAI,aAAa,IAAI,YAAY,OAAO,QAAQ;AAErE,QAAM,OAAO,MAAM;AAAA,IACjB,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM;AAAA,IAC5B;AAAA,IACA,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE;AAAA,EAC7B;AACA,SAAO,MAAM,IAAI,EACd,KAAK,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,OAAO;AAAA,IAC7D,IAAI;AAAA,IACJ,QAAQ,OAAO;AAAA,EACjB,EAAE,EACD,UAAU,CAAC,EAAE,OAAO,OAAO;AAAA,IAC1B,IAAI;AAAA,IACJ,OAAQ,QAAsC,SAAS;AAAA,EACzD,EAAE;AACN;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
3
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import {
|
|
6
|
+
fetchProviders,
|
|
7
|
+
fetchOrderAddresses,
|
|
8
|
+
fetchRates,
|
|
9
|
+
createShipment,
|
|
10
|
+
fetchDropOffPoints
|
|
11
|
+
} from "./shipmentApi.js";
|
|
12
|
+
const EMPTY_ADDRESS = { countryCode: "", postalCode: "", city: "", line1: "" };
|
|
13
|
+
const EMPTY_CONTACT = { phone: "", email: "" };
|
|
14
|
+
const DEFAULT_PACKAGE = { weightKg: 1, lengthCm: 20, widthCm: 15, heightCm: 10 };
|
|
15
|
+
const buildAddressFromDocument = (doc) => ({
|
|
16
|
+
countryCode: doc.country ?? "",
|
|
17
|
+
postalCode: doc.postal_code ?? "",
|
|
18
|
+
city: doc.city ?? "",
|
|
19
|
+
line1: doc.address_line1,
|
|
20
|
+
line2: doc.address_line2 ?? void 0
|
|
21
|
+
});
|
|
22
|
+
const isAddressValid = (addr) => addr.countryCode.length >= 2 && addr.postalCode.length > 0 && addr.city.length > 0 && addr.line1.length > 0;
|
|
23
|
+
const isPackageValid = (pkg) => pkg.weightKg > 0 && pkg.lengthCm > 0 && pkg.widthCm > 0 && pkg.heightCm > 0;
|
|
24
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
25
|
+
const PHONE_REGEX = /^[+\d][\d\s\-().]{6,}$/;
|
|
26
|
+
const isEmailValid = (email) => email === "" || EMAIL_REGEX.test(email);
|
|
27
|
+
const isPhoneValid = (phone) => phone === "" || PHONE_REGEX.test(phone);
|
|
28
|
+
const useShipmentWizard = () => {
|
|
29
|
+
const t = useT();
|
|
30
|
+
const router = useRouter();
|
|
31
|
+
const searchParams = useSearchParams();
|
|
32
|
+
const orderId = searchParams?.get("orderId") ?? null;
|
|
33
|
+
const [step, setStep] = React.useState("provider");
|
|
34
|
+
const [providers, setProviders] = React.useState([]);
|
|
35
|
+
const [isLoadingProviders, setIsLoadingProviders] = React.useState(true);
|
|
36
|
+
const [providerError, setProviderError] = React.useState(null);
|
|
37
|
+
const [selectedProvider, setSelectedProvider] = React.useState(null);
|
|
38
|
+
const [origin, setOrigin] = React.useState(EMPTY_ADDRESS);
|
|
39
|
+
const [destination, setDestination] = React.useState(EMPTY_ADDRESS);
|
|
40
|
+
const [packages, setPackages] = React.useState([DEFAULT_PACKAGE]);
|
|
41
|
+
const [labelFormat, setLabelFormat] = React.useState("pdf");
|
|
42
|
+
const [senderContact, setSenderContact] = React.useState(EMPTY_CONTACT);
|
|
43
|
+
const [receiverContact, setReceiverContact] = React.useState(EMPTY_CONTACT);
|
|
44
|
+
const [targetPoint, setTargetPoint] = React.useState("");
|
|
45
|
+
const [c2cSendingMethod, setC2cSendingMethod] = React.useState("");
|
|
46
|
+
const [dropOffPointQuery, setDropOffPointQuery] = React.useState("");
|
|
47
|
+
const [dropOffPoints, setDropOffPoints] = React.useState([]);
|
|
48
|
+
const [isFetchingDropOffPoints, setIsFetchingDropOffPoints] = React.useState(false);
|
|
49
|
+
const [dropOffPointsError, setDropOffPointsError] = React.useState(null);
|
|
50
|
+
const [rates, setRates] = React.useState([]);
|
|
51
|
+
const [isFetchingRates, setIsFetchingRates] = React.useState(false);
|
|
52
|
+
const [ratesError, setRatesError] = React.useState(null);
|
|
53
|
+
const [selectedRate, setSelectedRate] = React.useState(null);
|
|
54
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
55
|
+
const backHref = orderId ? `/backend/sales/orders/${orderId}` : "/backend/sales/orders";
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
const loadProviders = async () => {
|
|
58
|
+
setIsLoadingProviders(true);
|
|
59
|
+
setProviderError(null);
|
|
60
|
+
const result = await fetchProviders();
|
|
61
|
+
if (result.ok) {
|
|
62
|
+
setProviders(result.providers);
|
|
63
|
+
} else {
|
|
64
|
+
setProviderError(t("shipping_carriers.create.error.loadProviders", result.error));
|
|
65
|
+
}
|
|
66
|
+
setIsLoadingProviders(false);
|
|
67
|
+
};
|
|
68
|
+
void loadProviders();
|
|
69
|
+
}, [t]);
|
|
70
|
+
React.useEffect(() => {
|
|
71
|
+
if (!orderId) return;
|
|
72
|
+
const prefillAddresses = async () => {
|
|
73
|
+
const result = await fetchOrderAddresses(orderId);
|
|
74
|
+
if (!result.ok) return;
|
|
75
|
+
const items = result.items;
|
|
76
|
+
const shippingAddr = items.find((item) => item.purpose === "shipping") ?? items.find((item) => item.purpose === "delivery") ?? items[0];
|
|
77
|
+
if (shippingAddr) setDestination(buildAddressFromDocument(shippingAddr));
|
|
78
|
+
};
|
|
79
|
+
void prefillAddresses();
|
|
80
|
+
}, [orderId]);
|
|
81
|
+
const loadRates = async () => {
|
|
82
|
+
if (!selectedProvider) return;
|
|
83
|
+
setIsFetchingRates(true);
|
|
84
|
+
setRatesError(null);
|
|
85
|
+
setRates([]);
|
|
86
|
+
setSelectedRate(null);
|
|
87
|
+
const result = await fetchRates({
|
|
88
|
+
providerKey: selectedProvider,
|
|
89
|
+
origin,
|
|
90
|
+
destination,
|
|
91
|
+
packages,
|
|
92
|
+
...receiverContact.phone ? { receiverPhone: receiverContact.phone } : {},
|
|
93
|
+
...receiverContact.email ? { receiverEmail: receiverContact.email } : {}
|
|
94
|
+
});
|
|
95
|
+
if (result.ok) {
|
|
96
|
+
setRates(result.rates);
|
|
97
|
+
if (result.rates.length > 0) setSelectedRate(result.rates[0]);
|
|
98
|
+
} else {
|
|
99
|
+
setRatesError(t("shipping_carriers.create.error.fetchRates", result.error));
|
|
100
|
+
}
|
|
101
|
+
setIsFetchingRates(false);
|
|
102
|
+
};
|
|
103
|
+
const handleSubmit = async () => {
|
|
104
|
+
if (!selectedProvider || !selectedRate || !orderId) return;
|
|
105
|
+
setIsSubmitting(true);
|
|
106
|
+
const result = await createShipment({
|
|
107
|
+
providerKey: selectedProvider,
|
|
108
|
+
orderId,
|
|
109
|
+
origin,
|
|
110
|
+
destination,
|
|
111
|
+
packages,
|
|
112
|
+
serviceCode: selectedRate.serviceCode,
|
|
113
|
+
labelFormat,
|
|
114
|
+
...senderContact.phone ? { senderPhone: senderContact.phone } : {},
|
|
115
|
+
...senderContact.email ? { senderEmail: senderContact.email } : {},
|
|
116
|
+
...receiverContact.phone ? { receiverPhone: receiverContact.phone } : {},
|
|
117
|
+
...receiverContact.email ? { receiverEmail: receiverContact.email } : {},
|
|
118
|
+
...targetPoint ? { targetPoint } : {},
|
|
119
|
+
...c2cSendingMethod ? { c2cSendingMethod } : {}
|
|
120
|
+
});
|
|
121
|
+
setIsSubmitting(false);
|
|
122
|
+
if (result.ok) {
|
|
123
|
+
flash(t("shipping_carriers.create.success", "Shipment created successfully."), "success");
|
|
124
|
+
router.push(backHref);
|
|
125
|
+
} else {
|
|
126
|
+
flash(t("shipping_carriers.create.error.create", result.error), "error");
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const goToStep = (next) => setStep(next);
|
|
130
|
+
const handleProviderSelect = (providerKey) => {
|
|
131
|
+
setSelectedProvider(providerKey);
|
|
132
|
+
goToStep("configure");
|
|
133
|
+
};
|
|
134
|
+
const handleConfigureNext = () => {
|
|
135
|
+
void loadRates().then(() => goToStep("confirm"));
|
|
136
|
+
};
|
|
137
|
+
const searchDropOffPoints = async (query) => {
|
|
138
|
+
if (!selectedProvider) return;
|
|
139
|
+
setDropOffPointQuery(query);
|
|
140
|
+
setIsFetchingDropOffPoints(true);
|
|
141
|
+
setDropOffPointsError(null);
|
|
142
|
+
const isPostCode = /^\d{2}-?\d{3}$/.test(query.trim());
|
|
143
|
+
const resolvedType = c2cSendingMethod === "pop" ? "pop" : "parcel_locker";
|
|
144
|
+
const result = await fetchDropOffPoints({
|
|
145
|
+
providerKey: selectedProvider,
|
|
146
|
+
...isPostCode ? { postCode: query.trim() } : { query: query.trim() },
|
|
147
|
+
type: resolvedType
|
|
148
|
+
});
|
|
149
|
+
if (result.ok) {
|
|
150
|
+
setDropOffPoints(result.points);
|
|
151
|
+
} else {
|
|
152
|
+
setDropOffPointsError(t("shipping_carriers.create.error.fetchDropOffPoints", result.error));
|
|
153
|
+
setDropOffPoints([]);
|
|
154
|
+
}
|
|
155
|
+
setIsFetchingDropOffPoints(false);
|
|
156
|
+
};
|
|
157
|
+
const canProceedFromConfigure = isAddressValid(origin) && isAddressValid(destination) && packages.length > 0 && packages.every(isPackageValid) && isEmailValid(senderContact.email) && isPhoneValid(senderContact.phone) && isEmailValid(receiverContact.email) && isPhoneValid(receiverContact.phone);
|
|
158
|
+
const senderContactErrors = {
|
|
159
|
+
email: !isEmailValid(senderContact.email) ? t("shipping_carriers.create.error.invalidEmail", "Invalid email address.") : null,
|
|
160
|
+
phone: !isPhoneValid(senderContact.phone) ? t("shipping_carriers.create.error.invalidPhone", "Invalid phone number.") : null
|
|
161
|
+
};
|
|
162
|
+
const receiverContactErrors = {
|
|
163
|
+
email: !isEmailValid(receiverContact.email) ? t("shipping_carriers.create.error.invalidEmail", "Invalid email address.") : null,
|
|
164
|
+
phone: !isPhoneValid(receiverContact.phone) ? t("shipping_carriers.create.error.invalidPhone", "Invalid phone number.") : null
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
step,
|
|
168
|
+
backHref,
|
|
169
|
+
providers,
|
|
170
|
+
isLoadingProviders,
|
|
171
|
+
providerError,
|
|
172
|
+
selectedProvider,
|
|
173
|
+
origin,
|
|
174
|
+
destination,
|
|
175
|
+
packages,
|
|
176
|
+
labelFormat,
|
|
177
|
+
senderContact,
|
|
178
|
+
receiverContact,
|
|
179
|
+
targetPoint,
|
|
180
|
+
c2cSendingMethod,
|
|
181
|
+
isFetchingRates,
|
|
182
|
+
canProceedFromConfigure,
|
|
183
|
+
senderContactErrors,
|
|
184
|
+
receiverContactErrors,
|
|
185
|
+
dropOffPointQuery,
|
|
186
|
+
dropOffPoints,
|
|
187
|
+
isFetchingDropOffPoints,
|
|
188
|
+
dropOffPointsError,
|
|
189
|
+
rates,
|
|
190
|
+
ratesError,
|
|
191
|
+
selectedRate,
|
|
192
|
+
isSubmitting,
|
|
193
|
+
goToStep,
|
|
194
|
+
handleProviderSelect,
|
|
195
|
+
handleConfigureNext,
|
|
196
|
+
handleSubmit: () => void handleSubmit(),
|
|
197
|
+
setOrigin,
|
|
198
|
+
setDestination,
|
|
199
|
+
setPackages,
|
|
200
|
+
setLabelFormat,
|
|
201
|
+
setSenderContact,
|
|
202
|
+
setReceiverContact,
|
|
203
|
+
setTargetPoint,
|
|
204
|
+
setC2cSendingMethod,
|
|
205
|
+
setSelectedRate,
|
|
206
|
+
searchDropOffPoints: (query) => void searchDropOffPoints(query)
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
export {
|
|
210
|
+
useShipmentWizard
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=useShipmentWizard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts"],
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n fetchProviders,\n fetchOrderAddresses,\n fetchRates,\n createShipment,\n fetchDropOffPoints,\n} from './shipmentApi'\nimport type {\n WizardStep,\n Provider,\n Address,\n PackageDimension,\n ShippingRate,\n LabelFormat,\n DocumentAddress,\n ContactInfo,\n DropOffPoint,\n} from '../types'\n\nconst EMPTY_ADDRESS: Address = { countryCode: '', postalCode: '', city: '', line1: '' }\nconst EMPTY_CONTACT: ContactInfo = { phone: '', email: '' }\nconst DEFAULT_PACKAGE: PackageDimension = { weightKg: 1, lengthCm: 20, widthCm: 15, heightCm: 10 }\n\nconst buildAddressFromDocument = (doc: DocumentAddress): Address => ({\n countryCode: doc.country ?? '',\n postalCode: doc.postal_code ?? '',\n city: doc.city ?? '',\n line1: doc.address_line1,\n line2: doc.address_line2 ?? undefined,\n})\n\nconst isAddressValid = (addr: Address) =>\n addr.countryCode.length >= 2 &&\n addr.postalCode.length > 0 &&\n addr.city.length > 0 &&\n addr.line1.length > 0\n\nconst isPackageValid = (pkg: PackageDimension) =>\n pkg.weightKg > 0 && pkg.lengthCm > 0 && pkg.widthCm > 0 && pkg.heightCm > 0\n\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst isEmailValid = (email: string) => email === '' || EMAIL_REGEX.test(email)\nconst isPhoneValid = (phone: string) => phone === '' || PHONE_REGEX.test(phone)\n\nexport type ShipmentWizard = {\n step: WizardStep\n backHref: string\n\n // Provider step\n providers: Provider[]\n isLoadingProviders: boolean\n providerError: string | null\n selectedProvider: string | null\n\n // Configure step\n origin: Address\n destination: Address\n packages: PackageDimension[]\n labelFormat: LabelFormat\n senderContact: ContactInfo\n receiverContact: ContactInfo\n targetPoint: string\n c2cSendingMethod: string\n isFetchingRates: boolean\n canProceedFromConfigure: boolean\n senderContactErrors: { email: string | null; phone: string | null }\n receiverContactErrors: { email: string | null; phone: string | null }\n\n // Drop-off point search\n dropOffPointQuery: string\n dropOffPoints: DropOffPoint[]\n isFetchingDropOffPoints: boolean\n dropOffPointsError: string | null\n\n // Confirm step\n rates: ShippingRate[]\n ratesError: string | null\n selectedRate: ShippingRate | null\n isSubmitting: boolean\n\n // Actions\n goToStep: (step: WizardStep) => void\n handleProviderSelect: (providerKey: string) => void\n handleConfigureNext: () => void\n handleSubmit: () => void\n setOrigin: (address: Address) => void\n setDestination: (address: Address) => void\n setPackages: (packages: PackageDimension[]) => void\n setLabelFormat: (format: LabelFormat) => void\n setSenderContact: (contact: ContactInfo) => void\n setReceiverContact: (contact: ContactInfo) => void\n setTargetPoint: (point: string) => void\n setC2cSendingMethod: (method: string) => void\n setSelectedRate: (rate: ShippingRate) => void\n searchDropOffPoints: (query: string) => void\n}\n\nexport const useShipmentWizard = (): ShipmentWizard => {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const orderId = searchParams?.get('orderId') ?? null\n\n const [step, setStep] = React.useState<WizardStep>('provider')\n const [providers, setProviders] = React.useState<Provider[]>([])\n const [isLoadingProviders, setIsLoadingProviders] = React.useState(true)\n const [providerError, setProviderError] = React.useState<string | null>(null)\n const [selectedProvider, setSelectedProvider] = React.useState<string | null>(null)\n\n const [origin, setOrigin] = React.useState<Address>(EMPTY_ADDRESS)\n const [destination, setDestination] = React.useState<Address>(EMPTY_ADDRESS)\n const [packages, setPackages] = React.useState<PackageDimension[]>([DEFAULT_PACKAGE])\n const [labelFormat, setLabelFormat] = React.useState<LabelFormat>('pdf')\n const [senderContact, setSenderContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [receiverContact, setReceiverContact] = React.useState<ContactInfo>(EMPTY_CONTACT)\n const [targetPoint, setTargetPoint] = React.useState<string>('')\n const [c2cSendingMethod, setC2cSendingMethod] = React.useState<string>('')\n\n const [dropOffPointQuery, setDropOffPointQuery] = React.useState<string>('')\n const [dropOffPoints, setDropOffPoints] = React.useState<DropOffPoint[]>([])\n const [isFetchingDropOffPoints, setIsFetchingDropOffPoints] = React.useState(false)\n const [dropOffPointsError, setDropOffPointsError] = React.useState<string | null>(null)\n\n const [rates, setRates] = React.useState<ShippingRate[]>([])\n const [isFetchingRates, setIsFetchingRates] = React.useState(false)\n const [ratesError, setRatesError] = React.useState<string | null>(null)\n const [selectedRate, setSelectedRate] = React.useState<ShippingRate | null>(null)\n\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const backHref = orderId ? `/backend/sales/orders/${orderId}` : '/backend/sales/orders'\n\n React.useEffect(() => {\n const loadProviders = async () => {\n setIsLoadingProviders(true)\n setProviderError(null)\n const result = await fetchProviders()\n if (result.ok) {\n setProviders(result.providers)\n } else {\n setProviderError(t('shipping_carriers.create.error.loadProviders', result.error))\n }\n setIsLoadingProviders(false)\n }\n void loadProviders()\n }, [t])\n\n React.useEffect(() => {\n if (!orderId) return\n const prefillAddresses = async () => {\n const result = await fetchOrderAddresses(orderId)\n if (!result.ok) return\n const items = result.items\n const shippingAddr =\n items.find((item) => item.purpose === 'shipping') ??\n items.find((item) => item.purpose === 'delivery') ??\n items[0]\n if (shippingAddr) setDestination(buildAddressFromDocument(shippingAddr))\n }\n void prefillAddresses()\n }, [orderId])\n\n const loadRates = async () => {\n if (!selectedProvider) return\n setIsFetchingRates(true)\n setRatesError(null)\n setRates([])\n setSelectedRate(null)\n const result = await fetchRates({\n providerKey: selectedProvider,\n origin,\n destination,\n packages,\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n })\n if (result.ok) {\n setRates(result.rates)\n if (result.rates.length > 0) setSelectedRate(result.rates[0])\n } else {\n setRatesError(t('shipping_carriers.create.error.fetchRates', result.error))\n }\n setIsFetchingRates(false)\n }\n\n const handleSubmit = async () => {\n if (!selectedProvider || !selectedRate || !orderId) return\n setIsSubmitting(true)\n const result = await createShipment({\n providerKey: selectedProvider,\n orderId,\n origin,\n destination,\n packages,\n serviceCode: selectedRate.serviceCode,\n labelFormat,\n ...(senderContact.phone ? { senderPhone: senderContact.phone } : {}),\n ...(senderContact.email ? { senderEmail: senderContact.email } : {}),\n ...(receiverContact.phone ? { receiverPhone: receiverContact.phone } : {}),\n ...(receiverContact.email ? { receiverEmail: receiverContact.email } : {}),\n ...(targetPoint ? { targetPoint } : {}),\n ...(c2cSendingMethod ? { c2cSendingMethod } : {}),\n })\n setIsSubmitting(false)\n if (result.ok) {\n flash(t('shipping_carriers.create.success', 'Shipment created successfully.'), 'success')\n router.push(backHref)\n } else {\n flash(t('shipping_carriers.create.error.create', result.error), 'error')\n }\n }\n\n const goToStep = (next: WizardStep) => setStep(next)\n\n const handleProviderSelect = (providerKey: string) => {\n setSelectedProvider(providerKey)\n goToStep('configure')\n }\n\n const handleConfigureNext = () => {\n void loadRates().then(() => goToStep('confirm'))\n }\n\n const searchDropOffPoints = async (query: string) => {\n if (!selectedProvider) return\n setDropOffPointQuery(query)\n setIsFetchingDropOffPoints(true)\n setDropOffPointsError(null)\n const isPostCode = /^\\d{2}-?\\d{3}$/.test(query.trim())\n const resolvedType = c2cSendingMethod === 'pop' ? 'pop' : 'parcel_locker'\n const result = await fetchDropOffPoints({\n providerKey: selectedProvider,\n ...(isPostCode ? { postCode: query.trim() } : { query: query.trim() }),\n type: resolvedType,\n })\n if (result.ok) {\n setDropOffPoints(result.points)\n } else {\n setDropOffPointsError(t('shipping_carriers.create.error.fetchDropOffPoints', result.error))\n setDropOffPoints([])\n }\n setIsFetchingDropOffPoints(false)\n }\n\n const canProceedFromConfigure =\n isAddressValid(origin) &&\n isAddressValid(destination) &&\n packages.length > 0 &&\n packages.every(isPackageValid) &&\n isEmailValid(senderContact.email) &&\n isPhoneValid(senderContact.phone) &&\n isEmailValid(receiverContact.email) &&\n isPhoneValid(receiverContact.phone)\n\n const senderContactErrors = {\n email: !isEmailValid(senderContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(senderContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n const receiverContactErrors = {\n email: !isEmailValid(receiverContact.email) ? t('shipping_carriers.create.error.invalidEmail', 'Invalid email address.') : null,\n phone: !isPhoneValid(receiverContact.phone) ? t('shipping_carriers.create.error.invalidPhone', 'Invalid phone number.') : null,\n }\n\n return {\n step,\n backHref,\n providers,\n isLoadingProviders,\n providerError,\n selectedProvider,\n origin,\n destination,\n packages,\n labelFormat,\n senderContact,\n receiverContact,\n targetPoint,\n c2cSendingMethod,\n isFetchingRates,\n canProceedFromConfigure,\n senderContactErrors,\n receiverContactErrors,\n dropOffPointQuery,\n dropOffPoints,\n isFetchingDropOffPoints,\n dropOffPointsError,\n rates,\n ratesError,\n selectedRate,\n isSubmitting,\n goToStep,\n handleProviderSelect,\n handleConfigureNext,\n handleSubmit: () => void handleSubmit(),\n setOrigin,\n setDestination,\n setPackages,\n setLabelFormat,\n setSenderContact,\n setReceiverContact,\n setTargetPoint,\n setC2cSendingMethod,\n setSelectedRate,\n searchDropOffPoints: (query: string) => void searchDropOffPoints(query),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -2,6 +2,7 @@ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find"
|
|
|
2
2
|
import { CarrierShipment } from "../data/entities.js";
|
|
3
3
|
import { emitShippingEvent } from "../events.js";
|
|
4
4
|
import { getShippingAdapter } from "./adapter-registry.js";
|
|
5
|
+
import { isValidShippingTransition, ShipmentCancelNotAllowedError } from "./status-sync.js";
|
|
5
6
|
function createShippingCarrierService(deps) {
|
|
6
7
|
const { em, integrationCredentialsService } = deps;
|
|
7
8
|
async function findShipmentOrThrow(shipmentId, scope) {
|
|
@@ -34,11 +35,16 @@ function createShippingCarrierService(deps) {
|
|
|
34
35
|
organizationId: input.organizationId,
|
|
35
36
|
tenantId: input.tenantId
|
|
36
37
|
});
|
|
38
|
+
const mergedCredentials = {
|
|
39
|
+
...credentials,
|
|
40
|
+
...input.receiverPhone !== void 0 ? { receiverPhone: input.receiverPhone } : {},
|
|
41
|
+
...input.receiverEmail !== void 0 ? { receiverEmail: input.receiverEmail } : {}
|
|
42
|
+
};
|
|
37
43
|
return adapter.calculateRates({
|
|
38
44
|
origin: input.origin,
|
|
39
45
|
destination: input.destination,
|
|
40
46
|
packages: input.packages,
|
|
41
|
-
credentials
|
|
47
|
+
credentials: mergedCredentials
|
|
42
48
|
});
|
|
43
49
|
},
|
|
44
50
|
async createShipment(input) {
|
|
@@ -46,13 +52,22 @@ function createShippingCarrierService(deps) {
|
|
|
46
52
|
organizationId: input.organizationId,
|
|
47
53
|
tenantId: input.tenantId
|
|
48
54
|
});
|
|
55
|
+
const mergedCredentials = {
|
|
56
|
+
...credentials,
|
|
57
|
+
...input.senderPhone !== void 0 ? { senderPhone: input.senderPhone } : {},
|
|
58
|
+
...input.senderEmail !== void 0 ? { senderEmail: input.senderEmail } : {},
|
|
59
|
+
...input.receiverPhone !== void 0 ? { receiverPhone: input.receiverPhone } : {},
|
|
60
|
+
...input.receiverEmail !== void 0 ? { receiverEmail: input.receiverEmail } : {},
|
|
61
|
+
...input.targetPoint !== void 0 ? { targetPoint: input.targetPoint } : {},
|
|
62
|
+
...input.c2cSendingMethod !== void 0 ? { c2cSendingMethod: input.c2cSendingMethod } : {}
|
|
63
|
+
};
|
|
49
64
|
const created = await adapter.createShipment({
|
|
50
65
|
orderId: input.orderId,
|
|
51
66
|
origin: input.origin,
|
|
52
67
|
destination: input.destination,
|
|
53
68
|
packages: input.packages,
|
|
54
69
|
serviceCode: input.serviceCode,
|
|
55
|
-
credentials,
|
|
70
|
+
credentials: mergedCredentials,
|
|
56
71
|
labelFormat: input.labelFormat
|
|
57
72
|
});
|
|
58
73
|
const shipment = em.create(CarrierShipment, {
|
|
@@ -99,7 +114,7 @@ function createShippingCarrierService(deps) {
|
|
|
99
114
|
) : null;
|
|
100
115
|
const tracking = await adapter.getTracking({
|
|
101
116
|
shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,
|
|
102
|
-
trackingNumber: input.trackingNumber,
|
|
117
|
+
trackingNumber: input.trackingNumber ?? shipment?.trackingNumber,
|
|
103
118
|
credentials
|
|
104
119
|
});
|
|
105
120
|
if (shipment) {
|
|
@@ -113,6 +128,9 @@ function createShippingCarrierService(deps) {
|
|
|
113
128
|
async cancelShipment(input) {
|
|
114
129
|
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
115
130
|
const shipment = await findShipmentOrThrow(input.shipmentId, scope);
|
|
131
|
+
if (!isValidShippingTransition(shipment.unifiedStatus, "cancelled")) {
|
|
132
|
+
throw new ShipmentCancelNotAllowedError(shipment.unifiedStatus);
|
|
133
|
+
}
|
|
116
134
|
const { adapter, credentials } = await resolveAdapter(input.providerKey, {
|
|
117
135
|
organizationId: input.organizationId,
|
|
118
136
|
tenantId: input.tenantId
|
|
@@ -146,6 +164,21 @@ function createShippingCarrierService(deps) {
|
|
|
146
164
|
void 0,
|
|
147
165
|
scope
|
|
148
166
|
);
|
|
167
|
+
},
|
|
168
|
+
async searchDropOffPoints(input) {
|
|
169
|
+
const { adapter, credentials } = await resolveAdapter(input.providerKey, {
|
|
170
|
+
organizationId: input.organizationId,
|
|
171
|
+
tenantId: input.tenantId
|
|
172
|
+
});
|
|
173
|
+
if (!adapter.searchDropOffPoints) {
|
|
174
|
+
throw new Error(`Provider ${input.providerKey} does not support drop-off point search`);
|
|
175
|
+
}
|
|
176
|
+
return adapter.searchDropOffPoints({
|
|
177
|
+
query: input.query,
|
|
178
|
+
type: input.type,
|
|
179
|
+
postCode: input.postCode,
|
|
180
|
+
credentials
|
|
181
|
+
});
|
|
149
182
|
}
|
|
150
183
|
};
|
|
151
184
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/shipping_carriers/lib/shipping-service.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport { CarrierShipment } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport { getShippingAdapter } from './adapter-registry'\n\nexport function createShippingCarrierService(deps: {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n}) {\n const { em, integrationCredentialsService } = deps\n\n async function findShipmentOrThrow(\n shipmentId: string,\n scope: { organizationId: string; tenantId: string },\n ): Promise<CarrierShipment> {\n const shipment = await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n id: shipmentId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!shipment) {\n throw new Error('Shipment not found')\n }\n return shipment\n }\n\n async function resolveAdapter(providerKey: string, scope: { organizationId: string; tenantId: string }) {\n const adapter = getShippingAdapter(providerKey)\n if (!adapter) throw new Error(`No shipping adapter registered for provider: ${providerKey}`)\n const credentials = await integrationCredentialsService.resolve(`carrier_${providerKey}`, scope) ?? {}\n return { adapter, credentials }\n }\n\n return {\n async calculateRates(input: {\n providerKey: string\n origin: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n destination: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n packages: Array<{ weightKg: number; lengthCm: number; widthCm: number; heightCm: number }>\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n return adapter.calculateRates({\n origin: input.origin,\n destination: input.destination,\n packages: input.packages,\n credentials,\n })\n },\n\n async createShipment(input: {\n providerKey: string\n orderId: string\n origin: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n destination: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n packages: Array<{ weightKg: number; lengthCm: number; widthCm: number; heightCm: number }>\n serviceCode: string\n labelFormat?: 'pdf' | 'zpl' | 'png'\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const created = await adapter.createShipment({\n orderId: input.orderId,\n origin: input.origin,\n destination: input.destination,\n packages: input.packages,\n serviceCode: input.serviceCode,\n credentials,\n labelFormat: input.labelFormat,\n })\n const shipment = em.create(CarrierShipment, {\n orderId: input.orderId,\n providerKey: input.providerKey,\n carrierShipmentId: created.shipmentId,\n trackingNumber: created.trackingNumber,\n unifiedStatus: 'label_created',\n labelUrl: created.labelUrl ?? null,\n labelData: created.labelData ?? null,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n await em.persistAndFlush(shipment)\n await emitShippingEvent('shipping_carriers.shipment.created', {\n shipmentId: shipment.id,\n orderId: input.orderId,\n providerKey: input.providerKey,\n trackingNumber: created.trackingNumber,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n return shipment\n },\n\n async getTracking(input: {\n providerKey: string\n shipmentId?: string\n trackingNumber?: string\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const shipment = input.shipmentId\n ? await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n id: input.shipmentId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n deletedAt: null,\n },\n undefined,\n {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n },\n )\n : null\n const tracking = await adapter.getTracking({\n shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,\n trackingNumber: input.trackingNumber,\n credentials,\n })\n if (shipment) {\n shipment.unifiedStatus = tracking.status\n shipment.trackingEvents = tracking.events\n shipment.lastPolledAt = new Date()\n await em.flush()\n }\n return tracking\n },\n\n async cancelShipment(input: {\n providerKey: string\n shipmentId: string\n reason?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const shipment = await findShipmentOrThrow(input.shipmentId, scope)\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const result = await adapter.cancelShipment({\n shipmentId: shipment.carrierShipmentId,\n reason: input.reason,\n credentials,\n })\n shipment.unifiedStatus = result.status\n await em.flush()\n await emitShippingEvent('shipping_carriers.shipment.cancelled', {\n shipmentId: shipment.id,\n providerKey: input.providerKey,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n return result\n },\n\n async findShipmentByCarrierId(\n providerKey: string,\n carrierShipmentId: string,\n scope: { organizationId: string; tenantId: string },\n ) {\n return findOneWithDecryption(\n em,\n CarrierShipment,\n {\n providerKey,\n carrierShipmentId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n }\n}\n\nexport type ShippingCarrierService = ReturnType<typeof createShippingCarrierService>\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,6BAA6B;AAEtC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport { CarrierShipment } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport { getShippingAdapter } from './adapter-registry'\nimport type { UnifiedShipmentStatus } from './adapter'\nimport { isValidShippingTransition, ShipmentCancelNotAllowedError } from './status-sync'\n\nexport function createShippingCarrierService(deps: {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n}) {\n const { em, integrationCredentialsService } = deps\n\n async function findShipmentOrThrow(\n shipmentId: string,\n scope: { organizationId: string; tenantId: string },\n ): Promise<CarrierShipment> {\n const shipment = await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n id: shipmentId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!shipment) {\n throw new Error('Shipment not found')\n }\n return shipment\n }\n\n async function resolveAdapter(providerKey: string, scope: { organizationId: string; tenantId: string }) {\n const adapter = getShippingAdapter(providerKey)\n if (!adapter) throw new Error(`No shipping adapter registered for provider: ${providerKey}`)\n const credentials = await integrationCredentialsService.resolve(`carrier_${providerKey}`, scope) ?? {}\n return { adapter, credentials }\n }\n\n return {\n async calculateRates(input: {\n providerKey: string\n origin: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n destination: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n packages: Array<{ weightKg: number; lengthCm: number; widthCm: number; heightCm: number }>\n receiverPhone?: string\n receiverEmail?: string\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const mergedCredentials = {\n ...credentials,\n ...(input.receiverPhone !== undefined ? { receiverPhone: input.receiverPhone } : {}),\n ...(input.receiverEmail !== undefined ? { receiverEmail: input.receiverEmail } : {}),\n }\n return adapter.calculateRates({\n origin: input.origin,\n destination: input.destination,\n packages: input.packages,\n credentials: mergedCredentials,\n })\n },\n\n async createShipment(input: {\n providerKey: string\n orderId: string\n origin: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n destination: { countryCode: string; postalCode: string; city: string; line1: string; line2?: string }\n packages: Array<{ weightKg: number; lengthCm: number; widthCm: number; heightCm: number }>\n serviceCode: string\n labelFormat?: 'pdf' | 'zpl' | 'png'\n senderPhone?: string\n senderEmail?: string\n receiverPhone?: string\n receiverEmail?: string\n targetPoint?: string\n c2cSendingMethod?: string\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const mergedCredentials = {\n ...credentials,\n ...(input.senderPhone !== undefined ? { senderPhone: input.senderPhone } : {}),\n ...(input.senderEmail !== undefined ? { senderEmail: input.senderEmail } : {}),\n ...(input.receiverPhone !== undefined ? { receiverPhone: input.receiverPhone } : {}),\n ...(input.receiverEmail !== undefined ? { receiverEmail: input.receiverEmail } : {}),\n ...(input.targetPoint !== undefined ? { targetPoint: input.targetPoint } : {}),\n ...(input.c2cSendingMethod !== undefined ? { c2cSendingMethod: input.c2cSendingMethod } : {}),\n }\n const created = await adapter.createShipment({\n orderId: input.orderId,\n origin: input.origin,\n destination: input.destination,\n packages: input.packages,\n serviceCode: input.serviceCode,\n credentials: mergedCredentials,\n labelFormat: input.labelFormat,\n })\n const shipment = em.create(CarrierShipment, {\n orderId: input.orderId,\n providerKey: input.providerKey,\n carrierShipmentId: created.shipmentId,\n trackingNumber: created.trackingNumber,\n unifiedStatus: 'label_created',\n labelUrl: created.labelUrl ?? null,\n labelData: created.labelData ?? null,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n await em.persistAndFlush(shipment)\n await emitShippingEvent('shipping_carriers.shipment.created', {\n shipmentId: shipment.id,\n orderId: input.orderId,\n providerKey: input.providerKey,\n trackingNumber: created.trackingNumber,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n return shipment\n },\n\n async getTracking(input: {\n providerKey: string\n shipmentId?: string\n trackingNumber?: string\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const shipment = input.shipmentId\n ? await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n id: input.shipmentId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n deletedAt: null,\n },\n undefined,\n {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n },\n )\n : null\n const tracking = await adapter.getTracking({\n shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,\n trackingNumber: input.trackingNumber ?? shipment?.trackingNumber,\n credentials,\n })\n if (shipment) {\n shipment.unifiedStatus = tracking.status\n shipment.trackingEvents = tracking.events\n shipment.lastPolledAt = new Date()\n await em.flush()\n }\n return tracking\n },\n\n async cancelShipment(input: {\n providerKey: string\n shipmentId: string\n reason?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const shipment = await findShipmentOrThrow(input.shipmentId, scope)\n if (!isValidShippingTransition(shipment.unifiedStatus as UnifiedShipmentStatus, 'cancelled')) {\n throw new ShipmentCancelNotAllowedError(shipment.unifiedStatus)\n }\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n const result = await adapter.cancelShipment({\n shipmentId: shipment.carrierShipmentId,\n reason: input.reason,\n credentials,\n })\n shipment.unifiedStatus = result.status\n await em.flush()\n await emitShippingEvent('shipping_carriers.shipment.cancelled', {\n shipmentId: shipment.id,\n providerKey: input.providerKey,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n return result\n },\n\n async findShipmentByCarrierId(\n providerKey: string,\n carrierShipmentId: string,\n scope: { organizationId: string; tenantId: string },\n ) {\n return findOneWithDecryption(\n em,\n CarrierShipment,\n {\n providerKey,\n carrierShipmentId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n\n async searchDropOffPoints(input: {\n providerKey: string\n query?: string\n type?: string\n postCode?: string\n organizationId: string\n tenantId: string\n }) {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n })\n if (!adapter.searchDropOffPoints) {\n throw new Error(`Provider ${input.providerKey} does not support drop-off point search`)\n }\n return adapter.searchDropOffPoints({\n query: input.query,\n type: input.type,\n postCode: input.postCode,\n credentials,\n })\n },\n }\n}\n\nexport type ShippingCarrierService = ReturnType<typeof createShippingCarrierService>\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
class ShipmentCancelNotAllowedError extends Error {
|
|
2
|
+
constructor(status) {
|
|
3
|
+
super(`Shipment cannot be cancelled in its current status: ${status}`);
|
|
4
|
+
this.name = "ShipmentCancelNotAllowedError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
1
7
|
const VALID_SHIPPING_TRANSITIONS = {
|
|
2
8
|
label_created: ["picked_up", "in_transit", "cancelled"],
|
|
3
9
|
picked_up: ["in_transit", "cancelled"],
|
|
@@ -29,6 +35,7 @@ function getTerminalShippingEvent(status) {
|
|
|
29
35
|
return null;
|
|
30
36
|
}
|
|
31
37
|
export {
|
|
38
|
+
ShipmentCancelNotAllowedError,
|
|
32
39
|
TERMINAL_SHIPPING_STATUSES,
|
|
33
40
|
getTerminalShippingEvent,
|
|
34
41
|
isValidShippingTransition,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/shipping_carriers/lib/status-sync.ts"],
|
|
4
|
-
"sourcesContent": ["import type { UnifiedShipmentStatus } from './adapter'\nimport type { CarrierShipment } from '../data/entities'\nimport type { ShippingEventId } from '../events'\n\nconst VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {\n label_created: ['picked_up', 'in_transit', 'cancelled'],\n picked_up: ['in_transit', 'cancelled'],\n in_transit: ['out_for_delivery', 'delivered', 'returned', 'failed_delivery'],\n out_for_delivery: ['delivered', 'returned', 'failed_delivery'],\n failed_delivery: ['in_transit', 'out_for_delivery', 'delivered', 'returned', 'cancelled'],\n}\n\nexport const TERMINAL_SHIPPING_STATUSES: Set<UnifiedShipmentStatus> = new Set([\n 'delivered',\n 'returned',\n 'cancelled',\n])\n\nexport function isValidShippingTransition(from: UnifiedShipmentStatus, to: UnifiedShipmentStatus): boolean {\n if (from === to) return false\n const allowed = VALID_SHIPPING_TRANSITIONS[from]\n if (!allowed) return false\n return allowed.includes(to)\n}\n\nexport function syncShipmentStatus(shipment: CarrierShipment, newStatus: UnifiedShipmentStatus): boolean {\n const currentStatus = shipment.unifiedStatus as UnifiedShipmentStatus\n if (!isValidShippingTransition(currentStatus, newStatus)) return false\n shipment.unifiedStatus = newStatus\n return true\n}\n\nexport function getTerminalShippingEvent(status: UnifiedShipmentStatus): ShippingEventId | null {\n if (status === 'delivered') return 'shipping_carriers.shipment.delivered'\n if (status === 'returned') return 'shipping_carriers.shipment.returned'\n if (status === 'cancelled') return 'shipping_carriers.shipment.cancelled'\n return null\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { UnifiedShipmentStatus } from './adapter'\nimport type { CarrierShipment } from '../data/entities'\nimport type { ShippingEventId } from '../events'\n\nexport class ShipmentCancelNotAllowedError extends Error {\n constructor(status: string) {\n super(`Shipment cannot be cancelled in its current status: ${status}`)\n this.name = 'ShipmentCancelNotAllowedError'\n }\n}\n\nconst VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {\n label_created: ['picked_up', 'in_transit', 'cancelled'],\n picked_up: ['in_transit', 'cancelled'],\n in_transit: ['out_for_delivery', 'delivered', 'returned', 'failed_delivery'],\n out_for_delivery: ['delivered', 'returned', 'failed_delivery'],\n failed_delivery: ['in_transit', 'out_for_delivery', 'delivered', 'returned', 'cancelled'],\n}\n\nexport const TERMINAL_SHIPPING_STATUSES: Set<UnifiedShipmentStatus> = new Set([\n 'delivered',\n 'returned',\n 'cancelled',\n])\n\nexport function isValidShippingTransition(from: UnifiedShipmentStatus, to: UnifiedShipmentStatus): boolean {\n if (from === to) return false\n const allowed = VALID_SHIPPING_TRANSITIONS[from]\n if (!allowed) return false\n return allowed.includes(to)\n}\n\nexport function syncShipmentStatus(shipment: CarrierShipment, newStatus: UnifiedShipmentStatus): boolean {\n const currentStatus = shipment.unifiedStatus as UnifiedShipmentStatus\n if (!isValidShippingTransition(currentStatus, newStatus)) return false\n shipment.unifiedStatus = newStatus\n return true\n}\n\nexport function getTerminalShippingEvent(status: UnifiedShipmentStatus): ShippingEventId | null {\n if (status === 'delivered') return 'shipping_carriers.shipment.delivered'\n if (status === 'returned') return 'shipping_carriers.shipment.returned'\n if (status === 'cancelled') return 'shipping_carriers.shipment.cancelled'\n return null\n}\n"],
|
|
5
|
+
"mappings": "AAIO,MAAM,sCAAsC,MAAM;AAAA,EACvD,YAAY,QAAgB;AAC1B,UAAM,uDAAuD,MAAM,EAAE;AACrE,SAAK,OAAO;AAAA,EACd;AACF;AAEA,MAAM,6BAAsE;AAAA,EAC1E,eAAe,CAAC,aAAa,cAAc,WAAW;AAAA,EACtD,WAAW,CAAC,cAAc,WAAW;AAAA,EACrC,YAAY,CAAC,oBAAoB,aAAa,YAAY,iBAAiB;AAAA,EAC3E,kBAAkB,CAAC,aAAa,YAAY,iBAAiB;AAAA,EAC7D,iBAAiB,CAAC,cAAc,oBAAoB,aAAa,YAAY,WAAW;AAC1F;AAEO,MAAM,6BAAyD,oBAAI,IAAI;AAAA,EAC5E;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,0BAA0B,MAA6B,IAAoC;AACzG,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,UAAU,2BAA2B,IAAI;AAC/C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,SAAS,EAAE;AAC5B;AAEO,SAAS,mBAAmB,UAA2B,WAA2C;AACvG,QAAM,gBAAgB,SAAS;AAC/B,MAAI,CAAC,0BAA0B,eAAe,SAAS,EAAG,QAAO;AACjE,WAAS,gBAAgB;AACzB,SAAO;AACT;AAEO,SAAS,yBAAyB,QAAuD;AAC9F,MAAI,WAAW,YAAa,QAAO;AACnC,MAAI,WAAW,WAAY,QAAO;AAClC,MAAI,WAAW,YAAa,QAAO;AACnC,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|