Invoice Transaction

InvoiceTransaction is the reconciliation pivot record that links one Invoice to one Transaction, recording the exact amount (and optional accounting-currency equivalent) that the bank movement satisfies on the invoice. It is written exclusively by the reconciliation pipeline and by connector sync mapping targets; it is not mutated directly by the user-facing API. Key associations are Workspace (tenant scope), Invoice, Transaction, and optionally Subscription (for subscription-driven payments) and ExchangeRate (for multi-currency accounting conversion).

NamingValue
ObjectInvoice Transaction
Resource type (JSON:API type)invoice_transaction
Collection / records rootinvoice_transactions
REST base/v1/invoice-transactions
Entity classInvoiceTransaction

API operations

OperationMethod & pathStatus
ListGET /v1/invoice-transactionsโœ… Implemented
RetrieveGET /v1/invoice-transactions/{id}โœ… Implemented
CreatePOST /v1/invoice-transactions๐ŸŸก Planned
UpdatePATCH /v1/invoice-transactions/{id}๐ŸŸก Planned
DeleteDELETE /v1/invoice-transactions/{id}๐ŸŸก Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
invoice_transaction_idstring, UUIDโœ… Yesunique; generated by gen_random_uuid() on insertโ€”Public immutable identifier for this reconciliation link. Exposed on all API responses; the internal pk is never surfaced.
amountstring (decimal 12,2)โœ… YesCHECK amount > 0; max 12 digits total, 2 decimal placesโ€”The portion of the invoice satisfied by this transaction link, expressed in the transaction's original currency. Always positive โ€” direction is implied by the invoice/transaction relationship.
currencystring, enum (CurrencyCodeEnum)โœ… YesMust be a valid ISO 4217 code present in currency_code_enum Postgres native enumUSD, EUR, GBP, JPY, CHF, CAD, AUD, NZD โ€ฆ +6 moreISO 4217 currency code for the amount field. Matches the transaction's instructed currency at reconciliation time.
accounting_amountstring (decimal 12,2)โšช Nonullable; max 12 digits total, 2 decimal placesโ€”Amount converted to the workspace accounting currency using the linked exchange_rate. Null when no FX conversion was required (amount currency already equals accounting currency) or when the exchange rate was not available at reconciliation time.
accounting_currencystring, enum (CurrencyCodeEnum)โšช Nonullable; same Postgres native enum currency_code_enumUSD, EUR, GBP, JPY, CHF, CAD, AUD, NZD โ€ฆ +6 moreCurrency code for accounting_amount. Populated only when a multi-currency FX conversion was performed. Null when accounting_amount is null.
is_partialbooleanโœ… Yesdefault falsetrue, falseLegacy flag indicating the transaction only partially covers the invoice. Superseded by allocation_type; kept for one release cycle for backward compatibility with the reconciliation persister. Will be dropped once allocation_type (W19-P13) is fully rolled out.
allocation_typestring, enum (AllocationTypeEnum)โšช Nonullable; default 'full'; Postgres native enum allocation_type_enumfull, partial, overpayment, fee_deductionStructured classification of how the transaction satisfies the invoice. 'full' means complete payment; 'partial' means the transaction covers only part of the outstanding amount; 'overpayment' means the payment exceeds the invoice total; 'fee_deduction' means a service fee was deducted from the gross payment before crediting the invoice. Replaces is_partial. Null and 'full' are treated identically by the reconciliation P5 recompute service.
created_atstring, datetime, ๐Ÿ”’ systemโœ… Yesset on insert via onCreate lifecycle hook; never updatedโ€”ISO 8601 timestamp of when this reconciliation link was created by the pipeline or connector sync.
updated_atstring, datetime, ๐Ÿ”’ systemโšช Noset on insert and on every update via onCreate/onUpdate lifecycle hooksโ€”ISO 8601 timestamp of the last modification to this record. Updated automatically by MikroORM on every flush.
deleted_atstring, datetimeโšช Nonullable; null means active record; non-null means soft-deletedโ€”Soft-delete timestamp. All active-record queries must filter deleted_at IS NULL. A partial index idx_invoice_transactions_invoice_active on (invoice_pk) WHERE deleted_at IS NULL exists specifically to support the invoice-merge UPDATE path that rewrites links without a workspace bound.

Relationships

