-- Bygma Prisbog Database Schema - Final Version -- Perfectly aligned with actual CSV structure -- CSV columns: Varegrp;VareNr;Tekst;Enhed;BruttoPris;Opdat;Nettopris;DB-nr;EAN-nr;Std USE tilbudgivern; -- Drop existing triggers first to avoid conflicts DROP TRIGGER IF EXISTS tr_bygma_products_update_last_seen; DROP TRIGGER IF EXISTS tr_bygma_price_history_calculate_change; -- Drop existing tables in correct order to avoid foreign key constraints DROP TABLE IF EXISTS bygma_materials_mapping; DROP TABLE IF EXISTS bygma_price_history; DROP TABLE IF EXISTS bygma_import_log; DROP TABLE IF EXISTS bygma_product_groups; DROP TABLE IF EXISTS bygma_products; -- Main table for Bygma products - exactly matching CSV structure CREATE TABLE bygma_products ( id INT PRIMARY KEY AUTO_INCREMENT, -- Exact CSV columns varegrp VARCHAR(10), -- CSV: Varegrp vareNr VARCHAR(50) NOT NULL, -- CSV: VareNr (unique identifier) tekst TEXT, -- CSV: Tekst enhed VARCHAR(20), -- CSV: Enhed db_nr VARCHAR(50), -- CSV: DB-nr ean_nr VARCHAR(50), -- CSV: EAN-nr std VARCHAR(50), -- CSV: Std -- Current pricing (from most recent import) current_brutto_pris DECIMAL(15,2), -- CSV: BruttoPris current_netto_pris DECIMAL(15,2), -- CSV: Nettopris current_opdat TINYINT, -- CSV: Opdat -- Metadata first_seen DATE, last_seen DATE, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Indexes for performance UNIQUE KEY idx_vareNr (vareNr), INDEX idx_varegrp (varegrp), INDEX idx_tekst (tekst(100)), INDEX idx_current_prices (current_brutto_pris, current_netto_pris), INDEX idx_active (is_active), INDEX idx_last_seen (last_seen) ); -- Product groups for categorization CREATE TABLE bygma_product_groups ( id INT PRIMARY KEY AUTO_INCREMENT, varegrp VARCHAR(10) NOT NULL UNIQUE, group_name VARCHAR(255), description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_varegrp (varegrp) ); -- Price history to track all price changes over time CREATE TABLE bygma_price_history ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, -- Exact price fields from CSV brutto_pris DECIMAL(15,2), -- CSV: BruttoPris netto_pris DECIMAL(15,2), -- CSV: Nettopris opdat TINYINT, -- CSV: Opdat -- Price change tracking price_change_pct DECIMAL(8,4) DEFAULT 0, is_current BOOLEAN DEFAULT TRUE, -- Import metadata import_date DATE, import_file VARCHAR(500), import_batch_id VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES bygma_products(id) ON DELETE CASCADE, INDEX idx_product (product_id), INDEX idx_import_batch (import_batch_id), INDEX idx_import_date (import_date), INDEX idx_current (is_current), INDEX idx_prices (brutto_pris, netto_pris) ); -- Import log to track all imports CREATE TABLE bygma_import_log ( id INT PRIMARY KEY AUTO_INCREMENT, import_batch_id VARCHAR(100) NOT NULL UNIQUE, filename VARCHAR(500), filepath VARCHAR(500), file_hash VARCHAR(64), file_size BIGINT, -- Import statistics total_rows INT DEFAULT 0, processed_rows INT DEFAULT 0, successful_rows INT DEFAULT 0, failed_rows INT DEFAULT 0, new_products INT DEFAULT 0, updated_products INT DEFAULT 0, updated_prices INT DEFAULT 0, -- Status tracking status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending', error_summary TEXT, warnings TEXT, -- Timing started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP NULL, duration_seconds INT, -- User tracking imported_by VARCHAR(100), INDEX idx_batch_id (import_batch_id), INDEX idx_status (status), INDEX idx_started_at (started_at), INDEX idx_filename (filename(100)) ); -- Mapping table to connect Bygma products with existing materials CREATE TABLE bygma_materials_mapping ( id INT PRIMARY KEY AUTO_INCREMENT, bygma_product_id INT NOT NULL, material_id INT, -- Can be NULL if not mapped yet -- Mapping metadata mapping_type ENUM('auto', 'manual', 'ai_suggested') DEFAULT 'auto', mapping_confidence DECIMAL(5,4), -- 0.0 to 1.0 auto_update_price BOOLEAN DEFAULT FALSE, price_markup_pct DECIMAL(8,4) DEFAULT 0, -- Markup percentage created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, mapped_by VARCHAR(100), FOREIGN KEY (bygma_product_id) REFERENCES bygma_products(id) ON DELETE CASCADE, INDEX idx_bygma_product (bygma_product_id), INDEX idx_material (material_id), INDEX idx_mapping_type (mapping_type), INDEX idx_auto_update (auto_update_price), -- Ensure only one mapping per Bygma product UNIQUE KEY unique_bygma_mapping (bygma_product_id) ); -- Simplified Triggers (without recursive calls) DELIMITER // -- Trigger: Update last_seen and current prices when new price comes in CREATE TRIGGER tr_bygma_products_update_last_seen AFTER INSERT ON bygma_price_history FOR EACH ROW BEGIN UPDATE bygma_products SET last_seen = NEW.import_date, current_brutto_pris = NEW.brutto_pris, current_netto_pris = NEW.netto_pris, current_opdat = NEW.opdat, updated_at = CURRENT_TIMESTAMP WHERE id = NEW.product_id; END// -- Trigger: Calculate price change percentage and mark previous prices as not current CREATE TRIGGER tr_bygma_price_history_calculate_change BEFORE INSERT ON bygma_price_history FOR EACH ROW BEGIN DECLARE prev_price DECIMAL(15,2); -- Find previous price SELECT netto_pris INTO prev_price FROM bygma_price_history WHERE product_id = NEW.product_id AND is_current = TRUE ORDER BY created_at DESC LIMIT 1; -- Calculate change if there's a previous price IF prev_price IS NOT NULL AND prev_price > 0 THEN SET NEW.price_change_pct = ((NEW.netto_pris - prev_price) / prev_price) * 100; END IF; -- Mark all other prices for this product as not current UPDATE bygma_price_history SET is_current = FALSE WHERE product_id = NEW.product_id; END// DELIMITER ; -- Useful views for reporting and analysis -- View: Current prices with product details CREATE VIEW v_bygma_current_prices AS SELECT p.id, p.vareNr, p.tekst, p.enhed, p.varegrp, p.current_brutto_pris, p.current_netto_pris, p.current_opdat, p.last_seen, pg.group_name, mm.material_id as mapped_material_id, mm.mapping_type, mm.auto_update_price, mm.price_markup_pct FROM bygma_products p LEFT JOIN bygma_product_groups pg ON p.varegrp = pg.varegrp LEFT JOIN bygma_materials_mapping mm ON p.id = mm.bygma_product_id WHERE p.is_active = TRUE; -- View: Price history with changes CREATE VIEW v_bygma_price_changes AS SELECT ph.id, p.vareNr, p.tekst, ph.brutto_pris, ph.netto_pris, ph.opdat, ph.price_change_pct, ph.import_date, ph.import_file, ph.is_current FROM bygma_price_history ph JOIN bygma_products p ON ph.product_id = p.id ORDER BY ph.import_date DESC, p.vareNr; -- View: Import statistics CREATE VIEW v_bygma_import_stats AS SELECT il.id, il.import_batch_id, il.filename, il.total_rows, il.processed_rows, il.successful_rows, il.failed_rows, il.new_products, il.updated_products, il.updated_prices, il.status, il.started_at, il.completed_at, il.duration_seconds, il.imported_by, ROUND((il.successful_rows / NULLIF(il.total_rows, 0)) * 100, 2) as success_rate_pct FROM bygma_import_log il ORDER BY il.started_at DESC; -- Add some basic product groups based on common Bygma categories INSERT INTO bygma_product_groups (varegrp, group_name) VALUES ('1000', 'Tømmer og planker'), ('3310', 'Porebeton og isolering'), ('3320', 'Leca og letbeton'), ('3330', 'Mursten og blokke'), ('2000', 'Plader og isolering'), ('4000', 'Tagmaterialer'), ('5000', 'Vinduer og døre'), ('6000', 'Beton og cement'), ('7000', 'Værktøj'), ('8000', 'Beslag og fittings'), ('9000', 'Diverse byggematerialer') ON DUPLICATE KEY UPDATE group_name = VALUES(group_name);