-- Migration 001: Create Cost Tracking Tables -- Description: Initial schema for MarkiTect cost tracking system -- Created: 2025-10-04 -- Issue: #110 - Cost Tracking Database Schema -- Enable foreign key constraints PRAGMA foreign_keys = ON; -- Cost categories table CREATE TABLE IF NOT EXISTS cost_categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Cost items table (monthly recurring and one-time costs) CREATE TABLE IF NOT EXISTS cost_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER REFERENCES cost_categories(id), name TEXT NOT NULL, description TEXT, cost_type TEXT CHECK (cost_type IN ('monthly', 'one_time')) NOT NULL, amount_eur DECIMAL(10,2) NOT NULL CHECK (amount_eur >= 0), currency TEXT DEFAULT 'EUR', starting_from_date DATE NOT NULL, ending_date DATE, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT valid_date_range CHECK (ending_date IS NULL OR ending_date >= starting_from_date), CONSTRAINT active_ongoing CHECK (NOT (is_active = FALSE AND ending_date IS NULL)) ); -- Calculation periods table CREATE TABLE IF NOT EXISTS cost_periods ( id INTEGER PRIMARY KEY AUTOINCREMENT, period_start DATE NOT NULL, period_end DATE NOT NULL, period_type TEXT DEFAULT 'monthly', status TEXT CHECK (status IN ('open', 'calculating', 'closed')) DEFAULT 'open', total_costs DECIMAL(10,2) DEFAULT 0 CHECK (total_costs >= 0), active_issues_count INTEGER DEFAULT 0 CHECK (active_issues_count >= 0), cost_per_issue DECIMAL(10,2) DEFAULT 0 CHECK (cost_per_issue >= 0), loss_carried_forward DECIMAL(10,2) DEFAULT 0 CHECK (loss_carried_forward >= 0), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT valid_period CHECK (period_end >= period_start), CONSTRAINT unique_period UNIQUE (period_start, period_end) ); -- Cost transactions table (audit trail) CREATE TABLE IF NOT EXISTS cost_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, period_id INTEGER REFERENCES cost_periods(id), cost_item_id INTEGER REFERENCES cost_items(id), transaction_type TEXT CHECK (transaction_type IN ('cost_incurred', 'cost_allocated', 'loss_forward', 'adjustment')) NOT NULL, amount_eur DECIMAL(10,2) NOT NULL, issue_id INTEGER, transaction_date DATE NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT positive_allocated_amount CHECK ( transaction_type != 'cost_allocated' OR amount_eur > 0 ) ); -- Issue cost allocations table CREATE TABLE IF NOT EXISTS issue_cost_allocations ( id INTEGER PRIMARY KEY AUTOINCREMENT, issue_id INTEGER NOT NULL, period_id INTEGER REFERENCES cost_periods(id), allocated_amount DECIMAL(10,2) NOT NULL CHECK (allocated_amount > 0), allocation_date DATE NOT NULL, transaction_id INTEGER REFERENCES cost_transactions(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT unique_issue_period UNIQUE (issue_id, period_id) ); -- Issue activity log table CREATE TABLE IF NOT EXISTS issue_activity_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, issue_id INTEGER NOT NULL, activity_type TEXT CHECK (activity_type IN ('created', 'modified', 'closed', 'reopened', 'commented', 'status_changed')) NOT NULL, activity_date DATE NOT NULL, period_id INTEGER REFERENCES cost_periods(id), activity_details TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Performance indexes CREATE INDEX IF NOT EXISTS idx_cost_items_active ON cost_items(is_active); CREATE INDEX IF NOT EXISTS idx_cost_items_type ON cost_items(cost_type); CREATE INDEX IF NOT EXISTS idx_cost_items_dates ON cost_items(starting_from_date, ending_date); CREATE INDEX IF NOT EXISTS idx_cost_items_category ON cost_items(category_id); CREATE INDEX IF NOT EXISTS idx_cost_periods_status ON cost_periods(status); CREATE INDEX IF NOT EXISTS idx_cost_periods_dates ON cost_periods(period_start, period_end); CREATE INDEX IF NOT EXISTS idx_cost_transactions_period ON cost_transactions(period_id); CREATE INDEX IF NOT EXISTS idx_cost_transactions_type ON cost_transactions(transaction_type); CREATE INDEX IF NOT EXISTS idx_cost_transactions_issue ON cost_transactions(issue_id); CREATE INDEX IF NOT EXISTS idx_cost_transactions_date ON cost_transactions(transaction_date); CREATE INDEX IF NOT EXISTS idx_issue_allocations_issue ON issue_cost_allocations(issue_id); CREATE INDEX IF NOT EXISTS idx_issue_allocations_period ON issue_cost_allocations(period_id); CREATE INDEX IF NOT EXISTS idx_issue_activity_issue ON issue_activity_log(issue_id); CREATE INDEX IF NOT EXISTS idx_issue_activity_date ON issue_activity_log(activity_date); CREATE INDEX IF NOT EXISTS idx_issue_activity_period ON issue_activity_log(period_id); CREATE INDEX IF NOT EXISTS idx_issue_activity_type ON issue_activity_log(activity_type); -- Default cost categories INSERT OR IGNORE INTO cost_categories (name, description) VALUES ('Infrastructure', 'Server hosting, cloud services, and infrastructure costs'), ('Software', 'SaaS subscriptions, licenses, and software tools'), ('Domain & DNS', 'Domain registration, DNS services, SSL certificates'), ('Development Tools', 'IDEs, development platforms, and productivity tools'), ('AI & ML Services', 'LLM APIs, AI tools, and machine learning services'), ('Marketing', 'Marketing tools, analytics, and promotional services'), ('Support & Maintenance', 'Support contracts, maintenance fees, and updates'), ('One-time Expenses', 'Setup fees, equipment purchases, and project-specific costs'); -- Example cost items from issue description (commented out for manual addition) -- INSERT INTO cost_items (category_id, name, description, cost_type, amount_eur, starting_from_date) VALUES -- (1, 'Hosteurope Server', 'Monthly server hosting', 'monthly', 10.00, '2025-01-01'), -- (2, 'Bubble.io Plan', 'No-code platform subscription', 'monthly', 32.00, '2025-01-01'), -- (3, 'Coulomb.social Domain', 'Domain registration and hosting', 'monthly', 5.00, '2025-01-01'), -- (4, 'Claude Code Plan', 'AI coding assistant subscription', 'monthly', 20.00, '2025-01-01'), -- (5, 'Gemini Plan', 'LLM API for specification support', 'monthly', 20.00, '2025-01-01');