Files
iistwin/server/routes/task-crud-extra.routes.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

140 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from "express";
import { storage } from "../storage";
import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware";
import { tenantIsolation } from "../middleware/tenant.middleware";
export function registerTaskCrudExtraRoutes(router: ReturnType<typeof import("express").Router>): void {
router.put('/api/tasks/:id/notifications/read', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const taskId = parseInt(req.params.id);
const userId = req.user!.id;
const organizationId = req.organizationId!;
if (isNaN(taskId)) {
return res.status(400).json({ success: false, error: 'Неверный ID задачи' });
}
console.log(`[tasks/:id/notifications/read] Marking read for taskId=${taskId}, userId=${userId}, orgId=${organizationId}`);
await storage.markTaskNotificationsAsRead(taskId, userId, organizationId);
res.json({ success: true });
} catch (err) {
console.error('[tasks/:id/notifications/read]', err);
res.status(500).json({ success: false, error: 'Ошибка сервера' });
}
});
router.get('/api/tasks/:id/audit-log', authenticateToken, tenantIsolation, async (req: AuthenticatedRequest, res) => {
try {
const taskId = parseInt(req.params.id);
if (isNaN(taskId)) {
return res.status(400).json({ success: false, error: 'Неверный ID задачи' });
}
const task = await storage.getTask(taskId, req.organizationId!);
if (!task) {
return res.status(404).json({ success: false, error: 'Задача не найдена' });
}
const log = await storage.getTaskAuditLog(taskId, req.organizationId!);
res.json({ success: true, log });
} catch (error) {
console.error('Get task audit log error:', error);
res.status(500).json({ success: false, error: 'Ошибка при получении истории задачи' });
}
});
router.get('/api/tasks/:id/subtasks', async (req: AuthenticatedRequest, res) => {
try {
const taskId = parseInt(req.params.id);
if (isNaN(taskId)) {
return res.status(400).json({ success: false, error: 'Неверный ID задачи' });
}
const subtasks = await storage.getSubtasks(taskId, req.organizationId!);
res.json({ success: true, subtasks });
} catch (error) {
console.error('Get subtasks error:', error);
res.status(500).json({ success: false, error: 'Ошибка при получении подзадач' });
}
});
router.get('/api/tasks/:id/tree', async (req: AuthenticatedRequest, res) => {
try {
const taskId = parseInt(req.params.id);
if (isNaN(taskId)) {
return res.status(400).json({ success: false, error: 'Неверный ID задачи' });
}
const taskTree = await storage.getTaskTree(taskId, req.organizationId!);
if (!taskTree) {
return res.status(404).json({ success: false, error: 'Задача не найдена' });
}
res.json({ success: true, task: taskTree });
} catch (error) {
console.error('Get task tree error:', error);
res.status(500).json({ success: false, error: 'Ошибка при получении дерева задач' });
}
});
/**
* @swagger
* /api/tasks/{id}/related:
* get:
* tags: [Tasks]
* summary: Получить дерево связанных задач
* description: >
* Возвращает дерево задач, связанных с указанной через таблицу task_relations.
* Связи создаются либо через поля типа «задача» в форме, либо вручную через чат
* (кнопка «Привязать»). Каждый узел рекурсивно содержит своих родителей/детей
* в поле `nodes` (глубина до 10 уровней). Поле `fieldName` указывает имя поля
* связи или «Связь из чата» если привязка выполнена вручную.
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: ID задачи
* responses:
* 200:
* description: Дерево связанных задач
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* parents:
* type: array
* description: Задачи-родители (данная задача является дочерней)
* items:
* $ref: '#/components/schemas/RelatedTreeNode'
* children:
* type: array
* description: Задачи-дети (данная задача является родительской)
* items:
* $ref: '#/components/schemas/RelatedTreeNode'
* 400:
* description: Неверный ID задачи
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/api/tasks/:id/related', async (req: AuthenticatedRequest, res) => {
try {
const taskId = parseInt(req.params.id);
if (isNaN(taskId)) {
return res.status(400).json({ success: false, error: 'Неверный ID задачи' });
}
const relatedTasks = await storage.getRelatedTasks(taskId, req.organizationId!);
res.json({ success: true, ...relatedTasks });
} catch (error) {
console.error('Get related tasks error:', error);
res.status(500).json({ success: false, error: 'Ошибка при получении связанных задач' });
}
});
}