feat(automations): ctx.tasks.sendMessage — сообщение в чат задачи от Системы; userId в triggerData status_changed

This commit is contained in:
2026-09-08 16:31:59 +03:00
parent 163351b345
commit 0b2319b11d
13 changed files with 171 additions and 32 deletions

View File

@@ -414,7 +414,9 @@ export function registerUserManagementRoutes(router: Router): void {
appRole: z.string().min(1, 'Выберите роль доступа'),
organizationalRoleIds: z.array(z.number()).optional(),
sendInvite: z.boolean().default(true),
statusId: z.union([z.number(), z.null()]).optional()
statusId: z.union([z.number(), z.null()]).optional(),
// Необязательный явный пароль; состав дополнительно проверяет hashPassword (400 с текстом правила)
password: z.string().min(8, 'Пароль должен содержать минимум 8 символов').optional()
})),
async (req: AuthenticatedRequest, res) => {
try {
@@ -427,7 +429,8 @@ export function registerUserManagementRoutes(router: Router): void {
appRole: req.body.appRole,
organizationalRoleIds: req.body.organizationalRoleIds,
sendInvite: req.body.sendInvite,
statusId: req.body.statusId ?? undefined
statusId: req.body.statusId ?? undefined,
password: req.body.password
});
if (result.success && result.user) {

View File

@@ -6,6 +6,7 @@ import { authenticateToken, requirePermission, type AuthenticatedRequest } from
import { tenantIsolation } from "../middleware/tenant.middleware";
import { logAudit } from "../utils/audit";
import { AUTOMATION_WORKER_CODE } from "../workers/automation-worker-code";
import { sendTaskMessage } from "../services/task-message.service";
const router = Router();
@@ -31,6 +32,8 @@ const ALLOWED_STORAGE_METHODS = new Set([
'updateUser',
'setUserProfileFieldValueFromAutomation',
'recalcUserFieldAverageFromAutomation',
// Не storage-метод: обрабатывается отдельно в обработчике storage-call (сервис сообщений)
'sendTaskMessageFromAutomation',
]);
interface WorkerStorageCall {
@@ -96,6 +99,31 @@ export function runAutomationInWorker(
return;
}
// Сообщение в чат задачи из автоматизации: не storage-метод,
// идём через сервис сообщений (SSE, вложения, messageType 'status_change')
if (method === 'sendTaskMessageFromAutomation') {
try {
const [taskId, orgId, message] = args as [number, number, unknown];
const task = await storage.getTask(Number(taskId), Number(orgId));
if (!task) throw new Error(`Задача ${taskId} не найдена`);
const created = await sendTaskMessage({
task,
organizationId: Number(orgId),
message: String(message ?? ''),
messageType: 'status_change',
});
if (!settled) {
try { worker.postMessage({ type: 'storage-response', callId, result: created }); } catch { /* terminated */ }
}
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
if (!settled) {
try { worker.postMessage({ type: 'storage-response', callId, error: message }); } catch { /* terminated */ }
}
}
return;
}
try {
const fn = (storage as unknown as Record<string, (...a: unknown[]) => Promise<unknown>>)[method];
if (typeof fn !== 'function') {