ci / lint-test (push) Successful in 42s
account_number fragments exactly like bank_name did, from the same cause: ANZ's Access Advantage arrived as both '4085-56264' (4 statements) and '408556264' (1). uq_statement_identity keyed on it, so re-importing one period under the other spelling evaded the duplicate check. account_number_key is GENERATED ALWAYS — the number with separators stripped — and the unique index moves onto it. Derived rather than rewritten because the raw number is the readable one and some of it is structure: Up stores '633-123 / 176540052', a BSB and an account number, and flattening it would lose a distinction a human reads at a glance to fix a machine problem. The Amex mask and case are preserved; 'X' records which digits were redacted. Not the cause of the documented 31-row / $42,040.68 ANZ duplication — 107/142/143 overlap on different end dates, which that index cannot catch at any spelling. Adds statement_identity_drift: one account under two bank names, or one bank under two spellings of one number. Non-empty means fragmentation the normaliser could not know about. Currently one row, the ANZ pair above, now unified by key. Also documents the N8N regression this series caused. Check Known Bank compared the raw Gemini name against the canonical column before the insert, so nothing ever matched and every Zip statement was tagged pending and held for Slack approval — a stall, not just a noisy alert. Fixed in workflow FysADdFwEtwONQl4 by comparing like with like through normalize_bank_name(), passing currency so a genuinely new national entity still alerts. Verified live: 12s after vs 30-53s before.
271 lines
11 KiB
Plaintext
271 lines
11 KiB
Plaintext
generator client {
|
|
provider = "prisma-client"
|
|
output = "../src/generated/prisma"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
}
|
|
|
|
model trips {
|
|
id Int @id @default(autoincrement())
|
|
owner_id Int
|
|
name String
|
|
description String?
|
|
start_date DateTime? @db.Date
|
|
end_date DateTime? @db.Date
|
|
color String @default("#6366f1")
|
|
archived Boolean @default(false)
|
|
created_at DateTime @default(now())
|
|
overrides transaction_overrides[]
|
|
payments split_payments[]
|
|
}
|
|
|
|
model transaction_overrides {
|
|
id Int @id @default(autoincrement())
|
|
transaction_id Int @unique
|
|
merchant_normalized String?
|
|
category_override String?
|
|
notes String?
|
|
my_share_percent Decimal? @db.Decimal(5, 2)
|
|
updated_at DateTime @default(now()) @updatedAt
|
|
trip_id Int?
|
|
trip trips? @relation(fields: [trip_id], references: [id], onDelete: SetNull)
|
|
}
|
|
|
|
model participants {
|
|
id Int @id @default(autoincrement())
|
|
name String @unique
|
|
email String? @unique
|
|
created_at DateTime @default(now())
|
|
splits transaction_splits[]
|
|
account_owner_mappings account_owner_mappings[]
|
|
payments_sent split_payments[] @relation("payments_from")
|
|
payments_received split_payments[] @relation("payments_to")
|
|
order_reviews order_reviews[]
|
|
}
|
|
|
|
model account_owner_mappings {
|
|
id Int @id @default(autoincrement())
|
|
bank_name String
|
|
account_number String
|
|
owner_id Int
|
|
created_at DateTime @default(now())
|
|
owner participants @relation(fields: [owner_id], references: [id])
|
|
|
|
@@unique([bank_name, account_number])
|
|
}
|
|
|
|
model transaction_splits {
|
|
id Int @id @default(autoincrement())
|
|
transaction_id Int
|
|
participant_id Int
|
|
share_percent Decimal @db.Decimal(5, 2)
|
|
settled Boolean @default(false)
|
|
settled_at DateTime?
|
|
created_at DateTime @default(now())
|
|
participant participants @relation(fields: [participant_id], references: [id])
|
|
|
|
@@unique([transaction_id, participant_id])
|
|
}
|
|
|
|
model split_payments {
|
|
id Int @id @default(autoincrement())
|
|
from_participant_id Int
|
|
to_participant_id Int
|
|
amount Decimal @db.Decimal(10, 2)
|
|
payment_date DateTime @db.Date
|
|
notes String?
|
|
linked_transaction_id Int?
|
|
trip_id Int?
|
|
created_at DateTime @default(now())
|
|
from_participant participants @relation("payments_from", fields: [from_participant_id], references: [id])
|
|
to_participant participants @relation("payments_to", fields: [to_participant_id], references: [id])
|
|
trip trips? @relation(fields: [trip_id], references: [id], onDelete: SetNull)
|
|
|
|
@@index([trip_id])
|
|
}
|
|
|
|
model tags {
|
|
id Int @id @default(autoincrement())
|
|
name String @unique
|
|
color String @default("#6366f1")
|
|
created_at DateTime @default(now())
|
|
transaction_tags transaction_tags[]
|
|
}
|
|
|
|
model transaction_tags {
|
|
transaction_id Int
|
|
tag_id Int
|
|
created_at DateTime @default(now())
|
|
tag tags @relation(fields: [tag_id], references: [id], onDelete: Cascade)
|
|
|
|
@@id([transaction_id, tag_id])
|
|
}
|
|
|
|
model rules {
|
|
id Int @id @default(autoincrement())
|
|
owner_id Int
|
|
name String
|
|
conditions Json @default("[]")
|
|
actions Json @default("{}")
|
|
enabled Boolean @default(true)
|
|
manual_only Boolean @default(false)
|
|
priority Int @default(0)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @default(now()) @updatedAt
|
|
}
|
|
|
|
model budgets {
|
|
id Int @id @default(autoincrement())
|
|
owner_id Int
|
|
category String
|
|
month DateTime @db.Date
|
|
amount_limit Decimal @db.Decimal(10, 2)
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @default(now()) @updatedAt
|
|
|
|
@@unique([owner_id, category, month])
|
|
}
|
|
|
|
model statements {
|
|
id Int @id @default(autoincrement())
|
|
filename String
|
|
bank_name String?
|
|
card_name String?
|
|
account_type String?
|
|
account_number String
|
|
/// GENERATED ALWAYS (migration 0033) — account_number with separators
|
|
/// stripped, so '4085-56264' and '408556264' are one account. Backs
|
|
/// uq_statement_identity. Read-only: never write to it, display
|
|
/// account_number instead.
|
|
account_number_key String?
|
|
billing_start_date DateTime? @db.Date
|
|
billing_end_date DateTime? @db.Date
|
|
total_amount_due Decimal? @db.Decimal(12, 2)
|
|
minimum_amount_due Decimal? @db.Decimal(12, 2)
|
|
payment_due_date DateTime? @db.Date
|
|
event_created Boolean? @default(false)
|
|
tier_used String?
|
|
created_at DateTime? @default(now())
|
|
statement_type String @default("other")
|
|
currency String? @default("AUD")
|
|
opening_balance Decimal? @db.Decimal(12, 2)
|
|
closing_balance Decimal? @db.Decimal(12, 2)
|
|
total_credits Decimal? @db.Decimal(12, 2)
|
|
total_debits Decimal? @db.Decimal(12, 2)
|
|
interest_charged Decimal? @db.Decimal(12, 2)
|
|
fees_charged Decimal? @db.Decimal(12, 2)
|
|
credit_limit Decimal? @db.Decimal(12, 2)
|
|
available_credit Decimal? @db.Decimal(12, 2)
|
|
owner_id Int @default(1)
|
|
account_holder_name String?
|
|
exchange_rate_to_aud Decimal? @db.Decimal(10, 6)
|
|
paperless_doc_id Int? @unique
|
|
interest_rate Decimal? @db.Decimal(6, 3)
|
|
scheduled_repayment Decimal? @db.Decimal(12, 2)
|
|
repayment_frequency String?
|
|
redraw_available Decimal? @db.Decimal(12, 2)
|
|
loan_term_months Int?
|
|
transactions transactions[]
|
|
}
|
|
|
|
model transactions {
|
|
id Int @id @default(autoincrement())
|
|
statement_id Int?
|
|
transaction_date DateTime @db.Date
|
|
description String?
|
|
amount Decimal @db.Decimal(12, 2)
|
|
created_at DateTime? @default(now())
|
|
transaction_type String? @default("debit")
|
|
merchant_name String?
|
|
location String?
|
|
foreign_currency_amount Decimal? @db.Decimal(12, 2)
|
|
foreign_currency_code String?
|
|
category String?
|
|
row_index Int?
|
|
merchant_normalized String?
|
|
amount_aud Decimal? @db.Decimal(12, 2)
|
|
payment_method String? // card | cash | bank_transfer | other; NULL = unknown (migration 0016)
|
|
owner_id Int?
|
|
reconciled_with_id Int?
|
|
superseded_by_id Int?
|
|
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
|
|
source_account String? // account label as the source names it
|
|
statement statements? @relation(fields: [statement_id], references: [id], onDelete: Cascade)
|
|
reconciled_with transactions? @relation("reconciled", fields: [reconciled_with_id], references: [id], onDelete: SetNull)
|
|
reconciled_by transactions[] @relation("reconciled")
|
|
superseded_by transactions? @relation("superseded", fields: [superseded_by_id], references: [id], onDelete: SetNull)
|
|
supersedes transactions[] @relation("superseded")
|
|
expense_metadata expense_metadata?
|
|
order_reviews order_reviews[]
|
|
}
|
|
|
|
model expense_metadata {
|
|
id Int @id @default(autoincrement())
|
|
transaction_id Int? @unique
|
|
source String @default("email")
|
|
paperless_doc_id Int? @unique
|
|
source_email_subject String?
|
|
source_email_from String?
|
|
source_message_id String?
|
|
payment_method String?
|
|
payment_method_detail String?
|
|
order_reference String?
|
|
line_items Json @default("[]")
|
|
tax_amount Decimal? @db.Decimal(12, 2)
|
|
subtotal Decimal? @db.Decimal(12, 2)
|
|
merchant_normalized String?
|
|
amount Decimal? @db.Decimal(12, 2)
|
|
transaction_date DateTime? @db.Date
|
|
extraction_model String? @default("gemini-2.5-flash")
|
|
created_at DateTime? @default(now())
|
|
// In the database since migrations 0019/0020 but absent from this model until 0027.
|
|
// Regenerating the client from the stale definition would have dropped columns the order
|
|
// lane writes on every ingest.
|
|
card_last4 String?
|
|
currency String?
|
|
flags Json @default("[]")
|
|
reconciled_at DateTime? @db.Timestamptz(6)
|
|
matched_transaction_id Int?
|
|
platform String?
|
|
route Json?
|
|
// 0027 — receipt scans as a second producer on this lane.
|
|
tender_raw String?
|
|
receipt_sha256 String?
|
|
receipt_group String?
|
|
transaction transactions? @relation(fields: [transaction_id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([source, order_reference], name: "uq_expense_source_order")
|
|
}
|
|
|
|
model order_reviews {
|
|
id Int @id @default(autoincrement())
|
|
transaction_id Int
|
|
participant_id Int
|
|
rating String?
|
|
order_again Boolean?
|
|
note String?
|
|
item_verdicts Json @default("[]")
|
|
created_at DateTime @default(now())
|
|
updated_at DateTime @updatedAt
|
|
transaction transactions @relation(fields: [transaction_id], references: [id], onDelete: Cascade)
|
|
participant participants @relation(fields: [participant_id], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([transaction_id, participant_id], name: "order_reviews_transaction_participant_key")
|
|
}
|
|
|
|
model rule_apply_runs {
|
|
id Int @id @default(autoincrement())
|
|
owner_id Int
|
|
applied_at DateTime @default(now())
|
|
split_from DateTime? @db.Date
|
|
matched Int @default(0)
|
|
transactions_affected Int @default(0)
|
|
reverted_at DateTime?
|
|
snapshot Json @default("[]")
|
|
}
|