@open-mercato/core 0.6.6-develop.6204.1.30b1f58642 → 0.6.6-develop.6227.1.2695efff30
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +7 -4
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
5
|
+
import { trackingRefreshSchema } from "../../../data/validators.js";
|
|
6
|
+
import { shippingCarriersTag } from "../../openapi.js";
|
|
7
|
+
const metadata = {
|
|
8
|
+
path: "/shipping-carriers/tracking/refresh",
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ["shipping_carriers.manage"] }
|
|
10
|
+
};
|
|
11
|
+
async function POST(req) {
|
|
12
|
+
const auth = await getAuthFromRequest(req);
|
|
13
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
14
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
15
|
+
}
|
|
16
|
+
const payload = await readJsonSafe(req);
|
|
17
|
+
const parsed = trackingRefreshSchema.safeParse(payload);
|
|
18
|
+
if (!parsed.success) {
|
|
19
|
+
return NextResponse.json({ error: "Invalid payload", details: parsed.error.flatten() }, { status: 422 });
|
|
20
|
+
}
|
|
21
|
+
const container = await createRequestContainer();
|
|
22
|
+
const service = container.resolve("shippingCarrierService");
|
|
23
|
+
try {
|
|
24
|
+
const tracking = await service.refreshTracking({
|
|
25
|
+
...parsed.data,
|
|
26
|
+
organizationId: auth.orgId,
|
|
27
|
+
tenantId: auth.tenantId
|
|
28
|
+
});
|
|
29
|
+
return NextResponse.json(tracking);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const message = error instanceof Error ? error.message : "Failed to refresh tracking";
|
|
32
|
+
return NextResponse.json({ error: message }, { status: 502 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const openApi = {
|
|
36
|
+
tags: [shippingCarriersTag],
|
|
37
|
+
summary: "Refresh tracking",
|
|
38
|
+
methods: {
|
|
39
|
+
POST: {
|
|
40
|
+
summary: "Refresh tracking and persist the latest shipment status",
|
|
41
|
+
tags: [shippingCarriersTag],
|
|
42
|
+
responses: [
|
|
43
|
+
{ status: 200, description: "Tracking refreshed and shipment status persisted" },
|
|
44
|
+
{ status: 422, description: "Validation failed" },
|
|
45
|
+
{ status: 502, description: "Provider upstream error" }
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export {
|
|
51
|
+
POST,
|
|
52
|
+
metadata,
|
|
53
|
+
openApi
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/shipping_carriers/api/tracking/refresh/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { ShippingCarrierService } from '../../../lib/shipping-service'\nimport { trackingRefreshSchema } from '../../../data/validators'\nimport { shippingCarriersTag } from '../../openapi'\n\nexport const metadata = {\n path: '/shipping-carriers/tracking/refresh',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const payload = await readJsonSafe<unknown>(req)\n const parsed = trackingRefreshSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const tracking = await service.refreshTracking({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n return NextResponse.json(tracking)\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Failed to refresh tracking'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Refresh tracking',\n methods: {\n POST: {\n summary: 'Refresh tracking and persist the latest shipment status',\n tags: [shippingCarriersTag],\n responses: [\n { status: 200, description: 'Tracking refreshed and shipment status persisted' },\n { status: 422, description: 'Validation failed' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,SAAS,6BAA6B;AACtC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,0BAA0B,EAAE;AAC3E;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,UAAU,MAAM,aAAsB,GAAG;AAC/C,QAAM,SAAS,sBAAsB,UAAU,OAAO;AACtD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,wBAAwB;AAC1D,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,gBAAgB;AAAA,MAC7C,GAAG,OAAO;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,WAAO,aAAa,KAAK,QAAQ;AAAA,EACnC,SAAS,OAAgB;AACvB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,mBAAmB;AAAA,EAC1B,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,MAAM,CAAC,mBAAmB;AAAA,MAC1B,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mDAAmD;AAAA,QAC/E,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -46,6 +46,10 @@ const trackingQuerySchema = z.object({
|
|
|
46
46
|
message: "shipmentId or trackingNumber is required",
|
|
47
47
|
path: ["shipmentId"]
|
|
48
48
|
});
|
|
49
|
+
const trackingRefreshSchema = z.object({
|
|
50
|
+
providerKey: z.string().min(1),
|
|
51
|
+
shipmentId: z.string().uuid()
|
|
52
|
+
});
|
|
49
53
|
const cancelShipmentSchema = z.object({
|
|
50
54
|
providerKey: z.string().min(1),
|
|
51
55
|
shipmentId: z.string().uuid(),
|
|
@@ -63,6 +67,7 @@ export {
|
|
|
63
67
|
cancelShipmentSchema,
|
|
64
68
|
createShipmentSchema,
|
|
65
69
|
searchDropOffPointsQuerySchema,
|
|
66
|
-
trackingQuerySchema
|
|
70
|
+
trackingQuerySchema,
|
|
71
|
+
trackingRefreshSchema
|
|
67
72
|
};
|
|
68
73
|
//# sourceMappingURL=validators.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/shipping_carriers/data/validators.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\n\nconst addressSchema = z.object({\n countryCode: z.string().min(2).max(3),\n postalCode: z.string().min(1),\n city: z.string().min(1),\n line1: z.string().min(1),\n line2: z.string().optional(),\n})\n\nconst packageSchema = z.object({\n weightKg: z.number().positive(),\n lengthCm: z.number().positive(),\n widthCm: z.number().positive(),\n heightCm: z.number().positive(),\n})\n\n/** Permissive phone regex: allows +, digits, spaces, dashes, dots, and parentheses. Min 7 chars. */\nexport const PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst emailField = z.string().trim().email('Invalid email address.').max(320).optional()\nconst phoneField = z.string().trim().regex(PHONE_REGEX, 'Invalid phone number.').max(50).optional()\n\nexport const calculateRatesSchema = z.object({\n providerKey: z.string().min(1),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n receiverPhone: phoneField,\n receiverEmail: emailField,\n})\n\nexport const createShipmentSchema = z.object({\n providerKey: z.string().min(1),\n orderId: z.string().uuid(),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n serviceCode: z.string().min(1),\n labelFormat: z.enum(['pdf', 'zpl', 'png']).optional(),\n senderPhone: phoneField,\n senderEmail: emailField,\n receiverPhone: phoneField,\n receiverEmail: emailField,\n targetPoint: z.string().optional(),\n c2cSendingMethod: z.enum(['parcel_locker', 'dispatch_order', 'pop', 'any_point']).optional(),\n})\n\nexport const trackingQuerySchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid().optional(),\n trackingNumber: z.string().min(1).optional(),\n}).refine((value) => Boolean(value.shipmentId || value.trackingNumber), {\n message: 'shipmentId or trackingNumber is required',\n path: ['shipmentId'],\n})\n\nexport const cancelShipmentSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n reason: z.string().max(200).optional(),\n})\n\nexport const searchDropOffPointsQuerySchema = z.object({\n providerKey: z.string().min(1),\n query: z.string().optional(),\n type: z.string().optional(),\n postCode: z.string().optional(),\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EACpC,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAED,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;AAGM,MAAM,cAAc;AAE3B,MAAM,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,EAAE,IAAI,GAAG,EAAE,SAAS;AACvF,MAAM,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,uBAAuB,EAAE,IAAI,EAAE,EAAE,SAAS;AAE3F,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,UAAU,EAAE,MAAM,aAAa,EAAE,IAAI,CAAC;AAAA,EACtC,eAAe;AAAA,EACf,eAAe;AACjB,CAAC;AAEM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,SAAS,EAAE,OAAO,EAAE,KAAK;AAAA,EACzB,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,UAAU,EAAE,MAAM,aAAa,EAAE,IAAI,CAAC;AAAA,EACtC,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,aAAa,EAAE,KAAK,CAAC,OAAO,OAAO,KAAK,CAAC,EAAE,SAAS;AAAA,EACpD,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,kBAAkB,EAAE,KAAK,CAAC,iBAAiB,kBAAkB,OAAO,WAAW,CAAC,EAAE,SAAS;AAC7F,CAAC;AAEM,MAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAC7C,CAAC,EAAE,OAAO,CAAC,UAAU,QAAQ,MAAM,cAAc,MAAM,cAAc,GAAG;AAAA,EACtE,SAAS;AAAA,EACT,MAAM,CAAC,YAAY;AACrB,CAAC;AAEM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,YAAY,EAAE,OAAO,EAAE,KAAK;AAAA,EAC5B,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AACvC,CAAC;AAEM,MAAM,iCAAiC,EAAE,OAAO;AAAA,EACrD,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;",
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\nconst addressSchema = z.object({\n countryCode: z.string().min(2).max(3),\n postalCode: z.string().min(1),\n city: z.string().min(1),\n line1: z.string().min(1),\n line2: z.string().optional(),\n})\n\nconst packageSchema = z.object({\n weightKg: z.number().positive(),\n lengthCm: z.number().positive(),\n widthCm: z.number().positive(),\n heightCm: z.number().positive(),\n})\n\n/** Permissive phone regex: allows +, digits, spaces, dashes, dots, and parentheses. Min 7 chars. */\nexport const PHONE_REGEX = /^[+\\d][\\d\\s\\-().]{6,}$/\n\nconst emailField = z.string().trim().email('Invalid email address.').max(320).optional()\nconst phoneField = z.string().trim().regex(PHONE_REGEX, 'Invalid phone number.').max(50).optional()\n\nexport const calculateRatesSchema = z.object({\n providerKey: z.string().min(1),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n receiverPhone: phoneField,\n receiverEmail: emailField,\n})\n\nexport const createShipmentSchema = z.object({\n providerKey: z.string().min(1),\n orderId: z.string().uuid(),\n origin: addressSchema,\n destination: addressSchema,\n packages: z.array(packageSchema).min(1),\n serviceCode: z.string().min(1),\n labelFormat: z.enum(['pdf', 'zpl', 'png']).optional(),\n senderPhone: phoneField,\n senderEmail: emailField,\n receiverPhone: phoneField,\n receiverEmail: emailField,\n targetPoint: z.string().optional(),\n c2cSendingMethod: z.enum(['parcel_locker', 'dispatch_order', 'pop', 'any_point']).optional(),\n})\n\nexport const trackingQuerySchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid().optional(),\n trackingNumber: z.string().min(1).optional(),\n}).refine((value) => Boolean(value.shipmentId || value.trackingNumber), {\n message: 'shipmentId or trackingNumber is required',\n path: ['shipmentId'],\n})\n\nexport const trackingRefreshSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n})\n\nexport const cancelShipmentSchema = z.object({\n providerKey: z.string().min(1),\n shipmentId: z.string().uuid(),\n reason: z.string().max(200).optional(),\n})\n\nexport const searchDropOffPointsQuerySchema = z.object({\n providerKey: z.string().min(1),\n query: z.string().optional(),\n type: z.string().optional(),\n postCode: z.string().optional(),\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
3
3
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
4
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
4
5
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
6
|
import {
|
|
6
7
|
fetchProviders,
|
|
@@ -29,6 +30,9 @@ const useShipmentWizard = () => {
|
|
|
29
30
|
const t = useT();
|
|
30
31
|
const router = useRouter();
|
|
31
32
|
const searchParams = useSearchParams();
|
|
33
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
34
|
+
contextId: "shipping_carriers.shipment.create"
|
|
35
|
+
});
|
|
32
36
|
const orderId = searchParams?.get("orderId") ?? null;
|
|
33
37
|
const [step, setStep] = React.useState("provider");
|
|
34
38
|
const [providers, setProviders] = React.useState([]);
|
|
@@ -103,7 +107,7 @@ const useShipmentWizard = () => {
|
|
|
103
107
|
const handleSubmit = async () => {
|
|
104
108
|
if (!selectedProvider || !selectedRate || !orderId) return;
|
|
105
109
|
setIsSubmitting(true);
|
|
106
|
-
const
|
|
110
|
+
const payload = {
|
|
107
111
|
providerKey: selectedProvider,
|
|
108
112
|
orderId,
|
|
109
113
|
origin,
|
|
@@ -117,13 +121,29 @@ const useShipmentWizard = () => {
|
|
|
117
121
|
...receiverContact.email ? { receiverEmail: receiverContact.email } : {},
|
|
118
122
|
...targetPoint ? { targetPoint } : {},
|
|
119
123
|
...c2cSendingMethod ? { c2cSendingMethod } : {}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
const result = await runMutation({
|
|
127
|
+
operation: () => createShipment(payload),
|
|
128
|
+
context: {
|
|
129
|
+
operation: "create",
|
|
130
|
+
resourceKind: "shipping_carriers.shipment",
|
|
131
|
+
resourceId: orderId,
|
|
132
|
+
retryLastMutation
|
|
133
|
+
},
|
|
134
|
+
mutationPayload: payload
|
|
135
|
+
});
|
|
136
|
+
if (result.ok) {
|
|
137
|
+
flash(t("shipping_carriers.create.success", "Shipment created successfully."), "success");
|
|
138
|
+
router.push(backHref);
|
|
139
|
+
} else {
|
|
140
|
+
flash(t("shipping_carriers.create.error.create", result.error), "error");
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : t("shipping_carriers.create.error.create", "Failed to create shipment.");
|
|
144
|
+
flash(t("shipping_carriers.create.error.create", message), "error");
|
|
145
|
+
} finally {
|
|
146
|
+
setIsSubmitting(false);
|
|
127
147
|
}
|
|
128
148
|
};
|
|
129
149
|
const goToStep = (next) => setStep(next);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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": "AAAA,YAAY,WAAW;AACvB,SAAS,WAAW,uBAAuB;AAC3C,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\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 { CreateShipmentParams } 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 { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: 'shipping_carriers.shipment.create',\n })\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 payload: CreateShipmentParams = {\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 try {\n const result = await runMutation({\n operation: () => createShipment(payload),\n context: {\n operation: 'create',\n resourceKind: 'shipping_carriers.shipment',\n resourceId: orderId,\n retryLastMutation,\n },\n mutationPayload: payload as unknown as Record<string, unknown>,\n })\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 } catch (error) {\n const message = error instanceof Error ? error.message : t('shipping_carriers.create.error.create', 'Failed to create shipment.')\n flash(t('shipping_carriers.create.error.create', message), 'error')\n } finally {\n setIsSubmitting(false)\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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,13 @@ 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 {
|
|
5
|
+
import {
|
|
6
|
+
getTerminalShippingEvent,
|
|
7
|
+
isValidShippingTransition,
|
|
8
|
+
ShipmentCancelNotAllowedError,
|
|
9
|
+
syncShipmentStatus,
|
|
10
|
+
TERMINAL_SHIPPING_STATUSES
|
|
11
|
+
} from "./status-sync.js";
|
|
6
12
|
function createShippingCarrierService(deps) {
|
|
7
13
|
const { em, integrationCredentialsService } = deps;
|
|
8
14
|
async function findShipmentOrThrow(shipmentId, scope) {
|
|
@@ -112,16 +118,40 @@ function createShippingCarrierService(deps) {
|
|
|
112
118
|
tenantId: input.tenantId
|
|
113
119
|
}
|
|
114
120
|
) : null;
|
|
115
|
-
|
|
121
|
+
return adapter.getTracking({
|
|
116
122
|
shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,
|
|
117
123
|
trackingNumber: input.trackingNumber ?? shipment?.trackingNumber,
|
|
118
124
|
credentials
|
|
119
125
|
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
},
|
|
127
|
+
async refreshTracking(input) {
|
|
128
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId };
|
|
129
|
+
const shipment = await findShipmentOrThrow(input.shipmentId, scope);
|
|
130
|
+
const { adapter, credentials } = await resolveAdapter(input.providerKey, scope);
|
|
131
|
+
const tracking = await adapter.getTracking({
|
|
132
|
+
shipmentId: shipment.carrierShipmentId,
|
|
133
|
+
trackingNumber: shipment.trackingNumber,
|
|
134
|
+
credentials
|
|
135
|
+
});
|
|
136
|
+
const previousStatus = shipment.unifiedStatus;
|
|
137
|
+
shipment.trackingEvents = tracking.events;
|
|
138
|
+
shipment.lastPolledAt = /* @__PURE__ */ new Date();
|
|
139
|
+
const transitionApplied = syncShipmentStatus(shipment, tracking.status);
|
|
140
|
+
await em.flush();
|
|
141
|
+
if (transitionApplied) {
|
|
142
|
+
const eventPayload = {
|
|
143
|
+
shipmentId: shipment.id,
|
|
144
|
+
providerKey: input.providerKey,
|
|
145
|
+
previousStatus,
|
|
146
|
+
newStatus: tracking.status,
|
|
147
|
+
organizationId: input.organizationId,
|
|
148
|
+
tenantId: input.tenantId
|
|
149
|
+
};
|
|
150
|
+
await emitShippingEvent("shipping_carriers.shipment.status_changed", eventPayload);
|
|
151
|
+
if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {
|
|
152
|
+
const terminalEvent = getTerminalShippingEvent(tracking.status);
|
|
153
|
+
if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload);
|
|
154
|
+
}
|
|
125
155
|
}
|
|
126
156
|
return tracking;
|
|
127
157
|
},
|
|
@@ -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'\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.persist(shipment).flush()\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": "AACA,SAAS,6BAA6B;AAEtC,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AAEnC,
|
|
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 {\n getTerminalShippingEvent,\n isValidShippingTransition,\n ShipmentCancelNotAllowedError,\n syncShipmentStatus,\n TERMINAL_SHIPPING_STATUSES,\n} 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.persist(shipment).flush()\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 return adapter.getTracking({\n shipmentId: shipment?.carrierShipmentId ?? input.shipmentId,\n trackingNumber: input.trackingNumber ?? shipment?.trackingNumber,\n credentials,\n })\n },\n\n async refreshTracking(input: {\n providerKey: string\n shipmentId: 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, scope)\n const tracking = await adapter.getTracking({\n shipmentId: shipment.carrierShipmentId,\n trackingNumber: shipment.trackingNumber,\n credentials,\n })\n const previousStatus = shipment.unifiedStatus\n shipment.trackingEvents = tracking.events\n shipment.lastPolledAt = new Date()\n const transitionApplied = syncShipmentStatus(shipment, tracking.status)\n await em.flush()\n if (transitionApplied) {\n const eventPayload = {\n shipmentId: shipment.id,\n providerKey: input.providerKey,\n previousStatus,\n newStatus: tracking.status,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n await emitShippingEvent('shipping_carriers.shipment.status_changed', eventPayload)\n if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {\n const terminalEvent = getTerminalShippingEvent(tracking.status)\n if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload)\n }\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
|
}
|
|
@@ -6,7 +6,7 @@ const metadata = {
|
|
|
6
6
|
async function handle(job, ctx) {
|
|
7
7
|
const service = ctx.resolve("shippingCarrierService");
|
|
8
8
|
for (const shipmentId of job.payload.shipmentIds) {
|
|
9
|
-
await service.
|
|
9
|
+
await service.refreshTracking({
|
|
10
10
|
providerKey: job.payload.providerKey,
|
|
11
11
|
shipmentId,
|
|
12
12
|
organizationId: job.payload.scope.organizationId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/shipping_carriers/workers/status-poller.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { ShippingCarrierService } from '../lib/shipping-service'\n\ntype PollerJobPayload = {\n providerKey: string\n shipmentIds: string[]\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'shipping-carriers-status-poller',\n id: 'shipping-carriers:status-poller',\n concurrency: 2,\n}\n\nexport default async function handle(job: QueuedJob<PollerJobPayload>, ctx: HandlerContext): Promise<void> {\n const service = ctx.resolve<ShippingCarrierService>('shippingCarrierService')\n for (const shipmentId of job.payload.shipmentIds) {\n await service.
|
|
5
|
-
"mappings": "AAgBO,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAEA,eAAO,OAA8B,KAAkC,KAAoC;AACzG,QAAM,UAAU,IAAI,QAAgC,wBAAwB;AAC5E,aAAW,cAAc,IAAI,QAAQ,aAAa;AAChD,UAAM,QAAQ,
|
|
4
|
+
"sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { ShippingCarrierService } from '../lib/shipping-service'\n\ntype PollerJobPayload = {\n providerKey: string\n shipmentIds: string[]\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'shipping-carriers-status-poller',\n id: 'shipping-carriers:status-poller',\n concurrency: 2,\n}\n\nexport default async function handle(job: QueuedJob<PollerJobPayload>, ctx: HandlerContext): Promise<void> {\n const service = ctx.resolve<ShippingCarrierService>('shippingCarrierService')\n for (const shipmentId of job.payload.shipmentIds) {\n await service.refreshTracking({\n providerKey: job.payload.providerKey,\n shipmentId,\n organizationId: job.payload.scope.organizationId,\n tenantId: job.payload.scope.tenantId,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAgBO,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAEA,eAAO,OAA8B,KAAkC,KAAoC;AACzG,QAAM,UAAU,IAAI,QAAgC,wBAAwB;AAC5E,aAAW,cAAc,IAAI,QAAQ,aAAa;AAChD,UAAM,QAAQ,gBAAgB;AAAA,MAC5B,aAAa,IAAI,QAAQ;AAAA,MACzB;AAAA,MACA,gBAAgB,IAAI,QAAQ,MAAM;AAAA,MAClC,UAAU,IAAI,QAAQ,MAAM;AAAA,IAC9B,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,6 +9,7 @@ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
|
9
9
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
10
10
|
import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
11
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
12
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
12
13
|
import { useCustomFieldDefs } from "@open-mercato/ui/backend/utils/customFieldDefs";
|
|
13
14
|
import { Save, Plus, X } from "lucide-react";
|
|
14
15
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
@@ -17,6 +18,8 @@ import { locales as defaultLocales } from "@open-mercato/shared/lib/i18n/config"
|
|
|
17
18
|
import { ISO_639_1, isValidIso639, getIso639Label } from "@open-mercato/shared/lib/i18n/iso639";
|
|
18
19
|
import { formatEntityLabel, buildEntityListUrl, getRecordLabel, resolveBaseValue } from "../lib/helpers.js";
|
|
19
20
|
import { resolveFieldList } from "../lib/resolve-field-list.js";
|
|
21
|
+
const TRANSLATION_MUTATION_CONTEXT_ID = "translations.entity-translations";
|
|
22
|
+
const SUPPORTED_LOCALES_MUTATION_CONTEXT_ID = "translations.supported-locales";
|
|
20
23
|
function useTranslationLocales() {
|
|
21
24
|
return useQuery({
|
|
22
25
|
queryKey: ["translation-locales"],
|
|
@@ -170,6 +173,7 @@ function TranslationManager({
|
|
|
170
173
|
setEditedTranslations(parsed);
|
|
171
174
|
}
|
|
172
175
|
}, [translationSignature, translationData]);
|
|
176
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: TRANSLATION_MUTATION_CONTEXT_ID });
|
|
173
177
|
const mutation = useMutation({
|
|
174
178
|
mutationFn: async () => {
|
|
175
179
|
if (!entityType || !recordId) {
|
|
@@ -191,21 +195,35 @@ function TranslationManager({
|
|
|
191
195
|
console.warn("[translations] Save skipped: payload is empty \u2014 no locale contains any non-empty field");
|
|
192
196
|
throw new Error(t("translations.manager.errors.nothingToSave", "Nothing to save \u2014 enter a translation first"));
|
|
193
197
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
return runMutation({
|
|
199
|
+
operation: async () => {
|
|
200
|
+
const res = await withScopedApiRequestHeaders(
|
|
201
|
+
buildOptimisticLockHeader(translationData?.updatedAt),
|
|
202
|
+
() => apiCall(
|
|
203
|
+
`/api/translations/${encodeURIComponent(entityType)}/${encodeURIComponent(recordId)}`,
|
|
204
|
+
{
|
|
205
|
+
method: "PUT",
|
|
206
|
+
headers: { "content-type": "application/json" },
|
|
207
|
+
body: JSON.stringify(body)
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
throw new Error(t("translations.manager.errors.save", "Failed to save translations"));
|
|
202
213
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
214
|
+
return true;
|
|
215
|
+
},
|
|
216
|
+
context: {
|
|
217
|
+
formId: TRANSLATION_MUTATION_CONTEXT_ID,
|
|
218
|
+
entityType,
|
|
219
|
+
recordId,
|
|
220
|
+
resourceKind: "translation",
|
|
221
|
+
resourceId: recordId,
|
|
222
|
+
data: translationData ?? null,
|
|
223
|
+
retryLastMutation
|
|
224
|
+
},
|
|
225
|
+
mutationPayload: body
|
|
226
|
+
});
|
|
209
227
|
},
|
|
210
228
|
onSuccess: () => {
|
|
211
229
|
flash(t("translations.manager.flash.saved", "Translations saved"), "success");
|
|
@@ -415,15 +433,26 @@ function LocaleManager() {
|
|
|
415
433
|
const queryClient = useQueryClient();
|
|
416
434
|
const { data: locales = [], isLoading } = useTranslationLocales();
|
|
417
435
|
const [newLocale, setNewLocale] = React.useState("");
|
|
436
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({ contextId: SUPPORTED_LOCALES_MUTATION_CONTEXT_ID });
|
|
418
437
|
const mutation = useMutation({
|
|
419
438
|
mutationFn: async (updatedLocales) => {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
439
|
+
return runMutation({
|
|
440
|
+
operation: async () => {
|
|
441
|
+
const res = await apiCall("/api/translations/locales", {
|
|
442
|
+
method: "PUT",
|
|
443
|
+
headers: { "content-type": "application/json" },
|
|
444
|
+
body: JSON.stringify({ locales: updatedLocales })
|
|
445
|
+
});
|
|
446
|
+
if (!res.ok) throw new Error("Failed to save locales");
|
|
447
|
+
return res.result?.locales ?? updatedLocales;
|
|
448
|
+
},
|
|
449
|
+
context: {
|
|
450
|
+
formId: SUPPORTED_LOCALES_MUTATION_CONTEXT_ID,
|
|
451
|
+
resourceKind: "translation-locales",
|
|
452
|
+
retryLastMutation
|
|
453
|
+
},
|
|
454
|
+
mutationPayload: { locales: updatedLocales }
|
|
424
455
|
});
|
|
425
|
-
if (!res.ok) throw new Error("Failed to save locales");
|
|
426
|
-
return res.result?.locales ?? updatedLocales;
|
|
427
456
|
},
|
|
428
457
|
onSuccess: (result) => {
|
|
429
458
|
queryClient.setQueryData(["translation-locales"], result);
|