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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Three new CSVs in the export bundle, with matching README entries:
- company_bank_accounts.csv
- company_cards.csv (last4 only, joined with linked bank account name)
- company_addresses.csv
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Visible to admin, manager, and accountant. Placed between
Integrations and Export so the broader-audience tab appears first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New /companies/[id]/profile route. Single page, three sections:
Bank Accounts:
- Table view with masked account numbers (••••1234)
- Add form (always-on inline) with bank, account holder, number,
type, branch, SWIFT/BIC, IBAN, currency, primary toggle, notes
- Inline edit row (admin only) and Set Primary / Remove actions
- Audit log: bank_account_added/updated/removed
Cards:
- Add form gates input to last 4 digits (maxlength=4 + regex)
- Amber warning: "Last 4 digits only — never enter full PAN"
- Optional link to a bank account
- Brand select (Visa/Mastercard/Amex/JCB/UnionPay/Discover/Other)
- Audit log: card_added/removed (no edit — remove + re-add)
Addresses:
- Type enum (Legal/Shipping/Billing/Other) with type-coloured badges
- Grouped by type, default flag scoped per type
- Full Thai address fields plus contact person/phone
- Inline edit per row, Set Default per type, Remove
- Audit log: address_added/updated/removed
Read access: admin/manager/accountant. Edit: admin only.
All forms use enhance with reset:false to preserve state on error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>