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:
@@ -122,6 +122,23 @@ export const ausruestungsanfrageApi = {
|
||||
await api.patch(`/api/ausruestungsanfragen/positionen/${positionId}/geliefert`, { geliefert });
|
||||
},
|
||||
|
||||
// ── Item assignment ──
|
||||
assignItems: async (
|
||||
anfrageId: number,
|
||||
assignments: Array<{
|
||||
positionId: number;
|
||||
typ: 'ausruestung' | 'persoenlich' | 'keine';
|
||||
fahrzeugId?: string;
|
||||
standort?: string;
|
||||
userId?: string;
|
||||
benutzerName?: string;
|
||||
groesse?: string;
|
||||
kategorie?: string;
|
||||
}>,
|
||||
): Promise<void> => {
|
||||
await api.post(`/api/ausruestungsanfragen/requests/${anfrageId}/assign`, { assignments });
|
||||
},
|
||||
|
||||
updatePositionZurueckgegeben: async (positionId: number, altes_geraet_zurueckgegeben: boolean): Promise<void> => {
|
||||
await api.patch(`/api/ausruestungsanfragen/positionen/${positionId}/zurueckgegeben`, { altes_geraet_zurueckgegeben });
|
||||
},
|
||||
|
||||
64
frontend/src/services/personalEquipment.ts
Normal file
64
frontend/src/services/personalEquipment.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { api } from './api';
|
||||
import type {
|
||||
PersoenlicheAusruestung,
|
||||
CreatePersoenlicheAusruestungPayload,
|
||||
UpdatePersoenlicheAusruestungPayload,
|
||||
} from '../types/personalEquipment.types';
|
||||
|
||||
async function unwrap<T>(
|
||||
promise: ReturnType<typeof api.get<{ success: boolean; data: T }>>
|
||||
): Promise<T> {
|
||||
const response = await promise;
|
||||
if (response.data?.data === undefined || response.data?.data === null) {
|
||||
throw new Error('Invalid API response');
|
||||
}
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
export const personalEquipmentApi = {
|
||||
async getAll(params?: { user_id?: string; kategorie?: string; zustand?: string }): Promise<PersoenlicheAusruestung[]> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.user_id) qs.set('user_id', params.user_id);
|
||||
if (params?.kategorie) qs.set('kategorie', params.kategorie);
|
||||
if (params?.zustand) qs.set('zustand', params.zustand);
|
||||
return unwrap(api.get(`/api/persoenliche-ausruestung?${qs.toString()}`));
|
||||
},
|
||||
|
||||
async getMy(): Promise<PersoenlicheAusruestung[]> {
|
||||
return unwrap(api.get('/api/persoenliche-ausruestung/my'));
|
||||
},
|
||||
|
||||
async getByUserId(userId: string): Promise<PersoenlicheAusruestung[]> {
|
||||
return unwrap(api.get(`/api/persoenliche-ausruestung/user/${userId}`));
|
||||
},
|
||||
|
||||
async getById(id: string): Promise<PersoenlicheAusruestung> {
|
||||
return unwrap(api.get(`/api/persoenliche-ausruestung/${id}`));
|
||||
},
|
||||
|
||||
async create(data: CreatePersoenlicheAusruestungPayload): Promise<PersoenlicheAusruestung> {
|
||||
const response = await api.post<{ success: boolean; data: PersoenlicheAusruestung }>(
|
||||
'/api/persoenliche-ausruestung',
|
||||
data,
|
||||
);
|
||||
if (response.data?.data === undefined || response.data?.data === null) {
|
||||
throw new Error('Invalid API response');
|
||||
}
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async update(id: string, data: UpdatePersoenlicheAusruestungPayload): Promise<PersoenlicheAusruestung> {
|
||||
const response = await api.patch<{ success: boolean; data: PersoenlicheAusruestung }>(
|
||||
`/api/persoenliche-ausruestung/${id}`,
|
||||
data,
|
||||
);
|
||||
if (response.data?.data === undefined || response.data?.data === null) {
|
||||
throw new Error('Invalid API response');
|
||||
}
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await api.delete(`/api/persoenliche-ausruestung/${id}`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user