This commit is contained in:
Matthias Hochmeister
2026-03-16 12:11:32 +01:00
parent 8d03c13bee
commit 5f329bb5c1
4 changed files with 17 additions and 5 deletions

View File

@@ -23,6 +23,7 @@ router.patch('/:id', authenticate, requirePermission('bookings:write'), bookingC
// Soft-cancel (sets abgesagt=TRUE) // Soft-cancel (sets abgesagt=TRUE)
router.delete('/:id', authenticate, requirePermission('bookings:write'), bookingController.cancel.bind(bookingController)); router.delete('/:id', authenticate, requirePermission('bookings:write'), bookingController.cancel.bind(bookingController));
router.patch('/:id/cancel', authenticate, requirePermission('bookings:write'), bookingController.cancel.bind(bookingController));
// Hard-delete (admin only) // Hard-delete (admin only)
router.delete('/:id/force', authenticate, requirePermission('bookings:delete'), bookingController.hardDelete.bind(bookingController)); router.delete('/:id/force', authenticate, requirePermission('bookings:delete'), bookingController.hardDelete.bind(bookingController));

View File

@@ -277,7 +277,8 @@ function FahrzeugBuchungen() {
setDetailBooking(null); setDetailBooking(null);
loadData(); loadData();
} catch (e: unknown) { } catch (e: unknown) {
const msg = e instanceof Error ? e.message : 'Fehler beim Stornieren'; const axiosErr = e as { response?: { data?: { message?: string } }; message?: string };
const msg = axiosErr?.response?.data?.message || (e instanceof Error ? e.message : 'Fehler beim Stornieren');
notification.showError(msg); notification.showError(msg);
} finally { } finally {
setCancelLoading(false); setCancelLoading(false);

View File

@@ -90,7 +90,7 @@ export const bookingApi = {
cancel(id: string, abgesagt_grund: string): Promise<void> { cancel(id: string, abgesagt_grund: string): Promise<void> {
return api return api
.delete(`/api/bookings/${id}`, { data: { abgesagt_grund } }) .patch(`/api/bookings/${id}/cancel`, { abgesagt_grund })
.then(() => undefined); .then(() => undefined);
}, },

View File

@@ -905,11 +905,21 @@ async function scrapeMemberFahrgenehmigungen(
const pageData = await frame.evaluate(() => { const pageData = await frame.evaluate(() => {
const extractCellValue = (cell: Element): string => { const extractCellValue = (cell: Element): string => {
const input = cell.querySelector('input[type="text"], input:not([type])') as HTMLInputElement | null; const input = cell.querySelector('input[type="text"], input:not([type])') as HTMLInputElement | null;
if (input) return input.value?.trim() ?? ''; if (input && input.value?.trim()) return input.value.trim();
const sel = cell.querySelector('select') as HTMLSelectElement | null; const sel = cell.querySelector('select') as HTMLSelectElement | null;
if (sel) { if (sel) {
const opt = sel.options[sel.selectedIndex]; const idx = sel.selectedIndex;
return (opt?.text || opt?.value || '').trim(); if (idx >= 0 && sel.options[idx]) {
const t = (sel.options[idx].text || sel.options[idx].value || '').trim();
if (t) return t;
}
if (sel.value?.trim()) return sel.value.trim();
const selectedOpt = sel.querySelector('option[selected]') as HTMLOptionElement | null;
if (selectedOpt) {
const t = (selectedOpt.text || selectedOpt.value || '').trim();
if (t) return t;
}
// fall through to textContent if select is empty
} }
const anchor = cell.querySelector('a'); const anchor = cell.querySelector('a');
const atitle = anchor?.getAttribute('title')?.trim(); const atitle = anchor?.getAttribute('title')?.trim();