Поля профиля с options.readOnly: запрет редактирования «Оценки» и «Средней оценки»

- UI: read-only поля отображаются текстом (у history-number остаётся кнопка графика),
  исключены из сохранения; кнопка «Сохранить» скрыта, если все поля вкладки read-only
- Сервер: POST/PATCH field-values профиля отклоняют запись в поле с options.readOnly (400);
  автоматизации пишут через storage напрямую и не затрагиваются
- Данные: options.readOnly=true у полей «Оценка» и «Средняя оценка»
This commit is contained in:
2026-09-06 15:08:23 +03:00
parent b070f862c5
commit ddc2bd9491
2 changed files with 52 additions and 17 deletions

View File

@@ -6,6 +6,16 @@ import { isValidFieldType } from "@shared/field-types";
import { canManageUser } from "../utils/user-access";
export function registerUserProfileRoutes(router: Router): void {
// Поле профиля с options.readOnly менять через API нельзя — только из автоматизаций (storage напрямую)
async function assertProfileFieldEditable(fieldId: number, organizationId: number): Promise<string | null> {
const field = await storage.getUserProfileFieldById(fieldId, organizationId);
if (!field) return `Поле профиля ${fieldId} не найдено`;
if ((field.options as { readOnly?: boolean } | null)?.readOnly) {
return `Поле «${field.name}» доступно только для чтения`;
}
return null;
}
// === USER PROFILE TABS ===
router.get('/api/user-profile/tabs',
authenticateToken,
@@ -278,6 +288,10 @@ export function registerUserProfileRoutes(router: Router): void {
const changedBy = req.user!.id;
const createdValues = [];
for (const fv of fieldValues) {
const readOnlyError = await assertProfileFieldEditable(fv.fieldId, req.organizationId!);
if (readOnlyError) {
return res.status(400).json({ success: false, error: readOnlyError });
}
const existing = await storage.getUserProfileFieldValue(userId, fv.fieldId);
if (fv.value === null || fv.value === undefined) {
if (existing) {
@@ -327,6 +341,10 @@ export function registerUserProfileRoutes(router: Router): void {
const changedBy = req.user!.id;
const { value } = req.body;
const readOnlyError = await assertProfileFieldEditable(fieldId, req.organizationId!);
if (readOnlyError) {
return res.status(400).json({ success: false, error: readOnlyError });
}
const existing = await storage.getUserProfileFieldValue(userId, fieldId);
if (value === null || value === undefined) {
if (existing) {