Node.js/Express + SQLite + Vanilla JS app for managing employee commissions on telecom activations (Telekom, Vodafone, O2, 1&1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2139 lines
73 KiB
JavaScript
2139 lines
73 KiB
JavaScript
const express = require('express');
|
|
const path = require('path');
|
|
const cors = require('cors');
|
|
const multer = require('multer');
|
|
const fs = require('fs');
|
|
const { initDatabase, getDatabase, closeDatabase } = require('./database');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// Initialize database
|
|
initDatabase();
|
|
|
|
// Graceful shutdown handler
|
|
function gracefulShutdown(signal) {
|
|
console.log(`\n${signal} received, closing database connection...`);
|
|
closeDatabase();
|
|
process.exit(0);
|
|
}
|
|
|
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
|
|
// Middleware
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
app.use(express.static('public'));
|
|
|
|
// Create uploads directory if it doesn't exist
|
|
const uploadsDir = path.join(__dirname, 'uploads');
|
|
if (!fs.existsSync(uploadsDir)) {
|
|
fs.mkdirSync(uploadsDir);
|
|
}
|
|
|
|
// Allowed file types for uploads
|
|
const ALLOWED_FILE_TYPES = {
|
|
'application/pdf': ['.pdf'],
|
|
'image/jpeg': ['.jpg', '.jpeg'],
|
|
'image/png': ['.png'],
|
|
'application/msword': ['.doc'],
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx']
|
|
};
|
|
|
|
// File filter for multer
|
|
const fileFilter = (req, file, cb) => {
|
|
const allowedExtensions = Object.values(ALLOWED_FILE_TYPES).flat();
|
|
const ext = path.extname(file.originalname).toLowerCase();
|
|
|
|
if (allowedExtensions.includes(ext) && ALLOWED_FILE_TYPES[file.mimetype]) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error(`Dateityp nicht erlaubt: ${ext}. Erlaubt sind: PDF, JPG, PNG, DOC, DOCX`), false);
|
|
}
|
|
};
|
|
|
|
// Configure multer for file uploads
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
cb(null, 'uploads/');
|
|
},
|
|
filename: (req, file, cb) => {
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
cb(null, uniqueSuffix + path.extname(file.originalname));
|
|
}
|
|
});
|
|
|
|
const upload = multer({
|
|
storage: storage,
|
|
fileFilter: fileFilter,
|
|
limits: {
|
|
fileSize: 10 * 1024 * 1024 // 10MB limit
|
|
}
|
|
});
|
|
|
|
// Validation helper functions
|
|
function validateDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return dateString && !isNaN(date.getTime()) && /^\d{4}-\d{2}-\d{2}$/.test(dateString);
|
|
}
|
|
|
|
function validateHardware(hardware) {
|
|
// Hardware should be 15 characters if provided
|
|
return !hardware || hardware.length === 15;
|
|
}
|
|
|
|
function validateAmount(amount) {
|
|
// Amount should be positive or zero
|
|
return amount === null || amount === undefined || amount >= 0;
|
|
}
|
|
|
|
function validatePercentage(percentage) {
|
|
// Percentage should be between 0 and 100
|
|
return percentage === null || percentage === undefined || (percentage >= 0 && percentage <= 100);
|
|
}
|
|
|
|
// API Routes
|
|
|
|
// Get all providers
|
|
app.get('/api/anbieter', (req, res) => {
|
|
const db = getDatabase();
|
|
db.all('SELECT * FROM anbieter ORDER BY name', (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add new provider
|
|
app.post('/api/anbieter', (req, res) => {
|
|
const { name } = req.body;
|
|
const db = getDatabase();
|
|
|
|
db.run('INSERT INTO anbieter (name) VALUES (?)', [name], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ id: this.lastID, name });
|
|
|
|
});
|
|
});
|
|
|
|
// Delete provider
|
|
app.delete('/api/anbieter/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
// Check if provider has tariffs
|
|
db.get('SELECT COUNT(*) as tariff_count FROM tarife WHERE anbieter_id = ?', [id], (err, tariffeRow) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if provider has schaltungen
|
|
db.get('SELECT COUNT(*) as schaltung_count FROM schaltungen WHERE anbieter_id = ?', [id], (err, schaltungenRow) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if provider has global commission rules
|
|
// Only check critical dependencies (tariffs and schaltungen)
|
|
const criticalUsages = tariffeRow.tariff_count + schaltungenRow.schaltung_count;
|
|
|
|
if (criticalUsages > 0) {
|
|
const reasons = [];
|
|
if (tariffeRow.tariff_count > 0) reasons.push(`${tariffeRow.tariff_count} Tarif(e)`);
|
|
if (schaltungenRow.schaltung_count > 0) reasons.push(`${schaltungenRow.schaltung_count} Schaltung(en)`);
|
|
|
|
res.status(400).json({
|
|
error: `Anbieter kann nicht gelöscht werden: ${reasons.join(', ')} verwenden diesen Anbieter`
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// First delete all associated commission rules (these can be safely deleted)
|
|
db.run('DELETE FROM anbieter_provisionsregeln WHERE anbieter_id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
const deletedRules = this.changes;
|
|
|
|
// Then delete the provider
|
|
db.run('DELETE FROM anbieter WHERE id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Anbieter nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
let message = 'Anbieter erfolgreich gelöscht';
|
|
if (deletedRules > 0) {
|
|
message += ` (${deletedRules} Provisionsregel(n) wurden mit gelöscht)`;
|
|
}
|
|
|
|
res.json({ message });
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get tariffs by provider
|
|
// Get all tarife with anbieter names
|
|
app.get('/api/tarife', (req, res) => {
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT t.*, a.name as anbieter_name
|
|
FROM tarife t
|
|
JOIN anbieter a ON t.anbieter_id = a.id
|
|
ORDER BY a.name, t.name
|
|
`;
|
|
|
|
db.all(query, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
app.get('/api/tarife/:anbieter_id', (req, res) => {
|
|
const { anbieter_id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
db.all('SELECT * FROM tarife WHERE anbieter_id = ? ORDER BY name', [anbieter_id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Get single tariff by ID
|
|
app.get('/api/tarif/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
db.get('SELECT * FROM tarife WHERE id = ?', [id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
if (!row) {
|
|
res.status(404).json({ error: 'Tarif nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
res.json(row);
|
|
|
|
});
|
|
});
|
|
|
|
// Add new tariff - simplified
|
|
app.post('/api/tarife', (req, res) => {
|
|
const { name, anbieter_id, provision_euro, beschreibung } = req.body;
|
|
|
|
// Input validation
|
|
if (!name || !name.trim()) {
|
|
return res.status(400).json({ error: 'Tarifname ist erforderlich' });
|
|
}
|
|
if (!anbieter_id) {
|
|
return res.status(400).json({ error: 'Anbieter ist erforderlich' });
|
|
}
|
|
if (!validateAmount(provision_euro)) {
|
|
return res.status(400).json({ error: 'Provision muss eine positive Zahl sein' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
const query = `INSERT INTO tarife (name, anbieter_id, provision_euro, beschreibung) VALUES (?, ?, ?, ?)`;
|
|
|
|
db.run(query, [name, anbieter_id, provision_euro || 0, beschreibung || null], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
name,
|
|
anbieter_id,
|
|
provision_euro: provision_euro || 0,
|
|
beschreibung
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Update tariff - simplified
|
|
app.put('/api/tarife/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, provision_euro, beschreibung } = req.body;
|
|
const db = getDatabase();
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: 'Tarif-Name ist erforderlich' });
|
|
}
|
|
|
|
const query = `UPDATE tarife SET name = ?, provision_euro = ?, beschreibung = ? WHERE id = ?`;
|
|
|
|
db.run(query, [name, provision_euro || 0, beschreibung || null, id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Tarif nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Tarif erfolgreich aktualisiert' });
|
|
|
|
});
|
|
});
|
|
|
|
// Delete tariff
|
|
app.delete('/api/tarife/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
// Fast check if tariff is used in any schaltungen
|
|
db.get('SELECT EXISTS(SELECT 1 FROM schaltungen WHERE tarif_id = ?) as has_schaltungen', [id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (row.has_schaltungen) {
|
|
res.status(400).json({
|
|
error: `Tarif kann nicht gelöscht werden: Tarif wird in Schaltungen verwendet`
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete the tariff
|
|
db.run('DELETE FROM tarife WHERE id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Tarif nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Tarif erfolgreich gelöscht' });
|
|
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get all schaltungen with related data
|
|
app.get('/api/schaltungen', (req, res) => {
|
|
const db = getDatabase();
|
|
const query = `
|
|
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
|
|
`;
|
|
|
|
db.all(query, (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add new schaltung
|
|
app.post('/api/schaltungen', (req, res) => {
|
|
const {
|
|
schaltungs_id,
|
|
eingabe_datum,
|
|
schaltungs_datum,
|
|
vorname,
|
|
nachname,
|
|
firma,
|
|
rufnummer,
|
|
anbieter_id,
|
|
tarif_id,
|
|
hardware,
|
|
hardware_ek,
|
|
kundenverkaufspreis_netto
|
|
} = req.body;
|
|
|
|
// Input validation
|
|
if (!schaltungs_id || !schaltungs_id.trim()) {
|
|
return res.status(400).json({ error: 'Schaltungs-ID ist erforderlich' });
|
|
}
|
|
if (!validateDate(eingabe_datum)) {
|
|
return res.status(400).json({ error: 'Ungültiges Eingabe-Datum (Format: YYYY-MM-DD)' });
|
|
}
|
|
if (!validateDate(schaltungs_datum)) {
|
|
return res.status(400).json({ error: 'Ungültiges Schaltungs-Datum (Format: YYYY-MM-DD)' });
|
|
}
|
|
if (!vorname || !vorname.trim()) {
|
|
return res.status(400).json({ error: 'Vorname ist erforderlich' });
|
|
}
|
|
if (!nachname || !nachname.trim()) {
|
|
return res.status(400).json({ error: 'Nachname ist erforderlich' });
|
|
}
|
|
if (!rufnummer || !rufnummer.trim()) {
|
|
return res.status(400).json({ error: 'Rufnummer ist erforderlich' });
|
|
}
|
|
if (!anbieter_id) {
|
|
return res.status(400).json({ error: 'Anbieter ist erforderlich' });
|
|
}
|
|
if (!tarif_id) {
|
|
return res.status(400).json({ error: 'Tarif ist erforderlich' });
|
|
}
|
|
if (!validateHardware(hardware)) {
|
|
return res.status(400).json({ error: 'Hardware muss genau 15 Zeichen lang sein' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
// Validation: if hardware is provided, hardware_ek and kundenverkaufspreis_netto must be provided
|
|
if (hardware && hardware.trim() !== '') {
|
|
if (!validateAmount(hardware_ek)) {
|
|
return res.status(400).json({ error: 'Hardware-EK muss eine positive Zahl sein' });
|
|
}
|
|
if (!validateAmount(kundenverkaufspreis_netto)) {
|
|
return res.status(400).json({ error: 'Kundenverkaufspreis muss eine positive Zahl sein' });
|
|
}
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO schaltungen
|
|
(schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname, firma, rufnummer, anbieter_id, tarif_id, hardware, hardware_ek, kundenverkaufspreis_netto)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [
|
|
schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname,
|
|
firma, rufnummer, anbieter_id, tarif_id, hardware,
|
|
hardware_ek || null, kundenverkaufspreis_netto || null
|
|
], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ id: this.lastID, message: 'Schaltung erfolgreich erstellt' });
|
|
|
|
});
|
|
});
|
|
|
|
// Update schaltung
|
|
app.put('/api/schaltungen/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const {
|
|
schaltungs_id,
|
|
eingabe_datum,
|
|
schaltungs_datum,
|
|
vorname,
|
|
nachname,
|
|
firma,
|
|
rufnummer,
|
|
anbieter_id,
|
|
tarif_id,
|
|
hardware,
|
|
hardware_ek,
|
|
kundenverkaufspreis_netto,
|
|
tarifoptionen = []
|
|
} = req.body;
|
|
|
|
const db = getDatabase();
|
|
|
|
// Begin transaction
|
|
db.serialize(() => {
|
|
db.run('BEGIN TRANSACTION');
|
|
|
|
// Update schaltung
|
|
const updateQuery = `
|
|
UPDATE schaltungen
|
|
SET schaltungs_id = ?, eingabe_datum = ?, schaltungs_datum = ?, vorname = ?, nachname = ?,
|
|
firma = ?, rufnummer = ?, anbieter_id = ?, tarif_id = ?, hardware = ?,
|
|
hardware_ek = ?, kundenverkaufspreis_netto = ?
|
|
WHERE id = ?
|
|
`;
|
|
|
|
db.run(updateQuery, [
|
|
schaltungs_id, eingabe_datum, schaltungs_datum, vorname, nachname,
|
|
firma, rufnummer, anbieter_id, tarif_id, hardware,
|
|
hardware_ek, kundenverkaufspreis_netto, id
|
|
], function(err) {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
db.run('ROLLBACK');
|
|
res.status(404).json({ error: 'Schaltung nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete existing tarifoptionen
|
|
db.run('DELETE FROM schaltungen_tarifoptionen WHERE schaltung_id = ?', [id], (err) => {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Insert new tarifoptionen
|
|
if (tarifoptionen.length > 0) {
|
|
const insertTarifoptionQuery = 'INSERT INTO schaltungen_tarifoptionen (schaltung_id, tarifoption_id) VALUES (?, ?)';
|
|
let insertCount = 0;
|
|
|
|
tarifoptionen.forEach(tarifoptionId => {
|
|
db.run(insertTarifoptionQuery, [id, tarifoptionId], (err) => {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
insertCount++;
|
|
if (insertCount === tarifoptionen.length) {
|
|
// All tarifoptionen inserted successfully
|
|
db.run('COMMIT');
|
|
res.json({ message: 'Schaltung erfolgreich aktualisiert' });
|
|
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// No tarifoptionen to insert
|
|
db.run('COMMIT');
|
|
res.json({ message: 'Schaltung erfolgreich aktualisiert' });
|
|
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get tarifoptionen for a specific schaltung
|
|
app.get('/api/schaltungen/:id/tarifoptionen', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT st.tarifoption_id, tarifoptionen.name, tarifoptionen.provision_euro
|
|
FROM schaltungen_tarifoptionen st
|
|
JOIN tarifoptionen ON st.tarifoption_id = tarifoptionen.id
|
|
WHERE st.schaltung_id = ?
|
|
`;
|
|
|
|
db.all(query, [id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Delete schaltung with reason tracking
|
|
app.delete('/api/schaltungen/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { loeschgrund } = req.body;
|
|
|
|
if (!loeschgrund || loeschgrund.trim().length === 0) {
|
|
return res.status(400).json({ error: 'Löschgrund ist erforderlich' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
// Get schaltung details before deleting
|
|
const getQuery = `
|
|
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 = ?
|
|
`;
|
|
|
|
db.get(getQuery, [id], (err, schaltung) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (!schaltung) {
|
|
res.status(404).json({ error: 'Schaltung nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Insert into deleted schaltungen table
|
|
const insertQuery = `
|
|
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
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(insertQuery, [
|
|
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()
|
|
], function(insertErr) {
|
|
if (insertErr) {
|
|
res.status(500).json({ error: insertErr.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Get all documents for this schaltung before deleting
|
|
db.all('SELECT filepath FROM dokumente WHERE schaltung_id = ?', [id], (err, documents) => {
|
|
if (err) {
|
|
console.error('Error fetching documents for deletion:', err.message);
|
|
}
|
|
|
|
// Delete physical files from filesystem
|
|
if (documents && documents.length > 0) {
|
|
documents.forEach(doc => {
|
|
const filePath = path.join(__dirname, doc.filepath);
|
|
fs.unlink(filePath, (unlinkErr) => {
|
|
if (unlinkErr) {
|
|
console.error(`Error deleting file ${filePath}:`, unlinkErr.message);
|
|
} else {
|
|
console.log(`Deleted file: ${filePath}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Delete documents from database (will CASCADE from schaltungen deletion)
|
|
// Delete from schaltungen table (this will CASCADE delete dokumente and schaltungen_tarifoptionen)
|
|
db.run('DELETE FROM schaltungen WHERE id = ?', [id], function(deleteErr) {
|
|
if (deleteErr) {
|
|
res.status(500).json({ error: deleteErr.message });
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
message: 'Schaltung erfolgreich gelöscht und archiviert',
|
|
loeschgrund: loeschgrund.trim(),
|
|
deleted_files: documents ? documents.length : 0
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get deleted schaltungen
|
|
app.get('/api/geloeschte-schaltungen', (req, res) => {
|
|
const db = getDatabase();
|
|
|
|
db.all('SELECT * FROM geloeschte_schaltungen ORDER BY geloescht_am DESC', (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Upload document for schaltung
|
|
app.post('/api/schaltungen/:id/dokumente', upload.single('document'), (req, res) => {
|
|
const schaltung_id = req.params.id;
|
|
const file = req.file;
|
|
|
|
if (!file) {
|
|
return res.status(400).json({ error: 'Keine Datei hochgeladen' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
// First verify schaltung exists
|
|
db.get('SELECT id FROM schaltungen WHERE id = ?', [schaltung_id], (err, schaltung) => {
|
|
if (err) {
|
|
// Cleanup uploaded file
|
|
fs.unlink(file.path, (unlinkErr) => {
|
|
if (unlinkErr) console.error('Error cleaning up file:', unlinkErr);
|
|
});
|
|
return res.status(500).json({ error: err.message });
|
|
}
|
|
|
|
if (!schaltung) {
|
|
// Cleanup uploaded file
|
|
fs.unlink(file.path, (unlinkErr) => {
|
|
if (unlinkErr) console.error('Error cleaning up file:', unlinkErr);
|
|
});
|
|
return res.status(404).json({ error: 'Schaltung nicht gefunden' });
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO dokumente (schaltung_id, filename, original_filename, filepath, filesize, mimetype)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [
|
|
schaltung_id,
|
|
file.filename,
|
|
file.originalname,
|
|
file.path,
|
|
file.size,
|
|
file.mimetype
|
|
], function(err) {
|
|
if (err) {
|
|
// Cleanup uploaded file on database error
|
|
fs.unlink(file.path, (unlinkErr) => {
|
|
if (unlinkErr) console.error('Error cleaning up file:', unlinkErr);
|
|
});
|
|
return res.status(500).json({ error: err.message });
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Dokument erfolgreich hochgeladen',
|
|
filename: file.originalname
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Error handler for multer
|
|
app.use((err, req, res, next) => {
|
|
if (err instanceof multer.MulterError) {
|
|
if (err.code === 'LIMIT_FILE_SIZE') {
|
|
return res.status(400).json({ error: 'Datei zu groß. Maximum: 10MB' });
|
|
}
|
|
return res.status(400).json({ error: err.message });
|
|
} else if (err) {
|
|
return res.status(400).json({ error: err.message });
|
|
}
|
|
next();
|
|
});
|
|
|
|
// Get documents for schaltung
|
|
app.get('/api/schaltungen/:id/dokumente', (req, res) => {
|
|
const schaltung_id = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
db.all('SELECT * FROM dokumente WHERE schaltung_id = ?', [schaltung_id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Download document
|
|
app.get('/api/dokumente/:id/download', (req, res) => {
|
|
const document_id = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
db.get('SELECT * FROM dokumente WHERE id = ?', [document_id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (!row) {
|
|
res.status(404).json({ error: 'Dokument nicht gefunden' });
|
|
return;
|
|
}
|
|
|
|
const filepath = path.join(__dirname, row.filepath);
|
|
res.download(filepath, row.original_filename);
|
|
|
|
});
|
|
});
|
|
|
|
// Delete document
|
|
app.delete('/api/dokumente/:id', (req, res) => {
|
|
const document_id = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
// First get the document info to delete the file
|
|
db.get('SELECT * FROM dokumente WHERE id = ?', [document_id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
if (!row) {
|
|
res.status(404).json({ error: 'Dokument nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete from database
|
|
db.run('DELETE FROM dokumente WHERE id = ?', [document_id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete physical file
|
|
const filepath = path.join(__dirname, row.filepath);
|
|
fs.unlink(filepath, (unlinkErr) => {
|
|
if (unlinkErr) {
|
|
console.error('Fehler beim Löschen der Datei:', unlinkErr);
|
|
// Continue anyway as database record is already deleted
|
|
}
|
|
res.json({ message: 'Dokument erfolgreich gelöscht' });
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Global Provider Commission Rules API
|
|
|
|
// Get global commission rules for provider
|
|
app.get('/api/anbieter/:id/provisionsregeln', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT apr.*, a.name as anbieter_name
|
|
FROM anbieter_provisionsregeln apr
|
|
JOIN anbieter a ON apr.anbieter_id = a.id
|
|
WHERE apr.anbieter_id = ? AND apr.aktiv = 1
|
|
ORDER BY apr.gueltig_ab DESC
|
|
`;
|
|
|
|
db.all(query, [id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add global provider commission rule
|
|
app.post('/api/anbieter-provisionsregeln', (req, res) => {
|
|
const {
|
|
anbieter_id,
|
|
provision_typ,
|
|
betrag,
|
|
beschreibung,
|
|
gueltig_ab,
|
|
verkaeufer_provision_typ,
|
|
verkaeufer_provision_betrag,
|
|
verkaeufer_provision_beschreibung
|
|
} = req.body;
|
|
const db = getDatabase();
|
|
|
|
// Validate provision type and amount
|
|
if (!['fix', 'prozent'].includes(provision_typ)) {
|
|
return res.status(400).json({ error: 'Ungültiger Provisionstyp' });
|
|
}
|
|
|
|
if (provision_typ === 'prozent' && (betrag < 0 || betrag > 100)) {
|
|
return res.status(400).json({ error: 'Prozentual muss zwischen 0 und 100 sein' });
|
|
}
|
|
|
|
if (provision_typ === 'fix' && betrag < 0) {
|
|
return res.status(400).json({ error: 'Fixer Betrag kann nicht negativ sein' });
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO anbieter_provisionsregeln (
|
|
anbieter_id, provision_typ, betrag, beschreibung, gueltig_ab,
|
|
verkaeufer_provision_typ, verkaeufer_provision_betrag, verkaeufer_provision_beschreibung
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [
|
|
anbieter_id,
|
|
provision_typ,
|
|
betrag,
|
|
beschreibung,
|
|
gueltig_ab || new Date().toISOString().split('T')[0],
|
|
verkaeufer_provision_typ || null,
|
|
verkaeufer_provision_betrag || null,
|
|
verkaeufer_provision_beschreibung || null
|
|
], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Globale Provisionsregel erfolgreich erstellt'
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Update global provider commission rule
|
|
app.put('/api/anbieter-provisionsregeln/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const {
|
|
provision_typ,
|
|
betrag,
|
|
beschreibung,
|
|
gueltig_ab,
|
|
aktiv,
|
|
verkaeufer_provision_typ,
|
|
verkaeufer_provision_betrag,
|
|
verkaeufer_provision_beschreibung
|
|
} = req.body;
|
|
const db = getDatabase();
|
|
|
|
// First get the current rule to preserve existing values
|
|
db.get('SELECT * FROM anbieter_provisionsregeln WHERE id = ?', [id], (err, current) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (!current) {
|
|
res.status(404).json({ error: 'Globale Provisionsregel nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
const updateQuery = `
|
|
UPDATE anbieter_provisionsregeln
|
|
SET provision_typ = ?, betrag = ?, beschreibung = ?, gueltig_ab = ?, aktiv = ?,
|
|
verkaeufer_provision_typ = ?, verkaeufer_provision_betrag = ?, verkaeufer_provision_beschreibung = ?
|
|
WHERE id = ?
|
|
`;
|
|
|
|
db.run(updateQuery, [
|
|
provision_typ !== undefined ? provision_typ : current.provision_typ,
|
|
betrag !== undefined ? betrag : current.betrag,
|
|
beschreibung !== undefined ? beschreibung : current.beschreibung,
|
|
gueltig_ab !== undefined ? gueltig_ab : current.gueltig_ab,
|
|
aktiv !== undefined ? aktiv : current.aktiv,
|
|
verkaeufer_provision_typ !== undefined ? verkaeufer_provision_typ : current.verkaeufer_provision_typ,
|
|
verkaeufer_provision_betrag !== undefined ? verkaeufer_provision_betrag : current.verkaeufer_provision_betrag,
|
|
verkaeufer_provision_beschreibung !== undefined ? verkaeufer_provision_beschreibung : current.verkaeufer_provision_beschreibung,
|
|
id
|
|
], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ message: 'Globale Provisionsregel erfolgreich aktualisiert' });
|
|
|
|
});
|
|
});
|
|
});
|
|
|
|
// Commission Rules API
|
|
|
|
// Get commission rules for tariff
|
|
app.get('/api/tarife/:id/provisionsregeln', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT pr.*, t.name as tarif_name, a.name as anbieter_name
|
|
FROM provisionsregeln pr
|
|
JOIN tarife t ON pr.tarif_id = t.id
|
|
JOIN anbieter a ON t.anbieter_id = a.id
|
|
WHERE pr.tarif_id = ? AND pr.aktiv = 1
|
|
ORDER BY pr.gueltig_ab DESC
|
|
`;
|
|
|
|
db.all(query, [id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add commission rule
|
|
app.post('/api/provisionsregeln', (req, res) => {
|
|
const { tarif_id, provision_typ, betrag, beschreibung, gueltig_ab } = req.body;
|
|
const db = getDatabase();
|
|
|
|
// Validate provision type and amount
|
|
if (!['fix', 'prozent'].includes(provision_typ)) {
|
|
return res.status(400).json({ error: 'Ungültiger Provisionstyp' });
|
|
}
|
|
|
|
if (provision_typ === 'prozent' && (betrag < 0 || betrag > 100)) {
|
|
return res.status(400).json({ error: 'Prozentual muss zwischen 0 und 100 sein' });
|
|
}
|
|
|
|
if (provision_typ === 'fix' && betrag < 0) {
|
|
return res.status(400).json({ error: 'Fixer Betrag kann nicht negativ sein' });
|
|
}
|
|
|
|
const query = `
|
|
INSERT INTO provisionsregeln (tarif_id, provision_typ, betrag, beschreibung, gueltig_ab)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [tarif_id, provision_typ, betrag, beschreibung, gueltig_ab || new Date().toISOString().split('T')[0]], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Provisionsregel erfolgreich erstellt'
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Update commission rule
|
|
app.put('/api/provisionsregeln/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { provision_typ, betrag, beschreibung, gueltig_ab, aktiv } = req.body;
|
|
const db = getDatabase();
|
|
|
|
// First get the current rule to preserve existing values
|
|
db.get('SELECT * FROM provisionsregeln WHERE id = ?', [id], (err, current) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (!current) {
|
|
res.status(404).json({ error: 'Provisionsregel nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Use current values as defaults
|
|
const updateQuery = `
|
|
UPDATE provisionsregeln
|
|
SET provision_typ = ?, betrag = ?, beschreibung = ?, gueltig_ab = ?, aktiv = ?
|
|
WHERE id = ?
|
|
`;
|
|
|
|
db.run(updateQuery, [
|
|
provision_typ !== undefined ? provision_typ : current.provision_typ,
|
|
betrag !== undefined ? betrag : current.betrag,
|
|
beschreibung !== undefined ? beschreibung : current.beschreibung,
|
|
gueltig_ab !== undefined ? gueltig_ab : current.gueltig_ab,
|
|
aktiv !== undefined ? aktiv : current.aktiv,
|
|
id
|
|
], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json({ message: 'Provisionsregel erfolgreich aktualisiert' });
|
|
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get commission calculation for schaltung
|
|
app.get('/api/schaltungen/:id/provision', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT
|
|
s.*,
|
|
t.name as tarif_name,
|
|
t.provision_euro as tarif_provision_euro,
|
|
t.beschreibung as tarif_beschreibung,
|
|
a.name as anbieter_name,
|
|
mp.prozentsatz as mitarbeiter_prozentsatz
|
|
FROM schaltungen s
|
|
JOIN tarife t ON s.tarif_id = t.id
|
|
JOIN anbieter a ON s.anbieter_id = a.id
|
|
LEFT JOIN mitarbeiter_prozentsaetze mp ON a.id = mp.anbieter_id
|
|
AND mp.gueltig_ab <= s.schaltungs_datum
|
|
WHERE s.id = ?
|
|
ORDER BY mp.gueltig_ab DESC
|
|
LIMIT 1
|
|
`;
|
|
|
|
db.get(query, [id], (err, row) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
if (!row) {
|
|
res.status(404).json({ error: 'Schaltung nicht gefunden' });
|
|
return;
|
|
}
|
|
|
|
// Get tarifoptionen for this schaltung
|
|
const tarifoptionenQuery = `
|
|
SELECT to.*
|
|
FROM tarifoptionen to
|
|
JOIN schaltungen_tarifoptionen st ON to.id = st.tarifoption_id
|
|
WHERE st.schaltung_id = ?
|
|
`;
|
|
|
|
db.all(tarifoptionenQuery, [id], (err, tarifoptionen) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
// Calculate shop commission with hierarchy
|
|
let shop_provision_typ = null;
|
|
let shop_provision_betrag = null;
|
|
let shop_provision_beschreibung = null;
|
|
let berechnete_shop_provision = 0;
|
|
let shop_quelle = 'Keine Shop-Provision definiert';
|
|
|
|
// Priority 1: Direct tariff commission
|
|
if (row.tarif_provision_typ && row.tarif_provision_betrag) {
|
|
shop_provision_typ = row.tarif_provision_typ;
|
|
shop_provision_betrag = row.tarif_provision_betrag;
|
|
shop_provision_beschreibung = row.tarif_provision_beschreibung;
|
|
shop_quelle = 'Direkte Tarif-Provision';
|
|
}
|
|
// Priority 2: Tariff-specific rule
|
|
else if (row.regel_provision_typ && row.regel_provision_betrag) {
|
|
shop_provision_typ = row.regel_provision_typ;
|
|
shop_provision_betrag = row.regel_provision_betrag;
|
|
shop_provision_beschreibung = row.regel_provision_beschreibung;
|
|
shop_quelle = 'Tarif-spezifische Regel';
|
|
}
|
|
// Priority 3: Global provider rule as fallback
|
|
else if (row.global_provision_typ && row.global_provision_betrag) {
|
|
shop_provision_typ = row.global_provision_typ;
|
|
shop_provision_betrag = row.global_provision_betrag;
|
|
shop_provision_beschreibung = row.global_provision_beschreibung;
|
|
shop_quelle = 'Globale Anbieter-Regel';
|
|
}
|
|
|
|
// Calculate shop commission
|
|
if (shop_provision_typ && shop_provision_betrag) {
|
|
if (shop_provision_typ === 'fix') {
|
|
berechnete_shop_provision = parseFloat(shop_provision_betrag);
|
|
} else if (shop_provision_typ === 'prozent') {
|
|
const basisBetrag = 100; // This could come from tariff table
|
|
berechnete_shop_provision = (parseFloat(shop_provision_betrag) / 100) * basisBetrag;
|
|
}
|
|
}
|
|
|
|
// Add tarifoptionen to shop commission
|
|
let tarifoptionen_provision = 0;
|
|
let verwendete_tarifoptionen = [];
|
|
if (tarifoptionen && tarifoptionen.length > 0) {
|
|
tarifoptionen.forEach(option => {
|
|
if (option.provision_betrag && option.provision_betrag > 0) {
|
|
tarifoptionen_provision += parseFloat(option.provision_betrag);
|
|
verwendete_tarifoptionen.push(option.name);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add tarifoptionen to total shop commission
|
|
berechnete_shop_provision += tarifoptionen_provision;
|
|
|
|
// Update shop commission description if tarifoptionen were added
|
|
if (tarifoptionen_provision > 0) {
|
|
const tarifoptionen_text = verwendete_tarifoptionen.join(', ');
|
|
shop_provision_beschreibung = shop_provision_beschreibung
|
|
? `${shop_provision_beschreibung} + Tarifoptionen: ${tarifoptionen_text}`
|
|
: `Tarifoptionen: ${tarifoptionen_text}`;
|
|
}
|
|
|
|
// Calculate employee commission with hierarchy
|
|
let mitarbeiter_provision_typ = null;
|
|
let mitarbeiter_provision_betrag = null;
|
|
let mitarbeiter_provision_beschreibung = null;
|
|
let berechnete_mitarbeiter_provision = 0;
|
|
let mitarbeiter_quelle = 'Keine Mitarbeiter-Provision definiert';
|
|
|
|
// Priority 1: Direct tariff employee commission
|
|
if (row.tarif_verkaeufer_provision_typ && row.tarif_verkaeufer_provision_betrag) {
|
|
mitarbeiter_provision_typ = row.tarif_verkaeufer_provision_typ;
|
|
mitarbeiter_provision_betrag = row.tarif_verkaeufer_provision_betrag;
|
|
mitarbeiter_provision_beschreibung = row.tarif_verkaeufer_provision_beschreibung;
|
|
mitarbeiter_quelle = 'Direkte Tarif-Verkäufer-Provision';
|
|
}
|
|
// Priority 2: Tariff-specific rule employee commission
|
|
else if (row.regel_verkaeufer_provision_typ && row.regel_verkaeufer_provision_betrag) {
|
|
mitarbeiter_provision_typ = row.regel_verkaeufer_provision_typ;
|
|
mitarbeiter_provision_betrag = row.regel_verkaeufer_provision_betrag;
|
|
mitarbeiter_provision_beschreibung = row.regel_verkaeufer_provision_beschreibung;
|
|
mitarbeiter_quelle = 'Tarif-spezifische Verkäufer-Regel';
|
|
}
|
|
// Priority 3: Global provider rule employee commission
|
|
else if (row.global_verkaeufer_provision_typ && row.global_verkaeufer_provision_betrag) {
|
|
mitarbeiter_provision_typ = row.global_verkaeufer_provision_typ;
|
|
mitarbeiter_provision_betrag = row.global_verkaeufer_provision_betrag;
|
|
mitarbeiter_provision_beschreibung = row.global_verkaeufer_provision_beschreibung;
|
|
mitarbeiter_quelle = 'Globale Anbieter-Verkäufer-Regel';
|
|
}
|
|
|
|
// Calculate employee commission
|
|
if (mitarbeiter_provision_typ && mitarbeiter_provision_betrag) {
|
|
if (mitarbeiter_provision_typ === 'fix') {
|
|
berechnete_mitarbeiter_provision = parseFloat(mitarbeiter_provision_betrag);
|
|
} else if (mitarbeiter_provision_typ === 'prozent') {
|
|
// If percentage, calculate based on shop commission
|
|
berechnete_mitarbeiter_provision = (parseFloat(mitarbeiter_provision_betrag) / 100) * berechnete_shop_provision;
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
id: row.id,
|
|
schaltungs_id: row.schaltungs_id,
|
|
schaltungs_datum: row.schaltungs_datum,
|
|
vorname: row.vorname,
|
|
nachname: row.nachname,
|
|
firma: row.firma,
|
|
tarif_name: row.tarif_name,
|
|
anbieter_name: row.anbieter_name,
|
|
// Shop commission
|
|
shop_provision_typ,
|
|
shop_provision_betrag,
|
|
shop_provision_beschreibung,
|
|
berechnete_shop_provision: berechnete_shop_provision.toFixed(2),
|
|
shop_quelle,
|
|
// Employee commission
|
|
mitarbeiter_provision_typ,
|
|
mitarbeiter_provision_betrag,
|
|
mitarbeiter_provision_beschreibung,
|
|
berechnete_mitarbeiter_provision: berechnete_mitarbeiter_provision.toFixed(2),
|
|
mitarbeiter_quelle
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
|
|
// Get commission overview for all schaltungen with hierarchy
|
|
app.get('/api/provisionen', async (req, res) => {
|
|
const db = getDatabase();
|
|
|
|
// Simplified query for new commission model - use subquery to get latest valid percentage
|
|
const query = `
|
|
SELECT
|
|
s.id,
|
|
s.schaltungs_id,
|
|
s.eingabe_datum,
|
|
s.schaltungs_datum,
|
|
s.vorname,
|
|
s.nachname,
|
|
s.firma,
|
|
s.hardware,
|
|
s.hardware_ek,
|
|
s.kundenverkaufspreis_netto,
|
|
t.name as tarif_name,
|
|
t.provision_euro as tarif_provision_euro,
|
|
a.name as anbieter_name,
|
|
a.id as anbieter_id,
|
|
(
|
|
SELECT mp.prozentsatz
|
|
FROM mitarbeiter_prozentsaetze mp
|
|
WHERE mp.anbieter_id = a.id
|
|
AND mp.gueltig_ab <= s.schaltungs_datum
|
|
ORDER BY mp.gueltig_ab DESC
|
|
LIMIT 1
|
|
) as mitarbeiter_prozentsatz
|
|
FROM schaltungen s
|
|
JOIN tarife t ON s.tarif_id = t.id
|
|
JOIN anbieter a ON s.anbieter_id = a.id
|
|
ORDER BY s.schaltungs_datum DESC
|
|
`;
|
|
|
|
try {
|
|
const rows = await new Promise((resolve, reject) => {
|
|
db.all(query, [], (err, data) => {
|
|
if (err) reject(err);
|
|
else resolve(data);
|
|
});
|
|
});
|
|
|
|
// Get tarifoptionen for all schaltungen first
|
|
const schaltungIds = rows.map(row => row.id);
|
|
let tarifoptionenMap = {};
|
|
|
|
if (schaltungIds.length > 0) {
|
|
const tarifoptionenQuery = `
|
|
SELECT
|
|
st.schaltung_id,
|
|
tarifoptionen.provision_euro
|
|
FROM schaltungen_tarifoptionen st
|
|
JOIN tarifoptionen ON st.tarifoption_id = tarifoptionen.id
|
|
WHERE st.schaltung_id IN (${schaltungIds.map(() => '?').join(',')})
|
|
`;
|
|
|
|
try {
|
|
const tarifoptionenRows = await new Promise((resolve, reject) => {
|
|
db.all(tarifoptionenQuery, schaltungIds, (err, tarifoptionenData) => {
|
|
if (err) reject(err);
|
|
else resolve(tarifoptionenData);
|
|
});
|
|
});
|
|
|
|
// Group tarifoptionen by schaltung_id
|
|
tarifoptionenRows.forEach(opt => {
|
|
if (!tarifoptionenMap[opt.schaltung_id]) {
|
|
tarifoptionenMap[opt.schaltung_id] = [];
|
|
}
|
|
tarifoptionenMap[opt.schaltung_id].push(parseFloat(opt.provision_euro) || 0);
|
|
});
|
|
} catch (error) {
|
|
console.error('Error loading tarifoptionen:', error);
|
|
}
|
|
}
|
|
|
|
// Process new commission calculation with hardware pricing and tarifoptionen
|
|
const processedRows = rows.map(row => {
|
|
// Base shop commission = Tarif provision
|
|
let shop_provision = parseFloat(row.tarif_provision_euro) || 0;
|
|
|
|
// Add tarifoptionen provision
|
|
const tarifoptionen = tarifoptionenMap[row.id] || [];
|
|
const tarifoptionen_provision = tarifoptionen.reduce((sum, opt) => sum + opt, 0);
|
|
shop_provision += tarifoptionen_provision;
|
|
|
|
// Enhanced calculation: Tarifprovision + Tarifoptionsprovision - Hardware-EK + Kundenverkaufspreis
|
|
if (row.hardware && row.hardware.trim() !== '') {
|
|
const hardware_ek = parseFloat(row.hardware_ek) || 0;
|
|
const kundenverkaufspreis = parseFloat(row.kundenverkaufspreis_netto) || 0;
|
|
|
|
// Apply the new formula: Tarifprovision + Tarifoptionen - Hardware-EK + Kundenverkaufspreis
|
|
shop_provision = shop_provision - hardware_ek + kundenverkaufspreis;
|
|
}
|
|
|
|
// Employee commission = Shop provision * Employee percentage
|
|
const mitarbeiter_prozentsatz = parseFloat(row.mitarbeiter_prozentsatz) || 0;
|
|
const mitarbeiter_provision = shop_provision * (mitarbeiter_prozentsatz / 100);
|
|
|
|
return {
|
|
id: row.id,
|
|
schaltungs_id: row.schaltungs_id,
|
|
eingabe_datum: row.eingabe_datum,
|
|
schaltungs_datum: row.schaltungs_datum,
|
|
vorname: row.vorname,
|
|
nachname: row.nachname,
|
|
firma: row.firma,
|
|
hardware: row.hardware,
|
|
hardware_ek: row.hardware_ek,
|
|
kundenverkaufspreis_netto: row.kundenverkaufspreis_netto,
|
|
tarif_name: row.tarif_name,
|
|
anbieter_name: row.anbieter_name,
|
|
// Legacy fields for frontend compatibility
|
|
provision_typ: 'fix',
|
|
provision_betrag: shop_provision,
|
|
provision_beschreibung: 'Neue einfache Berechnung',
|
|
berechnete_provision: shop_provision.toFixed(2),
|
|
quelle: 'Tarif-Provision',
|
|
// New fields
|
|
shop_provision_euro: shop_provision,
|
|
berechnete_shop_provision: shop_provision.toFixed(2),
|
|
mitarbeiter_prozentsatz: mitarbeiter_prozentsatz,
|
|
berechnete_mitarbeiter_provision: mitarbeiter_provision.toFixed(2),
|
|
mitarbeiter_quelle: `${mitarbeiter_prozentsatz}% von ${shop_provision}€`
|
|
};
|
|
});
|
|
|
|
// Calculate totals
|
|
const totalShopProvision = processedRows.reduce((sum, row) => {
|
|
return sum + parseFloat(row.berechnete_shop_provision);
|
|
}, 0);
|
|
|
|
const totalMitarbeiterProvision = processedRows.reduce((sum, row) => {
|
|
return sum + parseFloat(row.berechnete_mitarbeiter_provision);
|
|
}, 0);
|
|
|
|
res.json({
|
|
schaltungen: processedRows,
|
|
total_provision: totalShopProvision.toFixed(2), // Legacy field
|
|
total_shop_provision: totalShopProvision.toFixed(2),
|
|
total_mitarbeiter_provision: totalMitarbeiterProvision.toFixed(2),
|
|
total_gesamt_provision: (totalShopProvision + totalMitarbeiterProvision).toFixed(2)
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
console.error('Error in provisions API:', error);
|
|
res.status(500).json({ error: error.message });
|
|
|
|
}
|
|
});
|
|
|
|
// Tarifoptionen API
|
|
|
|
// Get all tarifoptionen with anbieter and tarif names for admin
|
|
app.get('/api/tarifoptionen', (req, res) => {
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT
|
|
t.*,
|
|
a.name as anbieter_name,
|
|
ta.name as tarif_name
|
|
FROM tarifoptionen t
|
|
JOIN anbieter a ON t.anbieter_id = a.id
|
|
LEFT JOIN tarife ta ON t.tarif_id = ta.id
|
|
WHERE t.aktiv = 1
|
|
ORDER BY a.name, t.tarif_id IS NULL DESC, COALESCE(ta.name, ''), t.name
|
|
`;
|
|
|
|
console.log('Executing tarifoptionen query...');
|
|
db.all(query, (err, rows) => {
|
|
if (err) {
|
|
console.error('Tarifoptionen API error:', err.message);
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
console.log(`Tarifoptionen API: returning ${rows.length} results`);
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Get tarifoptionen by tariff (includes both tariff-specific and anbieter-wide options)
|
|
app.get('/api/tarife/:tarif_id/tarifoptionen', (req, res) => {
|
|
const { tarif_id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
// Get all tarifoptionen for this tariff from the old table
|
|
// (includes both manually created and auto-synced from new structure)
|
|
const query = `
|
|
SELECT * FROM tarifoptionen
|
|
WHERE tarif_id = ? AND aktiv = 1
|
|
ORDER BY name
|
|
`;
|
|
|
|
db.all(query, [tarif_id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add new tarifoption
|
|
app.post('/api/tarifoptionen', (req, res) => {
|
|
const {
|
|
anbieter_id,
|
|
tarif_id,
|
|
name,
|
|
code,
|
|
provision_euro,
|
|
beschreibung
|
|
} = req.body;
|
|
|
|
// Validate that either anbieter_id is provided (for anbieter-wide) or tarif_id (for tariff-specific)
|
|
if (!anbieter_id && !tarif_id) {
|
|
return res.status(400).json({ error: 'Entweder Anbieter oder Tarif muss angegeben werden' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
// If tarif_id is provided, get anbieter_id from tariff
|
|
if (tarif_id && !anbieter_id) {
|
|
db.get('SELECT anbieter_id FROM tarife WHERE id = ?', [tarif_id], (err, tarif) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
if (!tarif) {
|
|
res.status(404).json({ error: 'Tarif nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
insertTarifoption(db, res, tarif.anbieter_id, tarif_id, name, code, provision_euro, beschreibung);
|
|
});
|
|
} else {
|
|
// Direct anbieter-wide option
|
|
insertTarifoption(db, res, anbieter_id, tarif_id || null, name, code, provision_euro, beschreibung);
|
|
}
|
|
});
|
|
|
|
function insertTarifoption(db, res, anbieter_id, tarif_id, name, code, provision_euro, beschreibung) {
|
|
const query = `
|
|
INSERT INTO tarifoptionen (
|
|
anbieter_id, tarif_id, name, code, provision_euro, beschreibung
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [
|
|
anbieter_id,
|
|
tarif_id,
|
|
name,
|
|
code || null,
|
|
provision_euro || 0,
|
|
beschreibung || null
|
|
], function(err) {
|
|
if (err) {
|
|
if (err.message.includes('UNIQUE constraint failed')) {
|
|
const scope = tarif_id ? 'für diesen Tarif' : 'für diesen Anbieter';
|
|
res.status(400).json({ error: `Eine Tarifoption mit diesem Namen existiert bereits ${scope}` });
|
|
} else {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Tarifoption erfolgreich erstellt'
|
|
});
|
|
|
|
});
|
|
}
|
|
|
|
// Get tarifoptionen for schaltung
|
|
app.get('/api/schaltungen/:id/tarifoptionen', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
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
|
|
`;
|
|
|
|
db.all(query, [id], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add tarifoption to schaltung
|
|
app.post('/api/schaltungen/:id/tarifoptionen', (req, res) => {
|
|
const schaltung_id = req.params.id;
|
|
const { tarifoption_id } = req.body;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
INSERT INTO schaltungen_tarifoptionen (schaltung_id, tarifoption_id)
|
|
VALUES (?, ?)
|
|
`;
|
|
|
|
db.run(query, [schaltung_id, tarifoption_id], function(err) {
|
|
if (err) {
|
|
if (err.message.includes('UNIQUE constraint failed')) {
|
|
res.status(400).json({ error: 'Tarifoption bereits hinzugefügt' });
|
|
} else {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Tarifoption erfolgreich hinzugefügt'
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Remove tarifoption from schaltung
|
|
app.delete('/api/schaltungen/:schaltung_id/tarifoptionen/:tarifoption_id', (req, res) => {
|
|
const { schaltung_id, tarifoption_id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
db.run('DELETE FROM schaltungen_tarifoptionen WHERE schaltung_id = ? AND tarifoption_id = ?', [schaltung_id, tarifoption_id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Verknüpfung nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Tarifoption erfolgreich entfernt' });
|
|
|
|
});
|
|
});
|
|
|
|
// Create new tarifoptionen with multiple tarif assignments
|
|
app.post('/api/tarifoptionen/multi', (req, res) => {
|
|
const {
|
|
anbieter_id,
|
|
tarif_ids = [], // Array of tarif IDs
|
|
is_anbieter_weit = false,
|
|
name,
|
|
code,
|
|
provision_euro,
|
|
beschreibung
|
|
} = req.body;
|
|
|
|
if (!anbieter_id || !name) {
|
|
return res.status(400).json({ error: 'Anbieter und Name sind erforderlich' });
|
|
}
|
|
|
|
if (!is_anbieter_weit && (!tarif_ids || tarif_ids.length === 0)) {
|
|
return res.status(400).json({ error: 'Bei tarif-spezifischen Optionen müssen Tarife ausgewählt werden' });
|
|
}
|
|
|
|
const db = getDatabase();
|
|
|
|
// Insert into new tarifoptionen table
|
|
const insertQuery = `
|
|
INSERT INTO tarifoptionen_new (name, code, provision_euro, beschreibung)
|
|
VALUES (?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(insertQuery, [name, code || null, provision_euro || 0, beschreibung || null], function(err) {
|
|
if (err) {
|
|
console.error('Error inserting tarifoption:', err);
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
const tarifoptionId = this.lastID;
|
|
|
|
if (is_anbieter_weit) {
|
|
// Get all tarifs for the anbieter
|
|
db.all('SELECT id FROM tarife WHERE anbieter_id = ?', [anbieter_id], (err, tarifs) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Insert into tarifoptionen_tarife for all tarifs
|
|
const assignments = tarifs.map(tarif => [tarifoptionId, tarif.id]);
|
|
insertTarifAssignmentsAndLegacy(db, res, tarifoptionId, assignments, name, code, provision_euro, beschreibung, anbieter_id);
|
|
});
|
|
} else {
|
|
// Insert only selected tarifs
|
|
const assignments = tarif_ids.map(tarif_id => [tarifoptionId, tarif_id]);
|
|
insertTarifAssignmentsAndLegacy(db, res, tarifoptionId, assignments, name, code, provision_euro, beschreibung, anbieter_id);
|
|
}
|
|
});
|
|
});
|
|
|
|
function insertTarifAssignments(db, res, tarifoptionId, assignments) {
|
|
const stmt = db.prepare('INSERT INTO tarifoptionen_tarife (tarifoption_id, tarif_id) VALUES (?, ?)');
|
|
|
|
assignments.forEach(assignment => {
|
|
stmt.run(assignment, (err) => {
|
|
if (err) {
|
|
console.error('Error inserting tarif assignment:', err);
|
|
}
|
|
});
|
|
});
|
|
|
|
stmt.finalize((err) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
} else {
|
|
res.json({
|
|
id: tarifoptionId,
|
|
message: 'Tarifoption erfolgreich erstellt',
|
|
assignments: assignments.length
|
|
});
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
function insertTarifAssignmentsAndLegacy(db, res, tarifoptionId, assignments, name, code, provision_euro, beschreibung, anbieter_id) {
|
|
// First insert into new structure (tarifoptionen_tarife)
|
|
const stmt = db.prepare('INSERT INTO tarifoptionen_tarife (tarifoption_id, tarif_id) VALUES (?, ?)');
|
|
|
|
assignments.forEach(assignment => {
|
|
stmt.run(assignment, (err) => {
|
|
if (err) {
|
|
console.error('Error inserting tarif assignment:', err);
|
|
}
|
|
});
|
|
});
|
|
|
|
stmt.finalize((err) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Now also insert into old structure (tarifoptionen) for backward compatibility with schaltungen
|
|
const legacyStmt = db.prepare('INSERT OR IGNORE INTO tarifoptionen (anbieter_id, tarif_id, name, code, provision_euro, beschreibung) VALUES (?, ?, ?, ?, ?, ?)');
|
|
|
|
assignments.forEach(assignment => {
|
|
const tarif_id = assignment[1]; // assignment is [tarifoptionId, tarif_id]
|
|
legacyStmt.run([anbieter_id, tarif_id, name, code, provision_euro || 0, beschreibung], (err) => {
|
|
if (err) {
|
|
console.error('Error inserting legacy tarifoption:', err);
|
|
}
|
|
});
|
|
});
|
|
|
|
legacyStmt.finalize((err) => {
|
|
if (err) {
|
|
console.error('Error finalizing legacy insert:', err);
|
|
}
|
|
|
|
res.json({
|
|
id: tarifoptionId,
|
|
message: 'Tarifoption erfolgreich erstellt',
|
|
assignments: assignments.length
|
|
});
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
// Get tarifoptionen with their tarif assignments
|
|
app.get('/api/tarifoptionen/multi', (req, res) => {
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT
|
|
tn.id,
|
|
tn.name,
|
|
tn.code,
|
|
tn.provision_euro,
|
|
tn.beschreibung,
|
|
tn.aktiv,
|
|
tn.created_at,
|
|
GROUP_CONCAT(t.name || ' (' || a.name || ')') as tarif_names,
|
|
GROUP_CONCAT(t.id) as tarif_ids
|
|
FROM tarifoptionen_new tn
|
|
LEFT JOIN tarifoptionen_tarife tt ON tn.id = tt.tarifoption_id
|
|
LEFT JOIN tarife t ON tt.tarif_id = t.id
|
|
LEFT JOIN anbieter a ON t.anbieter_id = a.id
|
|
WHERE tn.aktiv = 1
|
|
GROUP BY tn.id, tn.name, tn.code, tn.provision_euro, tn.beschreibung, tn.aktiv, tn.created_at
|
|
ORDER BY tn.created_at DESC
|
|
`;
|
|
|
|
db.all(query, [], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Delete tarifoption from new structure
|
|
app.delete('/api/tarifoptionen/multi/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const db = getDatabase();
|
|
|
|
// First get the tarifoption details to delete from old structure
|
|
db.get('SELECT name, code FROM tarifoptionen_new WHERE id = ?', [id], (err, option) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (!option) {
|
|
res.status(404).json({ error: 'Tarifoption nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete from tarifoptionen_tarife first (foreign key constraint)
|
|
db.run('DELETE FROM tarifoptionen_tarife WHERE tarifoption_id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete from old structure (match by name and code - will delete all matching entries across all tarifs)
|
|
db.run('DELETE FROM tarifoptionen WHERE name = ? AND (code = ? OR (code IS NULL AND ? IS NULL))',
|
|
[option.name, option.code, option.code], function(err) {
|
|
if (err) {
|
|
console.error('Error deleting from old tarifoptionen:', err);
|
|
// Don't fail the whole operation
|
|
}
|
|
console.log(`Deleted ${this.changes} entries from old tarifoptionen table`);
|
|
});
|
|
|
|
// Then delete from tarifoptionen_new
|
|
db.run('DELETE FROM tarifoptionen_new WHERE id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
res.json({ message: 'Tarifoption erfolgreich gelöscht' });
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Update tarifoption in new structure
|
|
app.put('/api/tarifoptionen/multi/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, code, provision_euro, beschreibung, anbieter_id, is_anbieter_weit, tarif_ids } = req.body;
|
|
const db = getDatabase();
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: 'Tarifoption-Name ist erforderlich' });
|
|
}
|
|
|
|
db.serialize(() => {
|
|
db.run('BEGIN TRANSACTION');
|
|
|
|
// Update the tarifoption itself
|
|
const updateQuery = `
|
|
UPDATE tarifoptionen_new
|
|
SET name = ?, code = ?, provision_euro = ?, beschreibung = ?
|
|
WHERE id = ?
|
|
`;
|
|
|
|
db.run(updateQuery, [name, code, provision_euro || 0, beschreibung, id], function(err) {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.changes === 0) {
|
|
db.run('ROLLBACK');
|
|
res.status(404).json({ error: 'Tarifoption nicht gefunden' });
|
|
|
|
return;
|
|
}
|
|
|
|
// Delete existing tarif associations from new structure
|
|
db.run('DELETE FROM tarifoptionen_tarife WHERE tarifoption_id = ?', [id], function(err) {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Also delete from old structure (match by name and code for cleanup - will delete all matching entries)
|
|
db.run('DELETE FROM tarifoptionen WHERE name = ? AND (code = ? OR (code IS NULL AND ? IS NULL))',
|
|
[name, code, code], function(err) {
|
|
if (err) {
|
|
console.error('Error deleting old tarifoptionen:', err);
|
|
// Don't fail the whole operation if old table cleanup fails
|
|
}
|
|
console.log(`Deleted ${this.changes} old tarifoptionen entries for update`);
|
|
});
|
|
|
|
// If anbieter-weit, get all tarifs for the anbieter
|
|
if (is_anbieter_weit && anbieter_id) {
|
|
db.all('SELECT id FROM tarife WHERE anbieter_id = ?', [anbieter_id], (err, tarifs) => {
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
res.status(500).json({ error: err.message });
|
|
|
|
return;
|
|
}
|
|
|
|
// Insert tarif associations for all tarifs of the anbieter
|
|
let completed = 0;
|
|
const total = tarifs.length;
|
|
|
|
if (total === 0) {
|
|
db.run('COMMIT');
|
|
res.json({ message: 'Tarifoption erfolgreich aktualisiert' });
|
|
|
|
return;
|
|
}
|
|
|
|
tarifs.forEach(tarif => {
|
|
db.run('INSERT INTO tarifoptionen_tarife (tarifoption_id, tarif_id) VALUES (?, ?)',
|
|
[id, tarif.id], function(err) {
|
|
completed++;
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Also insert into old structure
|
|
db.run('INSERT OR IGNORE INTO tarifoptionen (anbieter_id, tarif_id, name, code, provision_euro, beschreibung) VALUES (?, ?, ?, ?, ?, ?)',
|
|
[anbieter_id, tarif.id, name, code, provision_euro || 0, beschreibung], function(err) {
|
|
if (err) {
|
|
console.error('Error inserting into old tarifoptionen:', err);
|
|
}
|
|
});
|
|
|
|
if (completed === total) {
|
|
db.run('COMMIT');
|
|
if (!res.headersSent) {
|
|
res.json({ message: 'Tarifoption erfolgreich aktualisiert' });
|
|
}
|
|
|
|
}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
} else if (tarif_ids && tarif_ids.length > 0) {
|
|
// Insert specific tarif associations
|
|
let completed = 0;
|
|
const total = tarif_ids.length;
|
|
|
|
tarif_ids.forEach(tarifId => {
|
|
db.run('INSERT INTO tarifoptionen_tarife (tarifoption_id, tarif_id) VALUES (?, ?)',
|
|
[id, tarifId], function(err) {
|
|
completed++;
|
|
if (err) {
|
|
db.run('ROLLBACK');
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Also insert into old structure - need to get anbieter_id first
|
|
db.get('SELECT anbieter_id FROM tarife WHERE id = ?', [tarifId], (err, tarif) => {
|
|
if (!err && tarif) {
|
|
db.run('INSERT OR IGNORE INTO tarifoptionen (anbieter_id, tarif_id, name, code, provision_euro, beschreibung) VALUES (?, ?, ?, ?, ?, ?)',
|
|
[tarif.anbieter_id, tarifId, name, code, provision_euro || 0, beschreibung], function(err) {
|
|
if (err) {
|
|
console.error('Error inserting into old tarifoptionen:', err);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (completed === total) {
|
|
db.run('COMMIT');
|
|
if (!res.headersSent) {
|
|
res.json({ message: 'Tarifoption erfolgreich aktualisiert' });
|
|
}
|
|
|
|
}
|
|
}
|
|
);
|
|
});
|
|
} else {
|
|
db.run('COMMIT');
|
|
res.json({ message: 'Tarifoption erfolgreich aktualisiert' });
|
|
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// Update tarifoption
|
|
app.put('/api/tarifoptionen/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const { name, provision_betrag } = req.body;
|
|
const db = getDatabase();
|
|
|
|
if (!name) {
|
|
return res.status(400).json({ error: 'Tarifoption-Name ist erforderlich' });
|
|
}
|
|
|
|
const query = `
|
|
UPDATE tarifoptionen
|
|
SET name = ?, provision_betrag = ?
|
|
WHERE id = ?
|
|
`;
|
|
|
|
db.run(query, [name, provision_betrag || null, id], function(err) {
|
|
if (err) {
|
|
if (err.message.includes('UNIQUE constraint failed')) {
|
|
res.status(400).json({ error: 'Eine Tarifoption mit diesem Namen existiert bereits für diesen Tarif' });
|
|
} else {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Tarifoption nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Tarifoption erfolgreich aktualisiert' });
|
|
|
|
});
|
|
});
|
|
|
|
// Delete tarifoption
|
|
app.delete('/api/tarifoptionen/:id', (req, res) => {
|
|
const id = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
db.run('DELETE FROM tarifoptionen WHERE id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Tarifoption nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Tarifoption erfolgreich gelöscht' });
|
|
|
|
});
|
|
});
|
|
|
|
// Mitarbeiter-Prozentsätze endpoints
|
|
|
|
// Get mitarbeiter-prozentsaetze for specific anbieter
|
|
app.get('/api/anbieter/:id/mitarbeiter-prozentsaetze', (req, res) => {
|
|
const anbieterId = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
SELECT * FROM mitarbeiter_prozentsaetze
|
|
WHERE anbieter_id = ?
|
|
ORDER BY gueltig_ab DESC
|
|
`;
|
|
|
|
db.all(query, [anbieterId], (err, rows) => {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
res.json(rows);
|
|
|
|
});
|
|
});
|
|
|
|
// Add new mitarbeiter-prozentsatz
|
|
app.post('/api/mitarbeiter-prozentsaetze', (req, res) => {
|
|
const {
|
|
anbieter_id,
|
|
prozentsatz,
|
|
gueltig_ab,
|
|
beschreibung
|
|
} = req.body;
|
|
const db = getDatabase();
|
|
|
|
const query = `
|
|
INSERT INTO mitarbeiter_prozentsaetze (
|
|
anbieter_id, prozentsatz, gueltig_ab, beschreibung
|
|
)
|
|
VALUES (?, ?, ?, ?)
|
|
`;
|
|
|
|
db.run(query, [
|
|
anbieter_id,
|
|
prozentsatz,
|
|
gueltig_ab || new Date().toISOString().split('T')[0],
|
|
beschreibung || null
|
|
], function(err) {
|
|
if (err) {
|
|
if (err.message.includes('UNIQUE constraint failed')) {
|
|
res.status(400).json({ error: 'Ein Mitarbeiter-Prozentsatz für dieses Datum existiert bereits für diesen Anbieter' });
|
|
} else {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
return;
|
|
}
|
|
res.json({
|
|
id: this.lastID,
|
|
message: 'Mitarbeiter-Prozentsatz erfolgreich erstellt'
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
// Delete mitarbeiter-prozentsatz
|
|
app.delete('/api/mitarbeiter-prozentsaetze/:id', (req, res) => {
|
|
const id = req.params.id;
|
|
const db = getDatabase();
|
|
|
|
db.run('DELETE FROM mitarbeiter_prozentsaetze WHERE id = ?', [id], function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Mitarbeiter-Prozentsatz nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Mitarbeiter-Prozentsatz erfolgreich gelöscht' });
|
|
|
|
});
|
|
});
|
|
|
|
// Update mitarbeiter-provision for tarif
|
|
app.put('/api/tarife/:id/mitarbeiter-provision', (req, res) => {
|
|
const tarifId = req.params.id;
|
|
const { mitarbeiter_provision } = req.body;
|
|
const db = getDatabase();
|
|
|
|
db.run('UPDATE tarife SET mitarbeiter_provision = ? WHERE id = ?',
|
|
[mitarbeiter_provision || 0, tarifId],
|
|
function(err) {
|
|
if (err) {
|
|
res.status(500).json({ error: err.message });
|
|
return;
|
|
}
|
|
if (this.changes === 0) {
|
|
res.status(404).json({ error: 'Tarif nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ message: 'Mitarbeiter-Provision erfolgreich aktualisiert' });
|
|
|
|
});
|
|
});
|
|
|
|
// Serve main HTML file
|
|
app.get('/', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server läuft auf http://localhost:${PORT}`);
|
|
}); |