Модель данных
Оценка сотрудников: таблицы, связи и флаги (простыми словами)
Этот документ объясняет, как устроен процесс оценки в базе данных: какие есть таблицы, кто на кого ссылается, какие флаги в какой момент ставятся и как из всего этого делаются выборки. Цель — чтобы даже джун смог разобраться без чтения кода.
Если коротко: сотрудник выбирает коллег («друзяк»), которые будут его оценивать → руководитель согласовывает список → друзяки ставят оценки. Каждый из трёх этапов оставляет свой след в БД и меняет статусы.
1. Действующие лица
Один и тот же человек может одновременно играть несколько ролей:
| Роль | Кто это | Простыми словами |
|---|---|---|
| Аппликант | Тот, кого оценивают | «Меня оценивают» |
| Друзяка / Ассессор / Reviewer | Тот, кто оценивает | «Я оцениваю коллегу» |
| Руководитель | Согласовывает список друзяк | Линейный или функциональный |
| Подписчик (subscriber) | Учётка человека в чат-боте | Через него шлём сообщения/теги |
Важно: «друзяка», «ассессор» и «reviewer» — это одно и то же. В базе таблица
называется reviewers, а в коде — модель Assessor. Не пугайтесь разнобоя.
2. Таблицы и связи (карта)
erDiagram
employees ||--o{ subscribers : "uid = uid (учётки в ботах)"
employees ||--o| leaders : "employee_id (этот человек — руководитель)"
employees ||--o| employees_evaluation : "employee_id (его оценивают)"
employees_evaluation ||--o{ reviewers : "employee_evaluation_id"
employees ||--o{ reviewers : "employee_id (он оценивает)"
subscribers ||--o{ subscribers_tags : ""
tags ||--o{ subscribers_tags : ""
employees {
bigint id PK
uuid uid "связь с subscribers"
string full_name
json data "профиль (руководители, компания и т.д.)"
tinyint fired "NULL = работает"
bigint leader_id "один руководитель (скалярно)"
}
subscribers {
bigint id PK
uuid uid
bigint bot_id "какой бот"
}
leaders {
bigint id PK
bigint employee_id "кто это в employees"
string name "ФИО руководителя"
bool lineal "линейный?"
bool functional "функциональный?"
}
employees_evaluation {
bigint id PK
bigint employee_id "КОГО оценивают"
tinyint status "SelectStatus: 0/1/2"
bigint leader_id "руководитель на момент оценки"
bool leader_changed "руководитель правил список?"
}
reviewers {
bigint id PK
bigint employee_evaluation_id "чью оценку (кого оценивают)"
bigint employee_id "КТО оценивает (друзяка)"
tinyint status "AssessorStatus"
tinyint grade_status "AssessorStatus"
tinyint is_finished "Finish: 0/1"
}
Что хранит каждая таблица
-
employees— все сотрудники (справочник из импорта).uid— общий ключ для связи с ботом.fired = NULLзначит человек работает.leader_id— ссылка на одного руководителя (историческое поле, см. раздел про блокировку). -
subscribers— учётки людей в чат-ботах. У одного сотрудника (uid) может быть несколько подписчиков — по одному на каждый бот. Нужный бот выбираем поbot_id(grade-бот берётся из настройкиGRADE_BOT_ID). -
leaders— справочник руководителей с флагамиlineal/functional. Заполняется при импорте. Нужен, чтобы понять, кому отправлять список на согласование, и чтобы заблокировать выбор руководителя в друзяки. -
employees_evaluation— «лист оценки» одного оцениваемого. Одна строка = один аппликант. Здесь живёт статус этапов «выбор» и «согласование». -
reviewers— строки «друзяка X оценивает аппликанта Y». Здесь живёт статус этапа «оценка» (is_finished). -
tags/subscribers_tags— теги подписчиков (например, тег «должник»).
3. Ключевая связь: кто кого оценивает
Это самое важное и самое путающее место. Разберём на примере.
Аппликант Иванов выбрал троих друзяк: Петрова, Сидорова, Коваленко.
flowchart LR
EE["employees_evaluation<br/>employee_id = Иванов<br/>(его оценивают)"]
R1["reviewers<br/>employee_id = Петров"]
R2["reviewers<br/>employee_id = Сидоров"]
R3["reviewers<br/>employee_id = Коваленко"]
EE -->|employee_evaluation_id| R1
EE -->|employee_evaluation_id| R2
EE -->|employee_evaluation_id| R3
Читается так:
-
employees_evaluation.employee_id= КОГО оценивают (аппликант Иванов). -
reviewers.employee_id= КТО оценивает (друзяка Петров/Сидоров/Коваленко). -
reviewers.employee_evaluation_idсвязывает друзяку с листом оценки аппликанта.
Мнемоника: в
reviewersполеemployee_id— это всегда оценивающий (друзяка). А кого он оценивает — узнаём, перейдя поemployee_evaluation_idвemployees_evaluation.employee_id.
Отсюда две частые выборки:
- «Кого оцениваю я» →
reviewers WHERE employee_id = я→ по каждой строкеemployee_evaluation_id→ аппликант. - «Кто оценивает меня» →
employees_evaluation WHERE employee_id = я→ егоreviewers.
4. Флаги и их значения
employees_evaluation.status — этапы «выбор» и «согласование» (enum SelectStatus)
| Значение | Код | Что означает |
|---|---|---|
0 |
Created |
Аппликант ещё выбирает друзяк (в процессе) |
1 |
Approval |
Список отправлен руководителю, ждёт согласования |
2 |
Confirmed |
Руководитель согласовал список |
Плюс отдельный флаг leader_changed (bool): true, если руководитель не
просто согласовал, а сам отредактировал список друзяк.
reviewers.is_finished — этап «оценка» (enum Finish)
| Значение | Код | Что означает |
|---|---|---|
0 |
Pending |
Друзяка ещё не завершил оценку этого аппликанта |
1 |
Finished |
Оценка завершена (отправлена в K2) |
reviewers.status и reviewers.grade_status (enum AssessorStatus)
| Значение | Код | Что означает |
|---|---|---|
0 |
External |
Внешний тип связи |
1 |
Personal |
«Внутренний клиент» / личный выбор |
Это техническая пометка направления связи (кто кого выбрал). Для отчётов «внутренних клиентов» фильтруют по
grade_status = Personal. Джуну достаточно помнить: главный флаг завершённости оценки — этоis_finished, а не эти два.
Флаги в leaders
| Поле | Что означает |
|---|---|
lineal = 1 |
Человек является линейным руководителем |
functional = 1 |
Человек является функциональным руководителем |
5. Когда какой флаг ставится (жизненный цикл)
flowchart TD
A["Импорт сотрудников<br/>(cron 7:00)"] --> A1["employees заполнены<br/>leaders заполнены (lineal/functional)<br/>employees.leader_id проставлен"]
A1 --> B["Аппликант выбирает друзяк"]
B --> B1["employees_evaluation: status = Created (0)<br/>reviewers: is_finished = Pending (0)"]
B1 --> C["Аппликант отправил список руководителю"]
C --> C1["employees_evaluation: status = Approval (1)"]
C1 --> D{"Руководитель"}
D -->|Подтвердил| E["status = Confirmed (2)"]
D -->|Отредактировал и подтвердил| E2["status = Confirmed (2)<br/>leader_changed = true"]
E --> F["Друзяки ставят оценки"]
E2 --> F
F --> F1["Оценка ушла в K2<br/>reviewers: is_finished = Finished (1)"]
Пошагово:
-
Импорт (cron
import:employees, 7:00). Обновляютсяemployees. Создаётся справочникleaders(флагlineal/functionalставится при создании записи). Затем каждому сотруднику проставляетсяemployees.leader_id(правило: сотрудники двух компаний — Кернел Трейд31454383и Кернел Діджитал44880630— получают функционального руководителя, остальные — линейного). -
Выбор друзяк. Появляется строка
employees_evaluationсоstatus = Createdи строкиreviewersсis_finished = Pending. -
Отправка на согласование.
statusменяетсяCreated → Approval. -
Согласование руководителем.
statusменяетсяApproval → Confirmed. Если руководитель правил список — дополнительноleader_changed = true. -
Завершение оценки. Когда друзяка ответил на все вопросы и оценка ушла в K2,
его строка
reviewersполучаетis_finished = Finished.
6. Готовые выборки (шпаргалка)
Все выборки по этапам — прямо по статусам выше.
-- Этап 1: сколько людей ещё НЕ сделали выбор
SELECT COUNT(*) FROM employees_evaluation WHERE status = 0; -- Created
-- Этап 2: сколько списков руководитель ещё НЕ согласовал
SELECT COUNT(*) FROM employees_evaluation WHERE status <> 2; -- не Confirmed
-- из них ждут руководителя:
SELECT COUNT(*) FROM employees_evaluation WHERE status = 1; -- Approval
-- Этап 3: оценка
SELECT COUNT(*) FROM reviewers WHERE is_finished = 0; -- оценок не завершено
SELECT COUNT(DISTINCT employee_id) FROM reviewers WHERE is_finished = 0; -- людей не завершили
SELECT COUNT(*) FROM reviewers WHERE is_finished = 1; -- оценок завершено
Должники (кому вешаем тег и шлём напоминания) — это друзяки с незавершённой оценкой, у которых есть учётка в grade-боте:
SELECT DISTINCT s.id
FROM reviewers r
JOIN employees e ON e.id = r.employee_id -- друзяка (оценивающий)
JOIN subscribers s ON s.uid = e.uid -- его учётка в боте
WHERE r.is_finished = 0
AND s.bot_id = :grade_bot_id;
7. Блокировка выбора («недоступен»)
Когда аппликант ищет друзяк, часть найденных людей помечается как недоступные
(disabled = true) с пояснением. Причины (по коду
GradeService::checkProfileForAssessors):
| Причина (сообщение) | Когда |
|---|---|
Denied by rules |
Запрещено правилами поиска (search_rules) |
Your lead |
Кандидат — руководитель аппликанта (линейный ИЛИ функциональный) |
List is full |
У кандидата или у аппликанта достигнут лимит |
Bot is not connected... |
У кандидата нет учётки в боте |
Already in list |
Кандидат уже выбран |
This is your subordinate |
Кандидат — подчинённый аппликанта |
Про Your lead (это чинили в задаче): раньше блокировался только один
руководитель (из скалярного employees.leader_id). Теперь блокируются оба —
и линейный, и функциональный. Как определяются: из профиля аппликанта берутся
оба имени (linejnyjRukovoditel, funkcionalnyjRukovoditel), сопоставляются с
таблицей leaders по имени (с фолбэком на employees.full_name) и добавляется
исторический leader_id. Совпал с любым — «недоступен».
flowchart TD
P["Профиль аппликанта"] --> L1["linejnyjRukovoditel (имя)"]
P --> L2["funkcionalnyjRukovoditel (имя)"]
L1 --> M["Ищем в leaders по name → employee_id<br/>(фолбэк: employees.full_name)"]
L2 --> M
P --> L3["employees.leader_id (историческое)"]
M --> S["Множество id руководителей"]
L3 --> S
S --> C{"Кандидат в этом множестве?"}
C -->|Да| B["Недоступен — 'Your lead'"]
C -->|Нет| OK["Можно выбрать"]
8. Где что в коде
| Что | Файл |
|---|---|
| Логика выбора/блокировки друзяк | app/Domains/Grade/GradeService.php |
| Enum статусов | app/Domains/Grade/Enums/{SelectStatus,Finish,AssessorStatus}.php |
| Модели | app/Domains/Grade/Entities/{Employee,EmployeeEvaluation,Assessor}.php, Modules/Kernel/Entities/Leader.php |
Заполнение leaders / leader_id |
Modules/Kernel/Jobs/{StoreImportedData,UpdateLeaderJob,AttachLeaderToEmployee,CheckLeaders}.php |
| Завершение оценки (is_finished) | Modules/Kernel/Jobs/SendIndicatorsJob.php |
| Дашборд, автоматизация, процедуры | см. docs/grade-procedures.md |
Нет комментариев