179 lines
5.2 KiB
JavaScript
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);
|
|
});
|