NameTypeRequiredDescription
workspaceto-one (workspace)โœ… YesTenant boundary. Every InvoiceTransaction belongs to exactly one Workspace. Two composite indexes (workspace + invoice, workspace + transaction) are declared at the entity level for tenant-scoped lookup performance.
invoiceto-one (invoice)โœ… YesThe invoice this bank transaction is reconciled against. FK invoice_pk. A partial index idx_invoice_transactions_invoice_active covers invoice_pk WHERE deleted_at IS NULL to support invoice-merge rewrites that filter solely on invoice_pk without a workspace predicate.
transactionto-one (transaction)โœ… YesThe bank Transaction (debit or credit movement) that satisfies part or all of the linked invoice. FK transaction_pk.
subscriptionto-one (subscription)โšช NoOptional link to a Subscription when the payment originates from a recurring subscription contract. Null for one-off invoice payments. FK subscription_pk nullable.
exchange_rateto-one (exchange_rate)โšช NoThe ExchangeRate snapshot used to compute accounting_amount / accounting_currency during multi-currency reconciliation. Null when no FX conversion was needed. FK exchange_rate_pk nullable.

System-computed

  • invoice_transaction_id is generated via gen_random_uuid() as the Postgres column default and mirrored by randomUUID() in the TypeScript initializer โ€” both guarantee a unique UUID on every insert without application-level collision risk.
  • created_at is set to new Date() by an onCreate MikroORM lifecycle hook on the entity property initializer; it is never updated after insert.
  • updated_at is set by both onCreate and onUpdate hooks, so it reflects the timestamp of the most recent flush for any property change.
  • deleted_at is null on creation and is set to a non-null Date by soft-delete callers (reconciliation pipeline / connector sync unlink). All active-record queries must include deleted_at: null in their filter predicates.
  • is_partial defaults to false at the TypeScript level; this maps to DEFAULT false in Postgres. It is a deprecated surrogate for allocation_type and will be dropped in a future migration once W19-P13 is complete.
  • allocation_type defaults to AllocationTypeEnum.FULL ('full') at both the TypeScript level and the Postgres column default (DEFAULT 'full'). The reconciliation P5 recompute service treats NULL and 'full' as semantically identical.
  • accounting_amount and accounting_currency are pipeline-computed from the linked ExchangeRate at reconciliation time; they are null when the transaction currency already matches the workspace accounting currency or when FX data was unavailable.
  • This entity is one of the 12 target models in the MCP connector sync mapping pipeline (ConnectorMapping target model: invoice_transaction). Records may be created or soft-deleted by the connector sync persister as well as the Well reconciliation agent.
  • Three indexes are declared at the entity class level: composite (workspace_pk, invoice_pk), composite (workspace_pk, transaction_pk), and a partial expression index idx_invoice_transactions_invoice_active on (invoice_pk) WHERE deleted_at IS NULL โ€” the last index is required specifically for the invoice-merge UPDATE path that rewrites foreign keys without a workspace bound.

Example

{
  "data": {
    "type": "invoice_transaction",
    "id": "a3f7c291-84e2-4b10-9d2e-1f5b0c3e8a47",
    "attributes": {
      "invoice_transaction_id": "a3f7c291-84e2-4b10-9d2e-1f5b0c3e8a47",
      "amount": "2500.00",
      "currency": "EUR",
      "accounting_amount": "2712.50",
      "accounting_currency": "USD",
      "is_partial": false,
      "allocation_type": "full",
      "created_at": "2026-04-14T09:23:11.000Z",
      "updated_at": "2026-04-14T09:23:11.000Z",
      "deleted_at": null
    },
    "relationships": {
      "workspace": {
        "data": { "type": "workspace", "id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890" }
      },
      "invoice": {
        "data": { "type": "invoice", "id": "c9d8e7f6-a5b4-3c2d-1e0f-9a8b7c6d5e4f" }
      },
      "transaction": {
        "data": { "type": "transaction", "id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90" }
      },
      "subscription": {
        "data": null
      },
      "exchange_rate": {
        "data": { "type": "exchange_rate", "id": "e1f2a3b4-c5d6-7890-abcd-123456789abc" }
      }
    }
  }
}
Source: apps/api/src/database/entities/InvoiceTransaction.ts ยท domain: financial-graph ยท tier: Supporting