Note (2026-04-24): After this document was written, legal_entity was renamed to tenant and the old tenant was renamed to organization. Read references to these terms with the pre-rename meaning.

AR Module Design

Context

Orcha's first feature was Accounts Payable (AP) — ingesting, classifying, and matching incoming invoices. We're now adding Accounts Receivable (AR) as a parallel module, starting with the UI for creating and managing outgoing invoices.

AR will eventually have its own ingestion pipeline (external systems pushing/pulling data to create draft invoices), but this iteration focuses on the UI and data model foundation.

Decisions

Data Model

Document type rename: The document_type enum currently has invoice (implicitly incoming). Rename to incoming-invoice and add outgoing-invoice.

Migration steps:

  1. Add incoming-invoice and outgoing-invoice enum values
  2. UPDATE document SET type = 'incoming-invoice' WHERE type = 'invoice'
  3. Make content_hash and file_path nullable (outgoing invoices start as structured data, not files)
  4. Convert content_hash unique constraint to partial unique index excluding nulls
  5. Add status column to document table for AR workflow: draft, sent, paid
  6. Update all code referencing :invoice type to :incoming-invoice

Why same table: AR invoices will also have matching (to contracts), and the document table already holds multiple types (contracts, POs, GRNs). A separate table would need to mirror the same structure. The type enum already discriminates document types — an outgoing invoice is just another type.

Structured Data Schema Split

Three layers:

PDF Rendering

Protocol: PdfRenderer with render-to-pdf method taking an HTML string and options map, returning PDF bytes. Single implementation: OpenHtmlToPdfRenderer wrapping the existing PdfRendererBuilder pattern.

Shared across codebase: Extract from existing duplicated code in:

All three refactored to use the shared renderer.

Why OpenHTMLToPDF: Already in deps.edn, already used in two places, pure JVM (no infrastructure), fast enough for live preview (~50-100ms). Coded behind a protocol so the engine is replaceable if needed.

Template System

Multimethod dispatching on tenant, returning hiccup:

(defmulti render-invoice-template :tenant-id)

(defmethod render-invoice-template :default [invoice-data]
  [:div.invoice ...])

Custom templates = new defmethod per tenant, converting customer-provided HTML to hiccup as code. Hiccup → HTML string → OpenHTMLToPDF → PDF bytes.

No template engine (no Selmer), no S3 storage, no runtime loading, no template builder. Default template ships with Orcha.

Live PDF Preview

  1. User edits form field
  2. HTMX hx-post with throttle:300ms sends form data to server
  3. Server coerces to outgoing invoice structured data
  4. Multimethod renders tenant template as hiccup → HTML string
  5. OpenHTMLToPDF converts to PDF bytes (~50-100ms)
  6. Response streams PDF (Content-Type: application/pdf)
  7. Client-side iframe refreshes with new PDF

Target round-trip: under 500ms.

UI Specification

Invoice Create/Edit Form

Layout: Split pane — form on the left, PDF preview on the right. Responsive: preview collapses or moves below on small screens.

Form fields:

Header:

Line items table (editable rows):

Totals (calculated, readonly):

Notes:

Issuer comes from tenant/legal entity config, not the form.

Overview/List Screen

Mirrors AP list structure.

Columns:

Filters:

Sorting: Default by created date descending. Clickable column headers.

Actions:

Code Organization

New namespaces:

Modified namespaces:

Not created this iteration:

Deliberately Excluded