84 Commits

Author SHA1 Message Date
grabowski b4338c6814 Link source expense/invoice from account transaction rows
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 35s
Each transaction that was posted from an expense or invoice now shows
an 'Open expense →' or 'Open invoice →' link to the source record's
detail page. The sourceExpenseId/sourceInvoiceId fields were already
loaded — just needed the UI affordance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:10:07 +07:00
grabowski 06ae314b3c Convert project list and detail spent to base currency via FX rate
Deploy to LXC / deploy (push) Successful in 1m57s
Validate / validate (push) Successful in 38s
Both the projects list and the project detail stats (totalApproved,
totalPending) now left-join companyAccounts and multiply expense
amounts by fxRateToBase before summing, matching the overview and
budget page fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:03:08 +07:00
grabowski c570019fd8 Convert report amounts to base currency; add expense void action
Deploy to LXC / deploy (push) Successful in 1m57s
Validate / validate (push) Successful in 34s
Reports: all three aggregations (byCategory, byProject, byMonth)
left-join companyAccounts and multiply expense amounts by
fxRateToBase before summing, so USD expenses show correctly.

Expenses: new 'voided' status on expenseStatusEnum with voidedAt +
voidReason columns. Void button on the detail page (admin/manager/
accountant) requires a reason, reverses the ledger entry, and writes
an 'expense_voided' audit log entry. Status badge shows
strikethrough red.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 16:56:03 +07:00
grabowski 8ef2ef7465 Convert per-project spent to base currency via account FX rate
Deploy to LXC / deploy (push) Successful in 2m2s
Validate / validate (push) Successful in 36s
Both the overview and budget page queries now multiply each approved
expense amount by its account's fxRateToBase before summing. A -$434
USD expense on a USD account (rate 34.5) now contributes -14,973 THB
to the total, not -434. Expenses with no account fall back to rate 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 16:33:51 +07:00
grabowski ef6ba485d3 Auto-detect expense currency from the selected account
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 34s
On create: if accountId is set, use that account's currency; else fall
back to the company's base currency (not hardcoded THB).
On update: if account changes, also update the expense currency to
match, so the ledger entry posts in the right currency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 16:24:04 +07:00
grabowski e216a393e4 Use MutationObserver to constrain dynamically-rendered date inputs
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 29s
Previous afterNavigate hook missed inputs inside conditional blocks
that appear after user interaction (e.g. the "+ New Expense" form).
Replaced with a MutationObserver on document.body that catches every
<input type="date"> as it's added to the DOM and sets min/max.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 16:20:50 +07:00
grabowski 6d0fb30545 Constrain date inputs to 4-digit years (fixes yyyyyy-mm-dd display)
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 33s
Chrome/Blink renders <input type="date"> with a 6-digit year field
unless min/max attributes restrict the range. Added a root-layout
hook that auto-sets min=1900-01-01, max=2100-12-31 on every date
input on mount and after navigation — no need to edit 19 form files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 16:16:18 +07:00
grabowski 8376116765 Auto-resolve FX rate on account creation (base=1, else fetch from API)
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 37s
Account creation no longer requires the user to enter an FX rate.
On create:
  - If account currency == company base → fxRateToBase = 1
  - Otherwise → fetchRate(accountCurrency, baseCurrency) from the
    fawazahmed0 FX API (same helper the daily scheduler uses)
  - Fallback to 1 if API call fails
The manual override field is still shown on the edit form for admin
corrections, and the daily scheduler keeps it fresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 15:57:08 +07:00
grabowski 7367aa9572 Add '+ New Package' button to expense detail packages section
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 35s
Visible whenever the user can manage. Always routes to the package
creation page. The link-existing dropdown still shows when other
packages exist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:21:57 +07:00
grabowski 7465b498e0 Move invoice upload + package linking to the expense detail page
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 34s
List page expenses now show a "View details →" link that routes to
the detail page. The detail page gains:
  - Invoice file upload (with Paperless push if configured)
  - Paperless URL link field
  - Link / unlink packages to the expense (many-to-many)

Same actions exist on both pages for convenience, but the detail page
is the primary workspace for managing an expense.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:31:47 +07:00
grabowski 7fba11941f Fix double-counting: available = total (expenses already debit accounts)
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 35s
The previous Remaining Budget card subtracted approved expenses from
the account balance sum — but postExpenseTransaction already posts
negative-amount rows to the ledger, so the balance sum already reflects
them. Replaced with:
  - Available Cash (= sum of account balances)
  - Allocated (with % progress bar)
  - Unallocated (cash not assigned to any project)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:22:16 +07:00
