diff --git a/backend/src/database/migrations/063_fahrzeugbuchungen_feature_group.sql b/backend/src/database/migrations/063_fahrzeugbuchungen_feature_group.sql new file mode 100644 index 0000000..52bc859 --- /dev/null +++ b/backend/src/database/migrations/063_fahrzeugbuchungen_feature_group.sql @@ -0,0 +1,63 @@ +-- Migration 063: Split fahrzeugbuchungen into its own feature group +-- Moves kalender:view_bookings, kalender:manage_bookings, kalender:widget_bookings +-- into a new 'fahrzeugbuchungen' feature group with cleaner permission names. + +-- 1. Add new feature group +INSERT INTO feature_groups (id, label, sort_order) +VALUES ('fahrzeugbuchungen', 'Fahrzeugbuchungen', 2) +ON CONFLICT (id) DO NOTHING; + +-- Shift existing groups down to make room (kalender stays 1, fahrzeugbuchungen is 2) +UPDATE feature_groups SET sort_order = sort_order + 1 +WHERE id NOT IN ('kalender', 'fahrzeugbuchungen') AND sort_order >= 2; + +-- 2. Add new permissions +INSERT INTO permissions (id, feature_group_id, label, description, sort_order) VALUES + ('fahrzeugbuchungen:view', 'fahrzeugbuchungen', 'Ansehen', 'Buchungsliste anzeigen', 1), + ('fahrzeugbuchungen:create', 'fahrzeugbuchungen', 'Erstellen', 'Neue Fahrzeugbuchungen anlegen', 2), + ('fahrzeugbuchungen:manage', 'fahrzeugbuchungen', 'Verwalten', 'Buchungen bearbeiten, stornieren, löschen; Kategorien verwalten', 3), + ('fahrzeugbuchungen:widget', 'fahrzeugbuchungen', 'Widget', 'Dashboard-Widget für Fahrzeugbuchungen', 4) +ON CONFLICT (id) DO NOTHING; + +-- 3. Migrate existing group_permissions +-- kalender:view_bookings → fahrzeugbuchungen:view +-- kalender:manage_bookings → fahrzeugbuchungen:manage + fahrzeugbuchungen:create + fahrzeugbuchungen:view +-- kalender:widget_bookings → fahrzeugbuchungen:widget + +INSERT INTO group_permissions (authentik_group, permission_id) +SELECT authentik_group, 'fahrzeugbuchungen:view' +FROM group_permissions +WHERE permission_id = 'kalender:view_bookings' +ON CONFLICT DO NOTHING; + +INSERT INTO group_permissions (authentik_group, permission_id) +SELECT authentik_group, 'fahrzeugbuchungen:create' +FROM group_permissions +WHERE permission_id = 'kalender:manage_bookings' +ON CONFLICT DO NOTHING; + +INSERT INTO group_permissions (authentik_group, permission_id) +SELECT authentik_group, 'fahrzeugbuchungen:manage' +FROM group_permissions +WHERE permission_id = 'kalender:manage_bookings' +ON CONFLICT DO NOTHING; + +INSERT INTO group_permissions (authentik_group, permission_id) +SELECT authentik_group, 'fahrzeugbuchungen:view' +FROM group_permissions +WHERE permission_id = 'kalender:manage_bookings' +ON CONFLICT DO NOTHING; + +INSERT INTO group_permissions (authentik_group, permission_id) +SELECT authentik_group, 'fahrzeugbuchungen:widget' +FROM group_permissions +WHERE permission_id = 'kalender:widget_bookings' +ON CONFLICT DO NOTHING; + +-- 4. Remove old kalender booking permissions +DELETE FROM group_permissions WHERE permission_id IN ( + 'kalender:view_bookings', 'kalender:manage_bookings', 'kalender:widget_bookings' +); +DELETE FROM permissions WHERE id IN ( + 'kalender:view_bookings', 'kalender:manage_bookings', 'kalender:widget_bookings' +); diff --git a/frontend/src/components/admin/PermissionMatrixTab.tsx b/frontend/src/components/admin/PermissionMatrixTab.tsx index b6b7d2d..0df72d7 100644 --- a/frontend/src/components/admin/PermissionMatrixTab.tsx +++ b/frontend/src/components/admin/PermissionMatrixTab.tsx @@ -91,7 +91,10 @@ function buildReverseHierarchy(hierarchy: Record): Record> = { kalender: { 'Termine': ['view', 'create'], - 'Buchungen': ['view_bookings', 'manage_bookings'], + }, + fahrzeugbuchungen: { + 'Buchungen': ['view', 'create', 'manage'], + 'Widget': ['widget'], }, bestellungen: { 'Bestellungen': ['view', 'create', 'manage_orders', 'delete', 'export'], diff --git a/frontend/src/pages/BookingFormPage.tsx b/frontend/src/pages/BookingFormPage.tsx index 6181c86..93bbcb5 100644 --- a/frontend/src/pages/BookingFormPage.tsx +++ b/frontend/src/pages/BookingFormPage.tsx @@ -26,6 +26,8 @@ import { import { useQuery } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; +import ServiceModePage from '../components/shared/ServiceModePage'; +import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; import { bookingApi, fetchVehicles, kategorieApi } from '../services/bookings'; import type { CreateBuchungInput, BuchungsArt } from '../types/booking.types'; @@ -46,6 +48,7 @@ const EMPTY_FORM: CreateBuchungInput = { function BookingFormPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const { isFeatureEnabled } = usePermissionContext(); const notification = useNotification(); const isEdit = Boolean(id); @@ -231,6 +234,9 @@ function BookingFormPage() { return ( + {!isFeatureEnabled('fahrzeugbuchungen') ? ( + + ) : ( navigate('/fahrzeugbuchungen')}> @@ -537,6 +543,7 @@ function BookingFormPage() { + )} ); } diff --git a/frontend/src/pages/FahrzeugBuchungen.tsx b/frontend/src/pages/FahrzeugBuchungen.tsx index a45c491..fafa48e 100644 --- a/frontend/src/pages/FahrzeugBuchungen.tsx +++ b/frontend/src/pages/FahrzeugBuchungen.tsx @@ -48,6 +48,7 @@ import { useNavigate } from 'react-router-dom'; import { format, parseISO } from 'date-fns'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import ChatAwareFab from '../components/shared/ChatAwareFab'; +import ServiceModePage from '../components/shared/ServiceModePage'; import { useAuth } from '../contexts/AuthContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; @@ -93,7 +94,7 @@ function getCategoryLabel(buchungsArt: BuchungsArt, kategorien: BuchungsKategori function FahrzeugBuchungen() { const { user } = useAuth(); - const { hasPermission } = usePermissionContext(); + const { hasPermission, isFeatureEnabled } = usePermissionContext(); const notification = useNotification(); const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -230,6 +231,10 @@ function FahrzeugBuchungen() { }); // ── Render ───────────────────────────────────────────────────────────────── + if (!isFeatureEnabled('fahrzeugbuchungen')) { + return ; + } + return ( diff --git a/frontend/src/pages/Kalender.tsx b/frontend/src/pages/Kalender.tsx index 0a7ce59..a45cf61 100644 --- a/frontend/src/pages/Kalender.tsx +++ b/frontend/src/pages/Kalender.tsx @@ -64,6 +64,7 @@ import { } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; +import ServiceModePage from '../components/shared/ServiceModePage'; import ChatAwareFab from '../components/shared/ChatAwareFab'; import { toGermanDateTime, fromGermanDate, fromGermanDateTime } from '../utils/dateInput'; @@ -1625,7 +1626,7 @@ function VeranstaltungFormDialog({ export default function Kalender() { const navigate = useNavigate(); - const { hasPermission } = usePermissionContext(); + const { hasPermission, isFeatureEnabled } = usePermissionContext(); const notification = useNotification(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); @@ -1873,6 +1874,10 @@ export default function Kalender() { // ── Render ─────────────────────────────────────────────────────────────────── + if (!isFeatureEnabled('kalender')) { + return ; + } + return (