From 2b4dd6b010dbc1d07743762e2049728ba1cc4c61 Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 11 Jul 2026 10:31:20 +0200 Subject: [PATCH] chore(release): bump version to 2.3.28 --- VERSION | 2 +- ...2_invoice_error_finder_schema_backfill.sql | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 migrations/212_invoice_error_finder_schema_backfill.sql diff --git a/VERSION b/VERSION index e487952..f7fb770 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3.27 +2.3.28 diff --git a/migrations/212_invoice_error_finder_schema_backfill.sql b/migrations/212_invoice_error_finder_schema_backfill.sql new file mode 100644 index 0000000..6497a59 --- /dev/null +++ b/migrations/212_invoice_error_finder_schema_backfill.sql @@ -0,0 +1,164 @@ +-- 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' + )), + 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();