import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { createActive, type CustomerAddressUsagePurpose, type CustomerBankAccountUsagePurpose, } from "../domain/customerAccount"; import type { Transaction } from "../generated/kysely-tailordb"; import { AddressInactiveError, AddressNotOwnedByPartnerError, BankAccountInactiveError, BankAccountNotOwnedByPartnerError, CompanyNotFoundError, CurrencyNotFoundError, DuplicateAccountError, PartnerNotActiveError, PartnerNotFoundError, } from "../lib/errors.generated"; import type { OrganizationQueries, PrimitivesQueries } from "../module"; import { createBusinessPartnerRepository, type BusinessPartnerRepository, } from "../repository/businessPartnerRepository"; import { createCustomerAccountRepository, type CustomerAccountRepository, } from "../repository/customerAccountRepository"; export interface CustomerAddressUsageInput { addressId: string; purpose: CustomerAddressUsagePurpose; isDefault?: boolean; } export interface CustomerBankAccountUsageInput { bankAccountId: string; purpose: CustomerBankAccountUsagePurpose; isDefault?: boolean; } export interface CreateCustomerAccountInput { partnerId: string; companyId: string; code: string; name: string; preferredCurrencyId?: string; addressUsages?: CustomerAddressUsageInput[]; bankAccountUsages?: CustomerBankAccountUsageInput[]; } export async function run< CF extends Record, AUF extends Record, BUF extends Record, >( db: Transaction, input: Omit & CF & { addressUsages?: (CustomerAddressUsageInput & AUF)[]; bankAccountUsages?: (CustomerBankAccountUsageInput & BUF)[]; }, ctx: CommandContext, organizationQueries: Pick, primitivesQueries: Pick, repositoryFactory: ( db: Transaction, ) => CustomerAccountRepository = createCustomerAccountRepository, businessPartnerRepositoryFactory: ( db: Transaction, ) => BusinessPartnerRepository = createBusinessPartnerRepository, ) { const { partnerId, companyId, code, name, preferredCurrencyId, addressUsages = [], bankAccountUsages = [], ...customFields } = input; const businessPartnerRepository = businessPartnerRepositoryFactory(db); const partner = await businessPartnerRepository.findById(partnerId, { forUpdate: true }); if (!partner) return err(new PartnerNotFoundError(partnerId)); if (partner.header.status !== "ACTIVE") return err(new PartnerNotActiveError(partnerId)); const { company } = (await organizationQueries.getCompany(db, { id: companyId }, ctx)).value; if (!company) return err(new CompanyNotFoundError(companyId)); if (preferredCurrencyId) { const { currency } = (await primitivesQueries.getCurrency(db, { id: preferredCurrencyId }, ctx)) .value; if (!currency) return err(new CurrencyNotFoundError(preferredCurrencyId)); } const repository = repositoryFactory(db); if (await repository.findByCompanyAndCode(companyId, code, { forUpdate: true })) { return err(new DuplicateAccountError(`${companyId}:${code}`)); } const addressById = new Map(partner.addresses.map((address) => [address.id, address])); for (const usage of addressUsages) { 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((account) => [account.id, account])); for (const usage of bankAccountUsages) { const bankAccount = bankAccountById.get(usage.bankAccountId); if (!bankAccount) return err(new BankAccountNotOwnedByPartnerError(usage.bankAccountId)); if (!bankAccount.active) return err(new BankAccountInactiveError(usage.bankAccountId)); } const created = createActive({ partnerId, companyId, code, name, preferredCurrencyId, customFields, addressUsages: addressUsages.map(({ addressId, purpose, isDefault = false, ...fields }) => ({ addressId, purpose, isDefault, customFields: fields, })), bankAccountUsages: bankAccountUsages.map( ({ bankAccountId, purpose, isDefault = false, ...fields }) => ({ bankAccountId, purpose, isDefault, customFields: fields, }), ), }); if (!created.ok) return created; await repository.save(created.value); return ok({ customerAccountId: created.value.id }); }