import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { update as updateAccount, validateDefaultExpenseAccount } from "../domain/supplierAccount"; import type { Transaction } from "../generated/kysely-tailordb"; import { AddressInactiveError, AddressNotOwnedByPartnerError, BankAccountInactiveError, BankAccountNotOwnedByPartnerError, CurrencyNotFoundError, SupplierAccountNotFoundError, } from "../lib/errors.generated"; import type { CoaManagementQueries, PrimitivesQueries } from "../module"; import { createBusinessPartnerRepository, type BusinessPartnerRepository, } from "../repository/businessPartnerRepository"; import { createSupplierAccountRepository, type SupplierAccountRepository, } from "../repository/supplierAccountRepository"; import type { SupplierAddressUsageInput, SupplierBankAccountUsageInput, } from "./createSupplierAccount"; export interface SupplierAccountHeaderPatch { name?: string; preferredCurrencyId?: string | null; defaultExpenseAccountId?: string | null; } export interface SupplierAddressUsagePatch { isDefault?: boolean; } export interface SupplierBankAccountUsagePatch { isDefault?: boolean; } export interface SupplierAddressUsageEdit { usageId: string; usagePatch: SupplierAddressUsagePatch; } export interface SupplierBankAccountUsageEdit { usageId: string; usagePatch: SupplierBankAccountUsagePatch; } export interface UpdateSupplierAccountInput { id: string; headerPatch?: SupplierAccountHeaderPatch; addAddressUsages?: SupplierAddressUsageInput[]; updateAddressUsages?: SupplierAddressUsageEdit[]; removeAddressUsageIds?: string[]; addBankAccountUsages?: SupplierBankAccountUsageInput[]; updateBankAccountUsages?: SupplierBankAccountUsageEdit[]; removeBankAccountUsageIds?: string[]; } export async function run< CF extends Record, AUF extends Record, BUF extends Record, >( db: Transaction, input: Omit< UpdateSupplierAccountInput, | "headerPatch" | "addAddressUsages" | "updateAddressUsages" | "addBankAccountUsages" | "updateBankAccountUsages" > & { headerPatch?: SupplierAccountHeaderPatch & Partial; addAddressUsages?: (SupplierAddressUsageInput & AUF)[]; updateAddressUsages?: { usageId: string; usagePatch: SupplierAddressUsagePatch & Partial; }[]; addBankAccountUsages?: (SupplierBankAccountUsageInput & BUF)[]; updateBankAccountUsages?: { usageId: string; usagePatch: SupplierBankAccountUsagePatch & Partial; }[]; }, ctx: CommandContext, primitivesQueries: Pick, coaManagementQueries: Pick, repositoryFactory: ( db: Transaction, ) => SupplierAccountRepository = createSupplierAccountRepository, 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 SupplierAccountNotFoundError(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)); } if (headerPatch?.defaultExpenseAccountId) { const { account: defaultExpenseAccount } = ( await coaManagementQueries.getAccount(db, { id: headerPatch.defaultExpenseAccountId }, ctx) ).value; const validation = validateDefaultExpenseAccount( headerPatch.defaultExpenseAccountId, account.header.companyId, defaultExpenseAccount, ); if (!validation.ok) return validation; } 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, defaultExpenseAccountId, ...headerCustomFields } = headerPatch ?? {}; const updated = updateAccount(account, { headerPatch: { name, preferredCurrencyId, defaultExpenseAccountId, 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({ supplierAccountId: updated.value.id }); }