feat: personal equipment tracking, order assignment, purge fix, widget consolidation

- Migration 084: new persoenliche_ausruestung table with catalog link, user
  assignment, soft delete; adds zuweisung_typ/ausruestung_id/persoenlich_id
  columns to ausruestung_anfrage_positionen; seeds feature group + 5 permissions

- Fix user data purge: table was shop_anfragen, renamed to ausruestung_anfragen
  in mig 046 — caused full transaction rollback. Also keep mitglieder_profile
  row but NULL FDISK-synced fields (dienstgrad, geburtsdatum, etc.) instead of
  deleting the profile

- Personal equipment CRUD: backend service/controller/routes at
  /api/persoenliche-ausruestung; frontend page with DataTable, user filter,
  catalog Autocomplete, FAB create dialog; widget in Status group; sidebar
  entry (Checkroom icon); card in MitgliedDetail Tab 0

- Ausruestungsanfrage item assignment: when a request reaches erledigt,
  auto-opens ItemAssignmentDialog listing all delivered positions; each item
  can be assigned as general equipment (vehicle/storage), personal item (user,
  prefilled with requester), or not tracked; POST /requests/:id/assign backend

- StatCard refactored to use WidgetCard as outer shell for consistent header
  styling across all dashboard widget templates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-04-13 19:19:35 +02:00
parent b477e5dbe0
commit 1215e9ea70
23 changed files with 1700 additions and 63 deletions

View File

@@ -0,0 +1,82 @@
-- Migration: 084_persoenliche_ausruestung
-- Creates persoenliche_ausruestung table, adds assignment columns to
-- ausruestung_anfrage_positionen, and seeds feature_group + permissions.
-- 1. Create persoenliche_ausruestung table
CREATE TABLE IF NOT EXISTS persoenliche_ausruestung (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bezeichnung TEXT NOT NULL,
kategorie TEXT,
artikel_id INT REFERENCES ausruestung_artikel(id) ON DELETE SET NULL,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
benutzer_name TEXT,
groesse TEXT,
seriennummer TEXT,
inventarnummer TEXT,
anschaffung_datum DATE,
zustand TEXT DEFAULT 'gut' CHECK (zustand IN ('gut','beschaedigt','abgaengig','verloren')),
notizen TEXT,
anfrage_id INT REFERENCES ausruestung_anfragen(id) ON DELETE SET NULL,
anfrage_position_id INT REFERENCES ausruestung_anfrage_positionen(id) ON DELETE SET NULL,
erstellt_von UUID REFERENCES users(id) ON DELETE SET NULL,
erstellt_am TIMESTAMPTZ DEFAULT NOW(),
aktualisiert_am TIMESTAMPTZ DEFAULT NOW(),
geloescht_am TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_persoenliche_ausruestung_user
ON persoenliche_ausruestung(user_id) WHERE geloescht_am IS NULL;
CREATE INDEX IF NOT EXISTS idx_persoenliche_ausruestung_artikel
ON persoenliche_ausruestung(artikel_id);
-- Auto-update aktualisiert_am trigger (uses the aktualisiert_am variant from migration 018)
CREATE TRIGGER trg_persoenliche_ausruestung_aktualisiert_am
BEFORE UPDATE ON persoenliche_ausruestung
FOR EACH ROW EXECUTE FUNCTION update_aktualisiert_am_column();
-- 2. Add assignment columns to ausruestung_anfrage_positionen
ALTER TABLE ausruestung_anfrage_positionen
ADD COLUMN IF NOT EXISTS zuweisung_typ TEXT CHECK (zuweisung_typ IN ('ausruestung','persoenlich','keine'));
ALTER TABLE ausruestung_anfrage_positionen
ADD COLUMN IF NOT EXISTS zuweisung_ausruestung_id UUID REFERENCES ausruestung(id) ON DELETE SET NULL;
ALTER TABLE ausruestung_anfrage_positionen
ADD COLUMN IF NOT EXISTS zuweisung_persoenlich_id UUID REFERENCES persoenliche_ausruestung(id) ON DELETE SET NULL;
-- 3. Feature group + permissions
INSERT INTO feature_groups (id, name, maintenance_mode)
VALUES ('persoenliche_ausruestung', 'Persönliche Ausrüstung', false)
ON CONFLICT DO NOTHING;
INSERT INTO permissions (id, feature_group_id, description) VALUES
('persoenliche_ausruestung:view', 'persoenliche_ausruestung', 'Eigene persönliche Ausrüstung anzeigen'),
('persoenliche_ausruestung:view_all', 'persoenliche_ausruestung', 'Alle persönliche Ausrüstung anzeigen'),
('persoenliche_ausruestung:create', 'persoenliche_ausruestung', 'Persönliche Ausrüstung erstellen'),
('persoenliche_ausruestung:edit', 'persoenliche_ausruestung', 'Persönliche Ausrüstung bearbeiten'),
('persoenliche_ausruestung:delete', 'persoenliche_ausruestung', 'Persönliche Ausrüstung löschen')
ON CONFLICT DO NOTHING;
-- Seed permissions for groups: admin, kommandant, gruppenkommandant, zeugmeister get all; others get view only
INSERT INTO group_permissions (group_name, permission_id)
SELECT g.name, p.id
FROM (VALUES
('dashboard_admin'),
('dashboard_kommandant'),
('dashboard_gruppenkommandant'),
('dashboard_zeugmeister')
) AS g(name)
CROSS JOIN permissions p
WHERE p.feature_group_id = 'persoenliche_ausruestung'
ON CONFLICT DO NOTHING;
-- All other groups get view only
INSERT INTO group_permissions (group_name, permission_id)
SELECT g.name, 'persoenliche_ausruestung:view'
FROM (VALUES
('dashboard_feuerwehrmitglied'),
('dashboard_atemschutztraeger'),
('dashboard_fahrmeister'),
('dashboard_jugend')
) AS g(name)
ON CONFLICT DO NOTHING;