import { Response } from 'express'; import * as contractTaskService from '../services/contractTask.service.js'; import * as contractService from '../services/contract.service.js'; import * as customerService from '../services/customer.service.js'; import * as appSettingService from '../services/appSetting.service.js'; import { logChange } from '../services/audit.service.js'; import { ApiResponse, AuthRequest } from '../types/index.js'; // ==================== ALL TASKS (Dashboard & Task List) ==================== export async function getAllTasks(req: AuthRequest, res: Response): Promise { try { const { status, customerId } = req.query; // Für Kundenportal: Filter auf erlaubte Kunden let customerPortalCustomerIds: number[] | undefined; let customerPortalEmails: string[] | undefined; if (req.user?.isCustomerPortal && req.user.customerId) { customerPortalCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; const customers = await customerService.getCustomersByIds(customerPortalCustomerIds); customerPortalEmails = customers .map((c: { id: number; portalEmail: string | null }) => c.portalEmail) .filter((email: string | null): email is string => !!email); } const tasks = await contractTaskService.getAllTasks({ status: status as 'OPEN' | 'COMPLETED' | undefined, customerId: customerId ? parseInt(customerId as string) : undefined, customerPortalCustomerIds, customerPortalEmails, }); res.json({ success: true, data: tasks } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden der Aufgaben', } as ApiResponse); } } export async function getTaskStats(req: AuthRequest, res: Response): Promise { try { // Für Kundenportal: Filter auf erlaubte Kunden let customerPortalCustomerIds: number[] | undefined; let customerPortalEmails: string[] | undefined; if (req.user?.isCustomerPortal && req.user.customerId) { customerPortalCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; const customers = await customerService.getCustomersByIds(customerPortalCustomerIds); customerPortalEmails = customers .map((c: { id: number; portalEmail: string | null }) => c.portalEmail) .filter((email: string | null): email is string => !!email); } const stats = await contractTaskService.getTaskStats({ customerPortalCustomerIds, customerPortalEmails, }); res.json({ success: true, data: stats } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden der Statistik', } as ApiResponse); } } // ==================== TASKS BY CONTRACT ==================== export async function getTasks(req: AuthRequest, res: Response): Promise { try { const contractId = parseInt(req.params.contractId); const { status } = req.query; // Prüfe Zugriff auf den Vertrag const contract = await contractService.getContractById(contractId); if (!contract) { res.status(404).json({ success: false, error: 'Vertrag nicht gefunden', } as ApiResponse); return; } // Für Kundenportal: Zugriffsprüfung if (req.user?.isCustomerPortal && req.user.customerId) { const allowedCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; if (!allowedCustomerIds.includes(contract.customerId)) { res.status(403).json({ success: false, error: 'Kein Zugriff auf diesen Vertrag', } as ApiResponse); return; } } // Für Kundenportal-Benutzer: Lade E-Mails der erlaubten Kunden let customerPortalEmails: string[] | undefined; if (req.user?.isCustomerPortal && req.user.customerId) { const allowedCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; const customers = await customerService.getCustomersByIds(allowedCustomerIds); customerPortalEmails = customers .map((c: { id: number; portalEmail: string | null }) => c.portalEmail) .filter((email: string | null): email is string => !!email); } const tasks = await contractTaskService.getTasksByContract({ contractId, status: status as 'OPEN' | 'COMPLETED' | undefined, customerPortalEmails, }); res.json({ success: true, data: tasks } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden der Aufgaben', } as ApiResponse); } } export async function createTask(req: AuthRequest, res: Response): Promise { try { const contractId = parseInt(req.params.contractId); const { title, description, visibleInPortal } = req.body; if (!title) { res.status(400).json({ success: false, error: 'Titel ist erforderlich', } as ApiResponse); return; } const createdBy = req.user?.email; // Für Kundenportal-Benutzer: visibleInPortal wird automatisch auf true gesetzt const finalVisibleInPortal = req.user?.isCustomerPortal ? true : visibleInPortal; const task = await contractTaskService.createTask({ contractId, title, description, visibleInPortal: finalVisibleInPortal, createdBy, }); await logChange({ req, action: 'CREATE', resourceType: 'ContractTask', resourceId: task.id.toString(), label: `Aufgabe "${title}" erstellt`, }); res.status(201).json({ success: true, data: task } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Aufgabe', } as ApiResponse); } } // Für Kundenportal-Benutzer: Support-Anfrage erstellen (ohne contracts:update Permission) export async function createSupportTicket(req: AuthRequest, res: Response): Promise { try { // Prüfe ob Support-Tickets aktiviert sind const supportEnabled = await appSettingService.getSettingBool('customerSupportTicketsEnabled'); if (!supportEnabled) { res.status(403).json({ success: false, error: 'Support-Anfragen sind nicht aktiviert', } as ApiResponse); return; } const contractId = parseInt(req.params.contractId); const { title, description } = req.body; if (!title) { res.status(400).json({ success: false, error: 'Titel ist erforderlich', } as ApiResponse); return; } // Prüfe Zugriff auf den Vertrag const contract = await contractService.getContractById(contractId); if (!contract) { res.status(404).json({ success: false, error: 'Vertrag nicht gefunden', } as ApiResponse); return; } // Zugriffsprüfung für Kundenportal if (req.user?.customerId) { const allowedCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; if (!allowedCustomerIds.includes(contract.customerId)) { res.status(403).json({ success: false, error: 'Kein Zugriff auf diesen Vertrag', } as ApiResponse); return; } } const createdBy = req.user?.email; const task = await contractTaskService.createTask({ contractId, title, description, visibleInPortal: true, // Immer sichtbar im Portal createdBy, }); await logChange({ req, action: 'CREATE', resourceType: 'ContractTask', resourceId: task.id.toString(), label: `Support-Anfrage "${title}" erstellt`, }); res.status(201).json({ success: true, data: task } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Support-Anfrage', } as ApiResponse); } } export async function updateTask(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); const { title, description, visibleInPortal } = req.body; const task = await contractTaskService.updateTask(taskId, { title, description, visibleInPortal, }); await logChange({ req, action: 'UPDATE', resourceType: 'ContractTask', resourceId: taskId.toString(), label: `Aufgabe aktualisiert`, }); res.json({ success: true, data: task } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Aufgabe', } as ApiResponse); } } export async function completeTask(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); const task = await contractTaskService.completeTask(taskId); await logChange({ req, action: 'UPDATE', resourceType: 'ContractTask', resourceId: taskId.toString(), label: `Aufgabe abgeschlossen`, }); res.json({ success: true, data: task } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Abschließen der Aufgabe', } as ApiResponse); } } export async function reopenTask(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); const task = await contractTaskService.reopenTask(taskId); await logChange({ req, action: 'UPDATE', resourceType: 'ContractTask', resourceId: taskId.toString(), label: `Aufgabe wiedereröffnet`, }); res.json({ success: true, data: task } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Wiedereröffnen der Aufgabe', } as ApiResponse); } } export async function deleteTask(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); await contractTaskService.deleteTask(taskId); await logChange({ req, action: 'DELETE', resourceType: 'ContractTask', resourceId: taskId.toString(), label: `Aufgabe gelöscht`, }); res.json({ success: true, message: 'Aufgabe gelöscht' } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Löschen der Aufgabe', } as ApiResponse); } } // ==================== SUBTASKS ==================== export async function createSubtask(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); const { title } = req.body; if (!title) { res.status(400).json({ success: false, error: 'Titel ist erforderlich', } as ApiResponse); return; } const createdBy = req.user?.email; const subtask = await contractTaskService.createSubtask({ taskId, title, createdBy, }); await logChange({ req, action: 'CREATE', resourceType: 'ContractSubtask', resourceId: subtask.id.toString(), label: `Unteraufgabe "${title}" erstellt`, }); res.status(201).json({ success: true, data: subtask } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Unteraufgabe', } as ApiResponse); } } // Kundenportal: Antwort auf eigenes Ticket erstellen export async function createCustomerReply(req: AuthRequest, res: Response): Promise { try { const taskId = parseInt(req.params.taskId); const { title } = req.body; if (!title) { res.status(400).json({ success: false, error: 'Antwort ist erforderlich', } as ApiResponse); return; } // Hole den Task const task = await contractTaskService.getTaskById(taskId); if (!task) { res.status(404).json({ success: false, error: 'Anfrage nicht gefunden', } as ApiResponse); return; } // Prüfe ob der Kunde berechtigt ist (eigenes Ticket oder freigegebener Kunde) if (req.user?.isCustomerPortal && req.user.customerId) { const allowedCustomerIds = [req.user.customerId, ...(req.user.representedCustomerIds || [])]; const customers = await customerService.getCustomersByIds(allowedCustomerIds); const allowedEmails = customers .map((c: { id: number; portalEmail: string | null }) => c.portalEmail) .filter((email: string | null): email is string => !!email); // Task muss entweder visibleInPortal sein ODER vom Kunden erstellt worden sein const isOwnTask = task.createdBy && allowedEmails.includes(task.createdBy); if (!task.visibleInPortal && !isOwnTask) { res.status(403).json({ success: false, error: 'Kein Zugriff auf diese Anfrage', } as ApiResponse); return; } } else { res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer', } as ApiResponse); return; } const createdBy = req.user?.email; const subtask = await contractTaskService.createSubtask({ taskId, title, createdBy, }); await logChange({ req, action: 'CREATE', resourceType: 'ContractSubtask', resourceId: subtask.id.toString(), label: `Kundenantwort erstellt`, }); res.status(201).json({ success: true, data: subtask } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Antwort', } as ApiResponse); } } export async function updateSubtask(req: AuthRequest, res: Response): Promise { try { const subtaskId = parseInt(req.params.subtaskId); const { title } = req.body; if (!title) { res.status(400).json({ success: false, error: 'Titel ist erforderlich', } as ApiResponse); return; } const subtask = await contractTaskService.updateSubtask(subtaskId, { title }); await logChange({ req, action: 'UPDATE', resourceType: 'ContractSubtask', resourceId: subtaskId.toString(), label: `Unteraufgabe aktualisiert`, }); res.json({ success: true, data: subtask } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Unteraufgabe', } as ApiResponse); } } export async function completeSubtask(req: AuthRequest, res: Response): Promise { try { const subtaskId = parseInt(req.params.subtaskId); const subtask = await contractTaskService.completeSubtask(subtaskId); await logChange({ req, action: 'UPDATE', resourceType: 'ContractSubtask', resourceId: subtaskId.toString(), label: `Unteraufgabe abgeschlossen`, }); res.json({ success: true, data: subtask } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Abschließen der Unteraufgabe', } as ApiResponse); } } export async function reopenSubtask(req: AuthRequest, res: Response): Promise { try { const subtaskId = parseInt(req.params.subtaskId); const subtask = await contractTaskService.reopenSubtask(subtaskId); await logChange({ req, action: 'UPDATE', resourceType: 'ContractSubtask', resourceId: subtaskId.toString(), label: `Unteraufgabe wiedereröffnet`, }); res.json({ success: true, data: subtask } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Wiedereröffnen der Unteraufgabe', } as ApiResponse); } } export async function deleteSubtask(req: AuthRequest, res: Response): Promise { try { const subtaskId = parseInt(req.params.subtaskId); await contractTaskService.deleteSubtask(subtaskId); await logChange({ req, action: 'DELETE', resourceType: 'ContractSubtask', resourceId: subtaskId.toString(), label: `Unteraufgabe gelöscht`, }); res.json({ success: true, message: 'Unteraufgabe gelöscht' } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Löschen der Unteraufgabe', } as ApiResponse); } }