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).
| Naming | Value |
|---|---|
| Object | Invoice Transaction |
Resource type (JSON:API type) | invoice_transaction |
| Collection / records root | invoice_transactions |
| REST base | /v1/invoice-transactions |
| Entity class | InvoiceTransaction |
API operations
| Operation | Method & path | Status |
|---|---|---|
| List | GET /v1/invoice-transactions | โ Implemented |
| Retrieve | GET /v1/invoice-transactions/{id} | โ Implemented |
| Create | POST /v1/invoice-transactions | ๐ก Planned |
| Update | PATCH /v1/invoice-transactions/{id} | ๐ก Planned |
| Delete | DELETE /v1/invoice-transactions/{id} | ๐ก Planned |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|---|---|---|---|---|
| invoice_transaction_id | string, UUID | โ Yes | unique; 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. |
| amount | string (decimal 12,2) | โ Yes | CHECK 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. |
| currency | string, enum (CurrencyCodeEnum) | โ Yes | Must be a valid ISO 4217 code present in currency_code_enum Postgres native enum | USD, EUR, GBP, JPY, CHF, CAD, AUD, NZD โฆ +6 more | ISO 4217 currency code for the amount field. Matches the transaction's instructed currency at reconciliation time. |
| accounting_amount | string (decimal 12,2) | โช No | nullable; 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_currency | string, enum (CurrencyCodeEnum) | โช No | nullable; same Postgres native enum currency_code_enum | USD, EUR, GBP, JPY, CHF, CAD, AUD, NZD โฆ +6 more | Currency code for accounting_amount. Populated only when a multi-currency FX conversion was performed. Null when accounting_amount is null. |
| is_partial | boolean | โ Yes | default false | true, false | Legacy 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_type | string, enum (AllocationTypeEnum) | โช No | nullable; default 'full'; Postgres native enum allocation_type_enum | full, partial, overpayment, fee_deduction | Structured 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_at | string, datetime, ๐ system | โ Yes | set 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_at | string, datetime, ๐ system | โช No | set 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_at | string, datetime | โช No | nullable; 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
| Name | Type | Required | Description |
|---|---|---|---|
| workspace | to-one (workspace) | โ Yes | Tenant 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. |
| invoice | to-one (invoice) | โ Yes | The 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. |
| transaction | to-one (transaction) | โ Yes | The bank Transaction (debit or credit movement) that satisfies part or all of the linked invoice. FK transaction_pk. |
| subscription | to-one (subscription) | โช No | Optional link to a Subscription when the payment originates from a recurring subscription contract. Null for one-off invoice payments. FK subscription_pk nullable. |
| exchange_rate | to-one (exchange_rate) | โช No | The 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" }
}
}
}
}apps/api/src/database/entities/InvoiceTransaction.ts ยท domain: financial-graph ยท tier: Supporting