void: a statementless row for a charge that never posted (migration 0040)
ci / lint-test (push) Successful in 54s
ci / lint-test (push) Successful in 54s
A card notification is sent for an authorisation; a released hold never reaches a statement. Txn 7951 (a $1,040.41 Airbnb hold, booking cancelled the next day) sat in the reconcile queue forever and counted as spend. Neither reconcile (needs a statement twin) nor supersede (needs a survivor) can express it. - transactions.voided_at + void_reason; CHECKs: reason mandatory, statementless only - EXCLUDE_RECONCILED_SOURCE excludes voided rows; reconcile queue, findAutoMatches, both adoption helpers, annual charges and the manual reconcile POST filter it; merge refuses a voided row on either side - POST/DELETE /api/transactions/<id>/void; refuses reconciled, superseded and settled-split rows - "Never posted" button on /reconcile - wallet-ingestion deliberately still sees voided rows, so a late twin or a prompt_version re-post cannot rebook the hold Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -190,7 +190,9 @@ model transactions {
|
||||
owner_id Int?
|
||||
reconciled_with_id Int?
|
||||
superseded_by_id Int?
|
||||
principal_amount Decimal? @db.Decimal(12, 2)
|
||||
voided_at DateTime? @db.Timestamptz(6) // a charge that never posted (migration 0040)
|
||||
void_reason String?
|
||||
principal_amount Decimal? @db.Decimal(12, 2)
|
||||
interest_amount Decimal? @db.Decimal(12, 2)
|
||||
source String? // feed this row came from, e.g. "frollo"; NULL = statement/manual (migration 0028)
|
||||
source_ref String? // the provider's own transaction id — idempotency key for re-imports
|
||||
|
||||
Reference in New Issue
Block a user