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>
- L2: Add server_tokens off to nginx configs to hide version
- M1: Add X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
Permissions-Policy headers to all nginx routes
- L3: Add global NoCacheInterceptor (Cache-Control: no-store) on all
API responses to prevent caching of sensitive financial data
- C1: Disable open registration by default (ALLOW_OPEN_REGISTRATION env)
- H3: Add logout endpoint with correct HTTP 200 status code
- M2: Implement full password reset flow (forgot-password, reset-password,
change-password) with hashed tokens, 15-min expiry, single-use
- Reduce JWT access token expiry from 24h to 1h
- Add EmailService stub (logs to shared.email_log)
- Add DB migration 016 for password_reset_tokens table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Refresh Recommendations now shows inline processing banner with
animated progress bar while keeping existing results visible (dimmed).
Auto-scrolls to AI section and shows titled notification on completion.
- Investment recommendations now auto-calculate purchase and maturity
dates from a configurable start date (defaults to today) in the
"Add to Plan" modal, so scenarios build projections immediately.
- Projection engine computes per-investment and total interest earned,
ROI percentage, and total principal invested. Summary cards on the
Investment Scenario detail page display these metrics prominently.
- Replaced dropdown action menu with inline Edit/Execute/Remove
icon buttons matching the assessment scenarios pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When adding a multi-stage investment strategy (e.g. CD ladder) from AI
recommendations to a board planning scenario, all component investments
are now created as separate rows instead of collapsing into a single
investment. The AI prompt now instructs inclusion of a components array,
the backend loops through components to create individual scenario
investments, and the frontend passes and displays component details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reorder sidebar: Assessment Scenarios now directly under Budget Planning
- Simplify special assessment form: remove Total Amount, keep Per Unit only
- Replace Duration field from free-text installments to dropdown (one-time/quarterly/6mo/annual)
- Update Change column display to show total per-unit with duration label
- Fix Reserve Coverage to use planned capital project costs instead of budget expenses
- Include capital_projects table in projection engine alongside projects table
- Replace actions dropdown menu with inline Edit/Remove icon buttons
- Remove Refresh Projection button (projection auto-refreshes on changes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix compound inflation: use Math.pow(1 + rate/100, yearsGap) instead of
flat rate so multi-year gaps (e.g., 2026→2029) compound annually
- Budget Planner: add CSV import flow + Download Template button; show proper
empty state when no base budget exists with Create/Import options
- Budget Manager: remove CSV import, Download Template, and Save buttons;
redirect users to Budget Planner when no budget exists for selected year
- Fix getAvailableYears to return null latestBudgetYear when no budgets exist
and include current year in year selector for fresh tenants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The accounts table uses 'name' not 'account_name'. Alias it correctly
in the getPlan JOIN query.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds budget planning capability under Board Planning, allowing HOA boards
to model future year budgets with configurable per-year inflation rates.
Backend:
- New budget_plans + budget_plan_lines tables (migration 014)
- BudgetPlanningService: CRUD, inflation generation (per-month preservation),
status workflow (planning → approved → ratified), ratify-to-official copy
- 8 new API endpoints on board-planning controller
- Projection engine (both board-planning and reports) now falls back to
planned budgets via UNION ALL query when no official budget exists
- Extended year range from 3 to dynamic based on projection months
Frontend:
- BudgetPlanningPage with monthly grid table (mirrors BudgetsPage pattern)
- Year selector, inflation rate control, status progression buttons
- Inline editing with save, confirmation modals for status changes
- Manual edit tracking with visual indicator
- Summary cards for income/expense totals
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The projected interest was extrapolating from sparse YTD journal entries,
producing inaccurate results early in the year. Now uses the same
rate-based est_monthly_interest calculation (from account balances and
investment rates) for remaining months, consistent with the dashboard KPI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dashboard Quick Stats:
- Create Capital Projects section with "Planned Capital Spend 2026"
- Fix Interest Earned YTD to pull from actual journal entries on
interest income accounts instead of unrealized investment gains
- Add Interest Earned YoY showing projected current year vs last year
actuals with percentage change badge
Monthly Actuals:
- Default to read-only view when actuals are already reconciled
- Show "Edit Actuals" button instead of "Save Actuals" for reconciled months
- Add confirmation modal warning that editing will void existing journal
entry before allowing edits
- New months without actuals open directly in edit mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The missing-data warning was checking the reserve_components table,
which users never populate. All reserve data lives in the projects
table. Now only warns when no reserve-funded projects exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The health score funded ratio was only reading from the reserve_components
table (replacement_cost), but users enter their reserve data on the
Projects page using estimated_cost. When reserve_components is empty,
the funded ratio now falls back to reserve-funded projects for:
- Total replacement cost (estimated_cost)
- Component funding status (current_fund_balance)
- Urgent components due within 5 years (remaining_life_years)
- AI prompt component detail lines
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing-data warning when reserve_components table is empty so
users see "No reserve components found" on the dashboard
- Change AI prompt to show "N/A" instead of "0.0%" for funded ratio
when no components exist, preventing misleading "0% funded" reports
- Instruct AI not to report 0% funded when data is simply missing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C1: Disable Swagger UI in production (env gate)
- M1+M2: Add Helmet.js for security headers (CSP, X-Frame-Options,
X-Content-Type-Options, Referrer-Policy) and remove X-Powered-By
- H2: Add @nestjs/throttler rate limiting (5 req/min on login/register)
- M4: Remove orgSchema from JWT payload and client-side storage;
tenant middleware now resolves schema from orgId via cached DB lookup
- L1: Fix Chatwoot user identification (read from auth store on ready)
- Remove schemaName from frontend Organization type and UI displays
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix budget save 500 error caused by three data mismatches between
frontend and backend: wrapped payload ({lines:[...]}) vs expected
raw array, snake_case vs camelCase field names (account_id vs
accountId), and dec_amt vs dec for December values.
Add read-only budget view as default for existing budgets with an
"Edit Budget" button to enter edit mode, and Cancel to discard
changes - reducing accidental edits.
Bump version to 2026.03.10 across all packages and settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous aggregation used simple SUM(debit)/SUM(credit) which
always produced equal values for balanced entries. This was misleading
for entries with income/expense lines (e.g., monthly actuals).
Now, when an entry has income/expense lines, the totals reflect only
P&L account activity (expenses as debits, income as credits), excluding
the cash offset. For balance-sheet-only entries (opening balances,
adjustments), the full entry totals are shown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The findAll query was missing SUM aggregations, so the frontend received
no total_debit/total_credit fields and fell back to displaying $0.00.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an existing user was added to a new organization via the member
management UI, the password entered in the form was silently ignored.
This caused the user to be unable to log in with the password they
were given, since the hash in the database was from their original
account creation for a different org.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace misleading 'sent' status with 'pending' (no email capability)
- Show assessment group name instead of raw 'regular_assessment' type
- Add owner last name to invoice table
- Fix payment creation Internal Server Error (PostgreSQL $2 type cast)
- Add edit/delete capability for payment records with invoice recalc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Assessment groups can now define billing frequency (monthly, quarterly,
annual) with configurable due months and due day. Invoice generation
respects each group's schedule - only generating invoices when the
selected month is a billing month for that group. Adds a generation
preview showing which groups will be billed, period tracking on
invoices, and billing period context in the payments UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make all AI endpoints (health scores + investment recommendations)
fire-and-forget: POST returns immediately, frontend polls for results
- Extend AI API timeout from 2-5 min to 10 min for both services
- Add "last analysis failed — showing cached data" message to the
Investment Recommendations panel (matches health score widgets)
- Add status/error_message columns to ai_recommendations table
- Remove nginx AI timeout overrides (no longer needed)
- Users can now navigate away during AI processing without interruption
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address 4 issues identified in AI feature audit:
1. Reduce temperature from 0.3 to 0.1 for health score calculations
to reduce 16-40 point score volatility across runs
2. Add explicit cash runway classification rules to operating prompt
preventing the model from rating sub-3-month runway as "positive"
3. Pre-compute total special assessment income in both operating and
reserve prompts, eliminating per-unit vs total confusion ($300
vs $20,100)
4. Make YTD budget comparison actuals-aware: only compare months with
posted journal entries, show current month budget separately, and
add prompt guidance about month-end posting cadence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix systemic LEFT JOIN date filter bug in Balance Sheet, Income Statement,
and Cash Flow Statement by using parenthesized INNER JOIN pattern so
SUM(jel.debit/credit) respects date parameters
- Add Current Year Net Income synthetic equity line to Balance Sheet to
satisfy the accounting equation (A = L + E) during open fiscal periods
- Add investment_accounts balances to Balance Sheet assets and corresponding
equity lines for reserve/operating investment holdings
- Fix Cash Flow Statement beginning/ending cash always showing $0 by
replacing LIKE '%Cash%' filter with account_type = 'asset'
- Fix Year-End Package HTTP 500 by replacing broken invoices.vendor_id
query with journal-entry-based vendor payment lookup
- Fix Quarterly Report defaulting to previous quarter instead of current
- Fix Quarterly Report date subtitle off-by-one day from UTC parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Health Scores — separate operating/reserve refresh
- Added POST /health-scores/calculate/operating and /calculate/reserve
- Each health card now has its own Refresh button
- On failure, shows cached (last good) data with "last analysis failed"
watermark instead of blank "Error calculating score"
- Backend getLatestScores returns latest complete score + failure flag
2. Investment Planning — increased AI timeout to 5 minutes
- Backend callAI timeout: 180s → 300s
- Frontend axios timeout: set explicitly to 300s (was browser default)
- Host nginx proxy_read_timeout: 180s → 300s
- Loading message updated to reflect longer wait times
3. Capital Planning — Unscheduled column moved to rightmost position
- Kanban column order: current year → future → unscheduled (was leftmost)
- Puts immediate/near-term projects front and center
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Empty string date values from the frontend were being passed directly
to PostgreSQL, which cannot cast "" to DATE. Normalize empty strings
to null for all date columns in the update method and the dedicated
updatePlannedDate endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of 502 errors under 30 concurrent users: the production server
was running dev-mode infrastructure (Vite dev server, NestJS --watch,
no DB connection pooling, single Node.js process).
Changes:
- backend/Dockerfile: multi-stage prod build (compiled JS, no devDeps)
- frontend/Dockerfile: multi-stage prod build (static assets served by nginx)
- frontend/nginx.conf: SPA routing config for frontend container
- docker-compose.prod.yml: production overlay with tuned Postgres, memory
limits, health checks, restart policies
- nginx/production.conf: keepalive upstreams, proxy buffering, rate limiting
- backend/src/main.ts: Node.js clustering (1 worker per CPU, up to 4),
conditional request logging, production CORS
- backend/src/app.module.ts: TypeORM connection pool (max 30, min 5)
- docs/DEPLOYMENT.md: new Production Deployment section
Deploy with: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects imported via CSV that lack a target_year were invisible in Capital
Planning because findForPlanning() filtered on target_year IS NOT NULL. This
removes that filter and adds an "Unscheduled" Kanban column (orange background,
2-col layout) so users can drag unscheduled projects into year buckets.
Also bumps app version to 2026.3.2 (beta).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reserve fund analysis now includes 12-month forward projection with
special assessment income (by frequency), monthly budget data,
capital project costs, and investment maturities. AI prompt updated
to evaluate projected reserve liquidity and timing risks.
Both health score dashboard cards now show a subtle "Last updated"
timestamp at the bottom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The operating health score now includes forward-looking cash flow
projections using monthly budget data, assessment income schedules,
and operating project costs. AI prompt updated to evaluate projected
liquidity, timing risks, and year-end cash position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add daily AI health score calculation (0-100) for both operating and
reserve funds. Scores include trajectory tracking, factor analysis,
recommendations, and data readiness checks. Dashboard displays
graphical RingProgress gauges with color-coded scores, trend
indicators, and expandable detail popovers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The global WriteAccessGuard was checking req.user.role, but req.user is
set by JwtAuthGuard (a per-controller guard) which runs AFTER global guards.
TenantMiddleware sets req.userRole from the JWT before guards execute,
so we now check that property first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add global WriteAccessGuard that blocks POST/PUT/PATCH/DELETE for viewer role
- Add @AllowViewer() decorator for endpoints viewers need (switch-org, intro-seen, AI recommendations)
- Add useIsReadOnly hook to auth store for frontend role checks
- Hide write UI (add/edit/delete/import buttons, inline editors) in all 13 data pages for viewers
- Disable inline NumberInputs on Budgets and Monthly Actuals pages for viewers
- Skip onboarding wizard for viewer role users
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dashboard: Remove tenant name/role subtitle
- Cash Flow: Replace Operating/Reserve net cards with inflow vs outflow
breakdown showing In/Out amounts and signed net; replace Ending Cash
card with AI Financial Health status from saved recommendation
- Accounts: Auto-set first asset account per fund_type as primary on creation
- Investments: Add 5th summary card for projected annual interest earnings
- Sankey: Add Actuals/Budget/Forecast data source toggle and
All Funds/Operating/Reserve fund filter SegmentedControls with
backend support for budget-based and forecast (actuals+budget) queries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Feature 1 - How-To Intro Tour (react-joyride):
- 8-step guided walkthrough highlighting Dashboard, Accounts, Assessments,
Transactions, Budgets, Reports, and AI Investment Planning
- Runs automatically on first login, tracked via has_seen_intro flag on user
- Centralized step config in config/tourSteps.ts for easy text editing
- data-tour attributes on Sidebar nav items and Dashboard for targeting
Feature 2 - Tenant Onboarding Wizard:
- 3-step modal wizard: create operating account, assessment group + units,
import budget CSV
- Runs after tour completes, tracked via onboardingComplete in org settings JSONB
- Reuses existing API endpoints (POST /accounts, /assessment-groups, /units,
/budgets/:year/import)
Backend changes:
- Add has_seen_intro column to shared.users + migration
- Add PATCH /auth/intro-seen endpoint to mark tour complete
- Add PATCH /organizations/settings endpoint for org settings updates
- Include hasSeenIntro in login response, settings in switch-org response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap account interest query in subquery to avoid SUM(SUM(...)) nesting
- Replace nonexistent interest_earned column with current_value - principal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Accounts page: include investment accounts in Est. Monthly Interest calc,
add Fund column to investment table, split summary cards into Operating/Reserve
- Fix Cash Flow: ending balance now respects includeInvestments toggle
- Fix Budget Manager: separate operating/reserve income in summary cards
- Fix Projects: default sort by planned_date instead of name
- Add Vendors: last_negotiated date field with migration, CSV import/export
- New Quarterly Financial Report: budget vs actuals, over-budget flagging, YTD
- Enhance Dashboard: separate Operating/Reserve fund cards, expanded Quick Stats
with monthly interest, YTD interest earned, planned capital spend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rate fetcher now scrapes CD, Money Market, and High Yield Savings rates
from Bankrate.com with pauses between fetches to avoid rate limiting
- Historical rate data is preserved (no longer deleted on each fetch)
- Database migration adds rate_type column and tenant ai_recommendations table
- Backend returns market rates grouped by type with latest-batch-only queries
- AI prompt now includes all three rate types for comprehensive analysis
- AI recommendations are saved per-tenant for retrieval on page load
- Frontend: "Market CD Rates" replaced with "Today's Market Rates" tabbed view
- Rates section is collapsible (expanded by default) to save screen space
- Saved recommendations load automatically with "Last Updated" timestamp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enhancement 1 - Block suspended/archived org access:
- Add org status check in switchOrganization() (auth.service.ts)
- Filter suspended/archived orgs from login response (generateTokenResponse)
- Add org status guard with 60s cache in TenantMiddleware
- Frontend: filter orgs in SelectOrgPage, add 403 handler in api.ts
Enhancement 2 - Change tenant plan level:
- Add updatePlanLevel() to organizations.service.ts
- Add PUT /admin/organizations/:id/plan endpoint
- Frontend: clickable plan dropdown in Organizations table + confirmation modal
- Plan level Select in tenant detail drawer
Enhancement 3 - User impersonation:
- Add impersonateUser() to auth.service.ts with impersonatedBy JWT claim
- Add POST /admin/impersonate/:userId endpoint
- Frontend: Impersonate button in Users tab (disabled for admins)
- Impersonation state management in authStore (start/stop/persist)
- Orange impersonation banner in AppLayout header with stop button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TypeORM needs explicit type: 'varchar' for nullable string columns
to avoid reflecting the union type as Object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Database: Add login_history, ai_recommendation_log tables; is_platform_owner
column on users; subscription fields on organizations (payment_date,
confirmation_number, renewal_date)
- Backend: New AdminAnalyticsService with platform metrics, tenant detail, and
health score calculations (0-100 based on activity, budget, transactions,
members, AI usage)
- Backend: Login/org-switch now records to login_history; AI recommendations
logged to ai_recommendation_log; platform owner protected from superadmin toggle
- Frontend: 4-tab admin panel (Dashboard, Organizations, Users, Tenant Health)
with tenant detail drawer, subscription management, health scoring visualization
- Platform owner account (admin@hoaledgeriq.com) auto-redirects to admin panel
- Seed data includes platform owner account and sample login history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI was making incorrect liquidity warnings because it only saw
current cash positions and annual budget totals — it had no visibility
into the month-by-month cash flow forecast that includes:
- Special assessment collections (reserve fund income from homeowners)
- Monthly budget income/expense breakdown (not just annual totals)
- Investment maturity schedule (when CDs return cash)
- Capital project expense timing
Now the AI receives:
1. Full assessment schedule (regular + special, with frequency)
2. 12-month forward forecast with projected operating/reserve cash,
investment balances, and per-month income/expense drivers
3. Updated system prompt instructing the AI to use the forecast for
liquidity analysis rather than just current balances
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add toggle-able debug logging (AI_DEBUG env var) that logs prompts,
request metadata, raw responses, parsed output, and full error chains
- Replace Node.js native fetch() with https module for Docker Alpine
compatibility (fixes "fetch failed" error with large payloads)
- Reduce max_tokens from 16384 to 4096 (qwen3.5 doesn't need thinking
token budget)
- Strip <think> blocks from model responses
- Add AI_DEBUG to docker-compose.yml and .env.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kimi-k2.5 is a thinking model that times out on complex prompts (>3min).
qwen3.5-397b-a17b responds in ~2s with clean JSON output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>