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 { isValidFieldType } from "@shared/field-types"; export function registerFormStructureRoutes(router: ReturnType): void { // === FORM TABS ENDPOINTS === router.get('/api/forms/:id/tabs', 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 tabs = await storage.getFormTabsByCreationOrder(formId, req.organizationId!); res.json({ success: true, tabs }); } catch (error) { console.error('Get form tabs error:', error); res.status(500).json({ success: false, error: 'Ошибка при получении вкладок формы' }); } }); router.post('/api/forms/:id/tabs', 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 { tabs } = req.body; if (!Array.isArray(tabs)) { return res.status(400).json({ success: false, error: 'Вкладки должны быть массивом' }); } const savedTabs = await storage.bulkSaveFormTabs(formId, req.organizationId!, tabs); formsCache.invalidatePrefix(`forms:${req.organizationId}`); res.json({ success: true, tabs: savedTabs, message: 'Вкладки сохранены' }); } catch (error) { console.error('Save form tabs error:', error); res.status(500).json({ success: false, error: 'Ошибка при сохранении вкладок' }); } } ); // === FORM FIELDS ENDPOINTS === /** * @swagger * /api/forms/{id}/fields: * get: * tags: [Forms] * summary: Поля формы * post: * tags: [Forms] * summary: Добавить поле в форму */ router.get('/api/forms/:id/fields', 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 fields = await storage.getFormFieldsByCreationOrder(formId, req.organizationId!); res.json({ success: true, fields }); } catch (error) { console.error('Get form fields error:', error); res.status(500).json({ success: false, error: 'Ошибка при получении полей формы' }); } }); router.post('/api/forms/:id/fields', 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 { fields } = req.body; if (!Array.isArray(fields)) { return res.status(400).json({ success: false, error: 'Поля должны быть массивом' }); } const existingFields = await storage.getFormFields(formId, req.organizationId!); const incomingIds = fields.filter((f: Record) => f.id).map((f: Record) => f.id as number); const fieldsToDelete = existingFields.filter(existing => !incomingIds.includes(existing.id)); if (fieldsToDelete.length > 0) { const tasks = await storage.getTasksByForm(formId, req.organizationId!); const usedFieldIds = new Set(); for (const task of tasks) { const fieldValues = await storage.getTaskFieldValues(task.id, req.organizationId!); for (const value of fieldValues) { usedFieldIds.add(value.fieldId); } } for (const field of fieldsToDelete) { if (usedFieldIds.has(field.id)) { return res.status(400).json({ success: false, error: `Невозможно удалить поле "${field.name}" - оно используется в существующих задачах. Сначала удалите значения этого поля во всех задачах.` }); } } } const fieldIdsToDelete = fieldsToDelete.map(f => f.id); const normalizedFields = fields.map((f: Record) => ({ ...f, maxFileCount: f.maxFileCount != null && parseInt(String(f.maxFileCount)) > 0 ? parseInt(String(f.maxFileCount)) : null, maxFileSizeMB: f.maxFileSizeMB != null && parseInt(String(f.maxFileSizeMB)) > 0 ? parseInt(String(f.maxFileSizeMB)) : null, })); // Validate field types for new fields for (const field of fields as Array>) { const fieldId = field.id as number | undefined; const isNew = !fieldId || (typeof fieldId === 'number' && fieldId < 0); const fieldType = field.type as string; if (isNew && fieldType && !isValidFieldType(fieldType, 'form')) { return res.status(400).json({ success: false, error: `Тип поля "${fieldType}" нельзя создать напрямую в форме. Используйте глобальные поля.`, }); } } const resultFields = await storage.bulkSaveFormFields(formId, req.organizationId!, fieldIdsToDelete, normalizedFields as Parameters[3]); formsCache.invalidatePrefix(`forms:${req.organizationId}`); indexFormWithSummaryAsync(formId, req.organizationId!).catch(() => {}); res.status(201).json({ success: true, message: 'Поля сохранены', fields: resultFields }); } catch (error) { console.error('Save form fields error:', error); res.status(500).json({ success: false, error: 'Ошибка при сохранении полей' }); } } ); }