178 Commits

Author SHA1 Message Date
67c3dd9434 Update docker-compose.prod.yml 2026-05-22 10:27:25 -04:00
709d64553c Merge pull request '2026-May-Fixes' (#19) from 2026-May-Fixes into main
Reviewed-on: #19
2026-05-22 10:09:27 -04:00
1dc3353e6e feat: dynamic app version sourced from root VERSION file
Replaces the hardcoded version string in SettingsPage.tsx with a single
source of truth: a /VERSION file at the repo root. To cut a new release,
edit only that one file.

Frontend:
- vite.config.ts reads /VERSION at dev-server startup and injects it as
  the __APP_VERSION__ global via Vite's define mechanism (compile-time,
  zero runtime cost). Falls back to VITE_APP_VERSION env var for prod
  Docker builds that pass it as a build arg.
- vite-env.d.ts adds the TypeScript declaration for __APP_VERSION__.
- SettingsPage.tsx Badge now renders {__APP_VERSION__} instead of the
  literal string.

Backend:
- app.controller.ts reads /VERSION once at module load and includes
  "version" in both GET /api and GET /api/health responses.
- NewRelicTransactionInterceptor tags every NR transaction with
  newrelic.addCustomAttribute('appVersion', version) so releases can be
  compared in NRQL: SELECT average(duration) FROM Transaction WHERE
  appVersion = '2026.5.22'

Docker:
- docker-compose.yml mounts ./VERSION:/app/VERSION:ro in both backend
  and frontend dev containers.
- Production Dockerfiles include COPY VERSION ./ with a comment
  instructing CI to copy the root VERSION into each build context before
  docker build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:03:44 -04:00
922674eca4 fix: cash flow forecast drops investments purchased within the charted window
The Issue 2 fix made the opening investment balance point-in-time
(only CDs purchased before startYear-01-01), with a comment promising
that later purchases would be re-added "when their purchase month is
processed in the forecast loop" — but that loop code never existed.
The loop only ever subtracted maturing CDs, never added purchased ones.

Result: every CD bought during the charted window vanished from the
chart. For Pine Creek (all 5 CDs purchased in 2026) the operating
investment line showed $0 instead of $65,000 and reserve showed
$10,000 instead of $60,032.

Fix: build a purchaseIndex (mirroring maturityIndex) of investments
purchased on/after startYear-01-01, keyed by purchase year-month, and
credit each CD's value to the running investment balance in its
purchase month — applied before the historical/forecast branch so it
works for both actual and projected months.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:42:56 -04:00
ce3dc79e47 fix: resolve New Relic ghost traffic and blind APM transaction naming
Three root causes addressed:

1. nginx routing gap — bare GET /api (no trailing slash) fell through
   `location /api/` to the Vite dev proxy, which forwarded it to the
   backend as an unmatched path. Added `location = /api` exact-match
   block before the prefix block to catch it and proxy directly to
   the backend health handler.

2. AppController root handler — added @Get() (maps to GET /api with
   global prefix) so bare /api requests return a clean 200 instead of
   a 404 that registers as a phantom NR transaction.

3. New Relic transaction naming — NestJS's setGlobalPrefix('api')
   causes NR's Express instrumentation to bucket ALL requests into the
   generic "Expressjs/GET/api$" segment, making per-endpoint APM data
   completely useless. The new NewRelicTransactionInterceptor calls
   newrelic.setTransactionName() with "METHOD /route/pattern" for
   every request (after routing, so req.route is populated with the
   matched template). Gracefully no-ops in dev where NR is not loaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:27:31 -04:00
72161f81f5 fix: monthly actuals equity offset (Option A) + scenario activation creates accounts
Monthly Actuals — Option A:
- Replace operating cash account offset with per-fund equity account clearing
- Equity accounts 3000/3100 now absorb the net P&L from actuals entries
- Cash account is never touched by monthly actuals, eliminating the balance
  discrepancy that required manual cash adjustments
- Per-fund routing: operating income/expense clears to 3000, reserve to 3100
- Falls back gracefully if only one equity account exists

