new features

This commit is contained in:
Matthias Hochmeister
2026-03-23 18:43:30 +01:00
parent 202a658b8d
commit 1b13e4f89e
31 changed files with 1022 additions and 517 deletions

View File

@@ -0,0 +1,273 @@
import { Request, Response } from 'express';
import ausruestungsanfrageService from '../services/ausruestungsanfrage.service';
import notificationService from '../services/notification.service';
import logger from '../utils/logger';
class AusruestungsanfrageController {
// -------------------------------------------------------------------------
// Catalog Items
// -------------------------------------------------------------------------
async getItems(req: Request, res: Response): Promise<void> {
try {
const kategorie = req.query.kategorie as string | undefined;
const aktiv = req.query.aktiv !== undefined ? req.query.aktiv === 'true' : undefined;
const items = await ausruestungsanfrageService.getItems({ kategorie, aktiv });
res.status(200).json({ success: true, data: items });
} catch (error) {
logger.error('AusruestungsanfrageController.getItems error', { error });
res.status(500).json({ success: false, message: 'Artikel konnten nicht geladen werden' });
}
}
async getItemById(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
const item = await ausruestungsanfrageService.getItemById(id);
if (!item) {
res.status(404).json({ success: false, message: 'Artikel nicht gefunden' });
return;
}
res.status(200).json({ success: true, data: item });
} catch (error) {
logger.error('AusruestungsanfrageController.getItemById error', { error });
res.status(500).json({ success: false, message: 'Artikel konnte nicht geladen werden' });
}
}
async createItem(req: Request, res: Response): Promise<void> {
try {
const { bezeichnung } = req.body;
if (!bezeichnung || bezeichnung.trim().length === 0) {
res.status(400).json({ success: false, message: 'Bezeichnung ist erforderlich' });
return;
}
const item = await ausruestungsanfrageService.createItem(req.body, req.user!.id);
res.status(201).json({ success: true, data: item });
} catch (error) {
logger.error('AusruestungsanfrageController.createItem error', { error });
res.status(500).json({ success: false, message: 'Artikel konnte nicht erstellt werden' });
}
}
async updateItem(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
const item = await ausruestungsanfrageService.updateItem(id, req.body, req.user!.id);
if (!item) {
res.status(404).json({ success: false, message: 'Artikel nicht gefunden' });
return;
}
res.status(200).json({ success: true, data: item });
} catch (error) {
logger.error('AusruestungsanfrageController.updateItem error', { error });
res.status(500).json({ success: false, message: 'Artikel konnte nicht aktualisiert werden' });
}
}
async deleteItem(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
await ausruestungsanfrageService.deleteItem(id);
res.status(200).json({ success: true, message: 'Artikel gelöscht' });
} catch (error) {
logger.error('AusruestungsanfrageController.deleteItem error', { error });
res.status(500).json({ success: false, message: 'Artikel konnte nicht gelöscht werden' });
}
}
async getCategories(_req: Request, res: Response): Promise<void> {
try {
const categories = await ausruestungsanfrageService.getCategories();
res.status(200).json({ success: true, data: categories });
} catch (error) {
logger.error('AusruestungsanfrageController.getCategories error', { error });
res.status(500).json({ success: false, message: 'Kategorien konnten nicht geladen werden' });
}
}
// -------------------------------------------------------------------------
// Requests
// -------------------------------------------------------------------------
async getRequests(req: Request, res: Response): Promise<void> {
try {
const status = req.query.status as string | undefined;
const anfrager_id = req.query.anfrager_id as string | undefined;
const requests = await ausruestungsanfrageService.getRequests({ status, anfrager_id });
res.status(200).json({ success: true, data: requests });
} catch (error) {
logger.error('AusruestungsanfrageController.getRequests error', { error });
res.status(500).json({ success: false, message: 'Anfragen konnten nicht geladen werden' });
}
}
async getMyRequests(req: Request, res: Response): Promise<void> {
try {
const requests = await ausruestungsanfrageService.getMyRequests(req.user!.id);
res.status(200).json({ success: true, data: requests });
} catch (error) {
logger.error('AusruestungsanfrageController.getMyRequests error', { error });
res.status(500).json({ success: false, message: 'Anfragen konnten nicht geladen werden' });
}
}
async getRequestById(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
const request = await ausruestungsanfrageService.getRequestById(id);
if (!request) {
res.status(404).json({ success: false, message: 'Anfrage nicht gefunden' });
return;
}
res.status(200).json({ success: true, data: request });
} catch (error) {
logger.error('AusruestungsanfrageController.getRequestById error', { error });
res.status(500).json({ success: false, message: 'Anfrage konnte nicht geladen werden' });
}
}
async createRequest(req: Request, res: Response): Promise<void> {
try {
const { items, notizen } = req.body as {
items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string }[];
notizen?: string;
};
if (!items || items.length === 0) {
res.status(400).json({ success: false, message: 'Mindestens eine Position ist erforderlich' });
return;
}
for (const item of items) {
if (!item.bezeichnung || item.bezeichnung.trim().length === 0) {
res.status(400).json({ success: false, message: 'Bezeichnung ist für alle Positionen erforderlich' });
return;
}
if (!item.menge || item.menge < 1) {
res.status(400).json({ success: false, message: 'Menge muss mindestens 1 sein' });
return;
}
}
const request = await ausruestungsanfrageService.createRequest(req.user!.id, items, notizen);
res.status(201).json({ success: true, data: request });
} catch (error) {
logger.error('AusruestungsanfrageController.createRequest error', { error });
res.status(500).json({ success: false, message: 'Anfrage konnte nicht erstellt werden' });
}
}
async updateRequestStatus(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
const { status, admin_notizen } = req.body as {
status?: string;
admin_notizen?: string;
};
if (!status) {
res.status(400).json({ success: false, message: 'Status ist erforderlich' });
return;
}
const validStatuses = ['offen', 'genehmigt', 'abgelehnt', 'bestellt', 'erledigt'];
if (!validStatuses.includes(status)) {
res.status(400).json({ success: false, message: `Ungültiger Status. Erlaubt: ${validStatuses.join(', ')}` });
return;
}
// Fetch request to get anfrager_id for notification
const existing = await ausruestungsanfrageService.getRequestById(id);
if (!existing) {
res.status(404).json({ success: false, message: 'Anfrage nicht gefunden' });
return;
}
const updated = await ausruestungsanfrageService.updateRequestStatus(id, status, admin_notizen, req.user!.id);
// 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')}`
: `#${id}`;
await notificationService.createNotification({
user_id: existing.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}.`,
schwere: status === 'abgelehnt' ? 'warnung' : 'info',
link: '/ausruestungsanfrage',
quell_id: String(id),
quell_typ: 'ausruestung_anfrage',
});
}
res.status(200).json({ success: true, data: updated });
} catch (error) {
logger.error('AusruestungsanfrageController.updateRequestStatus error', { error });
res.status(500).json({ success: false, message: 'Status konnte nicht aktualisiert werden' });
}
}
async deleteRequest(req: Request, res: Response): Promise<void> {
try {
const id = Number(req.params.id);
await ausruestungsanfrageService.deleteRequest(id);
res.status(200).json({ success: true, message: 'Anfrage gelöscht' });
} catch (error) {
logger.error('AusruestungsanfrageController.deleteRequest error', { error });
res.status(500).json({ success: false, message: 'Anfrage konnte nicht gelöscht werden' });
}
}
// -------------------------------------------------------------------------
// Overview
// -------------------------------------------------------------------------
async getOverview(_req: Request, res: Response): Promise<void> {
try {
const overview = await ausruestungsanfrageService.getOverview();
res.status(200).json({ success: true, data: overview });
} catch (error) {
logger.error('AusruestungsanfrageController.getOverview error', { error });
res.status(500).json({ success: false, message: 'Übersicht konnte nicht geladen werden' });
}
}
// -------------------------------------------------------------------------
// Linking
// -------------------------------------------------------------------------
async linkToOrder(req: Request, res: Response): Promise<void> {
try {
const anfrageId = Number(req.params.id);
const { bestellung_id } = req.body as { bestellung_id?: number };
if (!bestellung_id) {
res.status(400).json({ success: false, message: 'bestellung_id ist erforderlich' });
return;
}
await ausruestungsanfrageService.linkToOrder(anfrageId, bestellung_id);
res.status(200).json({ success: true, message: 'Verknüpfung erstellt' });
} catch (error) {
logger.error('AusruestungsanfrageController.linkToOrder error', { error });
res.status(500).json({ success: false, message: 'Verknüpfung konnte nicht erstellt werden' });
}
}
async unlinkFromOrder(req: Request, res: Response): Promise<void> {
try {
const anfrageId = Number(req.params.id);
const bestellungId = Number(req.params.bestellungId);
await ausruestungsanfrageService.unlinkFromOrder(anfrageId, bestellungId);
res.status(200).json({ success: true, message: 'Verknüpfung entfernt' });
} catch (error) {
logger.error('AusruestungsanfrageController.unlinkFromOrder error', { error });
res.status(500).json({ success: false, message: 'Verknüpfung konnte nicht entfernt werden' });
}
}
}
export default new AusruestungsanfrageController();