const path = require('path'); const mysql = require('mysql2/promise'); require('dotenv').config({ path: path.join(__dirname, '..', 'backend', '.env') }); function number(value) { const parsed = Number(String(value || '').replace(',', '.')); return Number.isFinite(parsed) ? parsed : 0; } function inferUnit(productText = '') { const text = String(productText || '').toLowerCase(); if (/\bm2\b|m²|kvadrat|kvm/.test(text)) { return 'm2'; } if (/\blbm\b|løbende|lobende|meter| m\b/.test(text)) { return 'lbm'; } if (/\bstk\b|styk|pcs|pk\b/.test(text)) { return 'stk'; } if (/\btime\b|timer\b/.test(text)) { return 'timer'; } return 'stk'; } function materialKey(productText = '', productNumber = '') { if (productNumber && String(productNumber).trim()) { return `nr_${String(productNumber).trim().toLowerCase()}`; } const tokens = String(productText || '') .toLowerCase() .replace(/[^\p{L}\p{N}\s]/gu, ' ') .split(/\s+/) .filter((token) => token.length >= 3) .slice(0, 8); return tokens.length > 0 ? tokens.join('_') : 'ukendt_materiale'; } async function main() { if (!process.env.DB_PASSWORD) { throw new Error('DB_PASSWORD is required'); } const conn = await mysql.createConnection({ host: process.env.DB_HOST || '127.0.0.1', port: parseInt(process.env.DB_PORT, 10) || 3306, user: process.env.DB_USER || 'tilbudgivern_service', password: process.env.DB_PASSWORD, database: process.env.DB_NAME || 'tilbudgivern' }); try { await conn.execute( ` CREATE TABLE IF NOT EXISTS ordrestyring_materials_normalized ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, case_number VARCHAR(64) NOT NULL, line_id INT NOT NULL, raw_unit VARCHAR(64) NULL, normalized_unit VARCHAR(32) NOT NULL, quantity_raw DECIMAL(14,3) NULL, quantity_normalized DECIMAL(14,3) NULL, unit_price DECIMAL(14,2) NULL, line_total DECIMAL(14,2) NULL, product_text TEXT NULL, product_number VARCHAR(255) NULL, material_key VARCHAR(255) NULL, created_at_ordrestyring DATETIME NULL, synced_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uq_materials_norm_case_line (case_number, line_id), INDEX idx_materials_norm_case (case_number), INDEX idx_materials_norm_key (material_key), INDEX idx_materials_norm_unit (normalized_unit) ) ` ); const [rows] = await conn.execute( ` SELECT m.id, m.case_number, m.quantity, m.product_text, m.product_number, m.sales_price, m.created_at FROM ordrestyring_local.case_materials m WHERE m.case_number IS NOT NULL AND m.case_number <> '' ` ); await conn.execute('TRUNCATE TABLE ordrestyring_materials_normalized'); const insertSql = ` INSERT INTO ordrestyring_materials_normalized ( case_number, line_id, raw_unit, normalized_unit, quantity_raw, quantity_normalized, unit_price, line_total, product_text, product_number, material_key, created_at_ordrestyring ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const chunkSize = 1000; for (let i = 0; i < rows.length; i += chunkSize) { const chunk = rows.slice(i, i + chunkSize); for (const row of chunk) { const qtyRaw = number(row.quantity); const unit = inferUnit(row.product_text); const salesPrice = number(row.sales_price); const lineTotal = qtyRaw * salesPrice; const createdAt = row.created_at ? (Number(row.created_at) > 100000000000 ? new Date(Number(row.created_at)) : new Date(Number(row.created_at) * 1000)) : null; await conn.execute(insertSql, [ String(row.case_number), Number(row.id), null, unit, qtyRaw, qtyRaw, salesPrice, lineTotal, row.product_text || null, row.product_number || null, materialKey(row.product_text, row.product_number), createdAt && !Number.isNaN(createdAt.getTime()) ? createdAt : null ]); } } const [[stats]] = await conn.execute( ` SELECT COUNT(*) AS rows_inserted, COUNT(DISTINCT case_number) AS distinct_cases, SUM(CASE WHEN normalized_unit = 'm2' THEN 1 ELSE 0 END) AS m2_rows, SUM(CASE WHEN normalized_unit = 'lbm' THEN 1 ELSE 0 END) AS lbm_rows, SUM(CASE WHEN normalized_unit = 'stk' THEN 1 ELSE 0 END) AS stk_rows FROM ordrestyring_materials_normalized ` ); console.log(JSON.stringify({ table: 'ordrestyring_materials_normalized', rowsInserted: Number(stats.rows_inserted || 0), distinctCases: Number(stats.distinct_cases || 0), units: { m2: Number(stats.m2_rows || 0), lbm: Number(stats.lbm_rows || 0), stk: Number(stats.stk_rows || 0) } }, null, 2)); } finally { await conn.end(); } } main().catch((error) => { console.error(error); process.exit(1); });