From f2ab23f261f9c96f0fea124e3de1ab37ab936b38 Mon Sep 17 00:00:00 2001 From: Kerim Atilgan Date: Fri, 15 May 2026 16:36:22 +0200 Subject: [PATCH] Add VO management, tariff-option assignment, and 2-step schaltung wizard VOs (Vertriebspartner-Nummern) - New vos table (one row per VO, scoped to a provider). Migrations add tarife.vo_id and schaltungen.vo_id columns (nullable so legacy rows survive). - New /api/vos CRUD (list, create, update, delete with use-checks). - Admin: new "VOs (Vertriebspartner)" sub-tab. List/create with provider dropdown; edit via prompt; delete refuses while used. - /api/tarife?vo_id=X filter for fetching tariffs of a specific VO. Tariff editor - Add-tariff form requires a VO; new dependent VO dropdown that populates after the provider is chosen. - Edit-tariff modal now shows the VO dropdown and a checkbox grid of all anbieter-wide tarifoptionen. Saving the dialog applies the selection via the existing tarifoptionen_tarife table. - New GET /api/tarife/:id/tarifoptionen-multi returns the option set scoped to the tariff's provider, each annotated with whether it is currently assigned to this tariff. - POST/PUT /api/tarife accept vo_id (required for create, validated against the provider) and an optional tarifoption_ids array. Schaltung wizard - The Schaltung-erfassen tab is now a 2-step wizard: step 1 picks a provider from a button row (mirrors the screenshot the user shared); step 2 shows a 2-column form mirroring the Aktivierungs-/Vertragsdaten layout. - Step 2 cascades: VO -> tariffs filtered by VO -> tarifoptionen filtered by tariff. Erfasser is autofilled from the logged-in user (read-only). - Removed customer fields that the user does not need (Kundennummer, Kennwort/Service-PIN never existed; Rufnummer is now optional both in the form and in the API validation). - After saving, the wizard resets to step 1 ready for the next entry. Schema - tarife: vo_id added. - schaltungen: vo_id added; rufnummer is nullable in new DBs. Existing DBs may still have NOT NULL on rufnummer; the schaltungen route now sends an empty string instead of NULL to remain compatible. Co-Authored-By: Claude Opus 4.7 (1M context) --- database.js | 24 ++- public/index.html | 276 +++++++++++++++++---------- public/script.js | 429 ++++++++++++++++++++++++++++++++++-------- routes/schaltungen.js | 45 +++-- routes/tarife.js | 112 +++++++++-- routes/vos.js | 92 +++++++++ server.js | 2 + 7 files changed, 768 insertions(+), 212 deletions(-) create mode 100644 routes/vos.js diff --git a/database.js b/database.js index f14f721..4cf55a5 100644 --- a/database.js +++ b/database.js @@ -74,19 +74,35 @@ function initDatabase() { UNIQUE(anbieter_id, gueltig_ab) )`); + // Vertriebspartner-Nummern (VOs) per Anbieter. Each tariff is assigned to + // exactly one VO (nullable for legacy rows that pre-date this column). + db.run(`CREATE TABLE IF NOT EXISTS vos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + anbieter_id INTEGER NOT NULL, + nummer TEXT NOT NULL, + bezeichnung TEXT, + aktiv INTEGER NOT NULL DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (anbieter_id) REFERENCES anbieter (id), + UNIQUE(anbieter_id, nummer) + )`); + // Tarife db.run(`CREATE TABLE IF NOT EXISTS tarife ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, anbieter_id INTEGER, + vo_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), + FOREIGN KEY (vo_id) REFERENCES vos (id), UNIQUE(name, anbieter_id) )`); addColumnIfMissing(db, 'tarife', 'mitarbeiter_provision DECIMAL(10,2) DEFAULT 0'); + addColumnIfMissing(db, 'tarife', 'vo_id INTEGER'); // Tariff-specific commission rules. basis_euro: base amount for percent calculation. db.run(`CREATE TABLE IF NOT EXISTS provisionsregeln ( @@ -124,7 +140,9 @@ function initDatabase() { )`); addColumnIfMissing(db, 'anbieter_provisionsregeln', 'basis_euro DECIMAL(10,2)'); - // Schaltungen + // Schaltungen. rufnummer is nullable on new installs (the application + // also accepts NULL on existing DBs with NOT NULL columns since we just + // pass empty string when missing). vo_id captures which VO was used. db.run(`CREATE TABLE IF NOT EXISTS schaltungen ( id INTEGER PRIMARY KEY AUTOINCREMENT, schaltungs_id TEXT NOT NULL UNIQUE, @@ -133,8 +151,9 @@ function initDatabase() { vorname TEXT NOT NULL, nachname TEXT NOT NULL, firma TEXT, - rufnummer TEXT NOT NULL, + rufnummer TEXT, anbieter_id INTEGER NOT NULL, + vo_id INTEGER, tarif_id INTEGER NOT NULL, hardware TEXT, hardware_ek DECIMAL(10,2), @@ -145,6 +164,7 @@ function initDatabase() { )`); addColumnIfMissing(db, 'schaltungen', 'hardware_ek DECIMAL(10,2)'); addColumnIfMissing(db, 'schaltungen', 'kundenverkaufspreis_netto DECIMAL(10,2)'); + addColumnIfMissing(db, 'schaltungen', 'vo_id INTEGER'); // Dokumente (note: ON DELETE CASCADE only applied to new DBs - existing DBs // handled via explicit DELETE in server.js) diff --git a/public/index.html b/public/index.html index 1dec492..98a89d1 100644 --- a/public/index.html +++ b/public/index.html @@ -881,105 +881,130 @@ - +
-

Neue Schaltung erfassen

-
-
-
- - -
-
- - -
-
- -
- - -
- -
-
- - -
-
- - -
-
- - -
-
- -
- - -
- -
-
- - -
-
- - -
-
- - - - -
- - + +
+

Neue Schaltung — Schritt 1 von 2

+

Wählen Sie einen Anbieter.

+
+
+ + +