synch correction and db update
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* generate-cl-seed.cjs
|
||||
*
|
||||
* Reads both Punchlist_Reference sheets from the Excel templates and writes
|
||||
* sql/cl-seed.sql containing INSERT statements for:
|
||||
* - admin_categories (unique category values)
|
||||
* - admin_sub_categories (unique sub-category per category, with FK)
|
||||
* - admin_severities (unique severity values, normalised to title-case)
|
||||
* - admin_cl_records (every valid row – skips rows with no severity)
|
||||
*
|
||||
* Run inside the Docker app container:
|
||||
* node /workspace/scripts/generate-cl-seed.cjs
|
||||
*
|
||||
* Normalisation rules applied to severities:
|
||||
* - Leading / trailing whitespace stripped.
|
||||
* - Each word capitalised (title-case) so that "major", "Major" and
|
||||
* "MAJOR" all become "Major"; "on hold" / "On hold" both become "On Hold".
|
||||
* - Rows whose severity cell is empty, null or undefined are skipped entirely.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const XLSX = require('/app/node_modules/xlsx');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// ─── helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Escape a value for use inside a SQL single-quoted string literal.
|
||||
* - Replaces CR/LF sequences and bare LF with a single space so the SQL
|
||||
* file stays on one logical line per value (avoids broken multi-line
|
||||
* string literals that confuse some SQL clients).
|
||||
* - Escapes embedded single quotes by doubling them.
|
||||
*/
|
||||
function esc(v) {
|
||||
if (v == null) return '';
|
||||
return String(v)
|
||||
.replace(/\r\n|\r|\n/g, ' ') // collapse newlines to space
|
||||
.replace(/'/g, "''"); // escape single quotes
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to title-case.
|
||||
* "on hold" → "On Hold" "major" → "Major"
|
||||
*/
|
||||
function toTitleCase(str) {
|
||||
return str
|
||||
.trim()
|
||||
.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());
|
||||
}
|
||||
|
||||
// ─── load both files ─────────────────────────────────────────────────────────
|
||||
|
||||
const FILES = [
|
||||
'/workspace/cl_template/OVXXX_AURORA_ANT_CL_MW_V3.2_20220502.xlsx',
|
||||
'/workspace/cl_template/OVXXX_AURORA_CW_QC_MW_V3.2_20220502.xlsx',
|
||||
];
|
||||
|
||||
const allRows = []; // { sort_order, category, sub_category, desc_en, desc_fr, desc_nl, severity }
|
||||
|
||||
for (const filePath of FILES) {
|
||||
const wb = XLSX.readFile(filePath);
|
||||
const ws = wb.Sheets['Punchlist_Reference'];
|
||||
const data = XLSX.utils.sheet_to_json(ws, { header: 1 });
|
||||
|
||||
// Row 0 is the header; skip it.
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
const row = data[i];
|
||||
|
||||
// Column A (index 0) must have a numeric sort_order; empty trailing rows skipped.
|
||||
if (row[0] == null) continue;
|
||||
|
||||
const sortOrder = Number(row[0]);
|
||||
const category = String(row[4] ?? '').trim();
|
||||
const subCat = String(row[5] ?? '').trim();
|
||||
const descEn = String(row[6] ?? '').trim();
|
||||
const descFr = String(row[7] ?? '').trim();
|
||||
const descNl = String(row[8] ?? '').trim();
|
||||
const severityRaw = row[9];
|
||||
|
||||
// Skip rows that carry no severity value.
|
||||
if (severityRaw == null || String(severityRaw).trim() === '') {
|
||||
console.warn(` Skipping row sort_order=${sortOrder}: no severity value`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const severity = toTitleCase(String(severityRaw));
|
||||
|
||||
allRows.push({ sortOrder, category, subCat, descEn, descFr, descNl, severity });
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Loaded ${allRows.length} valid rows total.`);
|
||||
|
||||
// ─── derive unique lookup sets (insertion-order preserved via Map) ────────────
|
||||
|
||||
// Categories: unique by value
|
||||
const categoriesMap = new Map(); // value → true (just for dedup, order preserved)
|
||||
for (const r of allRows) {
|
||||
if (!categoriesMap.has(r.category)) categoriesMap.set(r.category, true);
|
||||
}
|
||||
const categories = [...categoriesMap.keys()]; // ordered list
|
||||
|
||||
// Sub-categories: unique by (category, subCat)
|
||||
const subCatMap = new Map(); // "category|||subCat" → { category, subCat }
|
||||
for (const r of allRows) {
|
||||
const key = `${r.category}|||${r.subCat}`;
|
||||
if (!subCatMap.has(key)) subCatMap.set(key, { category: r.category, subCat: r.subCat });
|
||||
}
|
||||
const subCats = [...subCatMap.values()];
|
||||
|
||||
// Severities: unique by normalised value
|
||||
const severitiesMap = new Map();
|
||||
for (const r of allRows) {
|
||||
if (!severitiesMap.has(r.severity)) severitiesMap.set(r.severity, true);
|
||||
}
|
||||
const severities = [...severitiesMap.keys()];
|
||||
|
||||
console.log(`Categories: ${categories.length}, SubCategories: ${subCats.length}, Severities: ${severities.length}`);
|
||||
|
||||
// ─── build SQL ───────────────────────────────────────────────────────────────
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push('-- ============================================================');
|
||||
lines.push('-- cl-seed.sql');
|
||||
lines.push('--');
|
||||
lines.push('-- Auto-generated from Punchlist_Reference sheets in:');
|
||||
lines.push('-- OVXXX_AURORA_ANT_CL_MW_V3.2_20220502.xlsx');
|
||||
lines.push('-- OVXXX_AURORA_CW_QC_MW_V3.2_20220502.xlsx');
|
||||
lines.push('--');
|
||||
lines.push('-- Generated: ' + new Date().toISOString());
|
||||
lines.push('--');
|
||||
lines.push('-- Severity values are normalised to title-case.');
|
||||
lines.push('-- Records with no severity are omitted.');
|
||||
lines.push('-- Use INSERT IGNORE so re-running the script is idempotent.');
|
||||
lines.push('-- ============================================================');
|
||||
lines.push('');
|
||||
lines.push('USE check_list;');
|
||||
lines.push('');
|
||||
|
||||
// ── 1. admin_categories ────────────────────────────────────────────────────
|
||||
lines.push('-- ── 1. Categories ────────────────────────────────────────────');
|
||||
lines.push('INSERT IGNORE INTO admin_categories (value) VALUES');
|
||||
const catValues = categories.map(c => ` ('${esc(c)}')`);
|
||||
lines.push(catValues.join(',\n') + ';');
|
||||
lines.push('');
|
||||
|
||||
// ── 2. admin_sub_categories ───────────────────────────────────────────────
|
||||
// FK to admin_categories resolved via a UNION ALL derived table.
|
||||
// (The VALUES ROW() syntax requires MariaDB ≥ 10.3.3; UNION ALL is universal.)
|
||||
lines.push('-- ── 2. Sub-categories (FK resolved via sub-select) ──────────');
|
||||
lines.push('INSERT IGNORE INTO admin_sub_categories (value, category_id)');
|
||||
lines.push('SELECT vals.value, c.id');
|
||||
lines.push('FROM (');
|
||||
const scValues = subCats.map(
|
||||
(sc, i) =>
|
||||
` ${i === 0 ? 'SELECT' : 'UNION ALL SELECT'} '${esc(sc.subCat)}' AS value, '${esc(sc.category)}' AS cat_value`
|
||||
);
|
||||
lines.push(scValues.join('\n'));
|
||||
lines.push(') AS vals');
|
||||
lines.push('JOIN admin_categories c ON c.value = vals.cat_value;');
|
||||
lines.push('');
|
||||
|
||||
// ── 3. admin_severities ──────────────────────────────────────────────────
|
||||
lines.push('-- ── 3. Severities ───────────────────────────────────────────');
|
||||
lines.push('INSERT IGNORE INTO admin_severities (value) VALUES');
|
||||
const sevValues = severities.map(s => ` ('${esc(s)}')`);
|
||||
lines.push(sevValues.join(',\n') + ';');
|
||||
lines.push('');
|
||||
|
||||
// ── 4. admin_cl_records ──────────────────────────────────────────────────
|
||||
lines.push('-- ── 4. Check-list records ───────────────────────────────────');
|
||||
lines.push('INSERT IGNORE INTO admin_cl_records');
|
||||
lines.push(' (sort_order, category, sub_category, severity, description_en, description_fr, description_nl)');
|
||||
lines.push('VALUES');
|
||||
const recValues = allRows.map(r =>
|
||||
` (${r.sortOrder}, '${esc(r.category)}', '${esc(r.subCat)}', '${esc(r.severity)}', '${esc(r.descEn)}', '${esc(r.descFr)}', '${esc(r.descNl)}')`
|
||||
);
|
||||
lines.push(recValues.join(',\n') + ';');
|
||||
lines.push('');
|
||||
|
||||
// ─── write output file ───────────────────────────────────────────────────────
|
||||
|
||||
const outPath = '/workspace/sql/cl-seed.sql';
|
||||
fs.writeFileSync(outPath, lines.join('\n'), 'utf8');
|
||||
console.log(`\nSQL written to ${outPath}`);
|
||||
console.log(` ${categories.length} categories`);
|
||||
console.log(` ${subCats.length} sub-categories`);
|
||||
console.log(` ${severities.length} severities`);
|
||||
console.log(` ${allRows.length} cl_records`);
|
||||
Reference in New Issue
Block a user