Per-Anbieter default VO with auto-select in the schaltung wizard
- New nullable anbieter.default_vo_id column (migration adds it to existing DBs); deleting a VO that some provider points at clears the reference automatically. - GET /api/anbieter now includes default_vo_id + default_vo_nummer + default_vo_bezeichnung so the wizard can preselect without an extra round-trip. - New PUT /api/anbieter/:id/default-vo endpoint. Validates the VO belongs to the given provider; accepts vo_id=null to clear. - VO admin table grew a "Standard" column and an "Als Standard" / "Standard entfernen" button on each row. - When the user picks a provider in step 1 of the wizard, the VO dropdown now preselects the default VO (falling back to the only VO if there is exactly one), and immediately loads the matching tariffs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
97ff779bcf
commit
041df61b24
5 files changed
+82
-16
No files matched your search
@@ -1273,6 +1273,7 @@
|
||||
|
||||
<div class="admin-content-section">
|
||||
<h4>Bestehende VOs</h4>
|
||||
<p style="color:#666; font-size:13px;">Die mit ★ markierte VO wird bei der Schaltungserfassung automatisch vorausgewählt.</p>
|
||||
<div class="table-container">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
@@ -1281,6 +1282,7 @@
|
||||
<th>Nummer</th>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Aktiv</th>
|
||||
<th>Standard</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
+51
-14
@@ -68,7 +68,7 @@ async function renderWizardAnbieterButtons() {
|
||||
btn.className = 'btn-success';
|
||||
btn.style.cssText = 'padding:14px 22px; font-size:15px; min-width:140px;';
|
||||
btn.textContent = a.name;
|
||||
btn.addEventListener('click', () => wizardSelectAnbieter(a.id, a.name));
|
||||
btn.addEventListener('click', () => wizardSelectAnbieter(a.id, a.name, a.default_vo_id));
|
||||
container.appendChild(btn);
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -76,15 +76,17 @@ async function renderWizardAnbieterButtons() {
|
||||
}
|
||||
}
|
||||
|
||||
async function wizardSelectAnbieter(anbieterId, anbieterName) {
|
||||
async function wizardSelectAnbieter(anbieterId, anbieterName, defaultVoId) {
|
||||
document.getElementById('anbieter').value = anbieterId;
|
||||
document.getElementById('wizardAnbieterLabel').textContent = '· ' + anbieterName;
|
||||
document.getElementById('wizardStep1').style.display = 'none';
|
||||
document.getElementById('wizardStep2').style.display = 'block';
|
||||
|
||||
// Load VOs for this provider
|
||||
const voSelect = document.getElementById('wizardVo');
|
||||
voSelect.innerHTML = '<option value="">VO wählen...</option>';
|
||||
document.getElementById('wizardTarif').innerHTML = '<option value="">Erst VO wählen...</option>';
|
||||
document.getElementById('tarifoptionenSection').style.display = 'none';
|
||||
|
||||
try {
|
||||
const vos = await apiRequest(`/api/vos?anbieter_id=${encodeURIComponent(anbieterId)}`);
|
||||
vos.forEach(v => {
|
||||
@@ -93,12 +95,20 @@ async function wizardSelectAnbieter(anbieterId, anbieterName) {
|
||||
});
|
||||
if (vos.length === 0) {
|
||||
showMessage('Für diesen Anbieter wurden noch keine VOs angelegt. Bitte in der Administration → VOs eine VO anlegen.', 'error');
|
||||
return;
|
||||
}
|
||||
// Preselect the provider's default VO if set, otherwise the first VO if it is the only one.
|
||||
let preselect = null;
|
||||
if (defaultVoId && vos.some(v => v.id === defaultVoId)) {
|
||||
preselect = defaultVoId;
|
||||
} else if (vos.length === 1) {
|
||||
preselect = vos[0].id;
|
||||
}
|
||||
if (preselect) {
|
||||
voSelect.value = String(preselect);
|
||||
await loadTarifeForWizard();
|
||||
}
|
||||
} catch (e) { /* error shown */ }
|
||||
|
||||
// Reset Tarif + Tarifoptionen
|
||||
document.getElementById('wizardTarif').innerHTML = '<option value="">Erst VO wählen...</option>';
|
||||
document.getElementById('tarifoptionenSection').style.display = 'none';
|
||||
}
|
||||
|
||||
function wizardBackToStep1() {
|
||||
@@ -4067,22 +4077,34 @@ async function loadAnbieterForVoForm() {
|
||||
async function loadVosAdmin() {
|
||||
const tbody = document.getElementById('vosList');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#666;">Lade...</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:#666;">Lade...</td></tr>';
|
||||
try {
|
||||
const vos = await apiRequest('/api/vos');
|
||||
// Load anbieter to know each one's default_vo_id.
|
||||
const [vos, anbieter] = await Promise.all([
|
||||
apiRequest('/api/vos'),
|
||||
apiRequest('/api/anbieter'),
|
||||
]);
|
||||
const defaultByAnbieter = {};
|
||||
anbieter.forEach(a => { defaultByAnbieter[a.id] = a.default_vo_id; });
|
||||
|
||||
tbody.innerHTML = '';
|
||||
if (!vos.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#666;">Keine VOs vorhanden</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:#666;">Keine VOs vorhanden</td></tr>';
|
||||
return;
|
||||
}
|
||||
vos.forEach(v => {
|
||||
const isDefault = defaultByAnbieter[v.anbieter_id] === v.id;
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${escapeHtml(v.anbieter_name)}</td>
|
||||
<td>${escapeHtml(v.nummer)}</td>
|
||||
<td>${escapeHtml(v.bezeichnung || '')}</td>
|
||||
<td>${v.aktiv ? '<span style="color:#27ae60;">Ja</span>' : '<span style="color:#c0392b;">Nein</span>'}</td>
|
||||
<td>${isDefault ? '<span title="Standard-VO" style="color:#f39c12; font-size:18px;">★</span>' : ''}</td>
|
||||
<td>
|
||||
${isDefault
|
||||
? `<button data-vo-action="unset-default" data-anbieter-id="${v.anbieter_id}" style="margin-right:5px;">Standard entfernen</button>`
|
||||
: `<button data-vo-action="set-default" data-anbieter-id="${v.anbieter_id}" data-id="${v.id}" style="margin-right:5px;">Als Standard</button>`}
|
||||
<button data-vo-action="edit" data-id="${v.id}"
|
||||
data-anbieter-name="${escapeHtml(v.anbieter_name)}"
|
||||
data-nummer="${escapeHtml(v.nummer)}"
|
||||
@@ -4094,21 +4116,36 @@ async function loadVosAdmin() {
|
||||
tr.querySelectorAll('button[data-vo-action]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.dataset.voAction;
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
if (action === 'edit') {
|
||||
editVo(id, btn.dataset.nummer, btn.dataset.bezeichnung, btn.dataset.aktiv === '1', btn.dataset.anbieterName);
|
||||
editVo(parseInt(btn.dataset.id, 10), btn.dataset.nummer, btn.dataset.bezeichnung, btn.dataset.aktiv === '1', btn.dataset.anbieterName);
|
||||
} else if (action === 'delete') {
|
||||
deleteVo(id, btn.dataset.label);
|
||||
deleteVo(parseInt(btn.dataset.id, 10), btn.dataset.label);
|
||||
} else if (action === 'set-default') {
|
||||
setAnbieterDefaultVo(parseInt(btn.dataset.anbieterId, 10), parseInt(btn.dataset.id, 10));
|
||||
} else if (action === 'unset-default') {
|
||||
setAnbieterDefaultVo(parseInt(btn.dataset.anbieterId, 10), null);
|
||||
}
|
||||
});
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (e) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#c0392b;">Fehler beim Laden</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:#c0392b;">Fehler beim Laden</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function setAnbieterDefaultVo(anbieterId, voId) {
|
||||
try {
|
||||
await apiRequest(`/api/anbieter/${anbieterId}/default-vo`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ vo_id: voId }),
|
||||
});
|
||||
showMessage(voId ? 'Standard-VO gesetzt' : 'Standard-VO entfernt');
|
||||
loadVosAdmin();
|
||||
} catch (e) { /* error already shown */ }
|
||||
}
|
||||
|
||||
async function addVo() {
|
||||
const anbieter_id = document.getElementById('voAnbieter').value;
|
||||
const nummer = document.getElementById('voNummer').value.trim();
|
||||
|
||||
Reference in new issue
Block a user