Scenario Activation (Issue 4):
- updateScenario now accepts userId and triggers materialisation when
  status transitions to 'active'
- Each pending scenario investment is created as a real investment_accounts
  record dated to its purchase_date (future dates are supported)
- Journal entries are posted at the purchase_date using the fund's primary
  cash account and equity offset (matching manual account creation)
- Rollover detection: if an existing active investment matures within 7 days
  of the new investment's purchase_date and shares the same fund_type, the
  system creates a maturity JE (proceeds → cash) and a reinvestment JE
  (cash → new CD) rather than a fresh cash deduction, then retires the
  source investment
- Per-investment failures are logged but do not abort the rest of the batch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:36:17 -04:00
ba072b90f0 fix: correct monthly actuals pre-population, void double-reversal, and cash flow history
- Monthly actuals grid now filters actual_amount to entry_type='monthly_actual'
  only, so other posted JEs in the same month don't bleed into the actuals UI
- Remove manual accounts.balance reversal from void() — the reversal JE's post()
  call already handles balance updates, preventing double-decrement per void
- Date void reversal entries to the original entry's date (not today) so
  historical monthly cash-flow periods stay accurate when actuals are re-edited
- Cash flow forecast now derives opening investment balances from investments
  purchased before the forecast start date rather than using current-snapshot
  totals, fixing historical months showing wrong investment balances

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:22:41 -04:00
4df796e977 Merge pull request 'feature-deploy-script' (#18) from feature-deploy-script into main
Reviewed-on: #18
2026-04-09 09:36:16 -04:00
a7e3f80eda Merge branch 'main' into feature-deploy-script 2026-04-09 09:36:06 -04:00
19bd19b0c4 docs: add Gitea Actions runner setup guide for production server
Step-by-step guide covering act_runner installation, registration,
host execution mode configuration, systemd service setup, and
troubleshooting for the HOALedgerIQ production deployment workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:35:49 -04:00
3e7463cf46 fix: replace curl with Docker health status and wget for health check
The health check used curl which is not installed on the prod server.
Replace with a dual approach:
1. Primary: check Docker's own container health status (already running
   via docker-compose.prod.yml healthcheck with wget inside container)
2. Secondary: wget from host as fallback signal

