-- Migration 100: Fixed-Price Agreements -- Creates table for monthly fixed-price hour agreements with binding periods -- Ready for future subscription system integration (v2) CREATE TABLE IF NOT EXISTS customer_fixed_price_agreements ( id SERIAL PRIMARY KEY, agreement_number VARCHAR(50) UNIQUE NOT NULL, -- Future subscription system integration subscription_id INTEGER DEFAULT NULL, -- Customer linkage customer_id INTEGER NOT NULL, customer_name VARCHAR(255), -- Product definition (v2: move to subscription_products table) monthly_hours DECIMAL(8,2) NOT NULL CHECK (monthly_hours > 0), hourly_rate DECIMAL(10,2) NOT NULL CHECK (hourly_rate >= 0), overtime_rate DECIMAL(10,2) NOT NULL CHECK (overtime_rate >= 0), internal_cost_rate DECIMAL(10,2) DEFAULT 350.00, -- For profit calculation rounding_minutes INTEGER DEFAULT 0 CHECK (rounding_minutes IN (0, 15, 30, 60)), -- Contract lifecycle start_date DATE NOT NULL, binding_months INTEGER DEFAULT 0 CHECK (binding_months >= 0), binding_end_date DATE, end_date DATE, notice_period_days INTEGER DEFAULT 30 CHECK (notice_period_days >= 0), auto_renew BOOLEAN DEFAULT false, -- Status tracking status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'expired', 'cancelled', 'pending_cancellation')), cancellation_requested_date DATE, cancellation_effective_date DATE, cancelled_by_user_id INTEGER, cancellation_reason TEXT, -- Billing integration (v2: subscription_billing handles this) billing_enabled BOOLEAN DEFAULT true, last_billed_period DATE, -- e-conomic integration (v2: move to subscription product mapping) economic_product_number VARCHAR(50), economic_overtime_product_number VARCHAR(50), -- Metadata notes TEXT, created_by_user_id INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Indexes for performance CREATE INDEX IF NOT EXISTS idx_fpa_customer ON customer_fixed_price_agreements(customer_id); CREATE INDEX IF NOT EXISTS idx_fpa_subscription ON customer_fixed_price_agreements(subscription_id); CREATE INDEX IF NOT EXISTS idx_fpa_status ON customer_fixed_price_agreements(status); CREATE INDEX IF NOT EXISTS idx_fpa_dates ON customer_fixed_price_agreements(start_date, end_date); -- Trigger to auto-generate agreement_number CREATE OR REPLACE FUNCTION generate_fpa_number() RETURNS TRIGGER AS $$ DECLARE new_number VARCHAR(50); day_count INTEGER; BEGIN -- Count agreements created today SELECT COUNT(*) INTO day_count FROM customer_fixed_price_agreements WHERE DATE(created_at) = CURRENT_DATE; -- Generate FPA-YYYYMMDD-XXX new_number := 'FPA-' || TO_CHAR(CURRENT_DATE, 'YYYYMMDD') || '-' || LPAD((day_count + 1)::TEXT, 3, '0'); NEW.agreement_number := new_number; -- Calculate binding_end_date IF NEW.binding_months > 0 THEN NEW.binding_end_date := NEW.start_date + (NEW.binding_months || ' months')::INTERVAL; ELSE NEW.binding_end_date := NULL; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_generate_fpa_number BEFORE INSERT OR UPDATE ON customer_fixed_price_agreements FOR EACH ROW EXECUTE FUNCTION generate_fpa_number(); -- Update timestamp trigger CREATE TRIGGER trigger_fpa_updated_at BEFORE UPDATE ON customer_fixed_price_agreements FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); COMMENT ON TABLE customer_fixed_price_agreements IS 'Fixed-price monthly hour agreements. Ready for v2 subscription system integration via subscription_id column.'; COMMENT ON COLUMN customer_fixed_price_agreements.subscription_id IS 'Future: Links to unified subscription system (Phase 2)'; COMMENT ON COLUMN customer_fixed_price_agreements.internal_cost_rate IS 'Internal hourly cost for profit margin calculation in reporting'; COMMENT ON COLUMN customer_fixed_price_agreements.binding_end_date IS 'Auto-calculated from start_date + binding_months. Enforced during cancellation.';