Initial commit: Provisions-Tracker application
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>
This commit is contained in:
commit
55ec916f35
10 files changed
+11024
No files matched your search
+333
@@ -0,0 +1,333 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, 'provision.db');
|
||||
|
||||
// Singleton database instance
|
||||
let dbInstance = null;
|
||||
|
||||
// Get or create database instance (Singleton pattern)
|
||||
function getDatabase() {
|
||||
if (!dbInstance) {
|
||||
dbInstance = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('Error opening database:', err.message);
|
||||
dbInstance = null;
|
||||
throw err;
|
||||
}
|
||||
console.log('Connected to SQLite database');
|
||||
});
|
||||
|
||||
// Set busy timeout to handle concurrent access
|
||||
dbInstance.configure('busyTimeout', 3000);
|
||||
}
|
||||
return dbInstance;
|
||||
}
|
||||
|
||||
// Enable foreign keys for a database connection
|
||||
function enableForeignKeys(db) {
|
||||
db.run('PRAGMA foreign_keys = ON', (err) => {
|
||||
if (err) {
|
||||
console.error('Error enabling foreign keys:', err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close database connection (for graceful shutdown)
|
||||
function closeDatabase() {
|
||||
if (dbInstance) {
|
||||
dbInstance.close((err) => {
|
||||
if (err) {
|
||||
console.error('Error closing database:', err.message);
|
||||
} else {
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
dbInstance = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initDatabase() {
|
||||
const db = getDatabase();
|
||||
|
||||
db.serialize(() => {
|
||||
// Anbieter (Providers) table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS anbieter (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// Mitarbeiter-Prozentsätze pro Anbieter (can be changed retrospectively)
|
||||
db.run(`CREATE TABLE IF NOT EXISTS mitarbeiter_prozentsaetze (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
anbieter_id INTEGER NOT NULL,
|
||||
prozentsatz DECIMAL(5,2) NOT NULL DEFAULT 0,
|
||||
gueltig_ab DATE DEFAULT CURRENT_DATE,
|
||||
beschreibung TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (anbieter_id) REFERENCES anbieter (id),
|
||||
UNIQUE(anbieter_id, gueltig_ab)
|
||||
)`);
|
||||
|
||||
// Tarife (Tariffs) table - simplified with fixed euro amounts only
|
||||
db.run(`CREATE TABLE IF NOT EXISTS tarife (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
anbieter_id INTEGER,
|
||||
provision_euro DECIMAL(10,2) DEFAULT 0,
|
||||
mitarbeiter_provision DECIMAL(10,2) DEFAULT 0,
|
||||
beschreibung TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (anbieter_id) REFERENCES anbieter (id),
|
||||
UNIQUE(name, anbieter_id)
|
||||
)`);
|
||||
|
||||
// Add mitarbeiter_provision column if it doesn't exist (for existing databases)
|
||||
db.run(`ALTER TABLE tarife ADD COLUMN mitarbeiter_provision DECIMAL(10,2) DEFAULT 0`, (err) => {
|
||||
// Ignore error if column already exists
|
||||
});
|
||||
|
||||
// Provision rules table (tariff-specific)
|
||||
db.run(`CREATE TABLE IF NOT EXISTS provisionsregeln (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tarif_id INTEGER NOT NULL,
|
||||
provision_typ TEXT NOT NULL CHECK(provision_typ IN ('fix', 'prozent')),
|
||||
betrag DECIMAL(10,2) NOT NULL,
|
||||
beschreibung TEXT,
|
||||
verkaeufer_provision_typ TEXT CHECK(verkaeufer_provision_typ IN ('fix', 'prozent')),
|
||||
verkaeufer_provision_betrag DECIMAL(10,2),
|
||||
verkaeufer_provision_beschreibung TEXT,
|
||||
gueltig_ab DATE DEFAULT CURRENT_DATE,
|
||||
aktiv BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tarif_id) REFERENCES tarife (id)
|
||||
)`);
|
||||
|
||||
// Global provider commission rules table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS anbieter_provisionsregeln (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
anbieter_id INTEGER NOT NULL,
|
||||
provision_typ TEXT NOT NULL CHECK(provision_typ IN ('fix', 'prozent')),
|
||||
betrag DECIMAL(10,2) NOT NULL,
|
||||
beschreibung TEXT,
|
||||
verkaeufer_provision_typ TEXT CHECK(verkaeufer_provision_typ IN ('fix', 'prozent')),
|
||||
verkaeufer_provision_betrag DECIMAL(10,2),
|
||||
verkaeufer_provision_beschreibung TEXT,
|
||||
gueltig_ab DATE DEFAULT CURRENT_DATE,
|
||||
aktiv BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (anbieter_id) REFERENCES anbieter (id)
|
||||
)`);
|
||||
|
||||
// Schaltungen (Connections/Commissions) table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS schaltungen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
schaltungs_id TEXT NOT NULL UNIQUE,
|
||||
eingabe_datum DATE NOT NULL,
|
||||
schaltungs_datum DATE NOT NULL,
|
||||
vorname TEXT NOT NULL,
|
||||
nachname TEXT NOT NULL,
|
||||
firma TEXT,
|
||||
rufnummer TEXT NOT NULL,
|
||||
anbieter_id INTEGER NOT NULL,
|
||||
tarif_id INTEGER NOT NULL,
|
||||
hardware TEXT,
|
||||
hardware_ek DECIMAL(10,2),
|
||||
kundenverkaufspreis_netto DECIMAL(10,2),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (anbieter_id) REFERENCES anbieter (id),
|
||||
FOREIGN KEY (tarif_id) REFERENCES tarife (id)
|
||||
)`);
|
||||
|
||||
// Add new hardware pricing columns if they don't exist (for existing databases)
|
||||
db.run(`ALTER TABLE schaltungen ADD COLUMN hardware_ek DECIMAL(10,2)`, (err) => {
|
||||
// Ignore error if column already exists
|
||||
});
|
||||
db.run(`ALTER TABLE schaltungen ADD COLUMN kundenverkaufspreis_netto DECIMAL(10,2)`, (err) => {
|
||||
// Ignore error if column already exists
|
||||
});
|
||||
|
||||
// Dokumente (Documents) table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS dokumente (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
schaltung_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
original_filename TEXT NOT NULL,
|
||||
filepath TEXT NOT NULL,
|
||||
filesize INTEGER,
|
||||
mimetype TEXT,
|
||||
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (schaltung_id) REFERENCES schaltungen (id) ON DELETE CASCADE
|
||||
)`);
|
||||
|
||||
// Deleted Schaltungen tracking table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS geloeschte_schaltungen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
original_id INTEGER NOT NULL,
|
||||
schaltungs_id TEXT NOT NULL,
|
||||
eingabe_datum DATE NOT NULL,
|
||||
schaltungs_datum DATE NOT NULL,
|
||||
vorname TEXT NOT NULL,
|
||||
nachname TEXT NOT NULL,
|
||||
firma TEXT,
|
||||
rufnummer TEXT NOT NULL,
|
||||
anbieter_id INTEGER NOT NULL,
|
||||
tarif_id INTEGER NOT NULL,
|
||||
hardware TEXT,
|
||||
anbieter_name TEXT NOT NULL,
|
||||
tarif_name TEXT NOT NULL,
|
||||
loeschgrund TEXT NOT NULL,
|
||||
geloescht_am DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
geloescht_von TEXT DEFAULT 'System'
|
||||
)`);
|
||||
|
||||
// Insert some default providers and tariffs for testing
|
||||
db.run(`INSERT OR IGNORE INTO anbieter (name) VALUES
|
||||
('Telekom'),
|
||||
('Vodafone'),
|
||||
('O2'),
|
||||
('1&1')`);
|
||||
|
||||
db.run(`INSERT OR IGNORE INTO tarife (name, anbieter_id, provision_euro, beschreibung) VALUES
|
||||
('MagentaMobil S', 1, 150.00, 'Telekom Basis-Tarif'),
|
||||
('MagentaMobil M', 1, 200.00, 'Telekom Standard-Tarif'),
|
||||
('MagentaMobil L', 1, 250.00, 'Telekom Premium-Tarif'),
|
||||
('VF green LTE 10GB mit Smartphone 5', 2, 190.00, 'Vodafone Standard-Paket'),
|
||||
('Red S', 2, 120.00, 'Vodafone Einsteiger-Tarif'),
|
||||
('Red M', 2, 160.00, 'Vodafone Standard-Tarif'),
|
||||
('Red L', 2, 200.00, 'Vodafone Premium-Tarif'),
|
||||
('o2 Free S', 3, 100.00, 'O2 Basis-Tarif'),
|
||||
('o2 Free M', 3, 140.00, 'O2 Standard-Tarif'),
|
||||
('o2 Free L', 3, 180.00, 'O2 Premium-Tarif')`);
|
||||
|
||||
// Insert mitarbeiter prozentsätze for each provider
|
||||
db.run(`INSERT OR IGNORE INTO mitarbeiter_prozentsaetze (anbieter_id, prozentsatz, beschreibung) VALUES
|
||||
(1, 8.00, 'Telekom Mitarbeiter-Provision'),
|
||||
(2, 10.00, 'Vodafone/Freenet Mitarbeiter-Provision'),
|
||||
(3, 7.50, 'O2 Mitarbeiter-Provision'),
|
||||
(4, 9.00, '1&1 Mitarbeiter-Provision')`);
|
||||
|
||||
// Insert some example global provider commission rules
|
||||
db.run(`INSERT OR IGNORE INTO anbieter_provisionsregeln (anbieter_id, provision_typ, betrag, beschreibung) VALUES
|
||||
(3, 'fix', 20.00, 'Standard O2 Provision'),
|
||||
(4, 'prozent', 12.00, '12% Standard 1&1 Provision')`);
|
||||
|
||||
// Tarifoptionen - updated structure for multiple tarif support
|
||||
db.run(`CREATE TABLE IF NOT EXISTS tarifoptionen_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
code TEXT,
|
||||
provision_euro DECIMAL(10,2) DEFAULT 0,
|
||||
beschreibung TEXT,
|
||||
aktiv BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// Many-to-many relationship between tarifoptionen and tarife
|
||||
db.run(`CREATE TABLE IF NOT EXISTS tarifoptionen_tarife (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tarifoption_id INTEGER NOT NULL,
|
||||
tarif_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tarifoption_id) REFERENCES tarifoptionen_new (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tarif_id) REFERENCES tarife (id) ON DELETE CASCADE,
|
||||
UNIQUE(tarifoption_id, tarif_id)
|
||||
)`);
|
||||
|
||||
// Keep old table for migration compatibility
|
||||
db.run(`CREATE TABLE IF NOT EXISTS tarifoptionen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tarif_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT,
|
||||
provision_euro DECIMAL(10,2) DEFAULT 0,
|
||||
beschreibung TEXT,
|
||||
aktiv BOOLEAN DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tarif_id) REFERENCES tarife (id),
|
||||
UNIQUE(tarif_id, name)
|
||||
)`);
|
||||
|
||||
// Schaltungen_Tarifoptionen (Many-to-Many relationship)
|
||||
db.run(`CREATE TABLE IF NOT EXISTS schaltungen_tarifoptionen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
schaltung_id INTEGER NOT NULL,
|
||||
tarifoption_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (schaltung_id) REFERENCES schaltungen (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (tarifoption_id) REFERENCES tarifoptionen (id),
|
||||
UNIQUE(schaltung_id, tarifoption_id)
|
||||
)`);
|
||||
|
||||
// Insert example tarifoptionen with simplified euro amounts (old format)
|
||||
db.run(`INSERT OR IGNORE INTO tarifoptionen (tarif_id, name, code, provision_euro, beschreibung) VALUES
|
||||
(1, 'Magenta Premium Option', 'MAG-PREM', 50.00, 'Premium-Zusatz für Telekom'),
|
||||
(1, 'Aktionscode Neukunde', 'NEU2024', 30.00, 'Neukunden-Bonus'),
|
||||
(4, '24 x 17€ MGP Rabatt GK A3958457', 'A3958457', -120.00, 'Vodafone Rabatt-Aktion'),
|
||||
(5, 'Red M Aktion Q1', 'RED-M-Q1', 25.00, 'Quartals-Aktion'),
|
||||
(6, 'Red L Premium Paket', 'RED-L-PREM', 35.00, 'Premium-Paket')`);
|
||||
|
||||
// Migration function to move data from old to new structure
|
||||
function migrateToNewTarifoptionen() {
|
||||
db.serialize(() => {
|
||||
// Check if migration is needed
|
||||
db.get(`SELECT COUNT(*) as count FROM tarifoptionen_new`, (err, row) => {
|
||||
if (err || row.count > 0) return; // Already migrated or error
|
||||
|
||||
console.log('Migrating tarifoptionen to new structure...');
|
||||
|
||||
// Get all unique tarifoptionen from old table
|
||||
db.all(`SELECT DISTINCT name, code, provision_euro, beschreibung, aktiv, created_at FROM tarifoptionen`, (err, oldOptions) => {
|
||||
if (err || !oldOptions.length) return;
|
||||
|
||||
oldOptions.forEach(option => {
|
||||
// Insert unique tarifoption into new table
|
||||
db.run(`INSERT OR IGNORE INTO tarifoptionen_new (name, code, provision_euro, beschreibung, aktiv, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[option.name, option.code, option.provision_euro, option.beschreibung, option.aktiv, option.created_at],
|
||||
function(err) {
|
||||
if (err) return;
|
||||
|
||||
const newTarifoptionId = this.lastID;
|
||||
|
||||
// Link this tarifoption to all tarifs it was originally assigned to
|
||||
db.all(`SELECT tarif_id FROM tarifoptionen WHERE name = ? AND code = ?`,
|
||||
[option.name, option.code],
|
||||
(err, tarifs) => {
|
||||
if (err) return;
|
||||
|
||||
tarifs.forEach(tarif => {
|
||||
db.run(`INSERT OR IGNORE INTO tarifoptionen_tarife (tarifoption_id, tarif_id) VALUES (?, ?)`,
|
||||
[newTarifoptionId, tarif.tarif_id]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Run migration only if not already done
|
||||
db.get(`SELECT COUNT(*) as count FROM tarifoptionen_new`, (err, row) => {
|
||||
if (!err && row && row.count === 0) {
|
||||
migrateToNewTarifoptionen();
|
||||
}
|
||||
});
|
||||
|
||||
// Keep existing individual tariff rules for backward compatibility
|
||||
db.run(`INSERT OR IGNORE INTO provisionsregeln (tarif_id, provision_typ, betrag, beschreibung) VALUES
|
||||
(1, 'fix', 25.00, 'Feste Provision für MagentaMobil S'),
|
||||
(2, 'fix', 35.00, 'Feste Provision für MagentaMobil M'),
|
||||
(3, 'fix', 45.00, 'Feste Provision für MagentaMobil L'),
|
||||
(4, 'prozent', 15.00, '15% Provision für Red S'),
|
||||
(5, 'prozent', 18.00, '18% Provision für Red M'),
|
||||
(6, 'prozent', 20.00, '20% Provision für Red L')`);
|
||||
});
|
||||
|
||||
// Enable foreign keys AFTER all tables and initial data are created
|
||||
enableForeignKeys(db);
|
||||
}
|
||||
|
||||
module.exports = { initDatabase, getDatabase, closeDatabase };
|
||||
Reference in new issue
Block a user