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