Implement Phase 2 features: roles, assessment groups, budget import, Kanban

- Add hierarchical roles: SuperUser Admin (is_superadmin flag), Tenant Admin,
  Tenant User with separate /admin route and admin panel
- Add Assessment Groups module for property type-based assessment rates
  (SFHs, Condos, Estate Lots with different regular/special rates)
- Enhance Chart of Accounts: initial balance on create (with journal entry),
  archive/restore accounts, edit all fields including account number & fund type
- Add Budget CSV import with downloadable template and account mapping
- Add Capital Projects Kanban board with drag-and-drop between year columns,
  table/kanban view toggle, and PDF export via browser print
- Update seed data with assessment groups, second test user, superadmin flag
- Create repeatable reseed.sh script for clean database population
- Fix AgingReportPage Mantine v7 Table prop compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 14:28:46 -05:00
parent e0272f9d8a
commit 01502e07bc
29 changed files with 1792 additions and 142 deletions

View File

@@ -48,14 +48,15 @@ BEGIN
-- Check if user exists
SELECT id INTO v_user_id FROM shared.users WHERE email = 'admin@sunrisevalley.org';
IF v_user_id IS NULL THEN
INSERT INTO shared.users (id, email, password_hash, first_name, last_name)
INSERT INTO shared.users (id, email, password_hash, first_name, last_name, is_superadmin)
VALUES (
uuid_generate_v4(),
'admin@sunrisevalley.org',
-- bcrypt hash of 'password123'
'$2b$10$1mtM00QBNQpAsyopajk3BeFY5DdxksvRYuM1E8qB.ePjCIYkfHMHO',
'Sarah',
'Johnson'
'Johnson',
true
) RETURNING id INTO v_user_id;
END IF;
@@ -78,6 +79,26 @@ IF v_org_id IS NULL THEN
VALUES (v_user_id, v_org_id, 'president');
END IF;
-- Create a second test user (viewer/homeowner)
DECLARE v_viewer_id UUID;
BEGIN
SELECT id INTO v_viewer_id FROM shared.users WHERE email = 'viewer@sunrisevalley.org';
IF v_viewer_id IS NULL THEN
INSERT INTO shared.users (id, email, password_hash, first_name, last_name, is_superadmin)
VALUES (
uuid_generate_v4(),
'viewer@sunrisevalley.org',
'$2b$10$1mtM00QBNQpAsyopajk3BeFY5DdxksvRYuM1E8qB.ePjCIYkfHMHO',
'Mike',
'Resident',
false
) RETURNING id INTO v_viewer_id;
INSERT INTO shared.user_organizations (user_id, organization_id, role)
VALUES (v_viewer_id, v_org_id, 'homeowner');
END IF;
END;
-- ============================================================
-- 2. Create tenant schema (if not exists)
-- ============================================================
@@ -147,6 +168,19 @@ CREATE TABLE IF NOT EXISTS %I.journal_entry_lines (
memo TEXT
)', v_schema);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.assessment_groups (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
regular_assessment DECIMAL(10,2) NOT NULL DEFAULT 0.00,
special_assessment DECIMAL(10,2) DEFAULT 0.00,
unit_count INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)', v_schema);
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.units (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
@@ -164,6 +198,7 @@ CREATE TABLE IF NOT EXISTS %I.units (
owner_phone VARCHAR(20),
is_rented BOOLEAN DEFAULT FALSE,
monthly_assessment DECIMAL(10,2),
assessment_group_id UUID,
status VARCHAR(20) DEFAULT ''active'',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
@@ -314,6 +349,7 @@ EXECUTE format('DELETE FROM %I.investment_accounts', v_schema);
EXECUTE format('DELETE FROM %I.reserve_components', v_schema);
EXECUTE format('DELETE FROM %I.vendors', v_schema);
EXECUTE format('DELETE FROM %I.units', v_schema);
EXECUTE format('DELETE FROM %I.assessment_groups', v_schema);
EXECUTE format('DELETE FROM %I.fiscal_periods', v_schema);
EXECUTE format('DELETE FROM %I.accounts', v_schema);
@@ -376,6 +412,15 @@ FOR v_month IN 1..12 LOOP
USING v_year - 1, v_month, 'closed';
END LOOP;
-- ============================================================
-- 4b. Seed Assessment Groups
-- ============================================================
EXECUTE format('INSERT INTO %I.assessment_groups (name, description, regular_assessment, special_assessment, unit_count) VALUES
(''Single Family Homes'', ''Standard single family detached homes (Units 1-20)'', 350.00, 0.00, 20),
(''Patio Homes'', ''Medium-sized patio homes (Units 21-35)'', 425.00, 0.00, 15),
(''Estate Lots'', ''Large estate lots (Units 36-50)'', 500.00, 75.00, 15)
', v_schema);
-- ============================================================
-- 5. Seed 50 units
-- ============================================================
@@ -394,17 +439,26 @@ DECLARE
'Mitchell','Carter','Roberts'];
v_unit_num INT;
v_assess NUMERIC;
v_ag_id UUID;
v_ag_name TEXT;
BEGIN
FOR v_unit_num IN 1..50 LOOP
-- Vary assessment based on unit size
v_assess := CASE
WHEN v_unit_num <= 20 THEN 350.00 -- standard
WHEN v_unit_num <= 35 THEN 425.00 -- medium
ELSE 500.00 -- large
END;
-- Vary assessment based on unit size and assign assessment group
IF v_unit_num <= 20 THEN
v_assess := 350.00;
v_ag_name := 'Single Family Homes';
ELSIF v_unit_num <= 35 THEN
v_assess := 425.00;
v_ag_name := 'Patio Homes';
ELSE
v_assess := 500.00;
v_ag_name := 'Estate Lots';
END IF;
EXECUTE format('INSERT INTO %I.units (unit_number, address_line1, city, state, zip_code, owner_name, owner_email, owner_phone, monthly_assessment, square_footage)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)', v_schema)
EXECUTE format('SELECT id FROM %I.assessment_groups WHERE name = $1', v_schema) INTO v_ag_id USING v_ag_name;
EXECUTE format('INSERT INTO %I.units (unit_number, address_line1, city, state, zip_code, owner_name, owner_email, owner_phone, monthly_assessment, square_footage, assessment_group_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)', v_schema)
USING
LPAD(v_unit_num::TEXT, 3, '0'),
(100 + v_unit_num * 2)::TEXT || ' Sunrise Valley Drive',
@@ -413,7 +467,8 @@ BEGIN
LOWER(v_first_names[v_unit_num]) || '.' || LOWER(v_last_names[v_unit_num]) || '@email.com',
'(480) 555-' || LPAD((1000 + v_unit_num)::TEXT, 4, '0'),
v_assess,
CASE WHEN v_unit_num <= 20 THEN 1200 WHEN v_unit_num <= 35 THEN 1600 ELSE 2000 END;
CASE WHEN v_unit_num <= 20 THEN 1200 WHEN v_unit_num <= 35 THEN 1600 ELSE 2000 END,
v_ag_id;
END LOOP;
END;
@@ -779,6 +834,7 @@ EXECUTE format('INSERT INTO %I.capital_projects (name, description, estimated_co
', v_schema) USING v_year;
RAISE NOTICE 'Seed data created successfully for Sunrise Valley HOA!';
RAISE NOTICE 'Login: admin@sunrisevalley.org / password123';
RAISE NOTICE 'Admin Login: admin@sunrisevalley.org / password123 (SuperAdmin + President)';
RAISE NOTICE 'Viewer Login: viewer@sunrisevalley.org / password123 (Homeowner)';
END $$;