Saltar al contenido principal

Accounts Payable Payments

Overview

The accounts-payable-payments module records supplier payments against AP bills. It handles the full payment lifecycle: validation, document number generation, audit logging, event emission for bill balance reconciliation, and PDF generation.


Architecture

This module follows Hexagonal Architecture with four layers:

domain/       — Repository interface + injection token (ACCOUNTS_PAYABLE_PAYMENTS_REPOSITORY)
application/ — Business logic (AccountsPayablePaymentsService)
infrastructure/ — Kysely DB adapter (AccountsPayablePaymentsRepository)
interfaces/ — HTTP controller, DTOs, query objects

The repository is injected via a Symbol token (ACCOUNTS_PAYABLE_PAYMENTS_REPOSITORY) so the service depends only on the domain interface, not the concrete implementation.

Module Dependencies

DependencyWhy
AccountsPayableBillsModuleLoads and validates referenced AP bills (via AccountsPayableBillsService)
PaymentMethodsModuleValidates that used payment methods are active
AuditLogModuleRecords create/update/void events in the audit log
PdfModuleGenerates payment PDF documents via Puppeteer
DatabaseModuleKysely database connection for queries and transactions

Domain Concepts

Payment

A payment represents money transferred to a supplier to settle one or more AP bills.

Key fields:

  • supplierId — the supplier being paid
  • detail.items — list of bill references with applied amounts
  • paymentDetail.items — list of payment methods used (cash, bank transfer, etc.)
  • totalAmount — total in payment currency
  • totalBaseAmount — total in base business currency
  • exchangeRate — rate used for cross-currency conversion
  • statusposted | void
  • documentNumber — auto-generated sequential document number
  • primaryBillId — optional shortcut reference when paying a single bill
  • notes — optional internal memo
  • referenceNumber — optional external reference (bank transfer ID, check number)

Void

A payment is voided (not deleted) when reversed. Voiding:

  • Sets status = void
  • Records voidedAt and voidedBy timestamps
  • Emits an accountsPayablePayment.update event
  • The bills module listens to this event and restores the bill's balanceDue

Main Use Cases

Create Payment

  1. Validate all bill IDs exist
  2. Ensure bill statuses are APPROVED or SCHEDULED
  3. Ensure totalAmount equals the sum of detail.items[*].amount
  4. Ensure payment amounts do not exceed each bill's balanceDue
  5. Ensure cross-currency payments provide a valid exchangeRate > 0
  6. Ensure all payment methods are active
  7. Generate sequential documentNumber inside a transaction
  8. Persist and emit accountsPayablePayment.create

Void Payment

  1. Fetch existing payment
  2. Set status = void, record voidedAt / voidedBy
  3. Persist inside a transaction with audit log entry
  4. Emit accountsPayablePayment.update (bills module restores balanceDue)

List Payments (Standard)

  • Paginated with optional businessId and supplierId filters
  • Sortable by: documentNumber, paymentDate, createdAt, totalAmount, status
  • Returns payment row joined with supplier name/tax fields

List Payments with Bill Items

  • Uses the vwAccountsPayablePaymentDetails database view
  • Supports additional accountsPayableBillId filter
  • Returns one row per payment-bill pair (useful for reconciliation UIs)

Generate PDF / Print Preview

  • GET /:id/pdf — streams a PDF with configurable margins and page size
  • GET /:id/print — returns raw HTML for the same template (debug/preview)

Validation Rules

RuleError CodeDescription
Bills must existBILL_NOT_FOUNDAll accountsPayableBillId values must resolve
Bill status must be payableBILL_STATUS_NOT_APPROVEDOnly APPROVED or SCHEDULED bills may be paid
Total must match itemsTOTAL_AMOUNT_MISMATCHtotalAmount must equal sum of detail.items[*].amount
No overpaymentOVERPAYMENTApplied amount cannot exceed the bill's balanceDue
FX required for cross-currencyFX_REQUIRED_FOR_CROSS_CURRENCYexchangeRate must be > 0 when bill and payment use different currencies
Payment method must be activePAYMENT_METHOD_INACTIVEAll payment methods in paymentDetail.items must be active
Empty detail itemsDETAIL_ITEMS_REQUIREDAt least one bill item is required
Cannot delete posted/voidDELETE_NOT_ALLOWED_FOR_POSTED_PAYMENTOnly draft payments may be deleted

API Endpoints

MethodPathDescription
POST/accounts-payable-paymentsCreate a new payment
GET/accounts-payable-paymentsList payments (paginated)
GET/accounts-payable-payments/with-bill-itemsList payments with bill line items
GET/accounts-payable-payments/:idGet payment by ID
GET/accounts-payable-payments/:id/pdfDownload payment as PDF
GET/accounts-payable-payments/:id/printPrint preview (HTML)
PATCH/accounts-payable-payments/:idUpdate / void a payment
DELETE/accounts-payable-payments/:idDelete a draft payment

Full Swagger documentation is available at /docs when the backend is running.


Example: Create Payment

POST /accounts-payable-payments
{
"supplierId": "uuid",
"status": "posted",
"currencyId": "uuid",
"currencyCode": "GTQ",
"minorUnit": 2,
"exchangeRate": 1.00,
"currency": { "id": "uuid", "name": "Quetzal", "symbol": "Q" },
"totalAmount": 95.00,
"totalBaseAmount": 95.00,
"paymentDate": "2026-03-12",
"businessId": "uuid",
"createdBy": "uuid",
"detail": {
"items": [
{ "accountsPayableBillId": "uuid", "amount": 95.00, "baseAmount": 95.00 }
]
},
"paymentDetail": {
"items": [
{
"paymentMethodId": "uuid",
"paymentMethodName": "Cash",
"amount": 95.00,
"baseAmount": 95.00,
"currencyId": "uuid",
"currencyCode": "GTQ",
"minorUnit": 2,
"exchangeRate": 1.00
}
]
},
"notes": "Monthly supplier payment",
"referenceNumber": "TRX-20260312-001"
}

Example: Void Payment

PATCH /accounts-payable-payments/:id
{
"status": "void",
"updatedBy": "uuid"
}

Events

EventEmitted WhenListener
accountsPayablePayment.createPayment is createdAccountsPayableBillsService — decrements balanceDue on referenced bills
accountsPayablePayment.updatePayment is updated or voidedAccountsPayableBillsService — restores balanceDue if voided

Database

  • Table: accountsPayablePayment
  • View: vwAccountsPayablePaymentDetails — joins payment rows with their bill line items (one row per bill-payment pair)
  • Reporting views (Metabase): accounts_payable_payments_v — see docs/accounts-payable/dashboards.md

Key Design Decision: JSONB for detail fields

detail and paymentDetail are stored as jsonb columns rather than normalized tables. This trades query flexibility for schema stability and write simplicity — payment records are essentially append-only financial documents.


Bruno API Collection

All endpoints are covered in: api-client/flowpos/collections/accounts-payable-payments/

FileEndpoint
accounts payable payments.ymlGET list
accounts payable payments with bill items.ymlGET with bill items
accounts payable payment.ymlPOST create
accounts payable payment by Id.ymlGET by ID
update accounts payable payment.ymlPATCH update/void
delete accounts payable payment.ymlDELETE
accounts payable payment pdf.ymlGET PDF
accounts payable payment print.ymlGET print preview