fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
139
server/routes/task-crud-extra.routes.ts
Normal file
139
server/routes/task-crud-extra.routes.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
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: 'Ошибка при получении связанных задач' });
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user