Also add diagnostic logging (container status + recent backend logs)
before triggering rollback on health check failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 09:22:28 -04:00
cefcc296fb Merge pull request 'fix: resolve unbound variable error in deploy script migration check' (#17) from feature-deploy-script into main
Reviewed-on: #17
2026-04-09 09:16:34 -04:00
2aad137bd7 fix: resolve unbound variable error in deploy script migration check
The APPLIED_MIGRATIONS associative array triggered "unbound variable"
under set -u when empty (first run / seed-existing). Fix by initializing
with =() and using a safe helper function with ${:-} default syntax.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 09:15:54 -04:00
f5bea7cdc2 Merge pull request 'fix: remove bc dependency from db-backup.sh format_size function' (#16) from feature-deploy-script into main
Reviewed-on: #16
2026-04-09 09:10:00 -04:00
e06ca74d1d fix: remove bc dependency from db-backup.sh format_size function
Replace bc-based floating point division with pure bash integer
arithmetic so the script works on minimal Ubuntu servers without
bc installed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 09:09:24 -04:00
5144da4680 Merge pull request 'feat: add production deploy script with auto-rollback and Gitea Actions workflow' (#15) from feature-deploy-script into main
Reviewed-on: #15
2026-04-09 09:06:54 -04:00
95c83a57b6 feat: add production deploy script with auto-rollback and Gitea Actions workflow
Add automated production deployment pipeline:
- scripts/deploy-prod.sh: Full deployment script with pre/post DB backups,
  migration tracking via shared.schema_migrations table, health checks,
  and automatic rollback on failure (restores DB, reverts code, rebuilds)
- .gitea/workflows/deploy.yml: Manual-trigger Gitea Actions workflow for
  intentional production deployments with optional --seed-existing flag
- scripts/db-backup.sh: Add --yes/-y flag to skip interactive confirmation
  prompts, enabling automated restore during rollback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 09:05:45 -04:00
83115c9b5c Merge pull request 'feat: add flexible capability-based RBAC with per-tenant customization' (#14) from feature-rbac into main
Reviewed-on: #14
2026-04-06 16:13:26 -04:00
c57dd3e155 Merge branch 'main' into feature-rbac 2026-04-06 16:13:17 -04:00
afe5633b0a Updating Version 2026-04-06 16:13:00 -04:00
43b10869f0 feat: add flexible capability-based RBAC with per-tenant customization
Introduces a capability layer on top of existing roles that controls
feature visibility and access. Capabilities follow an area.feature.action
taxonomy (~35 capabilities) with sensible defaults per role. Tenant admins
can customize via grant/revoke overrides stored in org settings JSONB.

Key changes:
- Add vice_president role to DB schema
- Backend: capability constants, resolution logic, CapabilityGuard (global),
  @RequireCapability decorator on all 16 tenant controllers
- Frontend: permission hooks (useCanEdit, useHasCapability), CapabilityGate
  component, sidebar filtering by capability, all 17 pages migrated from
  useIsReadOnly to capability-based checks
- New admin UI: /settings/permissions matrix page for per-tenant role
  customization with grant/revoke delta model
- GET /organizations/my-capabilities endpoint for capability refresh
- Validation of permissionOverrides in settings updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 15:28:14 -04:00
f76c67f51a Update Version 2026-04-05 09:16:51 -04:00
5fec296569 Merge pull request 'fix: normalize API URL to prevent duplicate /chat/completions path' (#13) from feature-shadowAI into main
Reviewed-on: #13
2026-04-05 08:19:26 -04:00
c981676bc7 Merge branch 'main' into feature-shadowAI 2026-04-05 08:19:15 -04:00
JoeBot
bd174fc22b fix: normalize API URL to prevent duplicate /chat/completions path
Users entering the full endpoint URL (e.g. https://openrouter.ai/api/v1/chat/completions)
caused a 404 because the code appended /chat/completions again. Now strips any trailing
/chat/completions before re-appending, and adds a hint in the UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 08:14:37 -04:00
827eef4f49 Merge pull request 'feat: add shadow AI benchmarking for admin model comparison' (#12) from feature-shadowAI into main
Reviewed-on: #12
2026-04-05 07:54:11 -04:00
JoeBot
4797669591 feat: add shadow AI benchmarking for admin model comparison
Add a new admin-only feature that allows the platform owner to benchmark
the production AI model against up to 2 alternate models (any OpenAI-compatible
API) using real tenant data, without impacting users.

Backend:
- Shared AI caller utility (ai-caller.ts) for OpenAI-compatible endpoints
- Shadow AI module with service, controller, and 3 entities
- 6 admin API endpoints for model config CRUD, run trigger, and history
- Auto-creates shadow_ai_models, shadow_runs, shadow_run_results tables
- Exposes health-scores and investment-planning prompt builders for reuse

Frontend:
- New admin page at /admin/shadow-ai with 3 tabs:
  - Model Configuration (production + 2 alternate slots)
  - Run Comparison (tenant select, feature select, side-by-side results)
  - History (filterable run log with detail drill-down)
- Full side-by-side output display with diff highlighting
- Sidebar navigation link for AI Benchmarking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 07:50:59 -04:00
629d112850 Merge pull request 'Add k6 load testing suite and CLAUDE.md' (#9) from claude/beautiful-gauss into main
Reviewed-on: #9
2026-04-02 17:42:35 -04:00
32506d6a2e Merge branch 'main' into claude/beautiful-gauss 2026-04-02 17:42:24 -04:00
9a60970837 Updated Version 2026-04-02 17:41:49 -04:00
1ade446187 Merge pull request 'ideation-feature' (#11) from ideation-feature into main
Reviewed-on: #11
2026-04-02 17:39:32 -04:00
JoeBot
d430b96b51 feat: add admin ideas management page with private notes
Adds a dedicated super admin page for managing idea submissions across
all tenants. Includes status summary cards, filterable/searchable table,
detail modal with status updates, and private admin notes for internal
tracking (sprint refs, thoughts, follow-ups). Notes are not visible to
tenant users.

- Database: admin_note column on shared.ideas (019 migration)
- Backend: PUT /admin/ideas/:id/note endpoint
- Frontend: AdminIdeasPage with table, filters, detail modal
- Sidebar: "Idea Submissions" nav link in admin sections
- Routing: /admin/ideas route under SuperAdminRoute guard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 17:35:30 -04:00
JoeBot
140cd7acb7 feat: add ideation feature with per-tenant toggle
Adds idea submission capability gated by a per-tenant feature flag.
Super admins can enable/disable ideation for specific tenants via the
admin tenant detail drawer. Users see a lightbulb icon in the header
when enabled, opening a modal to submit ideas (title + description).
Ideas are stored in shared schema for cross-tenant backlog querying.

- Database: shared.ideas table (018-ideas.sql migration)
- Backend: Ideas NestJS module (entity, service, controller)
- Admin API: GET /admin/ideas, PUT /admin/ideas/:id/status,
  PUT /admin/organizations/:id/settings
- Frontend: IdeaModal component, lightbulb ActionIcon in header
- Admin UI: Feature Toggles card with ideation Switch in drawer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 17:20:37 -04:00
2f6297ae68 fix: reserve fund health AI prompt uses planned dates instead of remaining life years
Remaining life years is documentation-only reference info. The board's
planned project date is the authoritative timeline for urgency assessment.
Updates data gathering, prompt construction, and system instructions to
base all urgency on target_year/target_month instead of remaining_life_years.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 14:28:37 -04:00
121b8138e3 fix: investment scenario detail blank screen and auto-renew refresh
Move useMemo hook above early returns to satisfy React Rules of Hooks,
fixing blank screen when navigating to scenario detail. Also re-fetch
scenario after projection updates so auto-renew renewal records appear
automatically without requiring manual navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 15:04:44 -04:00
2b331bb3ef feat: investment chart alignment, auto-renew records, fund transfers, capital planning report, and upcoming activities (v2026.3.24)
- Lock InvestmentTimeline and ProjectionChart to shared X axis range
- Auto-create renewal scenario_investments records when auto_renew is true
- Add fund transfer mechanism between asset accounts with journal entries
- Add Capital Planning Report (5-year forecast grouped by category)
- Add Upcoming Investment Activities dashboard card (maturities + planned purchases)
- Bump version to 2026.3.24

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 14:41:17 -04:00
ae856bfb2f Upload files to "load-tests" 2026-03-19 16:12:09 -04:00
31f8274b8d Upload files to "load-tests"
load test files
2026-03-19 16:11:32 -04:00
06bc0181f8 feat: add k6 load testing suite, NRQL query library, and CLAUDE.md
Add comprehensive load testing infrastructure:
- k6 auth-dashboard flow (login → profile → dashboard KPIs → widgets → refresh → logout)
- k6 CRUD flow (units, vendors, journal entries, payments, reports)
- Environment configs with staging/production/local thresholds
- Parameterized user pool CSV matching app roles
- New Relic NRQL query library (25+ queries for perf analysis)
- Empty baseline.json structure for all tested endpoints
- CLAUDE.md documenting full stack, auth, route map, and conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:49:22 -04:00
66e2f87a96 feat: UX enhancements, member limits, forecast fix, and menu cleanup (v2026.3.19)
- Onboarding wizard: add Reserve Account step between Operating and Assessments,
  redirect to Budget Planning on completion
- Dashboard: health score pending state shows clickable links to set up missing items
- Projects/Vendors: rich empty-state wizard screens with real-world examples and CTAs
- Investment Planning: auto-refresh AI recommendations when empty or stale (>30 days)
- Hide Invoices and Payments menus (see PARKING-LOT.md for re-enablement)
- Send welcome email via Resend when new members are added to a tenant
- Enforce 5-member limit for Starter/Standard/Professional plans (Enterprise unlimited)
- Cash flow forecast: only mark months as "Actual" when journal entries exist,
  fixing the issue where months without data showed as actuals
- Bump version to 2026.3.19

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 14:47:04 -04:00
db8b520009 fix: billing portal error, onboarding wizard improvements, budget empty state
- Fix "Manage Billing" button error for trial orgs without Stripe customer;
  add fallback to retrieve customer from subscription, show helpful message
  for trial users, and surface real error messages in the UI
- Add "Balance As-Of Date" field to onboarding wizard so opening balance
  journal entries use the correct statement date instead of today
- Add "Total Unit Count" field to onboarding wizard assessment group step
  so cash flow projections work immediately
- Remove broken budget upload step from onboarding wizard (was using legacy
  budgets endpoint); replace with guidance to use Budget Planning page
- Replace bare "No budget plan lines" text with rich onboarding-style card
  featuring download template and upload CSV action buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 09:43:49 -04:00
e2d72223c8 feat: add test data cleanup utility script
Interactive CLI for managing test organizations, users, and tenant schemas.
Supports list, delete-org, delete-user, purge-all, and reseed commands
with dry-run mode and safety guards for platform owner protection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 08:59:27 -04:00
a996208cb8 feat: add annual billing, free trial, upgrade/downgrade, and ACH invoice support
- Add monthly/annual billing toggle with 25% annual discount on pricing page
- Implement 14-day no-card free trial (server-side Stripe subscription creation)
- Enable upgrade/downgrade via Stripe Customer Portal
- Add admin-initiated ACH/invoice billing for enterprise customers
- Add billing card to Settings page with plan info and Manage Billing button
- Handle past_due status with read-only grace period access
- Add trial ending and trial expired email templates
- Add DB migration for billing_interval and collection_method columns
- Update ONBOARDING-AND-AUTH.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 08:04:51 -04:00
5845334454 fix: remove cash flow summary cards and restore area chart shading
Remove the 4 summary cards from the Cash Flow page as they don't
properly represent the story over time. Increase gradient opacity
on stacked area charts (cash flow and investment scenarios) from
0.3-0.4/0-0.05 to 0.6/0.15 for better visual shading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:41:13 -04:00
170461c359 Merge branch 'claude/reverent-moore' - Resend email integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:33:17 -04:00
aacec1cce3 feat: integrate Resend for transactional email delivery
Replace the stubbed email service with Resend API integration.
Emails are sent with branded HTML templates including activation,
welcome, payment failed, member invite, and password reset flows.

- Install resend@6.9.4 in backend
- Rewrite EmailService with Resend SDK + graceful fallback to
  stub mode when API key is not configured
- Add branded HTML email template with CTA buttons, preheader
  text, and fallback URL for all email types
- Add reply-to support (sales@hoaledgeriq.com in production)
- Track send status (sent/failed) in shared.email_log metadata
- Add RESEND_API_KEY, RESEND_FROM_ADDRESS, RESEND_REPLY_TO env
  vars to both docker-compose.yml and docker-compose.prod.yml
- Add sendPasswordResetEmail() method for future use

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:29:20 -04:00
6b12fcd7d7 Merge branch 'claude/reverent-moore' 2026-03-17 18:04:14 -04:00
8e58d04568 fix: add APP_URL and missing env vars to Docker Compose configs
APP_URL was never passed to the backend container, causing Stripe
checkout success_url to redirect to http://localhost instead of the
production domain. The prod overlay also completely replaced the base
environment block, dropping all Stripe, SSO, WebAuthn, and invite
token variables.

- Add APP_URL to base docker-compose.yml (default: http://localhost)
- Add all missing vars to docker-compose.prod.yml with production
  defaults (app.hoaledgeriq.com)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 17:51:34 -04:00
c2e52bee64 Merge pull request 'feat: enterprise pricing shows "Request Quote" linking to interest form' (#8) from claude/reverent-moore into main
Reviewed-on: #8
2026-03-17 07:53:16 -04:00
9cd641923d feat: enterprise pricing shows "Request Quote" linking to interest form
Enterprise plan no longer displays a fixed price. Instead it shows
"Request Quote" and the CTA opens the interest form on hoaledgeriq.com
in a new tab to capture leads for custom quotes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 07:47:19 -04:00