import { createContext, DomainError } from "@tailor-platform/erp-kit/app"; import { createResolver, t } from "@tailor-platform/sdk"; import { getDB } from "@/generated/kysely-tailordb"; import { apModulesExport } from "@/modules"; /** * postOutgoingPayment — post a DRAFT outgoing payment to the general ledger. * * Posting books the balanced cash-disbursement entry (Dr Accounts Payable / Cr Cash for * the settled amount, direction-aware for credit-memo settlements — erp-kit * 0.51) under `sourceDocumentType = OUTGOING_PAYMENT`, applies the settlement * allocations against the targeted AP due-schedule lines, and moves the payment * DRAFT -> POSTED in the same transaction. It rejects over-settlement (paying * more than an open item's balance), a total that does not reconcile with the * settlements, ineligible targets (e.g. an unposted invoice), and a payment * currency that differs from the company base currency. `id` is the * OutgoingPayment id. */ export default createResolver({ name: "postOutgoingPayment", operation: "mutation", input: { id: t.string().description("Outgoing payment ID"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { const result = await apModulesExport.commands.postOutgoingPayment( trx, { id: context.input.id }, ctx, ); if (!result.ok) { switch (result.error.code) { case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_NOT_FOUND": throw new DomainError(`Outgoing payment not found: ${context.input.id}`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_INVALID_STATUS": throw new DomainError(`Only a DRAFT payment can be posted: ${context.input.id}`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_OVER_SETTLEMENT": throw new DomainError(`A settlement exceeds the open balance of its target line.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_TOTAL_MISMATCH": throw new DomainError(`The payment total does not reconcile with its settlements.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_TARGET_INELIGIBLE": throw new DomainError( `A settlement target is not eligible (e.g. the invoice is not posted).`, ); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_INVALID_AMOUNT": throw new DomainError(`The payment amount must be a positive value.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_ACCOUNTING_PERIOD_NOT_FOUND": throw new DomainError(`No accounting period covers the payment date.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_JOURNAL_ENTRY_CREATE_FAILED": throw new DomainError(`Failed to create the journal entry for this payment.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_JOURNAL_ENTRY_POST_FAILED": throw new DomainError(`Failed to post the journal entry for this payment.`); case "ACCOUNT_PAYABLE_COMPANY_NOT_FOUND": throw new DomainError("The payment company does not exist."); case "ACCOUNT_PAYABLE_COMPANY_INACTIVE": throw new DomainError("The payment company is not active."); case "ACCOUNT_PAYABLE_AP_SUPPLIER_ACCOUNT_NOT_FOUND": throw new DomainError("The payment supplier does not exist."); case "ACCOUNT_PAYABLE_AP_INVALID_SUPPLIER_ACCOUNT": throw new DomainError( "The supplier account is not valid for this company or is inactive.", ); case "ACCOUNT_PAYABLE_AP_CURRENCY_NOT_FOUND": throw new DomainError("The payment currency is not configured."); case "ACCOUNT_PAYABLE_ACCOUNT_NOT_FOUND": throw new DomainError("A referenced GL account does not exist."); case "ACCOUNT_PAYABLE_ACCOUNT_INACTIVE": throw new DomainError("A referenced GL account is not ACTIVE."); case "ACCOUNT_PAYABLE_ACCOUNT_COMPANY_MISMATCH": throw new DomainError("A referenced GL account belongs to a different company."); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_SETTLEMENT_INVALID": throw new DomainError("A settlement allocation is invalid."); case "UNAUTHENTICATED": throw new DomainError("Authentication is required"); case "INSUFFICIENT_PERMISSION": throw new DomainError("You do not have permission to perform this action"); default: throw result.error satisfies never; } } return { id: result.value.outgoingPayment.id, status: result.value.outgoingPayment.status, }; }) .catch((err: unknown) => { if (err instanceof DomainError) throw err; throw new Error("Failed to post the outgoing payment", { cause: err }); }); }, output: t .object({ id: t.string().description("Posted outgoing payment ID"), status: t.string().description("Lifecycle status (POSTED after posting)"), }) .description("Posted outgoing payment"), });