import assert from "node:assert"; import type { Address, BaseAddress, Customer, CustomerAddAddressAction, CustomerAddBillingAddressIdAction, CustomerAddShippingAddressIdAction, CustomerAddStoreAction, CustomerChangeAddressAction, CustomerChangeEmailAction, CustomerRemoveAddressAction, CustomerRemoveBillingAddressIdAction, CustomerRemoveShippingAddressIdAction, CustomerRemoveStoreAction, CustomerSetAddressCustomFieldAction, CustomerSetAddressCustomTypeAction, CustomerSetAuthenticationModeAction, CustomerSetCompanyNameAction, CustomerSetCustomerGroupAction, CustomerSetCustomerNumberAction, CustomerSetCustomFieldAction, CustomerSetCustomTypeAction, CustomerSetDateOfBirthAction, CustomerSetDefaultBillingAddressAction, CustomerSetDefaultShippingAddressAction, CustomerSetExternalIdAction, CustomerSetFirstNameAction, CustomerSetKeyAction, CustomerSetLastNameAction, CustomerSetLocaleAction, CustomerSetMiddleNameAction, CustomerSetSalutationAction, CustomerSetStoresAction, CustomerSetTitleAction, CustomerSetVatIdAction, CustomerUpdateAction, InvalidInputError, InvalidJsonInputError, InvalidOperationError, } from "@commercetools/platform-sdk"; import { CommercetoolsError } from "#src/exceptions.ts"; import { generateRandomString } from "#src/helpers.ts"; import { hashPassword } from "#src/lib/password.ts"; import type { Writable } from "#src/types.ts"; import type { UpdateHandlerInterface } from "../abstract.ts"; import { AbstractUpdateHandler, type RepositoryContext } from "../abstract.ts"; import { createAddress } from "../helpers.ts"; export class CustomerUpdateHandler extends AbstractUpdateHandler implements Partial> { addAddress( _context: RepositoryContext, resource: Writable, { address }: CustomerAddAddressAction, ) { resource.addresses.push({ ...address, id: address.id ?? generateRandomString(5), } as BaseAddress); } addBillingAddressId( _context: RepositoryContext, resource: Writable, { addressId, addressKey }: CustomerAddBillingAddressIdAction, ) { const address = this._findAddress(resource, addressId, addressKey, true); assert(address?.id); // always true since we set required to true if (resource.billingAddressIds === undefined) { resource.billingAddressIds = []; } if (!resource.billingAddressIds.includes(address.id)) { resource.billingAddressIds.push(address.id); } } addShippingAddressId( _context: RepositoryContext, resource: Writable, { addressId, addressKey }: CustomerAddShippingAddressIdAction, ) { const address = this._findAddress(resource, addressId, addressKey, true); assert(address?.id); // always true since we set required to true if (resource.shippingAddressIds === undefined) { resource.shippingAddressIds = []; } if (!resource.shippingAddressIds.includes(address.id)) { resource.shippingAddressIds.push(address.id); } } addStore( context: RepositoryContext, resource: Writable, action: CustomerAddStoreAction, ) { throw new CommercetoolsError( { code: "InvalidOperation", message: "The action 'addStore' is not implemented yet", }, 400, ); } changeAddress( context: RepositoryContext, resource: Writable, { addressId, addressKey, address }: CustomerChangeAddressAction, ) { const current = this._findAddress(resource, addressId, addressKey, true); assert(current?.id); // always true since we set required to true const oldAddressIndex = resource.addresses.findIndex( (a) => a.id === current.id, ); const newAddress = createAddress( { ...address, id: current.id }, context.projectKey, this._storage, ); if (newAddress) { resource.addresses[oldAddressIndex] = newAddress; } } changeEmail( _context: RepositoryContext, resource: Writable, { email }: CustomerChangeEmailAction, ) { resource.email = email; } removeAddress( context: RepositoryContext, resource: Writable, action: CustomerRemoveAddressAction, ) { const address = this._findAddress( resource, action.addressId, action.addressKey, true, ); assert(address?.id); // always true since we set required to true resource.addresses = resource.addresses.filter((a) => a.id !== address.id); if (resource.shippingAddressIds) { resource.shippingAddressIds = resource.shippingAddressIds.filter( (id) => id !== address.id, ); } if (resource.billingAddressIds) { resource.billingAddressIds = resource.billingAddressIds.filter( (id) => id !== address.id, ); } if (resource.defaultShippingAddressId === address.id) { resource.defaultShippingAddressId = undefined; } if (resource.defaultBillingAddressId === address.id) { resource.defaultBillingAddressId = undefined; } } removeBillingAddressId( context: RepositoryContext, resource: Writable, action: CustomerRemoveBillingAddressIdAction, ) { const address = this._findAddress( resource, action.addressId, action.addressKey, true, ); assert(address?.id); // always true since we set required to true resource.billingAddressIds = resource.billingAddressIds?.filter( (id) => id !== address.id, ); if (resource.defaultBillingAddressId === address.id) { resource.defaultBillingAddressId = undefined; } } removeShippingAddressId( context: RepositoryContext, resource: Writable, action: CustomerRemoveShippingAddressIdAction, ) { const address = this._findAddress( resource, action.addressId, action.addressKey, true, ); assert(address?.id); // always true since we set required to true resource.shippingAddressIds = resource.shippingAddressIds?.filter( (id) => id !== address.id, ); if (resource.defaultShippingAddressId === address.id) { resource.defaultShippingAddressId = undefined; } } removeStore( context: RepositoryContext, resource: Writable, action: CustomerRemoveStoreAction, ) { throw new CommercetoolsError( { code: "InvalidOperation", message: "The action 'removeStore' is not implemented yet", }, 400, ); } setAddressCustomField( context: RepositoryContext, resource: Writable, action: CustomerSetAddressCustomFieldAction, ) { throw new CommercetoolsError( { code: "InvalidOperation", message: "The action 'setAddressCustomField' is not implemented yet", }, 400, ); } setAddressCustomType( context: RepositoryContext, resource: Writable, action: CustomerSetAddressCustomTypeAction, ) { throw new CommercetoolsError( { code: "InvalidOperation", message: "The action 'setAddressCustomType' is not implemented yet", }, 400, ); } setAuthenticationMode( _context: RepositoryContext, resource: Writable, { authMode, password }: CustomerSetAuthenticationModeAction, ) { if (resource.authenticationMode === authMode) { throw new CommercetoolsError( { code: "InvalidInput", message: `The customer is already using the '${resource.authenticationMode}' authentication mode.`, }, 400, ); } resource.authenticationMode = authMode; if (authMode === "ExternalAuth") { resource.password = undefined; return; } if (authMode === "Password") { resource.password = password ? hashPassword(password) : undefined; return; } throw new CommercetoolsError( { code: "InvalidJsonInput", message: "Request body does not contain valid JSON.", detailedErrorMessage: `actions -> authMode: Invalid enum value: '${authMode}'. Expected one of: 'Password','ExternalAuth'`, }, 400, ); } setCompanyName( _context: RepositoryContext, resource: Writable, { companyName }: CustomerSetCompanyNameAction, ) { resource.companyName = companyName; } async setCustomerGroup( context: RepositoryContext, resource: Writable, action: CustomerSetCustomerGroupAction, ) { if (!action.customerGroup) { throw new CommercetoolsError( { code: "InvalidOperation", message: "CustomerGroup is required.", }, 400, ); } const group = await this._storage.getByResourceIdentifier<"customer-group">( context.projectKey, action.customerGroup, ); resource.customerGroup = { typeId: "customer-group", id: group.id, }; } setCustomerNumber( _context: RepositoryContext, resource: Writable, { customerNumber }: CustomerSetCustomerNumberAction, ) { if (resource.customerNumber) { throw new CommercetoolsError( { code: "InvalidOperation", message: "A Customer number already exists and cannot be set again.", }, 400, ); } resource.customerNumber = customerNumber; } setCustomField( _context: RepositoryContext, resource: Writable, { name, value }: CustomerSetCustomFieldAction, ) { this._setCustomFieldValues(resource, { name, value }); } async setCustomType( context: RepositoryContext, resource: Writable, { type, fields }: CustomerSetCustomTypeAction, ) { await this._setCustomType(context, resource, { type, fields }); } setDateOfBirth( context: RepositoryContext, resource: Writable, action: CustomerSetDateOfBirthAction, ) { resource.dateOfBirth = action.dateOfBirth; } setDefaultBillingAddress( context: RepositoryContext, resource: Writable, action: CustomerSetDefaultBillingAddressAction, ) { const address = this._findAddress( resource, action.addressId, action.addressKey, true, ); assert(address?.id); // always true since we set required to true resource.defaultBillingAddressId = address.id; if (resource.billingAddressIds === undefined) { resource.billingAddressIds = []; } if (!resource.billingAddressIds.includes(address.id)) { resource.billingAddressIds.push(address.id); } } setDefaultShippingAddress( context: RepositoryContext, resource: Writable, action: CustomerSetDefaultShippingAddressAction, ) { const address = this._findAddress( resource, action.addressId, action.addressKey, true, ); assert(address?.id); // always true since we set required to true resource.defaultShippingAddressId = address.id; if (resource.shippingAddressIds === undefined) { resource.shippingAddressIds = []; } if (!resource.shippingAddressIds.includes(address.id)) { resource.shippingAddressIds.push(address.id); } } setExternalId( _context: RepositoryContext, resource: Writable, { externalId }: CustomerSetExternalIdAction, ) { resource.externalId = externalId; } setFirstName( _context: RepositoryContext, resource: Writable, { firstName }: CustomerSetFirstNameAction, ) { resource.firstName = firstName; } setKey( _context: RepositoryContext, resource: Writable, { key }: CustomerSetKeyAction, ) { resource.key = key; } setLastName( _context: RepositoryContext, resource: Writable, { lastName }: CustomerSetLastNameAction, ) { resource.lastName = lastName; } setLocale( _context: RepositoryContext, resource: Writable, { locale }: CustomerSetLocaleAction, ) { resource.locale = locale; } setMiddleName( context: RepositoryContext, resource: Writable, action: CustomerSetMiddleNameAction, ) { resource.middleName = action.middleName; } setSalutation( _context: RepositoryContext, resource: Writable, { salutation }: CustomerSetSalutationAction, ) { resource.salutation = salutation; } setStores( context: RepositoryContext, resource: Writable, action: CustomerSetStoresAction, ) { throw new CommercetoolsError( { code: "InvalidOperation", message: "The action 'setStores' is not implemented yet", }, 400, ); } setTitle( context: RepositoryContext, resource: Writable, action: CustomerSetTitleAction, ) { resource.title = action.title; } setVatId( _context: RepositoryContext, resource: Writable, { vatId }: CustomerSetVatIdAction, ) { resource.vatId = vatId; } private _findAddress( resource: Writable, addressId: string | undefined, addressKey: string | undefined, required = false, ): Address | undefined { if (addressKey) { const address = resource.addresses.find((a) => a.key === addressKey); if (!address) { throw new CommercetoolsError( { code: "InvalidOperation", message: `Customer does not contain an address with the key ${addressKey}.`, }, 400, ); } return address; } if (addressId) { const address = resource.addresses.find((a) => a.id === addressId); if (!address) { throw new CommercetoolsError( { code: "InvalidOperation", message: `Customer does not contain an address with the id ${addressId}.`, }, 400, ); } return address; } if (required) { throw new CommercetoolsError( { code: "InvalidOperation", message: "One of address 'addressId' or 'addressKey' is required.", }, 400, ); } } }