Инфраструктура автоматизаций: - триггер task.status_changed оживлён: вызов из executeTaskTransition (fire-and-forget) + фильтр triggerConfig.statusId - ctx.users.setFieldValue(userId, fieldCodeOrId, value): запись user_profile_field_values с аудитом; для history-number — append в user_field_history (миграция 0078) - документация sandbox обновлена (Automations.tsx, mcp.ts, AGENTS.md) по критическому правилу 2 Мультивыбор пользователей и ролей в user-поле: - чекбокс «Множественный выбор (пользователи и роли)» в настройках поля (form_fields.allow_multiple) - значение — массив ['user:ID','role:ID'], обратная совместимость с одиночными/legacy значениями - UserRoleMultiSelect в карточке задачи, реестре и диалоге создания (роли добавлены и в одиночный сценарий создания) - синк в task_assignees итерирует массив; role:ID раскрывается в членов роли; PATCH single — diff старого/нового значений - отображение массивов: реестр, title-шаблоны, условия полей, MCP update_task_fields - список орг-ролей (/api/roles) загружается всем пользователям (сервер и так был открыт) — роли доступны не только админам График history-number в профиле: - GET /api/users/:id/field-history/:fieldId?period=week|month|quarter|year (агрегация AVG по бакетам) - диалог с LineChart (recharts) и переключателем периодов по клику на иконку у поля
30 lines
1.3 KiB
TypeScript
30 lines
1.3 KiB
TypeScript
/**
|
|
* Хелперы для полей типа `user` (пользователь/роль) на сервере.
|
|
*
|
|
* Формат значения в task_field_values.value (jsonb):
|
|
* - одиночное: строка `user:<id>` / `role:<id>` или legacy-число (userId);
|
|
* - множественное (form_fields.allow_multiple): массив строк ["user:5", "role:2"].
|
|
*
|
|
* Одиночные значения читаются как массив из одного элемента (обратная совместимость).
|
|
* Аналог клиентского client/src/lib/userFieldValue.ts.
|
|
*/
|
|
|
|
/** Нормализует сырое значение user-поля в массив строк-элементов. */
|
|
export function parseUserFieldEntries(value: unknown): string[] {
|
|
if (value === null || value === undefined || value === '') return [];
|
|
if (Array.isArray(value)) {
|
|
return value
|
|
.map((v) => (v === null || v === undefined ? '' : String(v)))
|
|
.filter((v) => v !== '');
|
|
}
|
|
if (typeof value === 'string' && value.startsWith('[')) {
|
|
try {
|
|
const parsed = JSON.parse(value);
|
|
if (Array.isArray(parsed)) return parseUserFieldEntries(parsed);
|
|
} catch {
|
|
// не JSON — обрабатываем как одиночное значение
|
|
}
|
|
}
|
|
return [String(value)];
|
|
}
|