178 lines
5.2 KiB
JavaScript
178 lines
5.2 KiB
JavaScript
const path = require('path');
|
|
const mysql = require('mysql2/promise');
|
|
|
|
require('dotenv').config({
|
|
path: path.join(__dirname, '..', 'backend', '.env')
|
|
});
|
|
|
|
function toMs(value) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return null;
|
|
}
|
|
const n = Number(value);
|
|
if (!Number.isFinite(n) || n <= 0) {
|
|
return null;
|
|
}
|
|
return n > 100000000000 ? n : n * 1000;
|
|
}
|
|
|
|
function toDate(value) {
|
|
const ms = toMs(value);
|
|
if (!ms) {
|
|
return null;
|
|
}
|
|
const date = new Date(ms);
|
|
return Number.isNaN(date.getTime()) ? null : date;
|
|
}
|
|
|
|
function toTaskKey(remark = '') {
|
|
const tokens = String(remark || '')
|
|
.toLowerCase()
|
|
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
|
|
.split(/\s+/)
|
|
.filter((token) => token.length >= 3)
|
|
.slice(0, 6);
|
|
return tokens.length > 0 ? tokens.join('_') : 'ukendt_opgave';
|
|
}
|
|
|
|
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_hours_normalized (
|
|
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
case_number VARCHAR(64) NOT NULL,
|
|
hour_id INT NOT NULL,
|
|
is_case_linked TINYINT(1) NOT NULL DEFAULT 1,
|
|
start_at DATETIME NULL,
|
|
stop_at DATETIME NULL,
|
|
duration_hours DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
remark TEXT NULL,
|
|
hour_type INT NULL,
|
|
task_key VARCHAR(255) NULL,
|
|
synced_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_hours_norm_case_hour (case_number, hour_id),
|
|
INDEX idx_hours_norm_case (case_number),
|
|
INDEX idx_hours_norm_case_linked (is_case_linked),
|
|
INDEX idx_hours_norm_start (start_at)
|
|
)
|
|
`
|
|
);
|
|
|
|
await conn.execute(`
|
|
ALTER TABLE ordrestyring_hours_normalized
|
|
ADD COLUMN IF NOT EXISTS is_case_linked TINYINT(1) NOT NULL DEFAULT 1
|
|
`);
|
|
await conn.execute(`
|
|
CREATE INDEX IF NOT EXISTS idx_hours_norm_case_linked
|
|
ON ordrestyring_hours_normalized (is_case_linked)
|
|
`);
|
|
|
|
const [rows] = await conn.execute(
|
|
`
|
|
SELECT
|
|
h.id,
|
|
h.new_case_number,
|
|
h.start_time,
|
|
h.stop_time,
|
|
h.remark,
|
|
h.hour_type
|
|
FROM ordrestyring_local.hours h
|
|
`
|
|
);
|
|
|
|
await conn.execute('TRUNCATE TABLE ordrestyring_hours_normalized');
|
|
|
|
const insertSql = `
|
|
INSERT INTO ordrestyring_hours_normalized (
|
|
case_number,
|
|
hour_id,
|
|
is_case_linked,
|
|
start_at,
|
|
stop_at,
|
|
duration_hours,
|
|
remark,
|
|
hour_type,
|
|
task_key
|
|
) 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 start = toDate(row.start_time);
|
|
const stop = toDate(row.stop_time);
|
|
let durationHours = 0;
|
|
if (start && stop) {
|
|
const diff = stop.getTime() - start.getTime();
|
|
if (diff > 0) {
|
|
durationHours = Number((diff / 3600000).toFixed(2));
|
|
}
|
|
}
|
|
|
|
const rawCaseNumber = String(row.new_case_number || '').trim();
|
|
const isCaseLinked = rawCaseNumber.length > 0;
|
|
const hourTypeValue = row.hour_type !== null && row.hour_type !== undefined ? Number(row.hour_type) : null;
|
|
const normalizedCaseNumber = isCaseLinked
|
|
? rawCaseNumber
|
|
: `__NON_CASE__:${hourTypeValue !== null ? hourTypeValue : 'unknown'}`;
|
|
|
|
await conn.execute(insertSql, [
|
|
normalizedCaseNumber,
|
|
Number(row.id),
|
|
isCaseLinked ? 1 : 0,
|
|
start,
|
|
stop,
|
|
durationHours,
|
|
row.remark || null,
|
|
hourTypeValue,
|
|
toTaskKey(row.remark)
|
|
]);
|
|
}
|
|
}
|
|
|
|
const [[stats]] = await conn.execute(
|
|
`
|
|
SELECT
|
|
COUNT(*) AS rows_inserted,
|
|
COUNT(DISTINCT case_number) AS distinct_cases,
|
|
SUM(CASE WHEN is_case_linked = 1 THEN 1 ELSE 0 END) AS rows_case_linked,
|
|
SUM(CASE WHEN is_case_linked = 0 THEN 1 ELSE 0 END) AS rows_non_case,
|
|
SUM(CASE WHEN duration_hours > 0 THEN 1 ELSE 0 END) AS rows_with_duration,
|
|
SUM(CASE WHEN remark IS NOT NULL AND TRIM(remark) <> '' THEN 1 ELSE 0 END) AS rows_with_remark
|
|
FROM ordrestyring_hours_normalized
|
|
`
|
|
);
|
|
|
|
console.log(JSON.stringify({
|
|
table: 'ordrestyring_hours_normalized',
|
|
rowsInserted: Number(stats.rows_inserted || 0),
|
|
distinctCases: Number(stats.distinct_cases || 0),
|
|
rowsCaseLinked: Number(stats.rows_case_linked || 0),
|
|
rowsNonCase: Number(stats.rows_non_case || 0),
|
|
rowsWithDuration: Number(stats.rows_with_duration || 0),
|
|
rowsWithRemark: Number(stats.rows_with_remark || 0)
|
|
}, null, 2));
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|