import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import type { Transaction } from "../generated/kysely-tailordb"; import { validateOutgoingPaymentSettlementTargets } from "../internal/outgoingPaymentSettlementValidation"; import { AccountCompanyMismatchError, AccountInactiveError, AccountNotFoundError, ApSupplierAccountNotFoundError, ApCurrencyNotFoundError, ApInvalidSupplierAccountError, CompanyInactiveError, CompanyNotFoundError, OutgoingPaymentInvalidAmountError, OutgoingPaymentSettlementInvalidError, } from "../lib/errors.generated"; import type { BusinessPartnerQueries, CoaManagementQueries, OrganizationQueries, PrimitivesQueries, } from "../module"; export interface OutgoingPaymentSettlementInput { accountPayableDueScheduleLineId: string; settledAmount: string; } export interface CreateOutgoingPaymentInput { companyId: string; supplierAccountId: string; currencyId: string; paymentAccountId: string; paymentDate: Date; totalAmount: string; settlements?: OutgoingPaymentSettlementInput[]; } /** Function: createOutgoingPayment * Creates an editable outgoing payment with optional initial settlements. */ export async function run< CF extends Record = Record, SCF extends Record = Record, >( db: Transaction, input: Omit & CF & { settlements?: (OutgoingPaymentSettlementInput & SCF)[] }, ctx: CommandContext, organizationQueries: Pick, supplierAccountQueries: Pick, primitivesQueries: Pick, coaManagementQueries: Pick, ) { const { companyId, supplierAccountId, currencyId, paymentAccountId, paymentDate, totalAmount, settlements = [], ...paymentCustomFields } = input; if (new Decimal(totalAmount).lte(0)) { return err(new OutgoingPaymentInvalidAmountError("OutgoingPayment")); } for (const settlement of settlements) { if (new Decimal(settlement.settledAmount).lte(0)) { return err(new OutgoingPaymentInvalidAmountError(settlement.accountPayableDueScheduleLineId)); } } const { company } = (await organizationQueries.getCompany(db, { id: companyId }, ctx)).value; if (!company) return err(new CompanyNotFoundError(companyId)); if (company.status !== "ACTIVE") return err(new CompanyInactiveError(companyId)); const { account: supplierAccount } = ( await supplierAccountQueries.getSupplierAccount(db, { supplierAccountId }, ctx) ).value; if (!supplierAccount) return err(new ApSupplierAccountNotFoundError(supplierAccountId)); if (supplierAccount.accountStatus !== "ACTIVE" || supplierAccount.companyId !== companyId) { return err(new ApInvalidSupplierAccountError(supplierAccountId)); } const { currency } = (await primitivesQueries.getCurrency(db, { id: currencyId }, ctx)).value; if (!currency) return err(new ApCurrencyNotFoundError(currencyId)); const { account } = (await coaManagementQueries.getAccount(db, { id: paymentAccountId }, ctx)) .value; if (!account) return err(new AccountNotFoundError(paymentAccountId)); if (account.companyId !== companyId) { return err(new AccountCompanyMismatchError(paymentAccountId)); } if (account.status !== "ACTIVE") return err(new AccountInactiveError(paymentAccountId)); const dueScheduleLineIds = settlements.map( (settlement) => settlement.accountPayableDueScheduleLineId, ); if (new Set(dueScheduleLineIds).size !== dueScheduleLineIds.length) { return err(new OutgoingPaymentSettlementInvalidError("OutgoingPayment")); } const targetResult = await validateOutgoingPaymentSettlementTargets( db, { companyId, supplierAccountId, currencyId }, settlements, ); if (!targetResult.ok) return targetResult; const outgoingPayment = await db .insertInto("OutgoingPayment") .values({ ...(paymentCustomFields as Record), companyId, supplierAccountId, currencyId, paymentAccountId, paymentDate, totalAmount, status: "DRAFT", postedAt: null, reversalDate: null, reversedAt: null, cancelledAt: null, }) .returningAll() .executeTakeFirstOrThrow(); if (settlements.length > 0) { await db .insertInto("AccountPayableSettlement") .values( settlements.map((settlement) => { const { accountPayableDueScheduleLineId, settledAmount, ...settlementCustomFields } = settlement; return { ...(settlementCustomFields as Record), sourceType: "OUTGOING_PAYMENT" as const, outgoingPaymentId: outgoingPayment.id, accountPayableDueScheduleLineId, settledAmount, reversalOfSettlementId: null, }; }), ) .execute(); } return ok({ outgoingPayment }); }