@open-mercato/core 0.9.1-develop.7513.1.ae2715553f → 0.9.1-develop.7516.1.176184247d
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/communication_channels/lib/send-as-user.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/emails/route.js +44 -1
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +2 -2
- package/dist/modules/customers/lib/personEmailThreads.js +36 -1
- package/dist/modules/customers/lib/personEmailThreads.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +3 -9
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/route.js +33 -16
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/lib/routeHelpers.js +40 -1
- package/dist/modules/messages/lib/routeHelpers.js.map +2 -2
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +4 -7
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +29 -11
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js +2 -2
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/webhook-processor.js +60 -33
- package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +3 -3
- package/dist/modules/warranty_claims/commands/claim-lines.js +89 -68
- package/dist/modules/warranty_claims/commands/claim-lines.js.map +2 -2
- package/dist/modules/warranty_claims/commands/claims.js +14 -13
- package/dist/modules/warranty_claims/commands/claims.js.map +2 -2
- package/dist/modules/warranty_claims/commands/shared.js +8 -2
- package/dist/modules/warranty_claims/commands/shared.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/communication_channels/lib/send-as-user.ts +4 -0
- package/src/modules/customers/api/people/[id]/emails/route.ts +67 -1
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/personEmailThreads.ts +61 -0
- package/src/modules/messages/api/[id]/route.ts +2 -9
- package/src/modules/messages/api/route.ts +42 -20
- package/src/modules/messages/commands/messages.ts +7 -0
- package/src/modules/messages/i18n/de.json +1 -0
- package/src/modules/messages/i18n/en.json +1 -0
- package/src/modules/messages/i18n/es.json +1 -0
- package/src/modules/messages/i18n/ko.json +1 -0
- package/src/modules/messages/i18n/pl.json +1 -0
- package/src/modules/messages/lib/routeHelpers.ts +84 -1
- package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +4 -7
- package/src/modules/shipping_carriers/lib/shipping-service.ts +29 -11
- package/src/modules/shipping_carriers/lib/status-sync.ts +2 -2
- package/src/modules/shipping_carriers/workers/webhook-processor.ts +71 -39
- package/src/modules/warranty_claims/commands/claim-lines.ts +107 -66
- package/src/modules/warranty_claims/commands/claims.ts +14 -13
- package/src/modules/warranty_claims/commands/shared.ts +7 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LockMode } from "@mikro-orm/core";
|
|
1
2
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
3
|
import { CarrierShipment } from "../data/entities.js";
|
|
3
4
|
import { emitShippingEvent } from "../events.js";
|
|
@@ -152,7 +153,7 @@ function createShippingCarrierService(deps) {
|
|
|
152
153
|
trackingNumber: created.trackingNumber,
|
|
153
154
|
organizationId: input.organizationId,
|
|
154
155
|
tenantId: input.tenantId
|
|
155
|
-
});
|
|
156
|
+
}, { tenantId: input.tenantId, organizationId: input.organizationId });
|
|
156
157
|
return shipment;
|
|
157
158
|
} catch (error) {
|
|
158
159
|
if (claim && !carrierShipmentCreated && input.idempotencyKey && !isShipmentIdempotencyConflictError(error)) {
|
|
@@ -193,24 +194,41 @@ function createShippingCarrierService(deps) {
|
|
|
193
194
|
trackingNumber: shipment.trackingNumber,
|
|
194
195
|
credentials
|
|
195
196
|
});
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
const outcome = await em.transactional(async (tx) => {
|
|
198
|
+
const locked = await findOneWithDecryption(
|
|
199
|
+
tx,
|
|
200
|
+
CarrierShipment,
|
|
201
|
+
{
|
|
202
|
+
id: shipment.id,
|
|
203
|
+
organizationId: scope.organizationId,
|
|
204
|
+
tenantId: scope.tenantId,
|
|
205
|
+
deletedAt: null
|
|
206
|
+
},
|
|
207
|
+
{ lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },
|
|
208
|
+
scope
|
|
209
|
+
);
|
|
210
|
+
if (!locked) return null;
|
|
211
|
+
const previousStatus = locked.unifiedStatus;
|
|
212
|
+
locked.trackingEvents = tracking.events;
|
|
213
|
+
locked.lastPolledAt = /* @__PURE__ */ new Date();
|
|
214
|
+
const applied = syncShipmentStatus(locked, tracking.status);
|
|
215
|
+
await tx.flush();
|
|
216
|
+
return { applied, previousStatus };
|
|
217
|
+
}, { clear: true });
|
|
218
|
+
if (outcome?.applied) {
|
|
202
219
|
const eventPayload = {
|
|
203
220
|
shipmentId: shipment.id,
|
|
204
221
|
providerKey: input.providerKey,
|
|
205
|
-
previousStatus,
|
|
222
|
+
previousStatus: outcome.previousStatus,
|
|
206
223
|
newStatus: tracking.status,
|
|
207
224
|
organizationId: input.organizationId,
|
|
208
225
|
tenantId: input.tenantId
|
|
209
226
|
};
|
|
210
|
-
|
|
227
|
+
const eventScope = { tenantId: input.tenantId, organizationId: input.organizationId };
|
|
228
|
+
await emitShippingEvent("shipping_carriers.shipment.status_changed", eventPayload, eventScope);
|
|
211
229
|
if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {
|
|
212
230
|
const terminalEvent = getTerminalShippingEvent(tracking.status);
|
|
213
|
-
if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload);
|
|
231
|
+
if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload, eventScope);
|
|
214
232
|
}
|
|
215
233
|
}
|
|
216
234
|
return tracking;
|
|
@@ -237,7 +255,7 @@ function createShippingCarrierService(deps) {
|
|
|
237
255
|
providerKey: input.providerKey,
|
|
238
256
|
organizationId: input.organizationId,
|
|
239
257
|
tenantId: input.tenantId
|
|
240
|
-
});
|
|
258
|
+
}, { tenantId: input.tenantId, organizationId: input.organizationId });
|
|
241
259
|
return result;
|
|
242
260
|
},
|
|
243
261
|
async findShipmentByCarrierId(providerKey, carrierShipmentId, scope) {
|
|
@@ -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, type CarrierShipmentIdempotencyKey } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport { getShippingAdapter } from './adapter-registry'\nimport type { UnifiedShipmentStatus } from './adapter'\nimport {\n claimShipmentIdempotency,\n computeShipmentRequestHash,\n findShipmentIdempotencyClaim,\n isShipmentIdempotencyConflictError,\n releaseShipmentIdempotency,\n resolveShipmentIdempotency,\n ShipmentIdempotencyConflictError,\n} from './shipment-idempotency'\nimport {\n getTerminalShippingEvent,\n isValidShippingTransition,\n ShipmentCancelNotAllowedError,\n syncShipmentStatus,\n TERMINAL_SHIPPING_STATUSES,\n} from './status-sync'\n\nexport class CarrierShipmentNotFoundError extends Error {\n constructor() {\n super('Shipment not found')\n this.name = 'CarrierShipmentNotFoundError'\n }\n}\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 CarrierShipmentNotFoundError()\n }\n return shipment\n }\n\n async function findShipmentByTrackingNumberOrThrow(\n providerKey: string,\n trackingNumber: string,\n scope: { organizationId: string; tenantId: string },\n ): Promise<CarrierShipment> {\n const shipment = await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n providerKey,\n trackingNumber,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!shipment) {\n throw new CarrierShipmentNotFoundError()\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 idempotencyKey?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n let claim: CarrierShipmentIdempotencyKey | null = null\n if (input.idempotencyKey) {\n const { idempotencyKey: _idempotencyKey, ...fingerprint } = input\n const requestHash = computeShipmentRequestHash(fingerprint)\n const existing = await findShipmentIdempotencyClaim(em, input.idempotencyKey, input.providerKey, scope)\n if (existing) {\n if (existing.requestHash !== requestHash) {\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n if (existing.shipmentId) {\n return findShipmentOrThrow(existing.shipmentId, scope)\n }\n // Claim exists but the original request has not resolved yet (concurrent in-flight).\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n claim = await claimShipmentIdempotency(em, input.idempotencyKey, input.providerKey, requestHash, scope)\n if (!claim) {\n // Lost the claim race with a concurrent request creating the same shipment.\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n }\n let carrierShipmentCreated = false\n try {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)\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 carrierShipmentCreated = true\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 if (claim) {\n await resolveShipmentIdempotency(em, claim, shipment.id)\n }\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 } catch (error: unknown) {\n // Release the claim only when the carrier was NOT successfully called, so a retry can\n // proceed. If the carrier already created the shipment, keep the claim so a retry does\n // not produce a duplicate upstream shipment.\n if (claim && !carrierShipmentCreated && input.idempotencyKey && !isShipmentIdempotencyConflictError(error)) {\n await releaseShipmentIdempotency(em, input.idempotencyKey, input.providerKey, scope).catch(() => {})\n }\n throw error\n }\n },\n\n async getTracking(input: {\n providerKey: string\n shipmentId?: string\n trackingNumber?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)\n let shipment: CarrierShipment\n if (input.shipmentId) {\n shipment = await findShipmentOrThrow(input.shipmentId, scope)\n } else {\n const trackingNumber = input.trackingNumber\n if (!trackingNumber) {\n throw new CarrierShipmentNotFoundError()\n }\n shipment = await findShipmentByTrackingNumberOrThrow(input.providerKey, trackingNumber, scope)\n }\n return adapter.getTracking({\n shipmentId: shipment.carrierShipmentId,\n 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": "
|
|
4
|
+
"sourcesContent": ["import { LockMode } from '@mikro-orm/core'\nimport 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, type CarrierShipmentIdempotencyKey } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport { getShippingAdapter } from './adapter-registry'\nimport type { UnifiedShipmentStatus } from './adapter'\nimport {\n claimShipmentIdempotency,\n computeShipmentRequestHash,\n findShipmentIdempotencyClaim,\n isShipmentIdempotencyConflictError,\n releaseShipmentIdempotency,\n resolveShipmentIdempotency,\n ShipmentIdempotencyConflictError,\n} from './shipment-idempotency'\nimport {\n getTerminalShippingEvent,\n isValidShippingTransition,\n ShipmentCancelNotAllowedError,\n syncShipmentStatus,\n TERMINAL_SHIPPING_STATUSES,\n} from './status-sync'\n\nexport class CarrierShipmentNotFoundError extends Error {\n constructor() {\n super('Shipment not found')\n this.name = 'CarrierShipmentNotFoundError'\n }\n}\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 CarrierShipmentNotFoundError()\n }\n return shipment\n }\n\n async function findShipmentByTrackingNumberOrThrow(\n providerKey: string,\n trackingNumber: string,\n scope: { organizationId: string; tenantId: string },\n ): Promise<CarrierShipment> {\n const shipment = await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n providerKey,\n trackingNumber,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!shipment) {\n throw new CarrierShipmentNotFoundError()\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 idempotencyKey?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n let claim: CarrierShipmentIdempotencyKey | null = null\n if (input.idempotencyKey) {\n const { idempotencyKey: _idempotencyKey, ...fingerprint } = input\n const requestHash = computeShipmentRequestHash(fingerprint)\n const existing = await findShipmentIdempotencyClaim(em, input.idempotencyKey, input.providerKey, scope)\n if (existing) {\n if (existing.requestHash !== requestHash) {\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n if (existing.shipmentId) {\n return findShipmentOrThrow(existing.shipmentId, scope)\n }\n // Claim exists but the original request has not resolved yet (concurrent in-flight).\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n claim = await claimShipmentIdempotency(em, input.idempotencyKey, input.providerKey, requestHash, scope)\n if (!claim) {\n // Lost the claim race with a concurrent request creating the same shipment.\n throw new ShipmentIdempotencyConflictError(input.idempotencyKey)\n }\n }\n let carrierShipmentCreated = false\n try {\n const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)\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 carrierShipmentCreated = true\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 if (claim) {\n await resolveShipmentIdempotency(em, claim, shipment.id)\n }\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 }, { tenantId: input.tenantId, organizationId: input.organizationId })\n return shipment\n } catch (error: unknown) {\n // Release the claim only when the carrier was NOT successfully called, so a retry can\n // proceed. If the carrier already created the shipment, keep the claim so a retry does\n // not produce a duplicate upstream shipment.\n if (claim && !carrierShipmentCreated && input.idempotencyKey && !isShipmentIdempotencyConflictError(error)) {\n await releaseShipmentIdempotency(em, input.idempotencyKey, input.providerKey, scope).catch(() => {})\n }\n throw error\n }\n },\n\n async getTracking(input: {\n providerKey: string\n shipmentId?: string\n trackingNumber?: string\n organizationId: string\n tenantId: string\n }) {\n const scope = {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n const { adapter, credentials } = await resolveAdapter(input.providerKey, scope)\n let shipment: CarrierShipment\n if (input.shipmentId) {\n shipment = await findShipmentOrThrow(input.shipmentId, scope)\n } else {\n const trackingNumber = input.trackingNumber\n if (!trackingNumber) {\n throw new CarrierShipmentNotFoundError()\n }\n shipment = await findShipmentByTrackingNumberOrThrow(input.providerKey, trackingNumber, scope)\n }\n return adapter.getTracking({\n shipmentId: shipment.carrierShipmentId,\n 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 outcome = await em.transactional(async (tx) => {\n const locked = await findOneWithDecryption(\n tx,\n CarrierShipment,\n {\n id: shipment.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },\n scope,\n )\n if (!locked) return null\n const previousStatus = locked.unifiedStatus\n locked.trackingEvents = tracking.events\n locked.lastPolledAt = new Date()\n const applied = syncShipmentStatus(locked, tracking.status)\n await tx.flush()\n return { applied, previousStatus }\n }, { clear: true })\n if (outcome?.applied) {\n const eventPayload = {\n shipmentId: shipment.id,\n providerKey: input.providerKey,\n previousStatus: outcome.previousStatus,\n newStatus: tracking.status,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }\n const eventScope = { tenantId: input.tenantId, organizationId: input.organizationId }\n await emitShippingEvent('shipping_carriers.shipment.status_changed', eventPayload, eventScope)\n if (TERMINAL_SHIPPING_STATUSES.has(tracking.status)) {\n const terminalEvent = getTerminalShippingEvent(tracking.status)\n if (terminalEvent) await emitShippingEvent(terminalEvent, eventPayload, eventScope)\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 }, { tenantId: input.tenantId, organizationId: input.organizationId })\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
|
}
|
|
@@ -11,8 +11,8 @@ function isShipmentCancelNotAllowedError(error) {
|
|
|
11
11
|
return !!error && typeof error === "object" && error[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true;
|
|
12
12
|
}
|
|
13
13
|
const VALID_SHIPPING_TRANSITIONS = {
|
|
14
|
-
label_created: ["picked_up", "in_transit", "cancelled"],
|
|
15
|
-
picked_up: ["in_transit", "cancelled"],
|
|
14
|
+
label_created: ["picked_up", "in_transit", "out_for_delivery", "delivered", "failed_delivery", "returned", "cancelled"],
|
|
15
|
+
picked_up: ["in_transit", "out_for_delivery", "delivered", "failed_delivery", "returned", "cancelled"],
|
|
16
16
|
in_transit: ["out_for_delivery", "delivered", "returned", "failed_delivery"],
|
|
17
17
|
out_for_delivery: ["delivered", "returned", "failed_delivery"],
|
|
18
18
|
failed_delivery: ["in_transit", "out_for_delivery", "delivered", "returned", "cancelled"]
|
|
@@ -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\n// Use Symbol.for so the marker survives module duplication across bundle\n// boundaries \u2014 production builds can split this class into separate chunks,\n// which breaks `instanceof` (see isCrudHttpError in\n// @open-mercato/shared/lib/crud/errors for the same pattern).\nconst SHIPMENT_CANCEL_NOT_ALLOWED_MARKER = Symbol.for('@open-mercato/shipping_carriers/ShipmentCancelNotAllowedError')\n\nexport class ShipmentCancelNotAllowedError extends Error {\n readonly [SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] = true\n\n constructor(status: string) {\n super(`Shipment cannot be cancelled in its current status: ${status}`)\n this.name = 'ShipmentCancelNotAllowedError'\n }\n}\n\n/**\n * Type-safe check that works across module/bundle boundaries. Prefer this over\n * `instanceof ShipmentCancelNotAllowedError` in route handlers, where the thrown\n * error may originate from a different bundle than the one performing the check.\n */\nexport function isShipmentCancelNotAllowedError(error: unknown): error is ShipmentCancelNotAllowedError {\n return !!error\n && typeof error === 'object'\n && (error as Record<symbol, unknown>)[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true\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": "AAAA;AAQA,MAAM,qCAAqC,uBAAO,IAAI,+DAA+D;AAE9G,MAAM,uCAAsC,YACvC,yCADuC,IAAM;AAAA,EAGvD,YAAY,QAAgB;AAC1B,UAAM,uDAAuD,MAAM,EAAE;AAHvE,SAAU,MAAsC;AAI9C,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,gCAAgC,OAAwD;AACtG,SAAO,CAAC,CAAC,SACJ,OAAO,UAAU,YAChB,MAAkC,kCAAkC,MAAM;AAClF;AAEA,MAAM,6BAAsE;AAAA,EAC1E,eAAe,CAAC,aAAa,cAAc,WAAW;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { UnifiedShipmentStatus } from './adapter'\nimport type { CarrierShipment } from '../data/entities'\nimport type { ShippingEventId } from '../events'\n\n// Use Symbol.for so the marker survives module duplication across bundle\n// boundaries \u2014 production builds can split this class into separate chunks,\n// which breaks `instanceof` (see isCrudHttpError in\n// @open-mercato/shared/lib/crud/errors for the same pattern).\nconst SHIPMENT_CANCEL_NOT_ALLOWED_MARKER = Symbol.for('@open-mercato/shipping_carriers/ShipmentCancelNotAllowedError')\n\nexport class ShipmentCancelNotAllowedError extends Error {\n readonly [SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] = true\n\n constructor(status: string) {\n super(`Shipment cannot be cancelled in its current status: ${status}`)\n this.name = 'ShipmentCancelNotAllowedError'\n }\n}\n\n/**\n * Type-safe check that works across module/bundle boundaries. Prefer this over\n * `instanceof ShipmentCancelNotAllowedError` in route handlers, where the thrown\n * error may originate from a different bundle than the one performing the check.\n */\nexport function isShipmentCancelNotAllowedError(error: unknown): error is ShipmentCancelNotAllowedError {\n return !!error\n && typeof error === 'object'\n && (error as Record<symbol, unknown>)[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true\n}\n\nconst VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {\n label_created: ['picked_up', 'in_transit', 'out_for_delivery', 'delivered', 'failed_delivery', 'returned', 'cancelled'],\n picked_up: ['in_transit', 'out_for_delivery', 'delivered', 'failed_delivery', 'returned', '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": "AAAA;AAQA,MAAM,qCAAqC,uBAAO,IAAI,+DAA+D;AAE9G,MAAM,uCAAsC,YACvC,yCADuC,IAAM;AAAA,EAGvD,YAAY,QAAgB;AAC1B,UAAM,uDAAuD,MAAM,EAAE;AAHvE,SAAU,MAAsC;AAI9C,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,gCAAgC,OAAwD;AACtG,SAAO,CAAC,CAAC,SACJ,OAAO,UAAU,YAChB,MAAkC,kCAAkC,MAAM;AAClF;AAEA,MAAM,6BAAsE;AAAA,EAC1E,eAAe,CAAC,aAAa,cAAc,oBAAoB,aAAa,mBAAmB,YAAY,WAAW;AAAA,EACtH,WAAW,CAAC,cAAc,oBAAoB,aAAa,mBAAmB,YAAY,WAAW;AAAA,EACrG,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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LockMode } from "@mikro-orm/core";
|
|
1
2
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
3
|
import { CarrierShipment } from "../data/entities.js";
|
|
3
4
|
import { emitShippingEvent } from "../events.js";
|
|
@@ -6,73 +7,99 @@ import { getTerminalShippingEvent, syncShipmentStatus, TERMINAL_SHIPPING_STATUSE
|
|
|
6
7
|
import { claimWebhookProcessing, releaseWebhookClaim } from "../lib/webhook-utils.js";
|
|
7
8
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
8
9
|
const logger = createLogger("shipping_carriers").child({ component: "webhook-processor" });
|
|
10
|
+
function readWebhookJobPayload(data) {
|
|
11
|
+
if ("providerKey" in data) return data;
|
|
12
|
+
return data.payload;
|
|
13
|
+
}
|
|
9
14
|
const metadata = {
|
|
10
15
|
queue: "shipping-carriers-webhook",
|
|
11
16
|
id: "shipping-carriers:webhook-processor",
|
|
12
17
|
concurrency: 5
|
|
13
18
|
};
|
|
14
19
|
async function handle(job, ctx) {
|
|
20
|
+
const payload = readWebhookJobPayload(job.payload);
|
|
15
21
|
try {
|
|
16
22
|
const em = ctx.resolve("em");
|
|
17
|
-
const adapter = getShippingAdapter(
|
|
23
|
+
const adapter = getShippingAdapter(payload.providerKey);
|
|
18
24
|
if (!adapter) return;
|
|
19
|
-
const shipment =
|
|
25
|
+
const shipment = payload.shipmentId && payload.scope ? await findOneWithDecryption(
|
|
20
26
|
em,
|
|
21
27
|
CarrierShipment,
|
|
22
28
|
{
|
|
23
|
-
id:
|
|
24
|
-
organizationId:
|
|
25
|
-
tenantId:
|
|
29
|
+
id: payload.shipmentId,
|
|
30
|
+
organizationId: payload.scope.organizationId,
|
|
31
|
+
tenantId: payload.scope.tenantId,
|
|
26
32
|
deletedAt: null
|
|
27
33
|
},
|
|
28
34
|
void 0,
|
|
29
|
-
|
|
35
|
+
payload.scope
|
|
30
36
|
) : null;
|
|
31
37
|
if (!shipment) return;
|
|
32
38
|
const scope = { organizationId: shipment.organizationId, tenantId: shipment.tenantId };
|
|
33
39
|
const claimed = await claimWebhookProcessing(
|
|
34
40
|
em,
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
payload.event.idempotencyKey,
|
|
42
|
+
payload.providerKey,
|
|
37
43
|
scope,
|
|
38
|
-
|
|
44
|
+
payload.event.eventType
|
|
39
45
|
);
|
|
40
46
|
if (!claimed) {
|
|
41
47
|
return;
|
|
42
48
|
}
|
|
49
|
+
const carrierStatus = typeof payload.event.data.status === "string" ? payload.event.data.status : payload.event.eventType;
|
|
50
|
+
let transition = null;
|
|
43
51
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
await
|
|
64
|
-
|
|
52
|
+
const unifiedStatus2 = adapter.mapStatus(carrierStatus);
|
|
53
|
+
transition = await em.transactional(async (tx) => {
|
|
54
|
+
const locked = await findOneWithDecryption(
|
|
55
|
+
tx,
|
|
56
|
+
CarrierShipment,
|
|
57
|
+
{
|
|
58
|
+
id: shipment.id,
|
|
59
|
+
organizationId: scope.organizationId,
|
|
60
|
+
tenantId: scope.tenantId,
|
|
61
|
+
deletedAt: null
|
|
62
|
+
},
|
|
63
|
+
{ lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },
|
|
64
|
+
scope
|
|
65
|
+
);
|
|
66
|
+
if (!locked) return null;
|
|
67
|
+
const previousStatus = locked.unifiedStatus;
|
|
68
|
+
if (!syncShipmentStatus(locked, unifiedStatus2)) return null;
|
|
69
|
+
locked.carrierStatus = carrierStatus;
|
|
70
|
+
locked.lastWebhookAt = /* @__PURE__ */ new Date();
|
|
71
|
+
await tx.flush();
|
|
72
|
+
return { previousStatus, newStatus: unifiedStatus2 };
|
|
73
|
+
}, { clear: true });
|
|
65
74
|
} catch (error) {
|
|
66
75
|
await releaseWebhookClaim(
|
|
67
76
|
em,
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
payload.event.idempotencyKey,
|
|
78
|
+
payload.providerKey,
|
|
70
79
|
scope
|
|
71
80
|
);
|
|
72
81
|
throw error;
|
|
73
82
|
}
|
|
83
|
+
if (!transition) return;
|
|
84
|
+
const unifiedStatus = transition.newStatus;
|
|
85
|
+
const eventPayload = {
|
|
86
|
+
shipmentId: shipment.id,
|
|
87
|
+
providerKey: payload.providerKey,
|
|
88
|
+
previousStatus: transition.previousStatus,
|
|
89
|
+
newStatus: unifiedStatus,
|
|
90
|
+
carrierStatus,
|
|
91
|
+
organizationId: shipment.organizationId,
|
|
92
|
+
tenantId: shipment.tenantId
|
|
93
|
+
};
|
|
94
|
+
const eventScope = { tenantId: shipment.tenantId, organizationId: shipment.organizationId };
|
|
95
|
+
await emitShippingEvent("shipping_carriers.shipment.status_changed", eventPayload, eventScope);
|
|
96
|
+
if (TERMINAL_SHIPPING_STATUSES.has(unifiedStatus)) {
|
|
97
|
+
const terminalEvent = getTerminalShippingEvent(unifiedStatus);
|
|
98
|
+
if (!terminalEvent) return;
|
|
99
|
+
await emitShippingEvent(terminalEvent, eventPayload, eventScope);
|
|
100
|
+
}
|
|
74
101
|
} catch (error) {
|
|
75
|
-
logger.error("Job processing failed", { providerKey:
|
|
102
|
+
logger.error("Job processing failed", { providerKey: payload.providerKey, shipmentId: payload.shipmentId, err: error });
|
|
76
103
|
throw error;
|
|
77
104
|
}
|
|
78
105
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/shipping_carriers/workers/webhook-processor.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CarrierShipment } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport type { ShippingWebhookEvent } from '../lib/adapter'\nimport { getShippingAdapter } from '../lib/adapter-registry'\nimport { getTerminalShippingEvent, syncShipmentStatus, TERMINAL_SHIPPING_STATUSES } from '../lib/status-sync'\nimport { claimWebhookProcessing, releaseWebhookClaim } from '../lib/webhook-utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('shipping_carriers').child({ component: 'webhook-processor' })\n\ntype WebhookJobPayload = {\n providerKey: string\n event: ShippingWebhookEvent\n shipmentId?: string | null\n scope?: {\n organizationId: string\n tenantId: string\n } | null\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'shipping-carriers-webhook',\n id: 'shipping-carriers:webhook-processor',\n concurrency: 5,\n}\n\nexport default async function handle(job: QueuedJob<WebhookJobPayload>, ctx: HandlerContext): Promise<void> {\n try {\n const em = ctx.resolve<EntityManager>('em')\n const adapter = getShippingAdapter(
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import { LockMode } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CarrierShipment } from '../data/entities'\nimport { emitShippingEvent } from '../events'\nimport type { ShippingWebhookEvent, UnifiedShipmentStatus } from '../lib/adapter'\nimport { getShippingAdapter } from '../lib/adapter-registry'\nimport { getTerminalShippingEvent, syncShipmentStatus, TERMINAL_SHIPPING_STATUSES } from '../lib/status-sync'\nimport { claimWebhookProcessing, releaseWebhookClaim } from '../lib/webhook-utils'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('shipping_carriers').child({ component: 'webhook-processor' })\n\ntype WebhookJobPayload = {\n providerKey: string\n event: ShippingWebhookEvent\n shipmentId?: string | null\n scope?: {\n organizationId: string\n tenantId: string\n } | null\n}\n\ntype LegacyWebhookJobEnvelope = {\n name?: string\n payload: WebhookJobPayload\n}\n\nfunction readWebhookJobPayload(data: WebhookJobPayload | LegacyWebhookJobEnvelope): WebhookJobPayload {\n if ('providerKey' in data) return data\n return data.payload\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'shipping-carriers-webhook',\n id: 'shipping-carriers:webhook-processor',\n concurrency: 5,\n}\n\nexport default async function handle(job: QueuedJob<WebhookJobPayload | LegacyWebhookJobEnvelope>, ctx: HandlerContext): Promise<void> {\n const payload = readWebhookJobPayload(job.payload)\n try {\n const em = ctx.resolve<EntityManager>('em')\n const adapter = getShippingAdapter(payload.providerKey)\n if (!adapter) return\n\n const shipment = payload.shipmentId && payload.scope\n ? await findOneWithDecryption(\n em,\n CarrierShipment,\n {\n id: payload.shipmentId,\n organizationId: payload.scope.organizationId,\n tenantId: payload.scope.tenantId,\n deletedAt: null,\n },\n undefined,\n payload.scope,\n )\n : null\n if (!shipment) return\n\n const scope = { organizationId: shipment.organizationId, tenantId: shipment.tenantId }\n const claimed = await claimWebhookProcessing(\n em,\n payload.event.idempotencyKey,\n payload.providerKey,\n scope,\n payload.event.eventType,\n )\n if (!claimed) {\n return\n }\n\n const carrierStatus = typeof payload.event.data.status === 'string'\n ? payload.event.data.status\n : payload.event.eventType\n\n let transition: { previousStatus: string; newStatus: UnifiedShipmentStatus } | null = null\n try {\n const unifiedStatus = adapter.mapStatus(carrierStatus)\n transition = await em.transactional(async (tx) => {\n const locked = await findOneWithDecryption(\n tx,\n CarrierShipment,\n {\n id: shipment.id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },\n scope,\n )\n if (!locked) return null\n const previousStatus = locked.unifiedStatus\n if (!syncShipmentStatus(locked, unifiedStatus)) return null\n locked.carrierStatus = carrierStatus\n locked.lastWebhookAt = new Date()\n await tx.flush()\n return { previousStatus, newStatus: unifiedStatus }\n }, { clear: true })\n } catch (error) {\n await releaseWebhookClaim(\n em,\n payload.event.idempotencyKey,\n payload.providerKey,\n scope,\n )\n throw error\n }\n if (!transition) return\n\n const unifiedStatus = transition.newStatus\n const eventPayload = {\n shipmentId: shipment.id,\n providerKey: payload.providerKey,\n previousStatus: transition.previousStatus,\n newStatus: unifiedStatus,\n carrierStatus,\n organizationId: shipment.organizationId,\n tenantId: shipment.tenantId,\n }\n const eventScope = { tenantId: shipment.tenantId, organizationId: shipment.organizationId }\n await emitShippingEvent('shipping_carriers.shipment.status_changed', eventPayload, eventScope)\n if (TERMINAL_SHIPPING_STATUSES.has(unifiedStatus)) {\n const terminalEvent = getTerminalShippingEvent(unifiedStatus)\n if (!terminalEvent) return\n await emitShippingEvent(terminalEvent, eventPayload, eventScope)\n }\n } catch (error) {\n logger.error('Job processing failed', { providerKey: payload.providerKey, shipmentId: payload.shipmentId, err: error })\n throw error\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["unifiedStatus"]
|
|
7
7
|
}
|