Files
PriceForge/migrations/023_vendor_partnumber_global_mapping.sql

166 lines
6.2 KiB
SQL

-- Global vendor partnumber mapping foundation with backward compatibility.
ALTER TABLE lot_partnumbers
ADD COLUMN IF NOT EXISTS vendor VARCHAR(255) NOT NULL DEFAULT '' AFTER partnumber;
CREATE TABLE IF NOT EXISTS qt_lot_bundles (
bundle_lot_name VARCHAR(255) NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (bundle_lot_name)
);
CREATE TABLE IF NOT EXISTS qt_lot_bundle_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
bundle_lot_name VARCHAR(255) NOT NULL,
lot_name VARCHAR(255) NOT NULL,
qty DECIMAL(14,6) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_qt_lot_bundle_items_bundle_lot (bundle_lot_name, lot_name),
INDEX idx_qt_lot_bundle_items_lot_name (lot_name)
);
CREATE TABLE IF NOT EXISTS qt_vendor_partnumber_seen (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
source_type VARCHAR(32) NOT NULL,
vendor VARCHAR(255) NOT NULL DEFAULT '',
partnumber VARCHAR(255) NOT NULL,
description VARCHAR(10000) NULL,
last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_ignored TINYINT(1) NOT NULL DEFAULT 0,
ignored_at DATETIME NULL,
ignored_by VARCHAR(100) NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_qt_vendor_partnumber_seen_source_key (source_type, vendor, partnumber),
INDEX idx_qt_vendor_partnumber_seen_vendor_partnumber (vendor, partnumber),
INDEX idx_qt_vendor_partnumber_seen_ignored (is_ignored)
);
CREATE TABLE IF NOT EXISTS qt_bom (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
external_bom_id VARCHAR(255) NOT NULL,
source_system VARCHAR(100) NOT NULL,
payload JSON NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_qt_bom_source_external (source_system, external_bom_id)
);
ALTER TABLE qt_configurations
ADD COLUMN IF NOT EXISTS bom_id BIGINT UNSIGNED NULL AFTER competitor_pricelist_id;
-- Convert legacy many-to-one rows into bundle lots so (vendor,partnumber) can be unique.
CREATE TEMPORARY TABLE tmp_lp_dups AS
SELECT
vendor,
partnumber,
CONCAT('BND_', UPPER(SUBSTRING(MD5(CONCAT(vendor, '|', partnumber)), 1, 20))) AS bundle_lot_name,
MAX(description) AS description
FROM lot_partnumbers
GROUP BY vendor, partnumber
HAVING COUNT(*) > 1;
INSERT INTO lot (lot_name, lot_description, lot_category)
SELECT
d.bundle_lot_name,
CONCAT('Auto-generated bundle for ', d.partnumber, IF(d.vendor = '', '', CONCAT(' (', d.vendor, ')'))),
'BUNDLE'
FROM tmp_lp_dups d
LEFT JOIN lot l ON l.lot_name = d.bundle_lot_name
WHERE l.lot_name IS NULL;
INSERT INTO qt_lot_bundles (bundle_lot_name, is_active)
SELECT d.bundle_lot_name, 1
FROM tmp_lp_dups d
LEFT JOIN qt_lot_bundles b ON b.bundle_lot_name = d.bundle_lot_name
WHERE b.bundle_lot_name IS NULL;
INSERT INTO qt_lot_bundle_items (bundle_lot_name, lot_name, qty)
SELECT
d.bundle_lot_name,
lp.lot_name,
1
FROM tmp_lp_dups d
INNER JOIN lot_partnumbers lp
ON lp.vendor = d.vendor AND lp.partnumber = d.partnumber
WHERE TRIM(lp.lot_name) <> ''
ON DUPLICATE KEY UPDATE qty = VALUES(qty), updated_at = CURRENT_TIMESTAMP;
DELETE lp
FROM lot_partnumbers lp
INNER JOIN tmp_lp_dups d
ON lp.vendor = d.vendor AND lp.partnumber = d.partnumber;
INSERT INTO lot_partnumbers (vendor, partnumber, lot_name, description)
SELECT vendor, partnumber, bundle_lot_name, description
FROM tmp_lp_dups;
DROP TEMPORARY TABLE IF EXISTS tmp_lp_dups;
CREATE UNIQUE INDEX uq_lot_partnumbers_vendor_partnumber ON lot_partnumbers (vendor, partnumber);
CREATE INDEX idx_lot_partnumbers_vendor_partnumber ON lot_partnumbers (vendor, partnumber);
-- Add optional FKs if not present.
SET @fk_cfg_bom_exists := (
SELECT COUNT(*)
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND CONSTRAINT_NAME = 'fk_qt_configurations_bom'
);
SET @fk_cfg_bom_sql := IF(
@fk_cfg_bom_exists > 0,
'SELECT 1',
'ALTER TABLE qt_configurations ADD CONSTRAINT fk_qt_configurations_bom FOREIGN KEY (bom_id) REFERENCES qt_bom(id) ON DELETE SET NULL'
);
PREPARE stmt_cfg_bom FROM @fk_cfg_bom_sql;
EXECUTE stmt_cfg_bom;
DEALLOCATE PREPARE stmt_cfg_bom;
SET @fk_bundle_exists := (
SELECT COUNT(*)
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND CONSTRAINT_NAME = 'fk_qt_lot_bundles_lot'
);
SET @fk_bundle_sql := IF(
@fk_bundle_exists > 0,
'SELECT 1',
'ALTER TABLE qt_lot_bundles ADD CONSTRAINT fk_qt_lot_bundles_lot FOREIGN KEY (bundle_lot_name) REFERENCES lot(lot_name) ON DELETE CASCADE'
);
PREPARE stmt_bundle FROM @fk_bundle_sql;
EXECUTE stmt_bundle;
DEALLOCATE PREPARE stmt_bundle;
SET @fk_bundle_item_bundle_exists := (
SELECT COUNT(*)
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND CONSTRAINT_NAME = 'fk_qt_lot_bundle_items_bundle'
);
SET @fk_bundle_item_bundle_sql := IF(
@fk_bundle_item_bundle_exists > 0,
'SELECT 1',
'ALTER TABLE qt_lot_bundle_items ADD CONSTRAINT fk_qt_lot_bundle_items_bundle FOREIGN KEY (bundle_lot_name) REFERENCES qt_lot_bundles(bundle_lot_name) ON DELETE CASCADE'
);
PREPARE stmt_bundle_item_bundle FROM @fk_bundle_item_bundle_sql;
EXECUTE stmt_bundle_item_bundle;
DEALLOCATE PREPARE stmt_bundle_item_bundle;
SET @fk_bundle_item_lot_exists := (
SELECT COUNT(*)
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND CONSTRAINT_NAME = 'fk_qt_lot_bundle_items_lot'
);
SET @fk_bundle_item_lot_sql := IF(
@fk_bundle_item_lot_exists > 0,
'SELECT 1',
'ALTER TABLE qt_lot_bundle_items ADD CONSTRAINT fk_qt_lot_bundle_items_lot FOREIGN KEY (lot_name) REFERENCES lot(lot_name) ON DELETE RESTRICT'
);
PREPARE stmt_bundle_item_lot FROM @fk_bundle_item_lot_sql;
EXECUTE stmt_bundle_item_lot;
DEALLOCATE PREPARE stmt_bundle_item_lot;