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
+26
@@ -0,0 +1,26 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Database (contains real customer data)
|
||||||
|
provision.db
|
||||||
|
provision.db.backup
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
|
||||||
|
# Uploaded files (real customer documents)
|
||||||
|
uploads/
|
||||||
|
|
||||||
|
# Local Claude settings
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# OS / Editor
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a **Provisions-Tracker** (Commission Tracker) application for managing employee commissions on telecommunications activations ("Schaltungen"). Built as a Node.js/Express backend with SQLite database and vanilla JavaScript frontend.
|
||||||
|
|
||||||
|
**Purpose**: Track commissions for telecom providers (Telekom, Vodafone, O2, 1&1), including tariffs, customer data, documents, and complex commission calculations.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
# Production mode
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# Development mode with auto-restart
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
The application runs on `http://localhost:3000` by default (configurable via `PORT` environment variable).
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- SQLite database file: `provision.db` (auto-created on first run)
|
||||||
|
- Database initialization happens automatically via `initDatabase()` in [database.js](database.js)
|
||||||
|
- Backup file exists: `provision.db.backup`
|
||||||
|
- No migrations system - uses `CREATE TABLE IF NOT EXISTS` and `ALTER TABLE` with error handling
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Three-Tier Structure
|
||||||
|
|
||||||
|
**Backend** ([server.js](server.js) - ~1987 lines)
|
||||||
|
- Express.js REST API with ~40+ endpoints
|
||||||
|
- Handles business logic, commission calculations, and database operations
|
||||||
|
- File upload handling via Multer (10MB limit)
|
||||||
|
- CORS enabled for development
|
||||||
|
|
||||||
|
**Database** ([database.js](database.js) - 288 lines)
|
||||||
|
- SQLite with 12+ tables
|
||||||
|
- Key tables: `anbieter` (providers), `tarife` (tariffs), `schaltungen` (connections/commissions), `dokumente` (documents)
|
||||||
|
- Complex commission system with multiple rule types (fixed/percentage)
|
||||||
|
- Includes soft-delete tracking (`geloeschte_schaltungen`)
|
||||||
|
|
||||||
|
**Frontend** ([public/](public/))
|
||||||
|
- Single-page app: [index.html](public/index.html), [script.js](public/script.js) (~4000+ lines)
|
||||||
|
- Tab-based navigation (Erfassung, Übersicht, Provisionen, Admin, Gelöschte)
|
||||||
|
- Preloading strategy for fast tab switching
|
||||||
|
- Drag-and-drop file uploads
|
||||||
|
|
||||||
|
### Key Database Tables and Relationships
|
||||||
|
|
||||||
|
```
|
||||||
|
anbieter (providers)
|
||||||
|
├─→ tarife (tariffs) - provision_euro, mitarbeiter_provision
|
||||||
|
│ ├─→ provisionsregeln (tariff-specific commission rules)
|
||||||
|
│ └─→ tarifoptionen_tarife (M2M link to options)
|
||||||
|
├─→ anbieter_provisionsregeln (global provider commission rules)
|
||||||
|
└─→ mitarbeiter_prozentsaetze (employee percentage rates per provider)
|
||||||
|
|
||||||
|
schaltungen (main commissions table)
|
||||||
|
├─→ anbieter_id, tarif_id (foreign keys)
|
||||||
|
├─→ dokumente (uploaded documents)
|
||||||
|
├─→ schaltungen_tarifoptionen (M2M selected tariff options)
|
||||||
|
└─→ hardware_ek, kundenverkaufspreis_netto (hardware pricing)
|
||||||
|
|
||||||
|
tarifoptionen_new (tariff options - new structure)
|
||||||
|
└─→ tarifoptionen_tarife (M2M relationship to tarifs)
|
||||||
|
|
||||||
|
tarifoptionen (old structure - kept for migration compatibility)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commission Calculation System
|
||||||
|
|
||||||
|
The app supports multiple commission calculation methods with complex fallback logic:
|
||||||
|
|
||||||
|
1. **Tariff-specific rules** (`provisionsregeln` table)
|
||||||
|
- Fixed amount or percentage
|
||||||
|
- Employee commission (verkaeufer_provision)
|
||||||
|
|
||||||
|
2. **Global provider rules** (`anbieter_provisionsregeln` table)
|
||||||
|
- Applied when no tariff-specific rule exists
|
||||||
|
|
||||||
|
3. **Direct tariff amounts** (fallback)
|
||||||
|
- `tarife.provision_euro` - company commission
|
||||||
|
- `tarife.mitarbeiter_provision` - employee commission
|
||||||
|
|
||||||
|
4. **Tariff options** (`tarifoptionen`)
|
||||||
|
- Additional commission adjustments per activation
|
||||||
|
- Can be positive or negative (e.g., rebates)
|
||||||
|
- Many-to-many relationship (options can apply to multiple tariffs)
|
||||||
|
|
||||||
|
**API endpoint**: `GET /api/schaltungen/:id/provision` - calculates total commission for a specific activation.
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
|
||||||
|
The system includes an in-place migration from old tarifoptionen structure (one-to-many with tarifs) to new structure (many-to-many via `tarifoptionen_new` and `tarifoptionen_tarife`). Migration runs automatically on startup via `migrateToNewTarifoptionen()` in [database.js:228-266](database.js#L228-L266).
|
||||||
|
|
||||||
|
## API Endpoint Categories
|
||||||
|
|
||||||
|
### Anbieter (Providers)
|
||||||
|
- `GET /api/anbieter` - List all providers
|
||||||
|
- `POST /api/anbieter` - Add provider
|
||||||
|
- `DELETE /api/anbieter/:id` - Delete provider (with dependency checks)
|
||||||
|
- `GET /api/anbieter/:id/provisionsregeln` - Get global commission rules
|
||||||
|
- `POST /api/anbieter-provisionsregeln` - Add global rule
|
||||||
|
- `PUT /api/anbieter-provisionsregeln/:id` - Update global rule
|
||||||
|
|
||||||
|
### Tarife (Tariffs)
|
||||||
|
- `GET /api/tarife` - List all tariffs
|
||||||
|
- `GET /api/tarife/:anbieter_id` - List tariffs for provider
|
||||||
|
- `GET /api/tarif/:id` - Get single tariff
|
||||||
|
- `POST /api/tarife` - Add tariff
|
||||||
|
- `PUT /api/tarife/:id` - Update tariff
|
||||||
|
- `DELETE /api/tarife/:id` - Delete tariff (with dependency checks)
|
||||||
|
- `GET /api/tarife/:id/provisionsregeln` - Get tariff commission rules
|
||||||
|
- `POST /api/provisionsregeln` - Add tariff rule
|
||||||
|
- `PUT /api/provisionsregeln/:id` - Update tariff rule
|
||||||
|
- `PUT /api/tarife/:id/mitarbeiter-provision` - Update employee commission
|
||||||
|
|
||||||
|
### Schaltungen (Commissions/Activations)
|
||||||
|
- `GET /api/schaltungen` - List all activations
|
||||||
|
- `POST /api/schaltungen` - Create activation
|
||||||
|
- `PUT /api/schaltungen/:id` - Update activation
|
||||||
|
- `DELETE /api/schaltungen/:id` - Delete activation (soft delete with reason tracking)
|
||||||
|
- `GET /api/schaltungen/:id/provision` - Calculate commission for activation
|
||||||
|
- `GET /api/provisionen` - Get commission overview/report
|
||||||
|
- `GET /api/geloeschte-schaltungen` - List deleted activations
|
||||||
|
|
||||||
|
### Dokumente (Documents)
|
||||||
|
- `POST /api/schaltungen/:id/dokumente` - Upload document (multipart/form-data)
|
||||||
|
- `GET /api/schaltungen/:id/dokumente` - List documents for activation
|
||||||
|
- `GET /api/dokumente/:id/download` - Download document
|
||||||
|
- `DELETE /api/dokumente/:id` - Delete document (removes file from disk)
|
||||||
|
|
||||||
|
### Tarifoptionen (Tariff Options)
|
||||||
|
- `GET /api/tarifoptionen` - List all options (old structure)
|
||||||
|
- `GET /api/tarifoptionen/multi` - List all options (new structure)
|
||||||
|
- `POST /api/tarifoptionen/multi` - Add option (new structure)
|
||||||
|
- `PUT /api/tarifoptionen/multi/:id` - Update option (new structure)
|
||||||
|
- `DELETE /api/tarifoptionen/multi/:id` - Delete option (new structure)
|
||||||
|
- `GET /api/schaltungen/:id/tarifoptionen` - Get options for activation
|
||||||
|
- `POST /api/schaltungen/:id/tarifoptionen` - Link option to activation
|
||||||
|
- `DELETE /api/schaltungen/:schaltung_id/tarifoptionen/:tarifoption_id` - Unlink option
|
||||||
|
|
||||||
|
### Mitarbeiter (Employee Settings)
|
||||||
|
- `GET /api/anbieter/:id/mitarbeiter-prozentsaetze` - Get employee percentages for provider
|
||||||
|
- `POST /api/mitarbeiter-prozentsaetze` - Add percentage rate
|
||||||
|
- `DELETE /api/mitarbeiter-prozentsaetze/:id` - Delete percentage rate
|
||||||
|
|
||||||
|
## Important Implementation Details
|
||||||
|
|
||||||
|
### File Uploads
|
||||||
|
- Storage: `uploads/` directory (created automatically)
|
||||||
|
- Naming: `{timestamp}-{random}.{extension}`
|
||||||
|
- Limit: 10MB per file
|
||||||
|
- Supported types: PDF, JPG, JPEG, PNG, DOC, DOCX (enforced client-side)
|
||||||
|
- Files are physically deleted when document is removed
|
||||||
|
|
||||||
|
### Frontend State Management
|
||||||
|
- **Preloading**: App preloads all tab data in background after 1 second delay for faster navigation
|
||||||
|
- **Tab switching**: Uses cached data when available (`showTab()` checks if data exists)
|
||||||
|
- Global variables: `currentSchaltungId`, `uploadedFiles`
|
||||||
|
- Message system: Toast notifications via `showMessage()` overlay
|
||||||
|
|
||||||
|
### Database Connection Pattern
|
||||||
|
**Singleton Pattern** - A single persistent database connection is used throughout the application lifecycle:
|
||||||
|
```javascript
|
||||||
|
const db = getDatabase(); // Returns singleton instance
|
||||||
|
// ... do work ...
|
||||||
|
// NO db.close() - connection persists
|
||||||
|
```
|
||||||
|
- Connection is opened once on startup and reused
|
||||||
|
- Foreign keys are enabled after initialization (`PRAGMA foreign_keys = ON`)
|
||||||
|
- Busy timeout set to 3000ms for concurrent access handling
|
||||||
|
- Graceful shutdown closes connection on SIGINT/SIGTERM
|
||||||
|
|
||||||
|
### Delete Operations
|
||||||
|
- **Soft delete**: Schaltungen are moved to `geloeschte_schaltungen` table with reason and timestamp
|
||||||
|
- **CASCADE delete**: Deleting a Schaltung automatically removes:
|
||||||
|
- Associated documents from database (via `ON DELETE CASCADE`)
|
||||||
|
- Document files from filesystem (handled in endpoint)
|
||||||
|
- Linked tarifoptionen (via `ON DELETE CASCADE`)
|
||||||
|
- **Hard delete**: Individual documents physically removed from filesystem when deleted
|
||||||
|
- **Dependency checks**: Providers and Tariffs check for usage before deletion
|
||||||
|
|
||||||
|
### Language
|
||||||
|
- **UI/Database**: German (Anbieter, Tarife, Schaltungen, etc.)
|
||||||
|
- **Code**: Mixed German/English
|
||||||
|
- **Comments**: Mostly English in code, German in UI strings
|
||||||
|
|
||||||
|
## Testing Data
|
||||||
|
|
||||||
|
Default data seeded on first run:
|
||||||
|
- **Providers**: Telekom, Vodafone, O2, 1&1
|
||||||
|
- **Tariffs**: 10 example tariffs with commission amounts
|
||||||
|
- **Employee percentages**: 8-10% per provider
|
||||||
|
- **Tariff options**: 5 example options including rebates
|
||||||
|
- **Global rules**: Example O2 (fixed 20€) and 1&1 (12% percentage) rules
|
||||||
|
|
||||||
|
## Common Development Patterns
|
||||||
|
|
||||||
|
### Adding a New API Endpoint
|
||||||
|
1. Add route in [server.js](server.js) following existing patterns
|
||||||
|
2. Use `getDatabase()` to open connection
|
||||||
|
3. Always call `db.close()` in callbacks
|
||||||
|
4. Return JSON with proper error handling
|
||||||
|
5. Update frontend in [script.js](script.js) with corresponding API call
|
||||||
|
|
||||||
|
### Adding a Database Table
|
||||||
|
1. Add `CREATE TABLE IF NOT EXISTS` in [database.js](database.js) `initDatabase()`
|
||||||
|
2. Add within `db.serialize()` block
|
||||||
|
3. Consider foreign key constraints
|
||||||
|
4. Add seed data with `INSERT OR IGNORE` if needed
|
||||||
|
|
||||||
|
### Modifying Commission Logic
|
||||||
|
- Main calculation logic is in `GET /api/schaltungen/:id/provision`
|
||||||
|
- Fallback order: provisionsregeln → anbieter_provisionsregeln → direct tariff amounts
|
||||||
|
- Always recalculate when tariff/provider/options change
|
||||||
|
- Employee commission calculated separately from company commission
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Provisions-Tracker
|
||||||
|
|
||||||
|
Ein Tool zur Erfassung und Verwaltung von Mitarbeiter-Provisionen bei Schaltungen.
|
||||||
|
|
||||||
|
## Funktionen
|
||||||
|
|
||||||
|
- **Schaltungen erfassen**: Vollständige Erfassung aller relevanten Daten
|
||||||
|
- Schaltungs-ID, Eingabe-Datum, Schaltungs-/Aktivierungsdatum
|
||||||
|
- Kundendaten (Vorname, Nachname, optional Firma, Rufnummer)
|
||||||
|
- Anbieter und Tarif (konfigurierbar über Admin-Bereich)
|
||||||
|
- Hardware (15-stellige Eingabe)
|
||||||
|
- Dokumentenupload mit Drag & Drop
|
||||||
|
|
||||||
|
- **Übersicht**: Tabelle aller erfassten Schaltungen mit Filtermöglichkeiten
|
||||||
|
|
||||||
|
- **Administration**:
|
||||||
|
- Anbieter hinzufügen und verwalten
|
||||||
|
- Tarife pro Anbieter konfigurieren
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Abhängigkeiten installieren:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Server starten:
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder für Entwicklung mit automatischem Neustart:
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Anwendung im Browser öffnen:
|
||||||
|
```
|
||||||
|
http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technische Details
|
||||||
|
|
||||||
|
- **Backend**: Node.js mit Express
|
||||||
|
- **Datenbank**: SQLite (wird automatisch erstellt)
|
||||||
|
- **Frontend**: Vanilla HTML/CSS/JavaScript
|
||||||
|
- **Datei-Upload**: Multer mit 10MB Limit
|
||||||
|
|
||||||
|
## Datenbankstruktur
|
||||||
|
|
||||||
|
- `anbieter`: Telekommunikationsanbieter
|
||||||
|
- `tarife`: Tarife pro Anbieter
|
||||||
|
- `schaltungen`: Haupttabelle für Provisionserfassung
|
||||||
|
- `dokumente`: Hochgeladene Dateien pro Schaltung
|
||||||
|
|
||||||
|
## Standarddaten
|
||||||
|
|
||||||
|
Die Anwendung wird mit folgenden Testdaten initialisiert:
|
||||||
|
- Anbieter: Telekom, Vodafone, O2, 1&1
|
||||||
|
- Beispiel-Tarife für jeden Anbieter
|
||||||
|
|
||||||
|
## Sicherheitshinweise
|
||||||
|
|
||||||
|
- Dateien werden im `uploads/` Verzeichnis gespeichert
|
||||||
|
- Maximale Dateigröße: 10MB
|
||||||
|
- Unterstützte Dateitypen: PDF, JPG, JPEG, PNG, DOC, DOCX
|
||||||
+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 };
|
||||||
Generated
+2608
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "provision-tracker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Tool for tracking employee commissions",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Commission Calculation Service
|
||||||
|
*
|
||||||
|
* This service handles all commission calculations for schaltungen.
|
||||||
|
* It implements a fallback hierarchy for determining commission rates:
|
||||||
|
* 1. Direct tariff commission (highest priority)
|
||||||
|
* 2. Tariff-specific rules
|
||||||
|
* 3. Global provider rules (fallback)
|
||||||
|
* 4. Tariff options (additive)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate shop commission based on hierarchy
|
||||||
|
* @param {Object} schaltungData - Data from database including tariff and provider info
|
||||||
|
* @param {Array} tarifoptionen - Selected tariff options for this schaltung
|
||||||
|
* @returns {Object} Calculation result with amount, type, description, and source
|
||||||
|
*/
|
||||||
|
function calculateShopProvision(schaltungData, tarifoptionen) {
|
||||||
|
let provision_typ = null;
|
||||||
|
let provision_betrag = null;
|
||||||
|
let provision_beschreibung = null;
|
||||||
|
let berechnete_provision = 0;
|
||||||
|
let quelle = 'Keine Shop-Provision definiert';
|
||||||
|
|
||||||
|
// Priority 1: Direct tariff commission
|
||||||
|
if (schaltungData.tarif_provision_typ && schaltungData.tarif_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.tarif_provision_typ;
|
||||||
|
provision_betrag = schaltungData.tarif_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.tarif_provision_beschreibung;
|
||||||
|
quelle = 'Direkte Tarif-Provision';
|
||||||
|
}
|
||||||
|
// Priority 2: Tariff-specific rule
|
||||||
|
else if (schaltungData.regel_provision_typ && schaltungData.regel_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.regel_provision_typ;
|
||||||
|
provision_betrag = schaltungData.regel_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.regel_provision_beschreibung;
|
||||||
|
quelle = 'Tarif-spezifische Regel';
|
||||||
|
}
|
||||||
|
// Priority 3: Global provider rule as fallback
|
||||||
|
else if (schaltungData.global_provision_typ && schaltungData.global_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.global_provision_typ;
|
||||||
|
provision_betrag = schaltungData.global_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.global_provision_beschreibung;
|
||||||
|
quelle = 'Globale Anbieter-Regel';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate base commission
|
||||||
|
if (provision_typ && provision_betrag) {
|
||||||
|
if (provision_typ === 'fix') {
|
||||||
|
berechnete_provision = parseFloat(provision_betrag);
|
||||||
|
} else if (provision_typ === 'prozent') {
|
||||||
|
const basisBetrag = 100; // This could come from tariff table
|
||||||
|
berechnete_provision = (parseFloat(provision_betrag) / 100) * basisBetrag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tarifoptionen to 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
berechnete_provision += tarifoptionen_provision;
|
||||||
|
|
||||||
|
// Update description if tarifoptionen were added
|
||||||
|
if (tarifoptionen_provision > 0) {
|
||||||
|
const tarifoptionen_text = verwendete_tarifoptionen.join(', ');
|
||||||
|
provision_beschreibung = provision_beschreibung
|
||||||
|
? `${provision_beschreibung} + Tarifoptionen: ${tarifoptionen_text}`
|
||||||
|
: `Tarifoptionen: ${tarifoptionen_text}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
typ: provision_typ,
|
||||||
|
betrag: provision_betrag,
|
||||||
|
beschreibung: provision_beschreibung,
|
||||||
|
berechnete_provision: berechnete_provision,
|
||||||
|
quelle: quelle,
|
||||||
|
tarifoptionen_provision: tarifoptionen_provision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate employee commission based on hierarchy
|
||||||
|
* @param {Object} schaltungData - Data from database including tariff and provider info
|
||||||
|
* @param {number} shopProvision - Calculated shop commission (for percentage calculations)
|
||||||
|
* @returns {Object} Calculation result with amount, type, description, and source
|
||||||
|
*/
|
||||||
|
function calculateMitarbeiterProvision(schaltungData, shopProvision) {
|
||||||
|
let provision_typ = null;
|
||||||
|
let provision_betrag = null;
|
||||||
|
let provision_beschreibung = null;
|
||||||
|
let berechnete_provision = 0;
|
||||||
|
let quelle = 'Keine Mitarbeiter-Provision definiert';
|
||||||
|
|
||||||
|
// Priority 1: Direct tariff employee commission
|
||||||
|
if (schaltungData.tarif_verkaeufer_provision_typ && schaltungData.tarif_verkaeufer_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.tarif_verkaeufer_provision_typ;
|
||||||
|
provision_betrag = schaltungData.tarif_verkaeufer_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.tarif_verkaeufer_provision_beschreibung;
|
||||||
|
quelle = 'Direkte Tarif-Verkäufer-Provision';
|
||||||
|
}
|
||||||
|
// Priority 2: Tariff-specific rule employee commission
|
||||||
|
else if (schaltungData.regel_verkaeufer_provision_typ && schaltungData.regel_verkaeufer_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.regel_verkaeufer_provision_typ;
|
||||||
|
provision_betrag = schaltungData.regel_verkaeufer_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.regel_verkaeufer_provision_beschreibung;
|
||||||
|
quelle = 'Tarif-spezifische Verkäufer-Regel';
|
||||||
|
}
|
||||||
|
// Priority 3: Global provider rule employee commission
|
||||||
|
else if (schaltungData.global_verkaeufer_provision_typ && schaltungData.global_verkaeufer_provision_betrag) {
|
||||||
|
provision_typ = schaltungData.global_verkaeufer_provision_typ;
|
||||||
|
provision_betrag = schaltungData.global_verkaeufer_provision_betrag;
|
||||||
|
provision_beschreibung = schaltungData.global_verkaeufer_provision_beschreibung;
|
||||||
|
quelle = 'Globale Anbieter-Verkäufer-Regel';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate employee commission
|
||||||
|
if (provision_typ && provision_betrag) {
|
||||||
|
if (provision_typ === 'fix') {
|
||||||
|
berechnete_provision = parseFloat(provision_betrag);
|
||||||
|
} else if (provision_typ === 'prozent') {
|
||||||
|
// If percentage, calculate based on shop commission
|
||||||
|
berechnete_provision = (parseFloat(provision_betrag) / 100) * shopProvision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
typ: provision_typ,
|
||||||
|
betrag: provision_betrag,
|
||||||
|
beschreibung: provision_beschreibung,
|
||||||
|
berechnete_provision: berechnete_provision,
|
||||||
|
quelle: quelle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function to calculate all commissions for a schaltung
|
||||||
|
* @param {Object} schaltungData - Complete schaltung data with tariff and provider info
|
||||||
|
* @param {Array} tarifoptionen - Selected tariff options
|
||||||
|
* @returns {Object} Complete commission calculation result
|
||||||
|
*/
|
||||||
|
function calculateProvision(schaltungData, tarifoptionen) {
|
||||||
|
const shopResult = calculateShopProvision(schaltungData, tarifoptionen);
|
||||||
|
const mitarbeiterResult = calculateMitarbeiterProvision(schaltungData, shopResult.berechnete_provision);
|
||||||
|
|
||||||
|
return {
|
||||||
|
schaltung: {
|
||||||
|
id: schaltungData.id,
|
||||||
|
schaltungs_id: schaltungData.schaltungs_id,
|
||||||
|
tarif: schaltungData.tarif_name,
|
||||||
|
anbieter: schaltungData.anbieter_name
|
||||||
|
},
|
||||||
|
shop_provision: {
|
||||||
|
typ: shopResult.typ,
|
||||||
|
betrag: shopResult.betrag,
|
||||||
|
beschreibung: shopResult.beschreibung,
|
||||||
|
berechnete_provision: shopResult.berechnete_provision,
|
||||||
|
quelle: shopResult.quelle
|
||||||
|
},
|
||||||
|
mitarbeiter_provision: {
|
||||||
|
typ: mitarbeiterResult.typ,
|
||||||
|
betrag: mitarbeiterResult.betrag,
|
||||||
|
beschreibung: mitarbeiterResult.beschreibung,
|
||||||
|
berechnete_provision: mitarbeiterResult.berechnete_provision,
|
||||||
|
quelle: mitarbeiterResult.quelle,
|
||||||
|
prozentsatz: schaltungData.mitarbeiter_prozentsatz || 0
|
||||||
|
},
|
||||||
|
tarifoptionen: tarifoptionen || [],
|
||||||
|
tarifoptionen_provision: shopResult.tarifoptionen_provision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
calculateProvision,
|
||||||
|
calculateShopProvision,
|
||||||
|
calculateMitarbeiterProvision
|
||||||
|
};
|
||||||
+1799
File diff suppressed because it is too large.
Load diff
+3626
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user