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) <noreply@anthropic.com>
This commit is contained in:
1 parent
ea3a3fc12c
commit
f2ab23f261
7 files changed
+768
-212
No files matched your search
+180
-96
@@ -881,105 +881,130 @@
|
||||
<button class="nav-tab admin-only" onclick="showTab('users')" style="display:none">Benutzer</button>
|
||||
</div>
|
||||
|
||||
<!-- Schaltung erfassen Tab -->
|
||||
<!-- Schaltung erfassen Tab (2-step wizard) -->
|
||||
<div id="schaltung" class="tab-content active">
|
||||
<h2>Neue Schaltung erfassen</h2>
|
||||
<form id="schaltungForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="schaltungsId">Schaltungs-ID <span class="required">*</span></label>
|
||||
<input type="text" id="schaltungsId" name="schaltungs_id" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eingabeDatum">Eingabe-Datum <span class="required">*</span></label>
|
||||
<input type="date" id="eingabeDatum" name="eingabe_datum" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="schaltungsDatum">Schaltungs-/Aktivierungsdatum <span class="required">*</span></label>
|
||||
<input type="date" id="schaltungsDatum" name="schaltungs_datum" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row-thirds">
|
||||
<div class="form-group">
|
||||
<label for="vorname">Vorname <span class="required">*</span></label>
|
||||
<input type="text" id="vorname" name="vorname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="nachname">Nachname <span class="required">*</span></label>
|
||||
<input type="text" id="nachname" name="nachname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="firma">Firma (optional)</label>
|
||||
<input type="text" id="firma" name="firma">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rufnummer">Rufnummer/Kundennummer <span class="required">*</span></label>
|
||||
<input type="text" id="rufnummer" name="rufnummer" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="anbieter">Anbieter <span class="required">*</span></label>
|
||||
<select id="anbieter" name="anbieter_id" required onchange="loadTarife()">
|
||||
<option value="">Bitte wählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tarif">Tarif <span class="required">*</span></label>
|
||||
<select id="tarif" name="tarif_id" required onchange="loadTarifoptionen()">
|
||||
<option value="">Erst Anbieter wählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tarifoptionen/Aktionscodes -->
|
||||
<div class="form-group" id="tarifoptionenSection" style="display: none;">
|
||||
<label>Tarifoptionen/Aktionscodes (optional)</label>
|
||||
<div id="tarifoptionenList" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 10px; margin-top: 10px;">
|
||||
<!-- Optionen werden hier dynamisch eingefügt -->
|
||||
</div>
|
||||
<div id="noTarifoptionen" style="display: none; color: #666; font-style: italic; margin-top: 10px;">
|
||||
Keine Tarifoptionen für diesen Tarif verfügbar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="hardware">Hardware (optional)</label>
|
||||
<input type="text" id="hardware" name="hardware"
|
||||
title="Hardware optional" onchange="toggleHardwarePricing()">
|
||||
<!-- Step 1: Anbieter wählen -->
|
||||
<div id="wizardStep1">
|
||||
<h2>Neue Schaltung — Schritt 1 von 2</h2>
|
||||
<p style="color:#666; margin-bottom:20px;">Wählen Sie einen Anbieter.</p>
|
||||
<div id="wizardAnbieterButtons" style="display:flex; flex-wrap:wrap; gap:10px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: VO + Tarif + Tarifoptionen + Kundendaten -->
|
||||
<div id="wizardStep2" style="display:none;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:20px;">
|
||||
<h2 style="margin:0;">Neue Schaltung — Schritt 2 von 2 <small id="wizardAnbieterLabel" style="color:#3498db; font-weight:normal;"></small></h2>
|
||||
<button type="button" onclick="wizardBackToStep1()" style="padding:6px 12px;">← Anbieter ändern</button>
|
||||
</div>
|
||||
|
||||
<!-- Hardware Pricing Fields -->
|
||||
<div id="hardwarePricingFields" style="display: none;">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="hardwareEk">Hardware-EK (Einkaufspreis) <span class="required">*</span></label>
|
||||
<input type="number" id="hardwareEk" name="hardware_ek" step="0.01" min="0"
|
||||
placeholder="0.00" title="Hardware-Einkaufspreis in Euro">
|
||||
<form id="schaltungForm">
|
||||
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:30px;">
|
||||
<!-- Linke Spalte: Aktivierungsdaten -->
|
||||
<div>
|
||||
<h3 style="margin-top:0; margin-bottom:15px;">Aktivierungsdaten</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="wizardVo">VO-Auswahl <span class="required">*</span></label>
|
||||
<select id="wizardVo" required onchange="loadTarifeForWizard()">
|
||||
<option value="">VO wählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="wizardTarif">Tarif <span class="required">*</span></label>
|
||||
<select id="wizardTarif" name="tarif_id" required onchange="loadTarifoptionenForWizard()">
|
||||
<option value="">Erst VO wählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="tarifoptionenSection" style="display:none;">
|
||||
<label>Tarifoptionen</label>
|
||||
<div id="tarifoptionenList" style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:8px; margin-top:8px;"></div>
|
||||
<div id="noTarifoptionen" style="display:none; color:#666; font-style:italic; margin-top:8px;">Keine Tarifoptionen verfügbar</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="eingabeDatum">Erfassung <span class="required">*</span></label>
|
||||
<input type="date" id="eingabeDatum" name="eingabe_datum" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schaltungsDatum">Vertragsbeginn <span class="required">*</span></label>
|
||||
<input type="date" id="schaltungsDatum" name="schaltungs_datum" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Erfasser</label>
|
||||
<input type="text" id="wizardErfasser" readonly style="background:#f5f5f5;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="kundenverkaufspreis">Kundenverkaufspreis (netto) <span class="required">*</span></label>
|
||||
<input type="number" id="kundenverkaufspreis" name="kundenverkaufspreis_netto" step="0.01" min="0"
|
||||
placeholder="0.00" title="Kundenverkaufspreis netto in Euro">
|
||||
|
||||
<!-- Rechte Spalte: Vertrags-/Kundendaten -->
|
||||
<div>
|
||||
<h3 style="margin-top:0; margin-bottom:15px;">Vertragsdaten</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="schaltungsId">Schaltungs-ID <span class="required">*</span></label>
|
||||
<input type="text" id="schaltungsId" name="schaltungs_id" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row-thirds">
|
||||
<div class="form-group">
|
||||
<label for="firma">Firma (optional)</label>
|
||||
<input type="text" id="firma" name="firma">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vorname">Vorname <span class="required">*</span></label>
|
||||
<input type="text" id="vorname" name="vorname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="nachname">Nachname <span class="required">*</span></label>
|
||||
<input type="text" id="nachname" name="nachname" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rufnummer">Rufnummer (optional)</label>
|
||||
<input type="text" id="rufnummer" name="rufnummer">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="hardware">Hardware (optional, 15 Zeichen)</label>
|
||||
<input type="text" id="hardware" name="hardware" onchange="toggleHardwarePricing()">
|
||||
</div>
|
||||
|
||||
<div id="hardwarePricingFields" style="display:none;">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="hardwareEk">Hardware-EK</label>
|
||||
<input type="number" id="hardwareEk" name="hardware_ek" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="kundenverkaufspreis">Kundenverkaufspreis</label>
|
||||
<input type="number" id="kundenverkaufspreis" name="kundenverkaufspreis_netto" step="0.01" min="0" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Dokumente hochladen</label>
|
||||
<div class="file-upload-area" id="fileUploadArea">
|
||||
<p>Dateien hier hineinziehen oder klicken zum Auswählen</p>
|
||||
<input type="file" id="documentUpload" multiple accept=".pdf,.jpg,.jpeg,.png,.doc,.docx" style="display:none;">
|
||||
</div>
|
||||
<div id="uploadedFiles" class="documents-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Dokumente hochladen</label>
|
||||
<div class="file-upload-area" id="fileUploadArea">
|
||||
<p>Dateien hier hineinziehen oder klicken zum Auswählen</p>
|
||||
<input type="file" id="documentUpload" multiple accept=".pdf,.jpg,.jpeg,.png,.doc,.docx" style="display: none;">
|
||||
|
||||
<input type="hidden" id="anbieter" name="anbieter_id">
|
||||
|
||||
<div style="margin-top:25px;">
|
||||
<button type="submit" class="btn-success">Schaltung speichern</button>
|
||||
</div>
|
||||
<div id="uploadedFiles" class="documents-list"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-success">Schaltung speichern</button>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Übersicht Tab -->
|
||||
@@ -1158,6 +1183,10 @@
|
||||
<span class="admin-icon">🏢</span>
|
||||
<span>Anbieter</span>
|
||||
</button>
|
||||
<button class="admin-cat-btn" onclick="showAdminCategory('vos')">
|
||||
<span class="admin-icon">🔢</span>
|
||||
<span>VOs (Vertriebspartner)</span>
|
||||
</button>
|
||||
<button class="admin-cat-btn" onclick="showAdminCategory('tarife')">
|
||||
<span class="admin-icon">📋</span>
|
||||
<span>Tarife</span>
|
||||
@@ -1222,6 +1251,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VOs Management -->
|
||||
<div id="admin-vos" class="admin-category">
|
||||
<div class="admin-page">
|
||||
<div class="admin-header">
|
||||
<h3>VOs (Vertriebspartner-Nummern) verwalten</h3>
|
||||
<p>Pro Anbieter können beliebig viele VOs angelegt werden. Jeder Tarif wird genau einer VO zugeordnet.</p>
|
||||
</div>
|
||||
|
||||
<div class="admin-form-section">
|
||||
<h4>Neue VO hinzufügen</h4>
|
||||
<div class="add-form">
|
||||
<select id="voAnbieter" style="margin-right:8px;">
|
||||
<option value="">Anbieter wählen...</option>
|
||||
</select>
|
||||
<input type="text" id="voNummer" placeholder="VO-Nummer (z.B. 27518900)" style="margin-right:8px;">
|
||||
<input type="text" id="voBezeichnung" placeholder="Bezeichnung (z.B. Fachhandel)" style="margin-right:8px;">
|
||||
<button onclick="addVo()" class="btn-success">Hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-content-section">
|
||||
<h4>Bestehende VOs</h4>
|
||||
<div class="table-container">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Anbieter</th>
|
||||
<th>Nummer</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Aktiv</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vosList"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tarife Management -->
|
||||
<div id="admin-tarife" class="admin-category">
|
||||
<div class="admin-page">
|
||||
@@ -1234,9 +1303,12 @@
|
||||
<h4>Neuen Tarif hinzufügen</h4>
|
||||
<div class="tarif-form">
|
||||
<div class="form-row">
|
||||
<select id="tarifAnbieter">
|
||||
<select id="tarifAnbieter" onchange="loadVosForTarifForm()">
|
||||
<option value="">Anbieter wählen...</option>
|
||||
</select>
|
||||
<select id="tarifVo">
|
||||
<option value="">VO wählen...</option>
|
||||
</select>
|
||||
<input type="text" id="newTarif" placeholder="Name des Tarifs">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@@ -1259,6 +1331,7 @@
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Anbieter</th>
|
||||
<th>VO</th>
|
||||
<th>Tarif-Name</th>
|
||||
<th>Provision (€)</th>
|
||||
<th>Beschreibung</th>
|
||||
@@ -1572,7 +1645,7 @@
|
||||
|
||||
<!-- Edit Tariff Modal -->
|
||||
<div id="editTariffModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 30px; border-radius: 8px; width: 500px; max-width: 90%;">
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 30px; border-radius: 8px; width: 600px; max-width: 90%; max-height: 90vh; overflow-y: auto;">
|
||||
<h3>Tarif bearbeiten</h3>
|
||||
<form id="editTariffForm">
|
||||
<input type="hidden" id="editTariffId">
|
||||
@@ -1580,12 +1653,23 @@
|
||||
<label>Tarif-Name:</label>
|
||||
<input type="text" id="editTariffName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>VO:</label>
|
||||
<select id="editTariffVo">
|
||||
<option value="">VO wählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Shop-Provision (€):</label>
|
||||
<input type="number" id="editTariffProvisionBetrag" step="0.01" min="0" placeholder="Betrag in Euro">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label>Tarifoptionen (Anbieter-weit):</label>
|
||||
<div id="editTariffOptionenContainer" style="border:1px solid #ddd; border-radius:6px; padding:10px; max-height:200px; overflow-y:auto; background:#fafafa;">
|
||||
<small style="color:#666;">Keine Tarifoptionen für diesen Anbieter vorhanden</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
||||
<button type="submit" class="btn-success">Speichern</button>
|
||||
<button type="button" onclick="closeModal('editTariffModal')">Abbrechen</button>
|
||||
|
||||
Reference in new issue
Block a user