import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { createActive, type SupplierAddressPurpose, type SupplierBankAccountPurpose, validateDefaultExpenseAccount, } from "../domain/supplierAccount"; import type { Transaction } from "../generated/kysely-tailordb"; import { AddressInactiveError, AddressNotOwnedByPartnerError, BankAccountInactiveError, BankAccountNotOwnedByPartnerError, CompanyNotFoundError, CurrencyNotFoundError, DuplicateAccountError, PartnerNotActiveError, PartnerNotFoundError, } from "../lib/errors.generated"; import type { CoaManagementQueries, OrganizationQueries, PrimitivesQueries } from "../module"; import { createBusinessPartnerRepository, type BusinessPartnerRepository, } from "../repository/businessPartnerRepository"; import { createSupplierAccountRepository, type SupplierAccountRepository, } from "../repository/supplierAccountRepository"; export interface SupplierAddressUsageInput { addressId: string; purpose: SupplierAddressPurpose; isDefault?: boolean; } export interface SupplierBankAccountUsageInput { bankAccountId: string; purpose: SupplierBankAccountPurpose; isDefault?: boolean; } export interface CreateSupplierAccountInput { partnerId: string; companyId: string; code: string; name: string; preferredCurrencyId?: string; defaultExpenseAccountId?: string; addressUsages?: SupplierAddressUsageInput[]; bankAccountUsages?: SupplierBankAccountUsageInput[]; } export async function run< CF extends Record, AUF extends Record, BUF extends Record, >( db: Transaction, input: Omit & CF & { addressUsages?: (SupplierAddressUsageInput & AUF)[]; bankAccountUsages?: (SupplierBankAccountUsageInput & BUF)[]; }, ctx: CommandContext, organizationQueries: Pick, primitivesQueries: Pick, coaManagementQueries: Pick, repositoryFactory: ( db: Transaction, ) => SupplierAccountRepository = createSupplierAccountRepository, businessPartnerRepositoryFactory: ( db: Transaction, ) => BusinessPartnerRepository = createBusinessPartnerRepository, ) { const { partnerId, companyId, code, name, preferredCurrencyId, defaultExpenseAccountId, 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)); } if (defaultExpenseAccountId) { const { account } = ( await coaManagementQueries.getAccount(db, { id: defaultExpenseAccountId }, ctx) ).value; const validation = validateDefaultExpenseAccount(defaultExpenseAccountId, companyId, account); if (!validation.ok) return validation; } 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, defaultExpenseAccountId, 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({ supplierAccountId: created.value.id }); }