Sprint 6: Monthly actuals input, reconciliation, and file attachments

Add spreadsheet-style Monthly Actuals page for entering monthly actuals
against budget with auto-generated journal entries and reconciliation flag.
Add file attachment support (PDF, images, spreadsheets) on journal entries
for receipts and invoices. Enhance Budget vs Actual report with month
filter dropdown. Add reconciled badge to Transactions page. Replace bcrypt
with bcryptjs to fix Docker cross-platform native binding issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 11:48:57 -05:00
parent ea49b91bb3
commit 84822474f8
20 changed files with 9868 additions and 22 deletions

View File

@@ -72,7 +72,7 @@ export class TenantSchemaService {
reference_number VARCHAR(100),
entry_type VARCHAR(50) NOT NULL CHECK (entry_type IN (
'manual', 'assessment', 'payment', 'late_fee', 'transfer',
'adjustment', 'closing', 'opening_balance'
'adjustment', 'closing', 'opening_balance', 'monthly_actual'
)),
fiscal_period_id UUID NOT NULL REFERENCES "${s}".fiscal_periods(id),
source_type VARCHAR(50),
@@ -81,6 +81,7 @@ export class TenantSchemaService {
posted_by UUID,
posted_at TIMESTAMPTZ,
is_void BOOLEAN DEFAULT FALSE,
is_reconciled BOOLEAN DEFAULT FALSE,
voided_by UUID,
voided_at TIMESTAMPTZ,
void_reason TEXT,
@@ -313,7 +314,20 @@ export class TenantSchemaService {
updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
// Attachments (file storage for receipts/invoices)
`CREATE TABLE "${s}".attachments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
journal_entry_id UUID NOT NULL REFERENCES "${s}".journal_entries(id) ON DELETE CASCADE,
filename VARCHAR(255) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
file_size INTEGER NOT NULL,
file_data BYTEA NOT NULL,
uploaded_by UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
)`,
// Indexes
`CREATE INDEX "idx_${s}_att_je" ON "${s}".attachments(journal_entry_id)`,
`CREATE INDEX "idx_${s}_je_date" ON "${s}".journal_entries(entry_date)`,
`CREATE INDEX "idx_${s}_je_fiscal" ON "${s}".journal_entries(fiscal_period_id)`,
`CREATE INDEX "idx_${s}_jel_entry" ON "${s}".journal_entry_lines(journal_entry_id)`,