grabowski 94e38aca9c Redesign overview: income vs expenses split with net-position card
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 36s
Hero row is now a two-column green/red split showing Income and
Expenses side-by-side, with a full-width Net Position card below that
colours green or red based on the sign. Budget KPIs (Remaining,
Total, Allocated) moved to a secondary row underneath.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:11:13 +07:00
grabowski 00b8b239e0 Add expense detail page with edit (audit-logged) and clickable rows on projects
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 33s
New expense detail at /companies/[id]/expenses/[expenseId] with full
info, edit form for admin/manager/accountant, and audit log entry on
every edit (`expense_updated`). Project view expense rows are now
clickable and navigate to the detail page. Ledger re-posts
automatically if an approved expense's amount or account changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:05:17 +07:00
grabowski 26945285eb Show base-currency equivalent next to foreign-currency account balances
Deploy to LXC / deploy (push) Successful in 1m57s
Validate / validate (push) Successful in 34s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:55:37 +07:00
grabowski 2540a7603e Add Sales tab to primary company nav (admin/manager/accountant)
Deploy to LXC / deploy (push) Successful in 1m57s
Validate / validate (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:49:20 +07:00
grabowski 0795d78bdf Add confirmed-sales income to budget and overview
Budget page load now computes per-project income (net of withholding)
from confirmed sales. Overview has a full-width Income KPI showing
total confirmed net revenue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:48:06 +07:00
grabowski 1c15cbc36e Add sales CRUD with line items, taxes, withholding, and package linking
New routes: /companies/[id]/sales (list + create) and [saleId] (detail).
Per-line tax rate, single withholding % on sale. Computed totals:
subtotal, tax, gross, withholding, net receivable. Status flow:
draft → confirmed → voided. Packages linked via sale_packages junction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:46:05 +07:00
grabowski f56d4caec8 Link multiple packages to expenses via junction table
Added linkPackage/unlinkPackage actions and a collapsible package
checklist per expense. Linked packages display as clickable cyan chips
on the expense row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:56:39 +07:00
grabowski 34aab722b4 Add expense invoice upload with Paperless push + paperless URL link
Expenses now show Pending Invoice badge when no file/link attached.
Upload action saves file via existing uploads helper, optionally
pushes to Paperless-ngx if PAPERLESS_URL + PAPERLESS_TOKEN env set.
Download endpoint serves attached invoice with attachment disposition.
Paperless URL link provides a zero-integration alternative.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:54:13 +07:00
grabowski bbfab9faaa Add expense invoice fields, sales tables, and Paperless env vars
Expenses now have invoiceFileUrl, invoiceFileName, paperlessUrl,
paperlessDocumentId for supplier invoice attachment.

New expense_packages junction links expenses to multiple packages.

New sales + sale_line_items + sale_packages tables for income tracking
with per-line tax rate and per-sale withholding rate.

Added saleStatusEnum and 4 audit events: expense_invoice_uploaded,
sale_created, sale_confirmed, sale_voided.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:51:18 +07:00
grabowski 84c8beca15 Auto-refresh FX rates daily from fawazahmed0/exchange-api
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 33s
Scheduler checks every 15min; if 24h since last FX refresh, fetches
rates for all foreign-currency accounts and updates fxRateToBase.
Uses CDN primary + Cloudflare fallback with 10s timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:36:17 +07:00
grabowski 34b1524d3a Add FX rate per account, convert foreign balances to base currency in budget
Deploy to LXC / deploy (push) Successful in 1m55s
Validate / validate (push) Successful in 34s
Accounts now have fxRateToBase (default 1.0). The budget total query
multiplies each transaction by the account's rate, so a USD account
with rate 34.5 contributes correctly to the THB-denominated budget.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:24:00 +07:00
grabowski bc0699a992 Derive total budget from account balances instead of manual field
Deploy to LXC / deploy (push) Successful in 1m55s
Validate / validate (push) Successful in 38s
Total budget is now sum(account transaction amounts) across all
non-deleted accounts. Removed the manual 'Add Budget' action and form.
Budget page is now read-only for the total; allocations still work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:16:05 +07:00
grabowski 283f0d4dd1 Add invoice linking on expenses: optional FK, dropdown on add form, clickable chip
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:46:20 +07:00
grabowski 0710d63cc1 Add inline expense form on expenses tab with company-wide (General) option
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:26:18 +07:00
grabowski 5ff4f07ff4 Add invoice void with ledger reversal, required reason, and voided badge
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 48s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:18:35 +07:00
grabowski 0906a448b3 Add procedure instance detail with step completion and auto-complete
Deploy to LXC / deploy (push) Successful in 1m55s
Validate / validate (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:58:10 +07:00
grabowski 65cee9855c Add procedures templates, step management, and nav tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:51:29 +07:00
grabowski f1dd6877f6 Add procedures schema: templates, steps, instances, 7 audit events
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:47:36 +07:00
grabowski 8a23a849da Fix CSP: allow unsafe-inline scripts for SvelteKit hydration
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:32:12 +07:00
grabowski b4eda2d553 Fix security audit findings: auth scoping, OIDC hardening, CSP, file download
Deploy to LXC / deploy (push) Successful in 1m56s
Validate / validate (push) Successful in 33s
C3: Budget allocation now verifies project belongs to company
M4: Expense approve/reject scoped by company via project join
H2: OIDC cookies get secure flag on HTTPS
H3: OIDC auto-link only when email_verified by provider
H4: Content-Security-Policy + X-Content-Type-Options in hooks
M7: SSRF favicon redirect depth capped at 3
M2: File downloads use attachment disposition (not inline)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:18:28 +07:00
grabowski dbfd229ba8 Link service accounts to recurring bills with dropdown and display chip
Deploy to LXC / deploy (push) Successful in 2m1s
Validate / validate (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:58:43 +07:00
grabowski 1ce614186d Add service accounts page with CRUD, filter pills, and nav tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:52:33 +07:00
grabowski 493ffa4097 Add service accounts schema, enum, audit events, recurringBills FK
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:49:16 +07:00
grabowski a1fffebbf6 Add CI/CD deploy setup doc
Validate / validate (push) Successful in 31s
Deploy to LXC / deploy (push) Successful in 1m55s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:52:46 +07:00
grabowski 1fed8ee920 Add Gitea Actions deploy workflow
Mirrors the buildfor_life_repair workflow: SSH into LXC, reset working
tree, pull, npm ci, build, db:push, restart the buildfor-life-budget
systemd service, health-check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:49:33 +07:00
grabowski 639c261995 Regenerate deployment architecture diagram via beautiful-mermaid
Validate / validate (push) Successful in 30s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:42:08 +07:00
grabowski 5451a591ad Add deployment doc: Caddy + Tor + Yggdrasil + NetBird + external TLS proxy
Validate / validate (push) Successful in 32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:36:49 +07:00
grabowski fef69b653c Add inline rename/edit on project detail page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:09:52 +07:00
grabowski 57f3d42133 Redesign company overview: 4 compact KPIs, side-by-side projects + recent expenses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:06:19 +07:00
grabowski f51e156539 Restructure company nav: 8 primary tabs + HR/Ops/Admin dropdowns with active highlight
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:59:25 +07:00
grabowski 03526ff3b9 Restore pointer cursor on buttons (Tailwind v4 Preflight reset)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:35:46 +07:00
grabowski b43924f527 Add recurring bills UI with full CRUD, filters, overdue highlight, amount override
Validate / validate (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:23:53 +07:00
grabowski b611207d25 Add recurring bills poster, scheduler boot, and manual run stub
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:17:38 +07:00
grabowski bd87cd09f5 Add recurring bills schema and cycle math helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:13:38 +07:00
grabowski 70bb5954a0 Make entire account card clickable to open detail
Stretched-link pattern: absolute-positioned overlay anchor covers the
card; action controls (edit/archive/delete + inline forms) get
`relative z-10` so they float above and stay clickable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:25:03 +07:00
grabowski c1a575241f Fix zero balance on accounts list page
The correlated subquery in the SELECT was returning 0 for every row.
Replaced with a separate grouped-sum query joined in JS — same data, more
reliable SQL generation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:17:33 +07:00
grabowski 77c5d72e43 Reconciliation link, account CSVs in export, drop legacy bank/card tables
Validate / validate (push) Successful in 31s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 14:06:53 +07:00
grabowski 0d4fdb6fd7 Add account detail page with transaction history, filters, and CSV export
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:58:44 +07:00
grabowski 3a095851e9 Auto-post expenses and invoice payments to accounts ledger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:04:15 +07:00