Рейтинг сотрудников: history-number поле = последняя оценка, среднее за месяц — в поле options.avgFieldCode
- setUserProfileFieldValueFromAutomation: для history-number значением поля становится переданное число (последняя оценка), а не среднее - recomputeUserFieldMonthlyAverage: среднее за текущий месяц пишется в поле, указанное в options.avgFieldCode history-number поля (если не задано — в само поле, обратная совместимость) - Автоматизация «Оценка сотрудникам» (форма 11): сумма трёх числовых полей «Оценка 1/2/3» вместо одного select-поля - Документация sandbox обновлена во всех четырёх местах (UI, MCP, Obsidian, AGENTS.md)
This commit is contained in:
@@ -151,7 +151,8 @@ export class UserProfileStorage extends UsersStorage {
|
||||
throw new Error(`Поле профиля "${fieldCodeOrId}" не найдено`);
|
||||
}
|
||||
|
||||
// Для history-number: переданное число идёт в историю, значением поля становится среднее за текущий месяц
|
||||
// Для history-number: переданное число идёт в историю и становится значением поля (последняя оценка);
|
||||
// среднее за месяц пересчитывается отдельно — в поле, указанном в options.avgFieldCode (или в само поле)
|
||||
let storedValue = value;
|
||||
if (field.type === 'history-number') {
|
||||
const num = typeof value === 'number' ? value : parseFloat(String(value).replace(',', '.').trim());
|
||||
@@ -165,7 +166,8 @@ export class UserProfileStorage extends UsersStorage {
|
||||
value: String(num),
|
||||
taskId: taskId ?? null,
|
||||
});
|
||||
storedValue = await this.recomputeUserFieldMonthlyAverage(userId, field.id, organizationId);
|
||||
storedValue = num;
|
||||
await this.recomputeUserFieldMonthlyAverage(userId, field.id, organizationId);
|
||||
}
|
||||
|
||||
const existing = await this.getUserProfileFieldValue(userId, field.id);
|
||||
@@ -190,10 +192,20 @@ export class UserProfileStorage extends UsersStorage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Пересчитывает значение history-number поля как среднее за текущий календарный месяц.
|
||||
* Если в месяце нет записей — поле очищается (null). Возвращает новое значение.
|
||||
* Пересчитывает среднее за текущий календарный месяц по истории history-number поля.
|
||||
* Среднее пишется в поле, указанное в options.avgFieldCode самого history-number поля
|
||||
* (например «Оценка» → «Средняя оценка»); если avgFieldCode не задан — в само history-number поле.
|
||||
* Если в месяце нет записей — целевое поле очищается (null). Возвращает новое среднее.
|
||||
*/
|
||||
async recomputeUserFieldMonthlyAverage(userId: number, fieldId: number, organizationId: number) {
|
||||
const [field] = await db.select().from(userProfileFields).where(and(
|
||||
eq(userProfileFields.id, fieldId),
|
||||
eq(userProfileFields.organizationId, organizationId),
|
||||
));
|
||||
if (!field) {
|
||||
throw new Error(`Поле профиля ${fieldId} не найдено`);
|
||||
}
|
||||
|
||||
const [row] = await db
|
||||
.select({ avg: sql<number | null>`ROUND(AVG(${userFieldHistory.value})::numeric, 2)::float8` })
|
||||
.from(userFieldHistory)
|
||||
@@ -205,24 +217,35 @@ export class UserProfileStorage extends UsersStorage {
|
||||
));
|
||||
const avg = row?.avg != null ? Number(row.avg) : null;
|
||||
|
||||
const existing = await this.getUserProfileFieldValue(userId, fieldId);
|
||||
// Целевое поле для среднего: из options.avgFieldCode, иначе — само history-number поле
|
||||
let targetFieldId = fieldId;
|
||||
const avgFieldCode = (field.options as { avgFieldCode?: string } | null)?.avgFieldCode;
|
||||
if (avgFieldCode) {
|
||||
const target = await this.getUserProfileFieldByCode(avgFieldCode, organizationId);
|
||||
if (!target) {
|
||||
throw new Error(`Поле для среднего "${avgFieldCode}" (options.avgFieldCode поля "${field.name}") не найдено`);
|
||||
}
|
||||
targetFieldId = target.id;
|
||||
}
|
||||
|
||||
const existing = await this.getUserProfileFieldValue(userId, targetFieldId);
|
||||
if (existing) {
|
||||
if (existing.value !== avg) {
|
||||
await this.updateUserProfileFieldValue(userId, fieldId, { value: avg as UserProfileFieldValue['value'] });
|
||||
await this.updateUserProfileFieldValue(userId, targetFieldId, { value: avg as UserProfileFieldValue['value'] });
|
||||
await this.createUserProfileAuditLog({
|
||||
userId,
|
||||
organizationId,
|
||||
action: 'field.changed',
|
||||
fieldId,
|
||||
fieldId: targetFieldId,
|
||||
oldValue: existing.value ?? null,
|
||||
newValue: avg != null ? String(avg) : null,
|
||||
changedBy: null,
|
||||
changedByName: 'Автоматизация',
|
||||
metadata: { source: 'automation', reason: 'monthly-average-recalc' },
|
||||
metadata: { source: 'automation', reason: 'monthly-average-recalc', historyFieldId: fieldId },
|
||||
});
|
||||
}
|
||||
} else if (avg != null) {
|
||||
await this.createUserProfileFieldValue({ userId, fieldId, value: avg as InsertUserProfileFieldValue['value'] });
|
||||
await this.createUserProfileFieldValue({ userId, fieldId: targetFieldId, value: avg as InsertUserProfileFieldValue['value'] });
|
||||
}
|
||||
return avg;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user