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.

AP/AR Domain Isolation Design

Goal

Reorganize the codebase so that Accounts Payable (AP) logic is explicitly isolated into its own packages, preparing the architecture for Accounts Receivable (AR) to be introduced as a parallel vertical with its own pipeline, data sources, UI, and integrations.

Status: Implemented. The reorganization described here has been completed. See the ap-refactor branch for the implementation.

Context

The codebase is currently organized by technical layer (workers/, app/, schema/, db/) rather than by business domain. AP logic is spread across these layers:

Shared infrastructure (transcription, classification, extraction, auth, notifications) is already generic.

Key Decisions

  1. AP and AR are fully parallel verticals — no shared pipeline, acquisition, or ingestion logic between them.
  2. Domain prefix under each service (Option A) — the service layer (workers/, app/, schema/) remains the primary axis, with ap/ and ar/ as subdivisions within each.
  3. Shortened names: ap and ar, not accounts_payable and accounts_receivable.
  4. No Postgres schemas — table name prefixes instead. Cross-schema joins add friction, next-jdbc/HoneySQL schema ergonomics are poor, and the isolation needed is at the application level.
  5. Schemas (schema/) stay unsplit — no schema/ap/ or schema/ar/ until needed.
  6. Rename AP tables now — clean break avoids a migration later when AR arrives.

Namespace Reorganization

Workers

workers/
  ap/
    ingestion/                ← current workers/ingestion/
      orchestrator.clj
      transcription.clj
      classification.clj
      extraction.clj
      validation.clj
      fraud.clj
      post_process/
        accounts.clj
        cost_center.clj
        supplier.clj
        accruals.clj
        tax_compliance.clj
        financial_validation.clj
        ...
    acquisition/              ← current workers/acquisition/
    matching/                 ← current workers/matching/
  ar/
    ...                       ← future

App HTTP

AP list becomes a top-level route. Generic document management stays under /documents/.

app/http/
  ap.clj                     ← current app/http/documents/accounts_payable.clj
  ar.clj                     ← future
  documents/
    documents.clj             ← route assembly (stays)
    shared.clj                ← shared helpers (stays)
    upload.clj                ← generic upload (stays)
    management.clj            ← contracts, POs, GRNs list (stays)
    view.clj                  ← unified detail handler (stays)
    view/
      shared.clj              ← layout, SSE (stays)
      invoice.clj             ← type-specific renderer (stays — it renders invoice detail, not AP logic)
      contract.clj            ← stays
      purchase_order.clj      ← stays
      goods_received_note.clj ← stays
      notice.clj              ← stays

Integrations

integrations/
  ap/
    datev.clj                 ← current DATEV/MAESN integration
  ar/
    ...                       ← future (easybill.de, etc.)

Things That Don't Move

Routes

Current → Target

/documents/accounts-payable/...  →  /ap/...
/documents/upload                →  /documents/upload         (stays)
/documents/management/...        →  /documents/management/... (stays)
/documents/view/:id/...          →  /documents/view/:id/...   (stays)

Future

/ar/...                          →  AR list, bulk ops, AR-specific actions

AP and AR become top-level nav items alongside Documents, Settings, etc.

Database

Table Renames (AP-specific → ap_ prefix)

Current Target
doc_source ap_doc_source
doc_source_email ap_doc_source_email
doc_source_email_pending_sync ap_doc_source_email_pending_sync
doc_source_email_processed_messages ap_doc_source_email_processed_messages
doc_source_webhook ap_doc_source_webhook
doc_source_ses ap_doc_source_ses
doc_source_ses_processed ap_doc_source_ses_processed
ingestion ap_ingestion
ingestion_post_process_stat ap_ingestion_post_process_stat
document_cluster ap_document_cluster
document_match ap_document_match
datev_export_audit ap_datev_export_audit
supplier_verification ap_supplier_verification
supplier_verification_document ap_supplier_verification_document
acquisition_llm_stat ap_acquisition_llm_stat
qa_dataset_item ap_qa_dataset_item

Tables That Stay Unprefixed (shared)

Table Reason
tenant, legal_entity, legal_entity_* Org structure
identity, tenant_membership Auth
document Shared — gets domain column ('ap' / 'ar')
gl_accounts_dataset Chart of accounts, used by both AP and AR
cost_center_dataset Shared master data
business_partner_dataset Has both Kreditoren (AP) and Debitoren (AR)
booking_history_upload Shared — gets domain column when AR arrives
booking_history_item Shared — inherits domain from upload
notification_* Shared notification system
api_key, api_request_log Shared API infrastructure
oauth_*, http_session Auth infrastructure
fpna_data_map Shared FP&A

document Table Change

Add a domain column:

Integrant Config

Worker orchestrators get namespaced under domain:

;; Current
:com.getorcha.workers.ingestion/orchestrator
:com.getorcha.workers.acquisition/orchestrator
:com.getorcha.workers.matching.worker/orchestrator

;; Target
:com.getorcha.workers.ap.ingestion/orchestrator
:com.getorcha.workers.ap.acquisition/orchestrator
:com.getorcha.workers.ap.matching/orchestrator

;; Future
:com.getorcha.workers.ar.ingestion/orchestrator

What This Design Intentionally Defers