-- ALSO Cloud Billing foundation -- Import jobs, import lines queue, and mapping tables CREATE TABLE IF NOT EXISTS also_import_jobs ( id SERIAL PRIMARY KEY, source_type VARCHAR(32) NOT NULL CHECK (source_type IN ('api', 'xml', 'json_export', 'xml_export', 'csv')), source_label VARCHAR(255), status VARCHAR(32) NOT NULL DEFAULT 'new', file_name VARCHAR(512), import_version VARCHAR(128), imported_by_user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, imported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, started_at TIMESTAMP, finished_at TIMESTAMP, raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, log_json JSONB NOT NULL DEFAULT '[]'::jsonb, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_also_import_jobs_status ON also_import_jobs(status); CREATE INDEX IF NOT EXISTS idx_also_import_jobs_imported_at ON also_import_jobs(imported_at DESC); CREATE TABLE IF NOT EXISTS also_import_lines ( id BIGSERIAL PRIMARY KEY, import_job_id INTEGER NOT NULL REFERENCES also_import_jobs(id) ON DELETE CASCADE, line_no INTEGER, queue_status VARCHAR(32) NOT NULL DEFAULT 'new' CHECK (queue_status IN ( 'new', 'matching_products', 'matching_customers', 'ready_for_approval', 'approved', 'invoiced', 'error' )), source_line_ref VARCHAR(255), line_hash VARCHAR(128) NOT NULL, also_company_id VARCHAR(128), company VARCHAR(255), customer_id VARCHAR(128), account_id VARCHAR(128), vat VARCHAR(64), material_number VARCHAR(128), product_name VARCHAR(512), vendor VARCHAR(255), cost_amount NUMERIC(14, 4), sales_price NUMERIC(14, 4), unit_price NUMERIC(14, 4), total_price NUMERIC(14, 4), currency VARCHAR(16), billing_start DATE, charge_interval VARCHAR(64), billing_interval VARCHAR(64), billable_parameters NUMERIC(14, 4), matched_customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL, matched_product_id INTEGER REFERENCES products(id) ON DELETE SET NULL, validation_errors_json JSONB NOT NULL DEFAULT '[]'::jsonb, raw_line_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uniq_also_import_line_hash UNIQUE (import_job_id, line_hash) ); CREATE INDEX IF NOT EXISTS idx_also_import_lines_job ON also_import_lines(import_job_id); CREATE INDEX IF NOT EXISTS idx_also_import_lines_status ON also_import_lines(queue_status); CREATE INDEX IF NOT EXISTS idx_also_import_lines_material_vendor ON also_import_lines(material_number, vendor); CREATE INDEX IF NOT EXISTS idx_also_import_lines_customer_keys ON also_import_lines(also_company_id, customer_id, vat); CREATE INDEX IF NOT EXISTS idx_also_import_lines_unmatched_customer ON also_import_lines(matched_customer_id) WHERE matched_customer_id IS NULL; CREATE INDEX IF NOT EXISTS idx_also_import_lines_unmatched_product ON also_import_lines(matched_product_id) WHERE matched_product_id IS NULL; CREATE TABLE IF NOT EXISTS also_company_mapping ( id SERIAL PRIMARY KEY, also_company_id VARCHAR(128) NOT NULL UNIQUE, also_customer_id VARCHAR(128), customer_id INTEGER NOT NULL REFERENCES customers(id) ON DELETE CASCADE, match_confidence NUMERIC(5, 2), notes TEXT, is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_also_company_mapping_customer ON also_company_mapping(customer_id); CREATE TABLE IF NOT EXISTS also_product_mapping ( id SERIAL PRIMARY KEY, material_number VARCHAR(128) NOT NULL, vendor VARCHAR(255) NOT NULL, hub_product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE, product_name_snapshot VARCHAR(512), is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uniq_also_product_mapping UNIQUE (material_number, vendor) ); CREATE INDEX IF NOT EXISTS idx_also_product_mapping_product ON also_product_mapping(hub_product_id);