Files
tilbudgivern/scripts/normalize-material-units.js
2026-07-05 08:10:59 +00:00

179 lines
5.2 KiB
JavaScript

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);
});