const express = require('express'); const db = require('../db'); const asyncHandler = require('../middleware/asyncHandler'); const router = express.Router(); function validateDate(s) { return typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s) && !isNaN(new Date(s).getTime()); } function validateHardware(h) { return !h || String(h).length === 15; } function validateAmount(a) { return a === null || a === undefined || (Number.isFinite(parseFloat(a)) && parseFloat(a) >= 0); } function validateSchaltungBody(body) { const { schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname, rufnummer, anbieter_id, tarif_id, hardware, hardware_ek, kundenverkaufspreis_netto } = body || {}; if (!schaltungs_id || !schaltungs_id.trim()) return 'Schaltungs-ID ist erforderlich'; if (!validateDate(eingabe_datum)) return 'Ungültiges Eingabe-Datum (Format: YYYY-MM-DD)'; if (!validateDate(schaltungs_datum)) return 'Ungültiges Schaltungs-Datum (Format: YYYY-MM-DD)'; if (!vorname || !vorname.trim()) return 'Vorname ist erforderlich'; if (!nachname || !nachname.trim()) return 'Nachname ist erforderlich'; if (!rufnummer || !rufnummer.trim()) return 'Rufnummer ist erforderlich'; if (!anbieter_id) return 'Anbieter ist erforderlich'; if (!tarif_id) return 'Tarif ist erforderlich'; if (!validateHardware(hardware)) return 'Hardware muss genau 15 Zeichen lang sein'; if (hardware && hardware.trim() !== '') { if (!validateAmount(hardware_ek)) return 'Hardware-EK muss eine positive Zahl sein'; if (!validateAmount(kundenverkaufspreis_netto)) return 'Kundenverkaufspreis muss eine positive Zahl sein'; } return null; } router.get('/', asyncHandler(async (req, res) => { const rows = await db.all( `SELECT s.*, a.name AS anbieter_name, t.name AS tarif_name FROM schaltungen s JOIN anbieter a ON s.anbieter_id = a.id JOIN tarife t ON s.tarif_id = t.id ORDER BY s.created_at DESC` ); res.json(rows); })); router.post('/', asyncHandler(async (req, res) => { const err = validateSchaltungBody(req.body); if (err) return res.status(400).json({ error: err }); const b = req.body; try { const result = await db.run( `INSERT INTO schaltungen (schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname, firma, rufnummer, anbieter_id, tarif_id, hardware, hardware_ek, kundenverkaufspreis_netto) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ b.schaltungs_id, b.eingabe_datum, b.schaltungs_datum, b.vorname, b.nachname, b.firma || null, b.rufnummer, b.anbieter_id, b.tarif_id, b.hardware || null, b.hardware_ek || null, b.kundenverkaufspreis_netto || null, ] ); res.json({ id: result.lastID, message: 'Schaltung erfolgreich erstellt' }); } catch (e) { if (/UNIQUE/.test(e.message)) { return res.status(400).json({ error: 'Schaltungs-ID existiert bereits' }); } throw e; } })); router.put('/:id', asyncHandler(async (req, res) => { const { id } = req.params; const err = validateSchaltungBody(req.body); if (err) return res.status(400).json({ error: err }); const b = req.body; const tarifoptionen = Array.isArray(b.tarifoptionen) ? b.tarifoptionen : []; await db.transaction(async () => { const upd = await db.run( `UPDATE schaltungen SET schaltungs_id = ?, eingabe_datum = ?, schaltungs_datum = ?, vorname = ?, nachname = ?, firma = ?, rufnummer = ?, anbieter_id = ?, tarif_id = ?, hardware = ?, hardware_ek = ?, kundenverkaufspreis_netto = ? WHERE id = ?`, [ b.schaltungs_id, b.eingabe_datum, b.schaltungs_datum, b.vorname, b.nachname, b.firma || null, b.rufnummer, b.anbieter_id, b.tarif_id, b.hardware || null, b.hardware_ek || null, b.kundenverkaufspreis_netto || null, id, ] ); if (upd.changes === 0) { const e = new Error('Schaltung nicht gefunden'); e.status = 404; throw e; } await db.run('DELETE FROM schaltungen_tarifoptionen WHERE schaltung_id = ?', [id]); for (const tarifoptionId of tarifoptionen) { await db.run( 'INSERT INTO schaltungen_tarifoptionen (schaltung_id, tarifoption_id) VALUES (?, ?)', [id, tarifoptionId] ); } }); res.json({ message: 'Schaltung erfolgreich aktualisiert' }); })); router.delete('/:id', asyncHandler(async (req, res) => { const { id } = req.params; const { loeschgrund } = req.body || {}; if (!loeschgrund || !loeschgrund.trim()) { return res.status(400).json({ error: 'Löschgrund ist erforderlich' }); } const schaltung = await db.get( `SELECT s.*, a.name AS anbieter_name, t.name AS tarif_name FROM schaltungen s JOIN anbieter a ON s.anbieter_id = a.id JOIN tarife t ON s.tarif_id = t.id WHERE s.id = ?`, [id] ); if (!schaltung) return res.status(404).json({ error: 'Schaltung nicht gefunden' }); const documents = await db.all('SELECT filepath FROM dokumente WHERE schaltung_id = ?', [id]); const { safeUploadPath } = req.app.locals; const fs = require('fs'); await db.transaction(async () => { await db.run( `INSERT INTO geloeschte_schaltungen ( original_id, schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname, firma, rufnummer, anbieter_id, tarif_id, hardware, anbieter_name, tarif_name, loeschgrund, geloescht_von ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ schaltung.id, schaltung.schaltungs_id, schaltung.eingabe_datum, schaltung.schaltungs_datum, schaltung.vorname, schaltung.nachname, schaltung.firma, schaltung.rufnummer, schaltung.anbieter_id, schaltung.tarif_id, schaltung.hardware, schaltung.anbieter_name, schaltung.tarif_name, loeschgrund.trim(), req.session && req.session.username ? req.session.username : 'System', ] ); // Existing DBs may lack ON DELETE CASCADE on dokumente — delete explicitly. await db.run('DELETE FROM dokumente WHERE schaltung_id = ?', [id]); await db.run('DELETE FROM schaltungen WHERE id = ?', [id]); }); // Best-effort file cleanup, outside the transaction. let deletedFiles = 0; for (const doc of documents) { const filePath = safeUploadPath(doc.filepath); if (!filePath) { console.error(`Skipping file delete - invalid path: ${doc.filepath}`); continue; } try { fs.unlinkSync(filePath); deletedFiles++; } catch (e) { console.error(`Error deleting ${filePath}:`, e.message); } } res.json({ message: 'Schaltung erfolgreich gelöscht und archiviert', loeschgrund: loeschgrund.trim(), deleted_files: deletedFiles, }); })); // Tarifoptionen linked to a specific schaltung router.get('/:id/tarifoptionen', asyncHandler(async (req, res) => { const { id } = req.params; const rows = await db.all( `SELECT t.*, st.id AS schaltung_tarifoption_id FROM tarifoptionen t JOIN schaltungen_tarifoptionen st ON t.id = st.tarifoption_id WHERE st.schaltung_id = ? ORDER BY t.name`, [id] ); res.json(rows); })); router.post('/:id/tarifoptionen', asyncHandler(async (req, res) => { const schaltung_id = req.params.id; const { tarifoption_id } = req.body || {}; if (!tarifoption_id) return res.status(400).json({ error: 'tarifoption_id ist erforderlich' }); try { const result = await db.run( 'INSERT INTO schaltungen_tarifoptionen (schaltung_id, tarifoption_id) VALUES (?, ?)', [schaltung_id, tarifoption_id] ); res.json({ id: result.lastID, message: 'Tarifoption erfolgreich hinzugefügt' }); } catch (err) { if (/UNIQUE/.test(err.message)) { return res.status(400).json({ error: 'Tarifoption bereits hinzugefügt' }); } throw err; } })); router.delete('/:schaltung_id/tarifoptionen/:tarifoption_id', asyncHandler(async (req, res) => { const { schaltung_id, tarifoption_id } = req.params; const result = await db.run( 'DELETE FROM schaltungen_tarifoptionen WHERE schaltung_id = ? AND tarifoption_id = ?', [schaltung_id, tarifoption_id] ); if (result.changes === 0) return res.status(404).json({ error: 'Verknüpfung nicht gefunden' }); res.json({ message: 'Tarifoption erfolgreich entfernt' }); })); module.exports = router;