import type { CustomLineItemReturnItem, Delivery, GeneralError, InvalidOperationError, LineItemReturnItem, Order, OrderAddDeliveryAction, OrderAddParcelToDeliveryAction, OrderAddPaymentAction, OrderAddReturnInfoAction, OrderChangeOrderStateAction, OrderChangePaymentStateAction, OrderChangeShipmentStateAction, OrderRemovePaymentAction, OrderSetBillingAddressAction, OrderSetCustomerEmailAction, OrderSetCustomerIdAction, OrderSetCustomFieldAction, OrderSetCustomTypeAction, OrderSetDeliveryCustomFieldAction, OrderSetLineItemCustomFieldAction, OrderSetLineItemCustomTypeAction, OrderSetLocaleAction, OrderSetOrderNumberAction, OrderSetParcelCustomFieldAction, OrderSetPurchaseOrderNumberAction, OrderSetShippingAddressAction, OrderSetStoreAction, OrderTransitionStateAction, OrderUpdateAction, OrderUpdateSyncInfoAction, Parcel, ReferencedResourceNotFoundError, RequiredFieldError, ResourceNotFoundError, ReturnInfo, State, Store, SyncInfo, } from "@commercetools/platform-sdk"; import { CommercetoolsError } from "#src/exceptions.ts"; import { getBaseResourceProperties } from "#src/helpers.ts"; import type { Writable } from "#src/types.ts"; import type { RepositoryContext, UpdateHandlerInterface } from "../abstract.ts"; import { AbstractUpdateHandler } from "../abstract.ts"; import { createAddress, createCustomFields } from "../helpers.ts"; export class OrderUpdateHandler extends AbstractUpdateHandler implements Partial> { async addPayment( context: RepositoryContext, resource: Writable, { payment }: OrderAddPaymentAction, ) { const resolvedPayment = await this._storage.getByResourceIdentifier( context.projectKey, payment, ); if (!resolvedPayment) { throw new CommercetoolsError({ code: "ReferencedResourceNotFound", message: `Payment ${payment.id} not found`, typeId: "payment", }); } if (!resource.paymentInfo) { resource.paymentInfo = { payments: [], }; } resource.paymentInfo.payments.push({ typeId: "payment", id: payment.id!, }); } async removePayment( context: RepositoryContext, resource: Writable, { payment }: OrderRemovePaymentAction, ) { const resolvedPayment = await this._storage.getByResourceIdentifier( context.projectKey, payment, ); if (!resolvedPayment) { throw new CommercetoolsError({ code: "ReferencedResourceNotFound", message: `Payment ${payment.id} not found`, typeId: "payment", }); } if (!resource.paymentInfo) { return; } resource.paymentInfo.payments = resource.paymentInfo.payments.filter( (reference) => reference.id !== resolvedPayment.id, ); if (resource.paymentInfo.payments.length === 0) { resource.paymentInfo = undefined; } } async addReturnInfo( context: RepositoryContext, resource: Writable, info: OrderAddReturnInfoAction, ) { if (!resource.returnInfo) { resource.returnInfo = []; } const items = await Promise.all( info.items.map(async (item) => { const common = { ...getBaseResourceProperties(), quantity: item.quantity, paymentState: "Initial", shipmentState: item.shipmentState ?? "Advised", comment: item.comment, custom: await createCustomFields( item.custom, context.projectKey, this._storage, ), }; if (item.customLineItemId) { return { ...common, type: "CustomLineItemReturnItem", customLineItemId: item.customLineItemId, } as CustomLineItemReturnItem; } return { ...common, type: "LineItemReturnItem", lineItemId: item.customLineItemId || item.lineItemId, } as LineItemReturnItem; }), ); const resolved: ReturnInfo = { items, returnTrackingId: info.returnTrackingId, returnDate: info.returnDate, }; resource.returnInfo.push(resolved); } changeOrderState( context: RepositoryContext, resource: Writable, { orderState }: OrderChangeOrderStateAction, ) { resource.orderState = orderState; } changePaymentState( context: RepositoryContext, resource: Writable, { paymentState }: OrderChangePaymentStateAction, ) { resource.paymentState = paymentState; } changeShipmentState( context: RepositoryContext, resource: Writable, { shipmentState }: OrderChangeShipmentStateAction, ) { resource.shipmentState = shipmentState; } setBillingAddress( context: RepositoryContext, resource: Writable, { address }: OrderSetBillingAddressAction, ) { resource.billingAddress = createAddress( address, context.projectKey, this._storage, ); } setCustomerEmail( context: RepositoryContext, resource: Writable, { email }: OrderSetCustomerEmailAction, ) { resource.customerEmail = email; } setCustomerId( context: RepositoryContext, resource: Writable, { customerId }: OrderSetCustomerIdAction, ) { resource.customerId = customerId; } setCustomField( context: RepositoryContext, resource: Order, { name, value }: OrderSetCustomFieldAction, ) { this._setCustomFieldValues(resource, { name, value }); } async setCustomType( context: RepositoryContext, resource: Writable, { type, fields }: OrderSetCustomTypeAction, ) { await this._setCustomType(context, resource, { type, fields }); } async addDelivery( context: RepositoryContext, resource: Writable, { action, items, ...deliveryDraft }: OrderAddDeliveryAction, ) { if (!resource.shippingInfo) { throw new CommercetoolsError({ code: "InvalidOperation", message: "Resource has no shipping info", }); } if (!items) { throw new CommercetoolsError({ code: "RequiredField", message: "Delivery items are required", field: "items", }); } if (!resource.shippingInfo.deliveries) { resource.shippingInfo.deliveries = []; } const parcels: Parcel[] = await Promise.all( deliveryDraft.parcels?.map(async (p) => ({ ...getBaseResourceProperties(), ...p, custom: await createCustomFields( p.custom, context.projectKey, this._storage, ), })) ?? [], ); const delivery: Delivery = { ...getBaseResourceProperties(), ...deliveryDraft, parcels, items, address: createAddress( deliveryDraft.address, context.projectKey, this._storage, ), custom: await createCustomFields( deliveryDraft.custom, context.projectKey, this._storage, ), }; resource.shippingInfo.deliveries.push(delivery); } async addParcelToDelivery( context: RepositoryContext, resource: Writable, { deliveryId, deliveryKey, ...parcelDraft }: OrderAddParcelToDeliveryAction, ) { if (!resource.shippingInfo) { throw new CommercetoolsError({ code: "InvalidOperation", message: "Resource has no shipping info", }); } if (!deliveryId && !deliveryKey) { throw new CommercetoolsError({ code: "RequiredField", message: "Either deliveryId or deliveryKey is required", field: "deliveryId", }); } const delivery = resource.shippingInfo.deliveries?.find((d) => deliveryId ? d.id === deliveryId : d.key === deliveryKey, ) as Writable | undefined; if (!delivery) { throw new CommercetoolsError({ code: "InvalidOperation", message: `A delivery with ${ deliveryId ? `ID '${deliveryId}'` : `key '${deliveryKey}'` } was not found on this order.`, }); } const { parcelKey, ...parcel } = parcelDraft; if (!delivery.parcels) { delivery.parcels = []; } delivery.parcels.push({ ...getBaseResourceProperties(), ...parcel, key: parcelKey, custom: await createCustomFields( parcel.custom, context.projectKey, this._storage, ), }); } setDeliveryCustomField( context: RepositoryContext, resource: Writable, { deliveryId, name, value }: OrderSetDeliveryCustomFieldAction, ) { if (!resource.shippingInfo) { throw new CommercetoolsError({ code: "InvalidOperation", message: "Resource has no shipping info", }); } for (const delivery of resource.shippingInfo.deliveries || []) { if (delivery.id === deliveryId && delivery.custom?.fields) { delivery.custom.fields[name] = value; } } } setLineItemCustomField( context: RepositoryContext, resource: Order, { lineItemId, lineItemKey, name, value }: OrderSetLineItemCustomFieldAction, ) { const lineItem = resource.lineItems.find( (x) => (lineItemId && x.id === lineItemId) || (lineItemKey && x.key === lineItemKey), ); if (!lineItem) { // Check if line item is found throw new CommercetoolsError({ code: "General", message: lineItemKey ? `A line item with key '${lineItemKey}' not found.` : `A line item with ID '${lineItemId}' not found.`, }); } if (!lineItem.custom) { throw new CommercetoolsError({ code: "InvalidOperation", message: "Resource has no custom field", }); } lineItem.custom.fields[name] = value; } async setLineItemCustomType( context: RepositoryContext, resource: Writable, { lineItemId, lineItemKey, type, fields }: OrderSetLineItemCustomTypeAction, ) { const lineItem = resource.lineItems.find( (x) => (lineItemId && x.id === lineItemId) || (lineItemKey && x.key === lineItemKey), ); if (!lineItem) { // Check if line item is found throw new CommercetoolsError({ code: "General", message: lineItemKey ? `A line item with key '${lineItemKey}' not found.` : `A line item with ID '${lineItemId}' not found.`, }); } if (!type) { lineItem.custom = undefined; } else { const resolvedType = await this._storage.getByResourceIdentifier( context.projectKey, type, ); if (!resolvedType) { throw new CommercetoolsError({ code: "ReferencedResourceNotFound", message: `Type ${type} not found`, typeId: "type", }); } lineItem.custom = { type: { typeId: "type", id: resolvedType.id, }, fields: fields || {}, }; } } setLocale( context: RepositoryContext, resource: Writable, { locale }: OrderSetLocaleAction, ) { resource.locale = locale; } setOrderNumber( context: RepositoryContext, resource: Writable, { orderNumber }: OrderSetOrderNumberAction, ) { resource.orderNumber = orderNumber; } setParcelCustomField( context: RepositoryContext, resource: Writable, { parcelId, name, value }: OrderSetParcelCustomFieldAction, ) { if (!resource.shippingInfo) { throw new CommercetoolsError({ code: "InvalidOperation", message: "Resource has no shipping info", }); } for (const delivery of resource.shippingInfo.deliveries || []) { for (const parcel of delivery.parcels || []) { if (parcel.id === parcelId && parcel.custom?.fields) { parcel.custom.fields[name] = value; } } } } setPurchaseOrderNumber( context: RepositoryContext, resource: Writable, { purchaseOrderNumber }: OrderSetPurchaseOrderNumberAction, ) { resource.purchaseOrderNumber = purchaseOrderNumber; } setShippingAddress( context: RepositoryContext, resource: Writable, { address }: OrderSetShippingAddressAction, ) { resource.shippingAddress = createAddress( address, context.projectKey, this._storage, ); } async setStore( context: RepositoryContext, resource: Writable, { store }: OrderSetStoreAction, ) { if (!store) return; const resolvedType = await this._storage.getByResourceIdentifier( context.projectKey, store, ); if (!resolvedType) { throw new CommercetoolsError({ code: "ReferencedResourceNotFound", message: `No store found with key=${store.key}`, typeId: "store", }); } const storeReference = resolvedType as Store; resource.store = { typeId: "store", key: storeReference.key, }; } async transitionState( context: RepositoryContext, resource: Writable, { state }: OrderTransitionStateAction, ) { const resolvedType = (await this._storage.getByResourceIdentifier( context.projectKey, state, )) as State | null; if (!resolvedType) { throw new CommercetoolsError( { code: "ResourceNotFound", message: `No state found with key=${state.key} or id=${state.key}`, }, 404, ); } resource.state = { typeId: "state", id: resolvedType.id, obj: { ...resolvedType, key: state.key ?? "" }, }; } async updateSyncInfo( context: RepositoryContext, resource: Writable, { channel, externalId, syncedAt }: OrderUpdateSyncInfoAction, ) { if (!channel) return; const resolvedType = await this._storage.getByResourceIdentifier( context.projectKey, channel, ); if (!resolvedType) { throw new CommercetoolsError({ code: "ReferencedResourceNotFound", message: `Channel ${channel} not found`, typeId: "channel", }); } const syncData: SyncInfo = { channel: { typeId: "channel", id: resolvedType.id, }, externalId, syncedAt: syncedAt ?? new Date().toISOString(), }; if (!resource.syncInfo?.length) { resource.syncInfo = [syncData]; } else { const lastSyncInfo = resource.syncInfo[resource.syncInfo.length - 1]; if ( lastSyncInfo.channel.id !== syncData.channel.id || lastSyncInfo.externalId !== syncData.externalId ) { resource.syncInfo.push(syncData); } } } }