Initial commit: HOA Financial Intelligence Platform MVP
Multi-tenant financial management platform for homeowner associations featuring: - NestJS backend with 16 modules (auth, accounts, transactions, budgets, units, invoices, payments, vendors, reserves, investments, capital projects, reports) - React + Mantine frontend with dashboard, CRUD pages, and financial reports - Schema-per-tenant PostgreSQL isolation with JWT-based tenant resolution - Docker Compose infrastructure (nginx, backend, frontend, postgres, redis) - Comprehensive seed data for Sunrise Valley HOA demo - 39 API endpoints with Swagger documentation - Double-entry bookkeeping with journal entries - Budget vs actual reporting and Sankey cash flow visualization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
79
db/init/00-init.sql
Normal file
79
db/init/00-init.sql
Normal file
@@ -0,0 +1,79 @@
|
||||
-- HOA Financial Platform - Database Initialization
|
||||
-- Creates shared schema and base tables for multi-tenant architecture
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- Shared schema for cross-tenant data
|
||||
CREATE SCHEMA IF NOT EXISTS shared;
|
||||
|
||||
-- Organizations (HOAs)
|
||||
CREATE TABLE shared.organizations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
schema_name VARCHAR(63) NOT NULL UNIQUE,
|
||||
subdomain VARCHAR(63) UNIQUE,
|
||||
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'trial')),
|
||||
settings JSONB DEFAULT '{}',
|
||||
address_line1 VARCHAR(255),
|
||||
address_line2 VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(2),
|
||||
zip_code VARCHAR(10),
|
||||
phone VARCHAR(20),
|
||||
email VARCHAR(255),
|
||||
tax_id VARCHAR(20),
|
||||
fiscal_year_start_month INTEGER DEFAULT 1 CHECK (fiscal_year_start_month BETWEEN 1 AND 12),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Users (global, cross-tenant)
|
||||
CREATE TABLE shared.users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255),
|
||||
first_name VARCHAR(100),
|
||||
last_name VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
is_email_verified BOOLEAN DEFAULT FALSE,
|
||||
mfa_enabled BOOLEAN DEFAULT FALSE,
|
||||
mfa_secret VARCHAR(255),
|
||||
oauth_provider VARCHAR(50),
|
||||
oauth_provider_id VARCHAR(255),
|
||||
last_login_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- User-Organization memberships with roles
|
||||
CREATE TABLE shared.user_organizations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES shared.users(id) ON DELETE CASCADE,
|
||||
organization_id UUID NOT NULL REFERENCES shared.organizations(id) ON DELETE CASCADE,
|
||||
role VARCHAR(50) NOT NULL CHECK (role IN ('president', 'treasurer', 'secretary', 'member_at_large', 'manager', 'homeowner')),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, organization_id)
|
||||
);
|
||||
|
||||
-- Invitations
|
||||
CREATE TABLE shared.invitations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES shared.organizations(id) ON DELETE CASCADE,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
invited_by UUID NOT NULL REFERENCES shared.users(id),
|
||||
token VARCHAR(255) NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
accepted_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_user_orgs_user ON shared.user_organizations(user_id);
|
||||
CREATE INDEX idx_user_orgs_org ON shared.user_organizations(organization_id);
|
||||
CREATE INDEX idx_users_email ON shared.users(email);
|
||||
CREATE INDEX idx_orgs_schema ON shared.organizations(schema_name);
|
||||
CREATE INDEX idx_invitations_token ON shared.invitations(token);
|
||||
CREATE INDEX idx_invitations_email ON shared.invitations(email);
|
||||
784
db/seed/seed.sql
Normal file
784
db/seed/seed.sql
Normal file
@@ -0,0 +1,784 @@
|
||||
-- ============================================================
|
||||
-- HOA Financial Platform - Comprehensive Seed Data
|
||||
-- "Sunrise Valley HOA" - 50 units, full year of financial data
|
||||
-- ============================================================
|
||||
-- This script:
|
||||
-- 1. Creates a test user + organization
|
||||
-- 2. Provisions the tenant schema
|
||||
-- 3. Seeds 50 units with homeowner data
|
||||
-- 4. Creates 12 months of assessment invoices
|
||||
-- 5. Creates payments (some units are delinquent)
|
||||
-- 6. Creates vendor records and expense transactions
|
||||
-- 7. Creates budgets for current year
|
||||
-- 8. Creates reserve components, investments, capital projects
|
||||
-- ============================================================
|
||||
|
||||
-- Enable UUID generation
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Create test user and organization
|
||||
-- ============================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
v_org_id UUID;
|
||||
v_schema TEXT := 'tenant_sunrise_valley';
|
||||
v_year INT := EXTRACT(YEAR FROM CURRENT_DATE)::INT;
|
||||
v_month INT;
|
||||
v_unit_id UUID;
|
||||
v_invoice_id UUID;
|
||||
v_je_id UUID;
|
||||
v_fp_id UUID;
|
||||
v_ar_id UUID;
|
||||
v_cash_id UUID;
|
||||
v_income_id UUID;
|
||||
v_expense_account_id UUID;
|
||||
v_unit_rec RECORD;
|
||||
v_vendor_rec RECORD;
|
||||
v_acct_rec RECORD;
|
||||
v_inv_num TEXT;
|
||||
v_amount NUMERIC;
|
||||
v_inv_date DATE;
|
||||
v_due_date DATE;
|
||||
v_pay_date DATE;
|
||||
v_pay_amount NUMERIC;
|
||||
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)
|
||||
VALUES (
|
||||
uuid_generate_v4(),
|
||||
'admin@sunrisevalley.org',
|
||||
-- bcrypt hash of 'password123'
|
||||
'$2b$10$1mtM00QBNQpAsyopajk3BeFY5DdxksvRYuM1E8qB.ePjCIYkfHMHO',
|
||||
'Sarah',
|
||||
'Johnson'
|
||||
) RETURNING id INTO v_user_id;
|
||||
END IF;
|
||||
|
||||
-- Check if org exists
|
||||
SELECT id INTO v_org_id FROM shared.organizations WHERE schema_name = v_schema;
|
||||
IF v_org_id IS NULL THEN
|
||||
INSERT INTO shared.organizations (id, name, subdomain, address_line1, city, state, zip_code, schema_name)
|
||||
VALUES (
|
||||
uuid_generate_v4(),
|
||||
'Sunrise Valley HOA',
|
||||
'sunrise-valley',
|
||||
'100 Sunrise Valley Drive',
|
||||
'Scottsdale',
|
||||
'AZ',
|
||||
'85255',
|
||||
v_schema
|
||||
) RETURNING id INTO v_org_id;
|
||||
|
||||
INSERT INTO shared.user_organizations (user_id, organization_id, role)
|
||||
VALUES (v_user_id, v_org_id, 'president');
|
||||
END IF;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. Create tenant schema (if not exists)
|
||||
-- ============================================================
|
||||
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I', v_schema);
|
||||
|
||||
-- Create tables in tenant schema
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.accounts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
account_number INTEGER NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
account_type VARCHAR(50) NOT NULL,
|
||||
fund_type VARCHAR(20) NOT NULL,
|
||||
parent_account_id UUID,
|
||||
is_1099_reportable BOOLEAN DEFAULT FALSE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
is_system BOOLEAN DEFAULT FALSE,
|
||||
balance DECIMAL(15,2) DEFAULT 0.00,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(account_number)
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.fiscal_periods (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
year INTEGER NOT NULL,
|
||||
month INTEGER NOT NULL,
|
||||
status VARCHAR(20) DEFAULT ''open'',
|
||||
closed_by UUID,
|
||||
closed_at TIMESTAMPTZ,
|
||||
locked_by UUID,
|
||||
locked_at TIMESTAMPTZ,
|
||||
UNIQUE(year, month)
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.journal_entries (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
entry_date DATE NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
reference_number VARCHAR(100),
|
||||
entry_type VARCHAR(50) NOT NULL,
|
||||
fiscal_period_id UUID NOT NULL,
|
||||
source_type VARCHAR(50),
|
||||
source_id UUID,
|
||||
is_posted BOOLEAN DEFAULT FALSE,
|
||||
posted_by UUID,
|
||||
posted_at TIMESTAMPTZ,
|
||||
is_void BOOLEAN DEFAULT FALSE,
|
||||
voided_by UUID,
|
||||
voided_at TIMESTAMPTZ,
|
||||
void_reason TEXT,
|
||||
created_by UUID NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.journal_entry_lines (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
journal_entry_id UUID NOT NULL,
|
||||
account_id UUID NOT NULL,
|
||||
debit DECIMAL(15,2) DEFAULT 0.00,
|
||||
credit DECIMAL(15,2) DEFAULT 0.00,
|
||||
memo TEXT
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.units (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
unit_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
address_line1 VARCHAR(255),
|
||||
address_line2 VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(2),
|
||||
zip_code VARCHAR(10),
|
||||
square_footage INTEGER,
|
||||
lot_size DECIMAL(10,2),
|
||||
owner_user_id UUID,
|
||||
owner_name VARCHAR(255),
|
||||
owner_email VARCHAR(255),
|
||||
owner_phone VARCHAR(20),
|
||||
is_rented BOOLEAN DEFAULT FALSE,
|
||||
monthly_assessment DECIMAL(10,2),
|
||||
status VARCHAR(20) DEFAULT ''active'',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.invoices (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
invoice_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
unit_id UUID NOT NULL,
|
||||
invoice_date DATE NOT NULL,
|
||||
due_date DATE NOT NULL,
|
||||
invoice_type VARCHAR(50) NOT NULL,
|
||||
description TEXT,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
amount_paid DECIMAL(10,2) DEFAULT 0.00,
|
||||
status VARCHAR(20) DEFAULT ''draft'',
|
||||
journal_entry_id UUID,
|
||||
sent_at TIMESTAMPTZ,
|
||||
paid_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.payments (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
unit_id UUID NOT NULL,
|
||||
invoice_id UUID,
|
||||
payment_date DATE NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
payment_method VARCHAR(50),
|
||||
reference_number VARCHAR(100),
|
||||
stripe_payment_id VARCHAR(255),
|
||||
status VARCHAR(20) DEFAULT ''completed'',
|
||||
journal_entry_id UUID,
|
||||
received_by UUID,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.vendors (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
contact_name VARCHAR(255),
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(20),
|
||||
address_line1 VARCHAR(255),
|
||||
address_line2 VARCHAR(255),
|
||||
city VARCHAR(100),
|
||||
state VARCHAR(2),
|
||||
zip_code VARCHAR(10),
|
||||
tax_id VARCHAR(20),
|
||||
is_1099_eligible BOOLEAN DEFAULT FALSE,
|
||||
default_account_id UUID,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
ytd_payments DECIMAL(15,2) DEFAULT 0.00,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.budgets (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
fiscal_year INTEGER NOT NULL,
|
||||
account_id UUID NOT NULL,
|
||||
fund_type VARCHAR(20) NOT NULL,
|
||||
jan DECIMAL(12,2) DEFAULT 0, feb DECIMAL(12,2) DEFAULT 0,
|
||||
mar DECIMAL(12,2) DEFAULT 0, apr DECIMAL(12,2) DEFAULT 0,
|
||||
may DECIMAL(12,2) DEFAULT 0, jun DECIMAL(12,2) DEFAULT 0,
|
||||
jul DECIMAL(12,2) DEFAULT 0, aug DECIMAL(12,2) DEFAULT 0,
|
||||
sep DECIMAL(12,2) DEFAULT 0, oct DECIMAL(12,2) DEFAULT 0,
|
||||
nov DECIMAL(12,2) DEFAULT 0, dec_amt DECIMAL(12,2) DEFAULT 0,
|
||||
notes TEXT,
|
||||
UNIQUE(fiscal_year, account_id, fund_type)
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.reserve_components (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(100),
|
||||
description TEXT,
|
||||
useful_life_years INTEGER NOT NULL,
|
||||
remaining_life_years DECIMAL(5,1),
|
||||
replacement_cost DECIMAL(15,2) NOT NULL,
|
||||
current_fund_balance DECIMAL(15,2) DEFAULT 0.00,
|
||||
annual_contribution DECIMAL(12,2) DEFAULT 0.00,
|
||||
last_replacement_date DATE,
|
||||
next_replacement_date DATE,
|
||||
condition_rating INTEGER,
|
||||
account_id UUID,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.investment_accounts (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
institution VARCHAR(255),
|
||||
account_number_last4 VARCHAR(4),
|
||||
investment_type VARCHAR(50),
|
||||
fund_type VARCHAR(20) NOT NULL,
|
||||
principal DECIMAL(15,2) NOT NULL,
|
||||
interest_rate DECIMAL(6,4),
|
||||
maturity_date DATE,
|
||||
purchase_date DATE,
|
||||
current_value DECIMAL(15,2),
|
||||
account_id UUID,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
EXECUTE format('
|
||||
CREATE TABLE IF NOT EXISTS %I.capital_projects (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
estimated_cost DECIMAL(15,2) NOT NULL,
|
||||
actual_cost DECIMAL(15,2),
|
||||
target_year INTEGER NOT NULL,
|
||||
target_month INTEGER,
|
||||
status VARCHAR(20) DEFAULT ''planned'',
|
||||
reserve_component_id UUID,
|
||||
fund_source VARCHAR(20),
|
||||
priority INTEGER DEFAULT 3,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)', v_schema);
|
||||
|
||||
-- ============================================================
|
||||
-- 3. Seed Chart of Accounts
|
||||
-- ============================================================
|
||||
-- Clear existing data if re-running
|
||||
EXECUTE format('DELETE FROM %I.journal_entry_lines', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.journal_entries', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.payments', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.invoices', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.budgets', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.capital_projects', v_schema);
|
||||
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.fiscal_periods', v_schema);
|
||||
EXECUTE format('DELETE FROM %I.accounts', v_schema);
|
||||
|
||||
-- Insert chart of accounts
|
||||
EXECUTE format('INSERT INTO %I.accounts (account_number, name, account_type, fund_type, is_1099_reportable, is_system) VALUES
|
||||
(1000, ''Operating Cash - Checking'', ''asset'', ''operating'', false, true),
|
||||
(1010, ''Operating Cash - Savings'', ''asset'', ''operating'', false, true),
|
||||
(1100, ''Reserve Cash - Checking'', ''asset'', ''reserve'', false, true),
|
||||
(1110, ''Reserve Cash - Savings'', ''asset'', ''reserve'', false, true),
|
||||
(1120, ''Reserve Cash - Money Market'', ''asset'', ''reserve'', false, true),
|
||||
(1130, ''Reserve Cash - CDs'', ''asset'', ''reserve'', false, true),
|
||||
(1200, ''Accounts Receivable - Assessments'', ''asset'', ''operating'', false, true),
|
||||
(1210, ''Accounts Receivable - Late Fees'', ''asset'', ''operating'', false, true),
|
||||
(1300, ''Prepaid Insurance'', ''asset'', ''operating'', false, true),
|
||||
(2000, ''Accounts Payable'', ''liability'', ''operating'', false, true),
|
||||
(2100, ''Accrued Expenses'', ''liability'', ''operating'', false, true),
|
||||
(2200, ''Prepaid Assessments'', ''liability'', ''operating'', false, true),
|
||||
(3000, ''Operating Fund Balance'', ''equity'', ''operating'', false, true),
|
||||
(3100, ''Reserve Fund Balance'', ''equity'', ''reserve'', false, true),
|
||||
(3200, ''Retained Earnings'', ''equity'', ''operating'', false, true),
|
||||
(4000, ''Regular Assessments'', ''income'', ''operating'', false, true),
|
||||
(4010, ''Special Assessments'', ''income'', ''operating'', false, true),
|
||||
(4100, ''Late Fees'', ''income'', ''operating'', false, true),
|
||||
(4200, ''Interest Income - Operating'', ''income'', ''operating'', false, true),
|
||||
(4210, ''Interest Income - Reserve'', ''income'', ''reserve'', false, true),
|
||||
(4300, ''Transfer Fees'', ''income'', ''operating'', false, false),
|
||||
(4500, ''Other Income'', ''income'', ''operating'', false, false),
|
||||
(4600, ''Reserve Contributions'', ''income'', ''reserve'', false, true),
|
||||
(5000, ''Management Fees'', ''expense'', ''operating'', true, true),
|
||||
(5100, ''Insurance - Property'', ''expense'', ''operating'', false, true),
|
||||
(5110, ''Insurance - D&O'', ''expense'', ''operating'', false, true),
|
||||
(5200, ''Utilities - Water/Sewer'', ''expense'', ''operating'', false, true),
|
||||
(5210, ''Utilities - Electric (Common)'', ''expense'', ''operating'', false, true),
|
||||
(5230, ''Utilities - Trash/Recycling'', ''expense'', ''operating'', false, true),
|
||||
(5300, ''Landscape Maintenance'', ''expense'', ''operating'', true, true),
|
||||
(5400, ''Pool Maintenance'', ''expense'', ''operating'', true, true),
|
||||
(5500, ''Building Maintenance'', ''expense'', ''operating'', false, true),
|
||||
(5510, ''Janitorial'', ''expense'', ''operating'', true, true),
|
||||
(5600, ''Pest Control'', ''expense'', ''operating'', true, true),
|
||||
(5700, ''Legal Fees'', ''expense'', ''operating'', true, true),
|
||||
(5800, ''Accounting/Audit Fees'', ''expense'', ''operating'', true, true),
|
||||
(5900, ''Office & Admin Expenses'', ''expense'', ''operating'', false, true),
|
||||
(5920, ''Bank Fees'', ''expense'', ''operating'', false, true),
|
||||
(6000, ''Repairs & Maintenance'', ''expense'', ''operating'', false, true),
|
||||
(6500, ''Contingency/Miscellaneous'', ''expense'', ''operating'', false, true),
|
||||
(7000, ''Reserve - Roof Replacement'', ''expense'', ''reserve'', false, true),
|
||||
(7100, ''Reserve - Paving/Asphalt'', ''expense'', ''reserve'', false, true),
|
||||
(7200, ''Reserve - Pool Renovation'', ''expense'', ''reserve'', false, true),
|
||||
(7400, ''Reserve - Painting/Exterior'', ''expense'', ''reserve'', false, true),
|
||||
(7500, ''Reserve - Fencing'', ''expense'', ''reserve'', false, true)
|
||||
', v_schema);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. Create fiscal periods (current year + previous year)
|
||||
-- ============================================================
|
||||
FOR v_month IN 1..12 LOOP
|
||||
EXECUTE format('INSERT INTO %I.fiscal_periods (year, month, status) VALUES ($1, $2, $3)', v_schema)
|
||||
USING v_year, v_month, CASE WHEN v_month <= EXTRACT(MONTH FROM CURRENT_DATE)::INT THEN 'open' ELSE 'open' END;
|
||||
EXECUTE format('INSERT INTO %I.fiscal_periods (year, month, status) VALUES ($1, $2, $3)', v_schema)
|
||||
USING v_year - 1, v_month, 'closed';
|
||||
END LOOP;
|
||||
|
||||
-- ============================================================
|
||||
-- 5. Seed 50 units
|
||||
-- ============================================================
|
||||
DECLARE
|
||||
v_first_names TEXT[] := ARRAY['James','Mary','Robert','Patricia','John','Jennifer','Michael','Linda',
|
||||
'David','Elizabeth','William','Barbara','Richard','Susan','Joseph','Jessica','Thomas','Sarah',
|
||||
'Christopher','Karen','Charles','Lisa','Daniel','Nancy','Matthew','Betty','Anthony','Margaret',
|
||||
'Mark','Sandra','Donald','Ashley','Steven','Dorothy','Paul','Kimberly','Andrew','Emily',
|
||||
'Joshua','Donna','Kenneth','Michelle','Kevin','Carol','Brian','Amanda','George','Melissa',
|
||||
'Timothy','Deborah'];
|
||||
v_last_names TEXT[] := ARRAY['Smith','Johnson','Williams','Brown','Jones','Garcia','Miller','Davis',
|
||||
'Rodriguez','Martinez','Hernandez','Lopez','Gonzalez','Wilson','Anderson','Thomas','Taylor',
|
||||
'Moore','Jackson','Martin','Lee','Perez','Thompson','White','Harris','Sanchez','Clark',
|
||||
'Ramirez','Lewis','Robinson','Walker','Young','Allen','King','Wright','Scott','Torres',
|
||||
'Nguyen','Hill','Flores','Green','Adams','Nelson','Baker','Hall','Rivera','Campbell',
|
||||
'Mitchell','Carter','Roberts'];
|
||||
v_unit_num INT;
|
||||
v_assess NUMERIC;
|
||||
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;
|
||||
|
||||
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)
|
||||
USING
|
||||
LPAD(v_unit_num::TEXT, 3, '0'),
|
||||
(100 + v_unit_num * 2)::TEXT || ' Sunrise Valley Drive',
|
||||
'Scottsdale', 'AZ', '85255',
|
||||
v_first_names[v_unit_num] || ' ' || v_last_names[v_unit_num],
|
||||
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;
|
||||
END LOOP;
|
||||
END;
|
||||
|
||||
-- ============================================================
|
||||
-- 6. Get account IDs for transactions
|
||||
-- ============================================================
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 1200', v_schema) INTO v_ar_id;
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 1000', v_schema) INTO v_cash_id;
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 4000', v_schema) INTO v_income_id;
|
||||
|
||||
-- ============================================================
|
||||
-- 7. Create 12 months of invoices + payments
|
||||
-- ============================================================
|
||||
FOR v_month IN 1..12 LOOP
|
||||
-- Get fiscal period
|
||||
EXECUTE format('SELECT id FROM %I.fiscal_periods WHERE year = $1 AND month = $2', v_schema)
|
||||
INTO v_fp_id USING v_year, v_month;
|
||||
|
||||
v_inv_date := make_date(v_year, v_month, 1);
|
||||
v_due_date := make_date(v_year, v_month, 15);
|
||||
|
||||
-- Create invoice and payment for each unit
|
||||
FOR v_unit_rec IN EXECUTE format('SELECT id, unit_number, monthly_assessment FROM %I.units ORDER BY unit_number', v_schema) LOOP
|
||||
v_inv_num := 'INV-' || v_year || LPAD(v_month::TEXT, 2, '0') || '-' || v_unit_rec.unit_number;
|
||||
v_amount := v_unit_rec.monthly_assessment;
|
||||
|
||||
-- Skip future months
|
||||
IF v_inv_date > CURRENT_DATE THEN
|
||||
CONTINUE;
|
||||
END IF;
|
||||
|
||||
-- Create invoice
|
||||
EXECUTE format('INSERT INTO %I.invoices (invoice_number, unit_id, invoice_date, due_date, invoice_type, description, amount, status)
|
||||
VALUES ($1, $2, $3, $4, ''regular_assessment'', $5, $6, ''sent'') RETURNING id', v_schema)
|
||||
INTO v_invoice_id
|
||||
USING v_inv_num, v_unit_rec.id, v_inv_date, v_due_date,
|
||||
'Monthly Assessment - ' || TO_CHAR(v_inv_date, 'Month YYYY'), v_amount;
|
||||
|
||||
-- Create assessment journal entry (DR AR, CR Income)
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, source_type, source_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''assessment'', $3, ''invoice'', $4, true, NOW(), $5) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING v_inv_date, 'Assessment - Unit ' || v_unit_rec.unit_number, v_fp_id, v_invoice_id, v_user_id;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, $3, 0)', v_schema)
|
||||
USING v_je_id, v_ar_id, v_amount;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, $3)', v_schema)
|
||||
USING v_je_id, v_income_id, v_amount;
|
||||
|
||||
EXECUTE format('UPDATE %I.invoices SET journal_entry_id = $1 WHERE id = $2', v_schema) USING v_je_id, v_invoice_id;
|
||||
|
||||
-- Create payment (most units pay, some are delinquent)
|
||||
-- Units 45-50 are delinquent for the last 3 months
|
||||
-- Units 40-44 are late payers (paid partial)
|
||||
IF v_unit_rec.unit_number::INT >= 45 AND v_month >= (EXTRACT(MONTH FROM CURRENT_DATE)::INT - 2) THEN
|
||||
-- Delinquent - mark overdue, no payment
|
||||
EXECUTE format('UPDATE %I.invoices SET status = ''overdue'' WHERE id = $1', v_schema) USING v_invoice_id;
|
||||
ELSIF v_unit_rec.unit_number::INT >= 40 AND v_month >= (EXTRACT(MONTH FROM CURRENT_DATE)::INT - 1) THEN
|
||||
-- Partial payment
|
||||
v_pay_amount := v_amount * 0.5;
|
||||
v_pay_date := v_due_date + INTERVAL '5 days';
|
||||
IF v_pay_date <= CURRENT_DATE THEN
|
||||
EXECUTE format('INSERT INTO %I.payments (unit_id, invoice_id, payment_date, amount, payment_method, reference_number, received_by)
|
||||
VALUES ($1, $2, $3, $4, ''check'', $5, $6)', v_schema)
|
||||
USING v_unit_rec.id, v_invoice_id, v_pay_date, v_pay_amount, 'CHK-' || v_unit_rec.unit_number || '-' || v_month, v_user_id;
|
||||
|
||||
EXECUTE format('UPDATE %I.invoices SET amount_paid = $1, status = ''partial'' WHERE id = $2', v_schema)
|
||||
USING v_pay_amount, v_invoice_id;
|
||||
|
||||
-- Payment JE
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, source_type, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''payment'', $3, ''payment'', true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING v_pay_date, 'Payment - Unit ' || v_unit_rec.unit_number, v_fp_id, v_user_id;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, $3, 0)', v_schema)
|
||||
USING v_je_id, v_cash_id, v_pay_amount;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, $3)', v_schema)
|
||||
USING v_je_id, v_ar_id, v_pay_amount;
|
||||
END IF;
|
||||
ELSE
|
||||
-- Full payment
|
||||
v_pay_date := v_due_date - INTERVAL '3 days' + (random() * 10)::INT * INTERVAL '1 day';
|
||||
IF v_pay_date > CURRENT_DATE THEN
|
||||
v_pay_date := CURRENT_DATE;
|
||||
END IF;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.payments (unit_id, invoice_id, payment_date, amount, payment_method, reference_number, received_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)', v_schema)
|
||||
USING v_unit_rec.id, v_invoice_id, v_pay_date, v_amount,
|
||||
CASE WHEN random() < 0.6 THEN 'ach' WHEN random() < 0.8 THEN 'check' ELSE 'credit_card' END,
|
||||
'PAY-' || v_unit_rec.unit_number || '-' || v_month, v_user_id;
|
||||
|
||||
EXECUTE format('UPDATE %I.invoices SET amount_paid = $1, status = ''paid'', paid_at = $2 WHERE id = $3', v_schema)
|
||||
USING v_amount, v_pay_date, v_invoice_id;
|
||||
|
||||
-- Payment JE
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, source_type, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''payment'', $3, ''payment'', true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING v_pay_date, 'Payment - Unit ' || v_unit_rec.unit_number, v_fp_id, v_user_id;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, $3, 0)', v_schema)
|
||||
USING v_je_id, v_cash_id, v_amount;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, $3)', v_schema)
|
||||
USING v_je_id, v_ar_id, v_amount;
|
||||
END IF;
|
||||
|
||||
END LOOP; -- units
|
||||
END LOOP; -- months
|
||||
|
||||
-- ============================================================
|
||||
-- 8. Create vendors and expense transactions
|
||||
-- ============================================================
|
||||
EXECUTE format('INSERT INTO %I.vendors (name, contact_name, email, phone, address_line1, city, state, zip_code, tax_id, is_1099_eligible) VALUES
|
||||
(''Desert Star Management'', ''Mike Peters'', ''mike@desertstar.com'', ''(480) 555-2000'', ''500 N Scottsdale Rd'', ''Scottsdale'', ''AZ'', ''85251'', ''82-1234567'', true),
|
||||
(''Green Oasis Landscaping'', ''Rosa Gutierrez'', ''rosa@greenoasis.com'', ''(480) 555-2100'', ''1200 E Camelback'', ''Phoenix'', ''AZ'', ''85014'', ''82-2345678'', true),
|
||||
(''AquaBlue Pool Service'', ''Tom Bradley'', ''tom@aquablue.com'', ''(480) 555-2200'', ''300 W Indian School'', ''Phoenix'', ''AZ'', ''85013'', ''82-3456789'', true),
|
||||
(''Valley Pest Solutions'', ''Dan Kim'', ''dan@valleypest.com'', ''(480) 555-2300'', ''800 S Mill Ave'', ''Tempe'', ''AZ'', ''85281'', ''82-4567890'', true),
|
||||
(''Southwest Legal Group'', ''Lisa Chen'', ''lisa@swlegal.com'', ''(480) 555-2400'', ''2 N Central Ave'', ''Phoenix'', ''AZ'', ''85004'', ''82-5678901'', true),
|
||||
(''Saguaro CPA Group'', ''Jim Torres'', ''jim@saguarocpa.com'', ''(480) 555-2500'', ''4000 N Scottsdale Rd'', ''Scottsdale'', ''AZ'', ''85251'', ''82-6789012'', true),
|
||||
(''CleanRight Janitorial'', ''Pat Morgan'', ''pat@cleanright.com'', ''(480) 555-2600'', ''1500 N Hayden'', ''Scottsdale'', ''AZ'', ''85257'', ''82-7890123'', true),
|
||||
(''Arizona Insurance Group'', ''Amy Russell'', ''amy@azinsurance.com'', ''(480) 555-2700'', ''7000 E Shea Blvd'', ''Scottsdale'', ''AZ'', ''85254'', NULL, false)
|
||||
', v_schema);
|
||||
|
||||
-- Link vendor default accounts
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5000) WHERE name = ''Desert Star Management''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5300) WHERE name = ''Green Oasis Landscaping''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5400) WHERE name = ''AquaBlue Pool Service''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5600) WHERE name = ''Valley Pest Solutions''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5700) WHERE name = ''Southwest Legal Group''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5800) WHERE name = ''Saguaro CPA Group''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5510) WHERE name = ''CleanRight Janitorial''', v_schema, v_schema);
|
||||
EXECUTE format('UPDATE %I.vendors SET default_account_id = (SELECT id FROM %I.accounts WHERE account_number = 5100) WHERE name = ''Arizona Insurance Group''', v_schema, v_schema);
|
||||
|
||||
-- Create monthly expense transactions
|
||||
FOR v_month IN 1..LEAST(12, EXTRACT(MONTH FROM CURRENT_DATE)::INT) LOOP
|
||||
EXECUTE format('SELECT id FROM %I.fiscal_periods WHERE year = $1 AND month = $2', v_schema) INTO v_fp_id USING v_year, v_month;
|
||||
|
||||
-- Management Fee: $2,500/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5000', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 5), 'Management Fee - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 2500, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 2500)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Landscape: $3,200/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5300', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 10), 'Landscape Maintenance - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 3200, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 3200)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Pool: $800/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5400', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 10), 'Pool Maintenance - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 800, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 800)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Utilities Water: $1,200/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5200', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 15), 'Water/Sewer - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 1200, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 1200)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Electric: $650/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5210', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 18), 'Electric Common Areas - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 650, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 650)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Trash: $450/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5230', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 20), 'Trash/Recycling - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 450, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 450)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Pest Control: $200/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5600', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 25), 'Pest Control - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 200, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 200)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Janitorial: $600/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5510', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 28), 'Janitorial Service - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 600, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 600)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- Reserve contribution: $2,750/mo
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 1100', v_schema) INTO v_expense_account_id;
|
||||
DECLARE v_reserve_income_id UUID;
|
||||
BEGIN
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 4600', v_schema) INTO v_reserve_income_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''transfer'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, v_month, 1), 'Reserve Contribution - ' || TO_CHAR(make_date(v_year, v_month, 1), 'Mon YYYY'), v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 2750, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 2750)', v_schema) USING v_je_id, v_reserve_income_id;
|
||||
END;
|
||||
|
||||
END LOOP;
|
||||
|
||||
-- Quarterly expenses
|
||||
-- Insurance (annual paid quarterly): $4,500/quarter
|
||||
FOR v_month IN 1..4 LOOP
|
||||
EXIT WHEN (v_month - 1) * 3 + 1 > EXTRACT(MONTH FROM CURRENT_DATE)::INT;
|
||||
EXECUTE format('SELECT id FROM %I.fiscal_periods WHERE year = $1 AND month = $2', v_schema)
|
||||
INTO v_fp_id USING v_year, (v_month - 1) * 3 + 1;
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5100', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, (v_month - 1) * 3 + 1, 1), 'Property Insurance - Q' || v_month, v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 4500, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 4500)', v_schema) USING v_je_id, v_cash_id;
|
||||
END LOOP;
|
||||
|
||||
-- Annual Audit: $3,500 (January)
|
||||
EXECUTE format('SELECT id FROM %I.fiscal_periods WHERE year = $1 AND month = 1', v_schema) INTO v_fp_id USING v_year;
|
||||
EXECUTE format('SELECT id FROM %I.accounts WHERE account_number = 5800', v_schema) INTO v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, ''manual'', $3, true, NOW(), $4) RETURNING id', v_schema)
|
||||
INTO v_je_id
|
||||
USING make_date(v_year, 1, 15), 'Annual Audit Fee', v_fp_id, v_user_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 3500, 0)', v_schema) USING v_je_id, v_expense_account_id;
|
||||
EXECUTE format('INSERT INTO %I.journal_entry_lines (journal_entry_id, account_id, debit, credit) VALUES ($1, $2, 0, 3500)', v_schema) USING v_je_id, v_cash_id;
|
||||
|
||||
-- ============================================================
|
||||
-- 9. Budgets for current year
|
||||
-- ============================================================
|
||||
-- Income budgets
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 17500, 17500, 17500, 17500, 17500, 17500, 17500, 17500, 17500, 17500, 17500, 17500
|
||||
FROM %I.accounts WHERE account_number = 4000', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
|
||||
FROM %I.accounts WHERE account_number = 4100', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''reserve'', 2750, 2750, 2750, 2750, 2750, 2750, 2750, 2750, 2750, 2750, 2750, 2750
|
||||
FROM %I.accounts WHERE account_number = 4600', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
-- Expense budgets
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500
|
||||
FROM %I.accounts WHERE account_number = 5000', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 4500, 0, 0, 4500, 0, 0, 4500, 0, 0, 4500, 0, 0
|
||||
FROM %I.accounts WHERE account_number = 5100', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200, 1200
|
||||
FROM %I.accounts WHERE account_number = 5200', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650
|
||||
FROM %I.accounts WHERE account_number = 5210', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200, 3200
|
||||
FROM %I.accounts WHERE account_number = 5300', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800, 800
|
||||
FROM %I.accounts WHERE account_number = 5400', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600
|
||||
FROM %I.accounts WHERE account_number = 5510', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200
|
||||
FROM %I.accounts WHERE account_number = 5600', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450
|
||||
FROM %I.accounts WHERE account_number = 5230', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
EXECUTE format('INSERT INTO %I.budgets (fiscal_year, account_id, fund_type, jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec_amt)
|
||||
SELECT $1, id, ''operating'', 3500, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
FROM %I.accounts WHERE account_number = 5800', v_schema, v_schema)
|
||||
USING v_year;
|
||||
|
||||
-- ============================================================
|
||||
-- 10. Reserve components
|
||||
-- ============================================================
|
||||
EXECUTE format('INSERT INTO %I.reserve_components (name, category, description, useful_life_years, remaining_life_years, replacement_cost, current_fund_balance, annual_contribution, condition_rating, last_replacement_date, next_replacement_date) VALUES
|
||||
(''Roof - Building A'', ''Roofing'', ''Tile roof on main building A'', 25, 18.0, 125000, 35000, 5000, 7, ''2016-06-01'', ''2041-06-01''),
|
||||
(''Roof - Building B'', ''Roofing'', ''Tile roof on building B'', 25, 20.0, 95000, 20000, 3800, 8, ''2018-03-01'', ''2043-03-01''),
|
||||
(''Parking Lot Resurfacing'', ''Paving'', ''Asphalt parking areas'', 15, 8.0, 85000, 47000, 5667, 6, ''2019-09-01'', ''2034-09-01''),
|
||||
(''Pool Replaster'', ''Pool'', ''Replaster community pool'', 10, 4.0, 35000, 21000, 3500, 5, ''2020-04-01'', ''2030-04-01''),
|
||||
(''Exterior Paint'', ''Painting'', ''Full exterior paint all buildings'', 8, 3.0, 65000, 48750, 8125, 4, ''2021-10-01'', ''2029-10-01''),
|
||||
(''Perimeter Fencing'', ''Fencing'', ''Wrought iron perimeter fence'', 20, 14.0, 45000, 13500, 2250, 7, ''2018-01-01'', ''2038-01-01''),
|
||||
(''Clubhouse HVAC'', ''HVAC'', ''Central HVAC system for clubhouse'', 15, 9.0, 28000, 11200, 1867, 6, ''2020-08-01'', ''2035-08-01''),
|
||||
(''Irrigation System'', ''Landscape'', ''Landscape irrigation infrastructure'', 12, 6.0, 22000, 11000, 1833, 5, ''2020-01-01'', ''2032-01-01'')
|
||||
', v_schema);
|
||||
|
||||
-- ============================================================
|
||||
-- 11. Investment accounts
|
||||
-- ============================================================
|
||||
EXECUTE format('INSERT INTO %I.investment_accounts (name, institution, account_number_last4, investment_type, fund_type, principal, interest_rate, maturity_date, purchase_date, current_value) VALUES
|
||||
(''Reserve CD - 12 Month'', ''Chase Bank'', ''4521'', ''cd'', ''reserve'', 50000, 5.2500, ''2027-01-15'', ''2026-01-15'', 51312.50),
|
||||
(''Reserve CD - 6 Month'', ''Wells Fargo'', ''7834'', ''cd'', ''reserve'', 25000, 4.7500, ''2026-07-15'', ''2026-01-15'', 25593.75),
|
||||
(''Reserve Money Market'', ''Schwab'', ''9912'', ''money_market'', ''reserve'', 75000, 4.5000, NULL, ''2025-06-01'', 77625.00),
|
||||
(''Operating Savings'', ''Chase Bank'', ''3345'', ''savings'', ''operating'', 15000, 3.0000, NULL, ''2025-01-01'', 15450.00),
|
||||
(''Treasury Bills - 3 Mo'', ''Fidelity'', ''6678'', ''treasury'', ''reserve'', 30000, 5.0000, ''2026-05-15'', ''2026-02-15'', 30375.00)
|
||||
', v_schema);
|
||||
|
||||
-- ============================================================
|
||||
-- 12. Capital projects (5-year plan)
|
||||
-- ============================================================
|
||||
EXECUTE format('INSERT INTO %I.capital_projects (name, description, estimated_cost, target_year, target_month, status, fund_source, priority) VALUES
|
||||
(''Pool Heater Replacement'', ''Replace aging pool heater system'', 8500, $1, 4, ''approved'', ''reserve'', 2),
|
||||
(''Landscape Renovation - Phase 1'', ''Update entry and common area landscaping'', 15000, $1, 5, ''in_progress'', ''reserve'', 2),
|
||||
(''Security Camera Upgrade'', ''Replace analog cameras with HD IP system'', 12000, $1, 8, ''planned'', ''operating'', 3),
|
||||
(''Parking Lot Seal Coat'', ''Seal coat and re-stripe parking areas'', 18000, $1 + 1, 3, ''planned'', ''reserve'', 2),
|
||||
(''Clubhouse Furniture'', ''Replace common area furniture'', 8000, $1 + 1, 6, ''planned'', ''operating'', 4),
|
||||
(''Exterior Paint - Building A'', ''Full exterior repaint of Building A'', 35000, $1 + 1, 9, ''planned'', ''reserve'', 1),
|
||||
(''Playground Equipment'', ''New playground equipment and surfacing'', 22000, $1 + 2, 4, ''planned'', ''reserve'', 3),
|
||||
(''Roof Repair - Building B'', ''Patch and repair sections of Building B roof'', 15000, $1 + 2, 7, ''planned'', ''reserve'', 2),
|
||||
(''Irrigation System Overhaul'', ''Replace aging irrigation controllers and valves'', 12000, $1 + 3, 3, ''planned'', ''reserve'', 3),
|
||||
(''Pool Replastering'', ''Complete pool replaster and tile work'', 35000, $1 + 3, 9, ''planned'', ''reserve'', 1),
|
||||
(''Parking Lot Resurface'', ''Full asphalt resurfacing of parking areas'', 85000, $1 + 4, 5, ''planned'', ''reserve'', 1),
|
||||
(''Perimeter Fence Repair'', ''Replace damaged fence sections and repaint'', 8000, $1 + 4, 8, ''planned'', ''reserve'', 4)
|
||||
', v_schema) USING v_year;
|
||||
|
||||
RAISE NOTICE 'Seed data created successfully for Sunrise Valley HOA!';
|
||||
RAISE NOTICE 'Login: admin@sunrisevalley.org / password123';
|
||||
|
||||
END $$;
|
||||
Reference in New Issue
Block a user