rework internal order system

This commit is contained in:
Matthias Hochmeister
2026-03-24 08:41:24 +01:00
parent f982fbb2b6
commit 3c0a8a6832
9 changed files with 910 additions and 205 deletions

View File

@@ -5,6 +5,70 @@ import { permissionService } from '../services/permission.service';
import logger from '../utils/logger';
class AusruestungsanfrageController {
// -------------------------------------------------------------------------
// Categories (DB-backed)
// -------------------------------------------------------------------------
async getKategorien(_req: Request, res: Response): Promise<void> {
try {
const kategorien = await ausruestungsanfrageService.getKategorien();
res.status(200).json({ success: true, data: kategorien });
} catch (error) {
logger.error('AusruestungsanfrageController.getKategorien error', { error });
res.status(500).json({ success: false, message: 'Kategorien konnten nicht geladen werden' });
}
}
async createKategorie(req: Request, res: Response): Promise<void> {
try {
const { name } = req.body;
if (!name || name.trim().length === 0) {
res.status(400).json({ success: false, message: 'Name ist erforderlich' });
return;
}
const kategorie = await ausruestungsanfrageService.createKategorie(name.trim());
res.status(201).json({ success: true, data: kategorie });
} catch (error: any) {
if (error?.constraint === 'ausruestung_kategorien_katalog_name_key') {
res.status(409).json({ success: false, message: 'Kategorie existiert bereits' });
return;
}
logger.error('AusruestungsanfrageController.createKategorie error', { error });
res.status(500).json({ success: false, message: 'Kategorie konnte nicht erstellt werden' });
}
}
async updateKategorie(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
const { name } = req.body;
if (!name || name.trim().length === 0) {
res.status(400).json({ success: false, message: 'Name ist erforderlich' });
return;
}
const kategorie = await ausruestungsanfrageService.updateKategorie(id, name.trim());
if (!kategorie) {
res.status(404).json({ success: false, message: 'Kategorie nicht gefunden' });
return;
}
res.status(200).json({ success: true, data: kategorie });
} catch (error) {
logger.error('AusruestungsanfrageController.updateKategorie error', { error });
res.status(500).json({ success: false, message: 'Kategorie konnte nicht aktualisiert werden' });
}
}
async deleteKategorie(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
await ausruestungsanfrageService.deleteKategorie(id);
res.status(200).json({ success: true, message: 'Kategorie gelöscht' });
} catch (error) {
logger.error('AusruestungsanfrageController.deleteKategorie error', { error });
res.status(500).json({ success: false, message: 'Kategorie konnte nicht gelöscht werden' });
}
}
// -------------------------------------------------------------------------
// Catalog Items
// -------------------------------------------------------------------------
@@ -12,8 +76,9 @@ class AusruestungsanfrageController {
async getItems(req: Request, res: Response): Promise<void> {
try {
const kategorie = req.query.kategorie as string | undefined;
const kategorie_id = req.query.kategorie_id ? Number(req.query.kategorie_id) : undefined;
const aktiv = req.query.aktiv !== undefined ? req.query.aktiv === 'true' : undefined;
const items = await ausruestungsanfrageService.getItems({ kategorie, aktiv });
const items = await ausruestungsanfrageService.getItems({ kategorie, kategorie_id, aktiv });
res.status(200).json({ success: true, data: items });
} catch (error) {
logger.error('AusruestungsanfrageController.getItems error', { error });
@@ -87,6 +152,59 @@ class AusruestungsanfrageController {
}
}
// -------------------------------------------------------------------------
// Artikel Eigenschaften (characteristics)
// -------------------------------------------------------------------------
async getArtikelEigenschaften(req: Request, res: Response): Promise<void> {
try {
const artikelId = Number(req.params.id);
const eigenschaften = await ausruestungsanfrageService.getArtikelEigenschaften(artikelId);
res.status(200).json({ success: true, data: eigenschaften });
} catch (error) {
logger.error('AusruestungsanfrageController.getArtikelEigenschaften error', { error });
res.status(500).json({ success: false, message: 'Eigenschaften konnten nicht geladen werden' });
}
}
async upsertArtikelEigenschaft(req: Request, res: Response): Promise<void> {
try {
const artikelId = Number(req.params.id);
const { name, typ, optionen, pflicht, sort_order, eigenschaft_id } = req.body;
if (!name || name.trim().length === 0) {
res.status(400).json({ success: false, message: 'Name ist erforderlich' });
return;
}
if (!typ || !['options', 'freitext'].includes(typ)) {
res.status(400).json({ success: false, message: 'Typ muss "options" oder "freitext" sein' });
return;
}
const eigenschaft = await ausruestungsanfrageService.upsertArtikelEigenschaft(artikelId, {
id: eigenschaft_id,
name: name.trim(),
typ,
optionen: optionen || undefined,
pflicht: pflicht ?? false,
sort_order: sort_order ?? 0,
});
res.status(eigenschaft_id ? 200 : 201).json({ success: true, data: eigenschaft });
} catch (error) {
logger.error('AusruestungsanfrageController.upsertArtikelEigenschaft error', { error });
res.status(500).json({ success: false, message: 'Eigenschaft konnte nicht gespeichert werden' });
}
}
async deleteArtikelEigenschaft(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.eigenschaftId);
await ausruestungsanfrageService.deleteArtikelEigenschaft(id);
res.status(200).json({ success: true, message: 'Eigenschaft gelöscht' });
} catch (error) {
logger.error('AusruestungsanfrageController.deleteArtikelEigenschaft error', { error });
res.status(500).json({ success: false, message: 'Eigenschaft konnte nicht gelöscht werden' });
}
}
// -------------------------------------------------------------------------
// Requests
// -------------------------------------------------------------------------
@@ -131,7 +249,7 @@ class AusruestungsanfrageController {
async createRequest(req: Request, res: Response): Promise<void> {
try {
const { items, notizen, bezeichnung, fuer_benutzer_id } = req.body as {
items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string }[];
items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string; eigenschaften?: { eigenschaft_id: number; wert: string }[] }[];
notizen?: string;
bezeichnung?: string;
fuer_benutzer_id?: string;
@@ -179,7 +297,7 @@ class AusruestungsanfrageController {
const { bezeichnung, notizen, items } = req.body as {
bezeichnung?: string;
notizen?: string;
items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string }[];
items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string; eigenschaften?: { eigenschaft_id: number; wert: string }[] }[];
};
// Validate items if provided
@@ -209,8 +327,8 @@ class AusruestungsanfrageController {
// Check permission: owner + status=offen, OR ausruestungsanfrage:edit
const groups = req.user?.groups ?? [];
const canEditAny = groups.includes('dashboard_admin') || permissionService.hasPermission(groups, 'ausruestungsanfrage:edit');
const isOwner = existing.anfrager_id === req.user!.id;
if (!canEditAny && !(isOwner && existing.status === 'offen')) {
const isOwner = existing.anfrage.anfrager_id === req.user!.id;
if (!canEditAny && !(isOwner && existing.anfrage.status === 'offen')) {
res.status(403).json({ success: false, message: 'Keine Berechtigung zum Bearbeiten dieser Anfrage' });
return;
}
@@ -253,11 +371,11 @@ class AusruestungsanfrageController {
// Notify requester on status changes
if (['genehmigt', 'abgelehnt', 'bestellt', 'erledigt'].includes(status)) {
const orderLabel = existing.bestell_jahr && existing.bestell_nummer
? `${existing.bestell_jahr}/${String(existing.bestell_nummer).padStart(3, '0')}`
const orderLabel = existing.anfrage.bestell_jahr && existing.anfrage.bestell_nummer
? `${existing.anfrage.bestell_jahr}/${String(existing.anfrage.bestell_nummer).padStart(3, '0')}`
: `#${id}`;
await notificationService.createNotification({
user_id: existing.anfrager_id,
user_id: existing.anfrage.anfrager_id,
typ: 'ausruestung_anfrage',
titel: status === 'genehmigt' ? 'Anfrage genehmigt' : status === 'abgelehnt' ? 'Anfrage abgelehnt' : `Anfrage ${status}`,
nachricht: `Deine Ausrüstungsanfrage ${orderLabel} wurde ${status === 'genehmigt' ? 'genehmigt' : status === 'abgelehnt' ? 'abgelehnt' : status}.`,