import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { update as updateAccount } from "../domain/customerAccount"; import type { Transaction } from "../generated/kysely-tailordb"; import { AddressInactiveError, AddressNotOwnedByPartnerError, BankAccountInactiveError, BankAccountNotOwnedByPartnerError, CurrencyNotFoundError, CustomerAccountNotFoundError, } from "../lib/errors.generated"; import type { PrimitivesQueries } from "../module"; import { createBusinessPartnerRepository, type BusinessPartnerRepository, } from "../repository/businessPartnerRepository"; import { createCustomerAccountRepository, type CustomerAccountRepository, } from "../repository/customerAccountRepository"; import type { CustomerAddressUsageInput, CustomerBankAccountUsageInput, } from "./createCustomerAccount"; export interface CustomerAccountHeaderPatch { name?: string; preferredCurrencyId?: string | null; } export interface CustomerAddressUsagePatch { isDefault?: boolean; } export interface CustomerBankAccountUsagePatch { isDefault?: boolean; } export interface CustomerAddressUsageEdit { usageId: string; usagePatch: CustomerAddressUsagePatch; } export interface CustomerBankAccountUsageEdit { usageId: string; usagePatch: CustomerBankAccountUsagePatch; } export interface UpdateCustomerAccountInput { id: string; headerPatch?: CustomerAccountHeaderPatch; addAddressUsages?: CustomerAddressUsageInput[]; updateAddressUsages?: CustomerAddressUsageEdit[]; removeAddressUsageIds?: string[]; addBankAccountUsages?: CustomerBankAccountUsageInput[]; updateBankAccountUsages?: CustomerBankAccountUsageEdit[]; removeBankAccountUsageIds?: string[]; } export async function run< CF extends Record, AUF extends Record, BUF extends Record, >( db: Transaction, input: Omit< UpdateCustomerAccountInput, | "headerPatch" | "addAddressUsages" | "updateAddressUsages" | "addBankAccountUsages" | "updateBankAccountUsages" > & { headerPatch?: CustomerAccountHeaderPatch & Partial; addAddressUsages?: (CustomerAddressUsageInput & AUF)[]; updateAddressUsages?: { usageId: string; usagePatch: CustomerAddressUsagePatch & Partial; }[]; addBankAccountUsages?: (CustomerBankAccountUsageInput & BUF)[]; updateBankAccountUsages?: { usageId: string; usagePatch: CustomerBankAccountUsagePatch & Partial; }[]; }, ctx: CommandContext, primitivesQueries: Pick, repositoryFactory: ( db: Transaction, ) => CustomerAccountRepository = createCustomerAccountRepository, businessPartnerRepositoryFactory: ( db: Transaction, ) => BusinessPartnerRepository = createBusinessPartnerRepository, ) { const { id, headerPatch, addAddressUsages = [], updateAddressUsages = [], removeAddressUsageIds = [], addBankAccountUsages = [], updateBankAccountUsages = [], removeBankAccountUsageIds = [], } = input; const repository = repositoryFactory(db); const account = await repository.findById(id, { forUpdate: true }); if (!account) return err(new CustomerAccountNotFoundError(id)); const businessPartnerRepository = businessPartnerRepositoryFactory(db); const partner = await businessPartnerRepository.findById(account.header.partnerId, { forUpdate: true, }); if (headerPatch?.preferredCurrencyId) { const { currency } = ( await primitivesQueries.getCurrency(db, { id: headerPatch.preferredCurrencyId }, ctx) ).value; if (!currency) return err(new CurrencyNotFoundError(headerPatch.preferredCurrencyId)); } const addressById = new Map(partner?.addresses.map((address) => [address.id, address])); for (const usage of addAddressUsages) { const address = addressById.get(usage.addressId); if (!address) return err(new AddressNotOwnedByPartnerError(usage.addressId)); if (!address.active) return err(new AddressInactiveError(usage.addressId)); } const bankAccountById = new Map( partner?.bankAccounts.map((bankAccount) => [bankAccount.id, bankAccount]), ); for (const usage of addBankAccountUsages) { const bankAccount = bankAccountById.get(usage.bankAccountId); if (!bankAccount) return err(new BankAccountNotOwnedByPartnerError(usage.bankAccountId)); if (!bankAccount.active) return err(new BankAccountInactiveError(usage.bankAccountId)); } const { name, preferredCurrencyId, ...headerCustomFields } = headerPatch ?? {}; const updated = updateAccount(account, { headerPatch: { name, preferredCurrencyId, customFields: headerCustomFields }, addAddressUsages: addAddressUsages.map( ({ addressId, purpose, isDefault = false, ...customFields }) => ({ addressId, purpose, isDefault, customFields, }), ), updateAddressUsages: updateAddressUsages.map(({ usageId, usagePatch }) => { const { isDefault, ...customFields } = usagePatch; return { usageId, patch: { isDefault, customFields } }; }), removeAddressUsageIds, addBankAccountUsages: addBankAccountUsages.map( ({ bankAccountId, purpose, isDefault = false, ...customFields }) => ({ bankAccountId, purpose, isDefault, customFields, }), ), updateBankAccountUsages: updateBankAccountUsages.map(({ usageId, usagePatch }) => { const { isDefault, ...customFields } = usagePatch; return { usageId, patch: { isDefault, customFields } }; }), removeBankAccountUsageIds, }); if (!updated.ok) return updated; await repository.save(updated.value); return ok({ customerAccountId: updated.value.id }); }