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

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

72 lines
3.0 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 { notificationService, EVENT_TYPES } from "../services/notification.service";
import { eventBus } from "../routes/shared";
/**
* Отправляет уведомление о назначении ответственного по задаче.
* Централизованная функция — используется из всех endpoint'ов,
* где меняется assignedTo (assignees, transitions, crud-write, auto-transitions).
*
* Возвращает массив userId пользователей, которым было отправлено уведомление.
* Отправляет SSE notification events для real-time обновления UI.
*
* Если у назначенного пользователя нет подписки на task.assigned,
* создаёт уведомление принудительно (через targetUserIds), чтобы
* назначение задачи никогда не пропадало мимо пользователя.
*/
export async function notifyTaskAssigned(
task: { id: number; title: string; formId: number },
assigneeUserId: number,
triggeredBy: number | null,
organizationId: number
): Promise<number[]> {
const eventBase = {
type: EVENT_TYPES.TASK_ASSIGNED,
organizationId,
triggeredBy: triggeredBy ?? null,
taskId: task.id,
formId: task.formId,
payload: {
taskTitle: task.title,
assignedTo: assigneeUserId,
authorName: triggeredBy ? undefined : "Система",
message: `Вас назначили ответственным по задаче "${task.title}"`,
},
timestamp: new Date(),
};
// 1. Primary: normal subscriber-based flow
let notifiedUserIds = await notificationService.processEvent(eventBase);
if (!Array.isArray(notifiedUserIds)) {
console.warn(`[notifyTaskAssigned] processEvent returned non-array (${typeof notifiedUserIds}), treating as empty`);
notifiedUserIds = [];
}
// 2. Fallback: if subscription-based flow didn't notify the assignee,
// force-create the notification via targetUserIds (bypasses subscription check).
// This ensures task assignment is NEVER silently lost.
if (!notifiedUserIds.includes(assigneeUserId)) {
console.warn(
`[notifyTaskAssigned] User ${assigneeUserId} has no subscription for TASK_ASSIGNED. ` +
`Forcing notification via targetUserIds (taskId=${task.id}, formId=${task.formId})`
);
const forced = await notificationService.processEvent({
...eventBase,
targetUserIds: [assigneeUserId],
});
notifiedUserIds = Array.isArray(forced) ? forced : [];
}
// 3. Send SSE notification events for real-time UI update (badge + inbox count)
for (const userId of notifiedUserIds) {
console.log(`[SSE] Publishing notification event for user ${userId}, taskId=${task.id}`);
eventBus.publishEvent({
type: "notification",
data: { type: EVENT_TYPES.TASK_ASSIGNED, taskId: task.id },
organizationId,
userId,
});
}
return notifiedUserIds;
}