Inital Commit

This commit is contained in:
2026-03-22 19:26:35 +01:00
commit 705329d3c2
17 changed files with 5538 additions and 0 deletions

View 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 199 importieren; Spalten 100180 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',
);
}