Inital Commit
This commit is contained in:
156
scripts/import-anlagen-itt.mjs
Normal file
156
scripts/import-anlagen-itt.mjs
Normal file
@@ -0,0 +1,156 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { fileURLToPath } from 'url';
|
||||
import XLSX from 'xlsx';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.join(__dirname, '..');
|
||||
const xlsxPath = path.join(root, 'Anlagenliste ITT.xlsx');
|
||||
const dbPath = process.env.SQLITE_PATH || path.join(root, 'data', 'crm.db');
|
||||
|
||||
/** Zeile 9 (1-basiert): Spaltenbeschriftung / Feldnamen */
|
||||
const ZEILE_BESCHREIBUNG = 9;
|
||||
/** Zeile 7: Gruppierung / übergeordnete Rubrik pro Spalte (z. B. „Kunde“) */
|
||||
const ZEILE_GRUPPE = 7;
|
||||
/** Nur Spalten 1–99 importieren; Spalten 100–180 entfallen */
|
||||
const MAX_SPALTEN = 99;
|
||||
|
||||
if (!fs.existsSync(xlsxPath)) {
|
||||
console.error('Datei nicht gefunden:', xlsxPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function dedupeHeaders(raw) {
|
||||
const count = {};
|
||||
return raw.map((h) => {
|
||||
const base = String(h ?? '').trim() || 'Spalte';
|
||||
count[base] = (count[base] || 0) + 1;
|
||||
return count[base] === 1 ? base : `${base}_${count[base]}`;
|
||||
});
|
||||
}
|
||||
|
||||
function padRow(arr, width) {
|
||||
const a = Array.isArray(arr) ? [...arr] : [];
|
||||
while (a.length < width) a.push('');
|
||||
return a.slice(0, width);
|
||||
}
|
||||
|
||||
const wb = XLSX.readFile(xlsxPath);
|
||||
const sheet =
|
||||
wb.Sheets.Anlagen ||
|
||||
wb.Sheets[wb.SheetNames.find((n) => /anlagen/i.test(n))] ||
|
||||
wb.Sheets[wb.SheetNames[0]];
|
||||
|
||||
const aoa = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '' });
|
||||
const beschreibIdx = ZEILE_BESCHREIBUNG - 1;
|
||||
const gruppeIdx = ZEILE_GRUPPE - 1;
|
||||
|
||||
let width = 0;
|
||||
for (const r of [beschreibIdx, gruppeIdx]) {
|
||||
if (aoa[r] && aoa[r].length > width) width = aoa[r].length;
|
||||
}
|
||||
if (sheet['!ref']) {
|
||||
const range = XLSX.utils.decode_range(sheet['!ref']);
|
||||
width = Math.max(width, range.e.c + 1);
|
||||
}
|
||||
|
||||
width = Math.min(width, MAX_SPALTEN);
|
||||
|
||||
const rawRow9 = padRow(aoa[beschreibIdx] || [], width).map((h) =>
|
||||
String(h ?? '').trim(),
|
||||
);
|
||||
const rawRow7 = padRow(aoa[gruppeIdx] || [], width).map((h) =>
|
||||
String(h ?? '').trim(),
|
||||
);
|
||||
const headers = dedupeHeaders(rawRow9);
|
||||
|
||||
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
||||
const db = new DatabaseSync(dbPath);
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
|
||||
const cols = db.prepare('PRAGMA table_info(machines)').all();
|
||||
if (!cols.some((c) => c.name === 'extras')) {
|
||||
db.exec('ALTER TABLE machines ADD COLUMN extras TEXT');
|
||||
}
|
||||
|
||||
const replaceAll = process.argv.includes('--replace');
|
||||
if (replaceAll) {
|
||||
db.exec('DELETE FROM events');
|
||||
db.exec('DELETE FROM tickets');
|
||||
db.exec('DELETE FROM machines');
|
||||
console.log('Bestehende Maschinen/Tickets/Events gelöscht.');
|
||||
}
|
||||
|
||||
const insertMachine = db.prepare(
|
||||
`INSERT INTO machines (id, name, typ, seriennummer, standort, extras, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
|
||||
);
|
||||
|
||||
let imported = 0;
|
||||
let skipped = 0;
|
||||
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
for (let i = beschreibIdx + 1; i < aoa.length; i++) {
|
||||
const row = aoa[i];
|
||||
if (!row || !row.length) continue;
|
||||
const padded = padRow(row, width);
|
||||
const sn = String(padded[0] ?? '').trim();
|
||||
if (!/^ITT#/i.test(sn)) continue;
|
||||
|
||||
const dup = db
|
||||
.prepare('SELECT id FROM machines WHERE seriennummer = ?')
|
||||
.get(sn);
|
||||
if (dup) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const rowObj = {};
|
||||
headers.forEach((h, j) => {
|
||||
const v = padded[j];
|
||||
rowObj[h] =
|
||||
v === '' || v === undefined || v === null ? '' : String(v).trim();
|
||||
});
|
||||
|
||||
const typ = rowObj.Typ || '—';
|
||||
const standort =
|
||||
[rowObj.Stadt, rowObj.Land].filter(Boolean).join(', ') || '—';
|
||||
|
||||
const werteAlsListe = padded.map((v) =>
|
||||
v === '' || v === undefined || v === null ? '' : String(v).trim(),
|
||||
);
|
||||
|
||||
const extrasObj = {
|
||||
_beschriftungZeile9: rawRow9,
|
||||
_gruppeZeile7: rawRow7,
|
||||
_werteAlsListe: werteAlsListe,
|
||||
...rowObj,
|
||||
};
|
||||
|
||||
const extrasJson = JSON.stringify(extrasObj);
|
||||
const id = randomUUID();
|
||||
|
||||
insertMachine.run(id, sn, typ, sn, standort, extrasJson);
|
||||
imported += 1;
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
} catch (e) {
|
||||
db.exec('ROLLBACK');
|
||||
throw e;
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log(
|
||||
`Anlagenliste: ${imported} Maschinen importiert, ${skipped} übersprungen (Seriennr. schon vorhanden).`,
|
||||
);
|
||||
if (!replaceAll && skipped > 0 && imported === 0) {
|
||||
console.log(
|
||||
'Hinweis: Für Neuimport: npm run import:anlagen -- --replace',
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user