feat(persoenliche-ausruestung): show catalog category, remove size/date columns, make zustand admin-configurable
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import personalEquipmentService from '../services/personalEquipment.service';
|
||||
import settingsService from '../services/settings.service';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
const uuidString = z.string().regex(
|
||||
@@ -13,7 +14,7 @@ const isoDate = z.string().regex(
|
||||
'Erwartet ISO-Datum im Format YYYY-MM-DD',
|
||||
);
|
||||
|
||||
const ZustandEnum = z.enum(['gut', 'beschaedigt', 'abgaengig', 'verloren']);
|
||||
const ZustandEnum = z.string().min(1).max(50);
|
||||
|
||||
const EigenschaftInput = z.object({
|
||||
eigenschaft_id: z.number().int().positive().nullable().optional(),
|
||||
@@ -180,6 +181,42 @@ class PersonalEquipmentController {
|
||||
res.status(500).json({ success: false, message: 'Persönliche Ausrüstung konnte nicht gelöscht werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async getZustandOptions(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const setting = await settingsService.get('personal_equipment_zustand_options');
|
||||
const options = Array.isArray(setting?.value) ? setting!.value : [
|
||||
{ key: 'gut', label: 'Gut', color: 'success' },
|
||||
{ key: 'beschaedigt', label: 'Beschädigt', color: 'warning' },
|
||||
{ key: 'abgaengig', label: 'Abgängig', color: 'error' },
|
||||
{ key: 'verloren', label: 'Verloren', color: 'default' },
|
||||
];
|
||||
res.status(200).json({ success: true, data: options });
|
||||
} catch (error) {
|
||||
logger.error('personalEquipment.getZustandOptions error', { error });
|
||||
res.status(500).json({ success: false, message: 'Zustände konnten nicht geladen werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateZustandOptions(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const schema = z.array(z.object({
|
||||
key: z.string().min(1).max(50),
|
||||
label: z.string().min(1).max(100),
|
||||
color: z.enum(['success', 'warning', 'error', 'default', 'primary', 'secondary', 'info']),
|
||||
})).min(1);
|
||||
const parsed = schema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ success: false, message: 'Validierungsfehler', errors: parsed.error.flatten() });
|
||||
return;
|
||||
}
|
||||
await settingsService.set('personal_equipment_zustand_options', parsed.data, req.user!.id);
|
||||
res.status(200).json({ success: true, data: parsed.data });
|
||||
} catch (error) {
|
||||
logger.error('personalEquipment.updateZustandOptions error', { error });
|
||||
res.status(500).json({ success: false, message: 'Zustände konnten nicht gespeichert werden' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new PersonalEquipmentController();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Migration: 091_personal_equipment_configurable_zustand
|
||||
-- Makes the zustand field on persoenliche_ausruestung admin-configurable
|
||||
-- by removing the hard-coded CHECK constraint and seeding default options
|
||||
-- into app_settings.
|
||||
|
||||
-- 1. Drop the hard-coded CHECK constraint
|
||||
ALTER TABLE persoenliche_ausruestung DROP CONSTRAINT IF EXISTS persoenliche_ausruestung_zustand_check;
|
||||
|
||||
-- 2. Seed default zustand options into app_settings
|
||||
INSERT INTO app_settings (key, value, updated_by, updated_at)
|
||||
VALUES (
|
||||
'personal_equipment_zustand_options',
|
||||
'[{"key":"gut","label":"Gut","color":"success"},{"key":"beschaedigt","label":"Beschädigt","color":"warning"},{"key":"abgaengig","label":"Abgängig","color":"error"},{"key":"verloren","label":"Verloren","color":"default"}]',
|
||||
'system',
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
@@ -24,6 +24,10 @@ router.get('/user/:userId', authenticate, async (req, res, next) => {
|
||||
res.status(403).json({ success: false, message: 'Keine Berechtigung' });
|
||||
}, personalEquipmentController.getByUser.bind(personalEquipmentController));
|
||||
|
||||
// Zustand options — GET (all authenticated users), PUT (admin:write)
|
||||
router.get('/zustand-options', authenticate, personalEquipmentController.getZustandOptions.bind(personalEquipmentController));
|
||||
router.put('/zustand-options', authenticate, requirePermission('admin:write'), personalEquipmentController.updateZustandOptions.bind(personalEquipmentController));
|
||||
|
||||
// Single item
|
||||
router.get('/:id', authenticate, personalEquipmentController.getById.bind(personalEquipmentController));
|
||||
|
||||
|
||||
@@ -40,10 +40,12 @@ interface UpdatePersonalEquipmentData {
|
||||
const BASE_SELECT = `
|
||||
SELECT pa.*,
|
||||
COALESCE(u.given_name || ' ' || u.family_name, u.name) AS user_display_name,
|
||||
aa.bezeichnung AS artikel_bezeichnung
|
||||
aa.bezeichnung AS artikel_bezeichnung,
|
||||
akk.name AS artikel_kategorie_name
|
||||
FROM persoenliche_ausruestung pa
|
||||
LEFT JOIN users u ON u.id = pa.user_id
|
||||
LEFT JOIN ausruestung_artikel aa ON aa.id = pa.artikel_id
|
||||
LEFT JOIN ausruestung_kategorien_katalog akk ON akk.id = aa.kategorie_id
|
||||
WHERE pa.geloescht_am IS NULL
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user