import { Router } from "express"; import { storage } from "../storage"; import { requireFormAdminOrPermission, type AuthenticatedRequest } from "../middleware/auth.middleware"; import { formsCache } from "../utils/cache"; import { indexFormWithSummaryAsync } from "./task-helpers"; import { type Task } from "@shared/schema"; export function registerFormStatusRoutes(router: ReturnType): void { // === FORM STATUSES ENDPOINTS === /** * @swagger * /api/forms/{id}/statuses: * get: * tags: [Forms] * summary: Статусы формы * post: * tags: [Forms] * summary: Создать статус */ router.get('/api/forms/:id/statuses', async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); if (isNaN(formId)) { return res.status(400).json({ success: false, error: 'Неверный ID формы' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } const hasManageForms = await storage.hasAppRolePermission(req.user!.appRole, ['forms.manage']); if (!hasManageForms) { const canAccess = await storage.canUserAccessForm(req.user!.id, formId, req.organizationId!, 'participate'); if (!canAccess) { return res.status(403).json({ success: false, error: 'Нет доступа к этой форме' }); } } const statuses = await storage.getFormStatuses(formId, req.organizationId!); res.json({ success: true, statuses }); } catch (error) { console.error('Get form statuses error:', error); res.status(500).json({ success: false, error: 'Ошибка при получении статусов формы' }); } }); router.post('/api/forms/:id/statuses', requireFormAdminOrPermission('forms.manage'), async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); if (isNaN(formId)) { return res.status(400).json({ success: false, error: 'Неверный ID формы' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } const { statuses } = req.body; if (!Array.isArray(statuses)) { return res.status(400).json({ success: false, error: 'Статусы должны быть массивом' }); } const existingStatuses = await storage.getFormStatuses(formId, req.organizationId!); const incomingIds = statuses.filter((s: Record) => s.id).map((s: Record) => s.id as number); const statusesToDelete = existingStatuses.filter(existing => !incomingIds.includes(existing.id)); if (statusesToDelete.length > 0) { const tasks = await storage.getTasksByForm(formId, req.organizationId!); const usedStatusIds = new Set(tasks.map((t: Task) => t.currentStatusId)); for (const status of statusesToDelete) { if (usedStatusIds.has(status.id)) { return res.status(400).json({ success: false, error: `Невозможно удалить статус "${status.name}" - он используется в существующих задачах. Сначала измените статус всех задач, использующих этот статус.` }); } } } const statusIdsToDelete = statusesToDelete.map(s => s.id); const resultStatuses = await storage.bulkSaveFormStatuses(formId, req.organizationId!, statusIdsToDelete, statuses); formsCache.invalidatePrefix(`forms:${req.organizationId}`); indexFormWithSummaryAsync(formId, req.organizationId!).catch(() => {}); res.status(201).json({ success: true, message: 'Статусы сохранены', statuses: resultStatuses }); } catch (error) { console.error('Save form statuses error:', error); res.status(500).json({ success: false, error: 'Ошибка при сохранении статусов' }); } } ); // === STATUS TRANSITIONS ENDPOINTS === /** * @swagger * /api/forms/{id}/transitions: * get: * tags: [Forms] * summary: Переходы между статусами * post: * tags: [Forms] * summary: Создать переход */ router.get('/api/forms/:id/transitions', async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); if (isNaN(formId)) { return res.status(400).json({ success: false, error: 'Неверный ID формы' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } const hasManageForms = await storage.hasAppRolePermission(req.user!.appRole, ['forms.manage']); if (!hasManageForms) { const canAccess = await storage.canUserAccessForm(req.user!.id, formId, req.organizationId!, 'participate'); if (!canAccess) { return res.status(403).json({ success: false, error: 'Нет доступа к этой форме' }); } } const transitions = await storage.getStatusTransitions(formId, req.organizationId!); res.json({ success: true, transitions }); } catch (error) { console.error('Get transitions error:', error); res.status(500).json({ success: false, error: 'Ошибка при получении переходов' }); } } ); router.post('/api/forms/:id/transitions', requireFormAdminOrPermission('forms.manage'), async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); if (isNaN(formId)) { return res.status(400).json({ success: false, error: 'Неверный ID формы' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } const { fromStatusId, toStatusId, rejectToStatusId, actionType, actionName, conditions, requiredRole, assigneeUserId, assigneeConditions, approveButtonText, approveButtonColor, rejectButtonText, rejectButtonColor } = req.body; if (!fromStatusId || !toStatusId || !actionType) { return res.status(400).json({ success: false, error: 'Необходимо указать fromStatusId, toStatusId и actionType' }); } const transitionData = { formId, fromStatusId, toStatusId, rejectToStatusId: rejectToStatusId || null, actionType, actionName, conditions, requiredRole, assigneeUserId, assigneeConditions, approveButtonText: approveButtonText || 'Одобрить', approveButtonColor: approveButtonColor || '#22c55e', rejectButtonText: rejectButtonText || 'Отклонить', rejectButtonColor: rejectButtonColor || '#ef4444' }; const transition = await storage.createStatusTransition(transitionData); res.status(201).json({ success: true, message: 'Переход создан', transition }); } catch (error) { console.error('Create transition error:', error); res.status(500).json({ success: false, error: 'Ошибка при создании перехода' }); } } ); router.put('/api/forms/:id/transitions/:transitionId', requireFormAdminOrPermission('forms.manage'), async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); const transitionId = parseInt(req.params.transitionId); if (isNaN(formId) || isNaN(transitionId)) { return res.status(400).json({ success: false, error: 'Неверные параметры' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } const transition = await storage.updateStatusTransition(transitionId, formId, req.organizationId!, req.body); if (!transition) { return res.status(404).json({ success: false, error: 'Переход не найден' }); } res.json({ success: true, message: 'Переход обновлен', transition }); } catch (error) { console.error('Update transition error:', error); res.status(500).json({ success: false, error: 'Ошибка при обновлении перехода' }); } } ); router.delete('/api/forms/:id/transitions/:transitionId', requireFormAdminOrPermission('forms.manage'), async (req: AuthenticatedRequest, res) => { try { const formId = parseInt(req.params.id); const transitionId = parseInt(req.params.transitionId); if (isNaN(formId) || isNaN(transitionId)) { return res.status(400).json({ success: false, error: 'Неверные параметры' }); } const existingForm = await storage.getForm(formId, req.organizationId!); if (!existingForm) { return res.status(404).json({ success: false, error: 'Форма не найдена' }); } await storage.deleteStatusTransition(transitionId, formId, req.organizationId!); res.json({ success: true, message: 'Переход удален' }); } catch (error) { console.error('Delete transition error:', error); res.status(500).json({ success: false, error: 'Ошибка при удалении перехода' }); } } ); }