feat(finance): Phase 4 — Tags

- tags table: name, color; transaction_tags junction table
- GET/POST /api/tags; DELETE /api/tags/:id
- POST/DELETE /api/transactions/:id/tags for per-transaction tagging
- Bulk tag/untag via /api/transactions/bulk (action: tag/untag)
- Tags returned inline with transaction list via LATERAL join
- Tag filter on Transactions page
- Bulk "Tag as..." in bulk action bar
- Tag pills + "+" picker on each transaction row
- /tags page: create with color picker, list with counts, delete
This commit is contained in:
2026-03-08 16:28:03 +11:00
parent 35a5be97b0
commit 93450f7caa
11 changed files with 770 additions and 21 deletions
+13
View File
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
color TEXT NOT NULL DEFAULT '#6366f1',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS transaction_tags (
transaction_id INTEGER NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (transaction_id, tag_id)
);
+50
View File
@@ -15,3 +15,53 @@ model transaction_overrides {
notes String?
updated_at DateTime @default(now()) @updatedAt
}
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[]
}
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 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])
}