-- Backfill invoice error finder schema on environments where module migrations -- were not executed through the main production migration flow. CREATE TABLE IF NOT EXISTS invoice_error_finder_import_runs ( id SERIAL PRIMARY KEY, source_type VARCHAR(50) NOT NULL, started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP, status VARCHAR(20) NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'success', 'partial', 'failed')), records_imported INTEGER NOT NULL DEFAULT 0, records_failed INTEGER NOT NULL DEFAULT 0, error_message TEXT, triggered_by_user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, is_scheduled BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_ief_import_runs_source ON invoice_error_finder_import_runs(source_type, started_at DESC); CREATE TABLE IF NOT EXISTS invoice_error_finder_economic_invoices ( id SERIAL PRIMARY KEY, import_run_id INTEGER NOT NULL REFERENCES invoice_error_finder_import_runs(id) ON DELETE CASCADE, source_invoice_number VARCHAR(80), source_type VARCHAR(30) NOT NULL DEFAULT 'booked', customer_number INTEGER, customer_name VARCHAR(255), invoice_date DATE, due_date DATE, currency VARCHAR(10) DEFAULT 'DKK', net_amount NUMERIC(14,2), vat_amount NUMERIC(14,2), total_amount NUMERIC(14,2), source_raw JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_ief_economic_invoice_import UNIQUE (import_run_id, source_invoice_number, source_type) ); CREATE INDEX IF NOT EXISTS idx_ief_economic_invoices_run ON invoice_error_finder_economic_invoices(import_run_id); CREATE INDEX IF NOT EXISTS idx_ief_economic_invoices_customer ON invoice_error_finder_economic_invoices(customer_number); CREATE INDEX IF NOT EXISTS idx_ief_economic_invoices_date ON invoice_error_finder_economic_invoices(invoice_date); CREATE TABLE IF NOT EXISTS invoice_error_finder_economic_invoice_lines ( id SERIAL PRIMARY KEY, invoice_id INTEGER NOT NULL REFERENCES invoice_error_finder_economic_invoices(id) ON DELETE CASCADE, line_number INTEGER, product_number VARCHAR(100), product_name VARCHAR(500), description TEXT, quantity NUMERIC(14,4) NOT NULL DEFAULT 0, unit_price NUMERIC(14,4) NOT NULL DEFAULT 0, line_net_amount NUMERIC(14,2) NOT NULL DEFAULT 0, discount_percentage NUMERIC(5,2) DEFAULT 0, source_raw JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_ief_economic_lines_invoice ON invoice_error_finder_economic_invoice_lines(invoice_id); CREATE INDEX IF NOT EXISTS idx_ief_economic_lines_product ON invoice_error_finder_economic_invoice_lines(product_number); CREATE TABLE IF NOT EXISTS invoice_error_finder_simply_sales_orders ( id SERIAL PRIMARY KEY, import_run_id INTEGER NOT NULL REFERENCES invoice_error_finder_import_runs(id) ON DELETE CASCADE, source_record_id VARCHAR(80) NOT NULL, salesorder_no VARCHAR(80), account_id VARCHAR(80), customer_name VARCHAR(255), customer_cvr VARCHAR(32), subject TEXT, status VARCHAR(50), product_number VARCHAR(100), product_name VARCHAR(500), quantity NUMERIC(14,4) NOT NULL DEFAULT 0, unit_price NUMERIC(14,4) NOT NULL DEFAULT 0, total_amount NUMERIC(14,2) NOT NULL DEFAULT 0, start_period DATE, end_period DATE, source_raw JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_ief_simply_order_import UNIQUE (source_record_id) ); CREATE INDEX IF NOT EXISTS idx_ief_simply_orders_run ON invoice_error_finder_simply_sales_orders(import_run_id); CREATE INDEX IF NOT EXISTS idx_ief_simply_orders_account ON invoice_error_finder_simply_sales_orders(account_id); CREATE INDEX IF NOT EXISTS idx_ief_simply_orders_status ON invoice_error_finder_simply_sales_orders(status); CREATE TABLE IF NOT EXISTS invoice_error_finder_issues ( id SERIAL PRIMARY KEY, issue_type VARCHAR(50) NOT NULL CHECK (issue_type IN ( 'missing_line', 'open_order_not_invoiced', 'quantity_drop', 'price_change', 'new_item_never_invoiced' )), status VARCHAR(30) NOT NULL DEFAULT 'open' CHECK (status IN ( 'open', 'investigating', 'approved_change', 'error_found', 'ready_to_invoice', 'invoiced', 'ignored', 'resolved' )), customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL, customer_name VARCHAR(255), subscription_id INTEGER REFERENCES sag_subscriptions(id) ON DELETE SET NULL, simply_order_id INTEGER REFERENCES invoice_error_finder_simply_sales_orders(id) ON DELETE SET NULL, simply_source_record_id VARCHAR(80), sag_id INTEGER REFERENCES sag_sager(id) ON DELETE SET NULL, product_number VARCHAR(100), product_name VARCHAR(500), reference_period_start DATE, reference_period_end DATE, expected_quantity NUMERIC(14,4), actual_quantity NUMERIC(14,4), expected_price NUMERIC(14,4), actual_price NUMERIC(14,4), last_invoice_number VARCHAR(80), last_invoice_date DATE, sales_order_number VARCHAR(80), amount_impact NUMERIC(14,2), currency VARCHAR(10) DEFAULT 'DKK', assigned_user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, notes TEXT, ignored_until DATE, resolved_at TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_ief_issues_type ON invoice_error_finder_issues(issue_type); CREATE INDEX IF NOT EXISTS idx_ief_issues_status ON invoice_error_finder_issues(status); CREATE INDEX IF NOT EXISTS idx_ief_issues_customer ON invoice_error_finder_issues(customer_id); CREATE INDEX IF NOT EXISTS idx_ief_issues_period ON invoice_error_finder_issues(reference_period_start, reference_period_end); CREATE INDEX IF NOT EXISTS idx_ief_issues_assigned ON invoice_error_finder_issues(assigned_user_id) WHERE assigned_user_id IS NULL; CREATE OR REPLACE FUNCTION update_ief_issues_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trigger_ief_issues_updated_at ON invoice_error_finder_issues; CREATE TRIGGER trigger_ief_issues_updated_at BEFORE UPDATE ON invoice_error_finder_issues FOR EACH ROW EXECUTE FUNCTION update_ief_issues_updated_at();