- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
72 lines
3.0 KiB
TypeScript
72 lines
3.0 KiB
TypeScript
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;
|
||
}
|