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"; /** * createOutgoingPayment — create an editable (DRAFT) outgoing payment that * settles open account-payable due-schedule lines. * * A payment records who is paid (`supplierAccountId`), from which cash/bank GL * account (`paymentAccountId`), on what date, for how much, and an optional set * of settlements. Each settlement allocates part of the payment to an * `AccountPayableDueScheduleLine` — an invoice's due installment, or a credit * memo's negative installment (erp-kit 0.51 lets a payment settle invoices and * credit memos together at their signed net). Creation does NOT post to the GL; * that happens at `postOutgoingPayment`. `totalAmount` is a positive magnitude. */ export default createResolver({ name: "createOutgoingPayment", operation: "mutation", input: { companyId: t.string().description("Owning company whose ledger the payment posts to"), supplierAccountId: t.string().description("Supplier being paid"), currencyId: t .string() .description("Payment currency (must equal the company base currency to post)"), paymentAccountId: t.string().description("Cash/bank GL account the payment is drawn from"), paymentDate: t.string().description("Payment date (YYYY-MM-DD)"), totalAmount: t.string().description("Payment total — positive magnitude (decimal string)"), settlements: t .object( { accountPayableDueScheduleLineId: t .string() .description("Target AP due-schedule line to settle"), settledAmount: t .string() .description("Amount allocated to this line — positive magnitude (decimal string)"), }, { array: true }, ) .description("Settlement allocations (may be empty; editable until the payment is posted)"), }, body: async (context) => { const ctx = createContext(context); const db = getDB("main-db"); return db .transaction() .execute(async (trx) => { const result = await apModulesExport.commands.createOutgoingPayment( trx, { companyId: context.input.companyId, supplierAccountId: context.input.supplierAccountId, currencyId: context.input.currencyId, paymentAccountId: context.input.paymentAccountId, paymentDate: new Date(context.input.paymentDate), totalAmount: context.input.totalAmount, settlements: context.input.settlements.map((s) => ({ accountPayableDueScheduleLineId: s.accountPayableDueScheduleLineId, settledAmount: s.settledAmount, })), }, ctx, ); if (!result.ok) { switch (result.error.code) { case "ACCOUNT_PAYABLE_COMPANY_NOT_FOUND": throw new DomainError(`Company not found: ${context.input.companyId}`); case "ACCOUNT_PAYABLE_COMPANY_INACTIVE": throw new DomainError(`Company is not active: ${context.input.companyId}`); case "ACCOUNT_PAYABLE_AP_SUPPLIER_ACCOUNT_NOT_FOUND": throw new DomainError(`Supplier not found: ${context.input.supplierAccountId}`); case "ACCOUNT_PAYABLE_AP_INVALID_SUPPLIER_ACCOUNT": throw new DomainError( `The supplier account is not valid for this company or is inactive: ${context.input.supplierAccountId}`, ); case "ACCOUNT_PAYABLE_AP_CURRENCY_NOT_FOUND": throw new DomainError(`Currency not found: ${context.input.currencyId}`); case "ACCOUNT_PAYABLE_ACCOUNT_NOT_FOUND": throw new DomainError(`Payment account not found: ${context.input.paymentAccountId}`); case "ACCOUNT_PAYABLE_ACCOUNT_INACTIVE": throw new DomainError( `Payment account is not ACTIVE: ${context.input.paymentAccountId}`, ); case "ACCOUNT_PAYABLE_ACCOUNT_COMPANY_MISMATCH": throw new DomainError(`The payment account belongs to a different company.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_INVALID_AMOUNT": throw new DomainError(`The payment amount must be a positive value.`); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_SETTLEMENT_INVALID": throw new DomainError( `A settlement allocation is invalid (bad amount or ineligible target).`, ); case "ACCOUNT_PAYABLE_OUTGOING_PAYMENT_TARGET_INELIGIBLE": throw new DomainError( `A settlement target is not eligible (e.g. the invoice is not posted).`, ); 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 create the outgoing payment", { cause: err }); }); }, output: t .object({ id: t.string().description("Created outgoing payment ID"), status: t.string().description("Lifecycle status (DRAFT on create)"), }) .description("Created outgoing payment"), });