32 KiB
User Reader — API и доступ
Справочник по HTTP API микросервиса user-reader: адреса, аутентификация, эндпоинты, параметры, ответы и типовые сценарии вызова.
Общая документация по развёртыванию и модели данных: DEVELOPERS.md.
Интерактивная схема: /docs (Swagger), /redoc.
Содержание
- Базовый адрес
- Доступ и заголовки
- Матрица доступа к эндпоинтам
- Общие правила работы с API
- Служебные эндпоинты
- Сотрудники
- Трудозатраты (чтение)
- Табель (чтение)
- Табель (запись)
- Ошибки и коды ответа
- Типовые сценарии
- Примеры запросов
Базовый адрес
| Окружение | Base URL |
|---|---|
| Docker Compose (хост) | http://localhost:8090 |
| Docker Compose (другой контейнер) | http://user-reader:8090 |
| Прод / свой сервер | http://<хост>:<порт> |
Все пути API начинаются с /api/.
Кодировка: UTF-8, формат тела: JSON (кроме GET без тела).
Доступ и заголовки
Уровень 1 — service-to-service (API-ключ)
На сервере задаётся переменная окружения USER_READER_API_KEY.
| Состояние ключа | Поведение |
|---|---|
| Непустая строка | Все «защищённые» эндпоинты требуют ключ |
| Пустая строка | Проверка отключена (только для локальной отладки) |
Передать ключ одним из способов:
X-Api-Key: <ваш-ключ>
или
Authorization: Bearer <ваш-ключ>
При отсутствии или неверном ключе: 401 Unauthorized.
{
"detail": "Требуется ключ: заголовок X-Api-Key или Authorization: Bearer <ключ>"
}
Локальный ключ в docker-compose.yml (сменить на проде): local-dev-key-change-in-prod.
Уровень 2 — идентичность сотрудника (табель)
Для эндпоинтов табеля (чтение календаря, запись часов и отсутствий) дополнительно нужен заголовок:
X-Acting-Emp-Id: <целое число > 0>
Это id сотрудника в tMerakomisEmp, от имени которого выполняется операция (аналог cookie fg_emp_id в PHP).
Опционально в query или JSON-теле:
| Поле | Смысл |
|---|---|
emp_id |
За кого операция; если не передан — используется X-Acting-Emp-Id |
Права (админ, РП, делегат…) проверяются на сервере. Перед записью можно вызвать GET /api/labor/permissions.
Сводка заголовков
| Заголовок | Когда обязателен |
|---|---|
X-Api-Key или Authorization: Bearer |
Все /api/*, кроме /api/health, если ключ задан на сервере |
X-Acting-Emp-Id |
Эндпоинты табеля с пометкой Acting |
Content-Type: application/json |
PUT с телом |
CORS
Сервис отдаёт Access-Control-Allow-Origin: *. Браузерные клиенты с другого домена могут вызывать API при наличии ключа.
Матрица доступа к эндпоинтам
| Метод | Путь | API-ключ | X-Acting-Emp-Id |
|---|---|---|---|
| GET | /api/health |
— | — |
| GET | /api/meta |
✓ | — |
| GET | /api/employees |
✓ | — |
| GET | /api/employees/delta |
✓ | — |
| GET | /api/labor/meta |
✓ | — |
| GET | /api/projects |
✓ | — |
| GET | /api/sections |
✓ | — |
| GET | /api/project-members |
✓ | — |
| GET | /api/project-sections |
✓ | — |
| GET | /api/member-roles |
✓ | — |
| GET | /api/time-entries |
✓ | — |
| GET | /api/labor-summary |
✓ | — |
| GET | /api/work-report |
✓ | — |
| GET | /api/project-report |
✓ | — |
| GET | /api/calendar-days |
✓ | — |
| GET | /api/absence-types |
✓ | — |
| GET | /api/labor/permissions |
✓ | ✓ |
| GET | /api/time-summary |
✓ | ✓ |
| GET | /api/time-calendar |
✓ | ✓ |
| PUT | /api/time-entries |
✓ | ✓ |
| PUT | /api/project-members |
✓ | ✓ |
| PUT | /api/absences |
✓ | ✓ |
| PUT | /api/absences/range |
✓ | ✓ |
✓ — обязателен, если на сервере включён соответствующий режим (ключ / табель).
Пакетные запросы и fetch_all
| Метод | Путь | API-ключ | Описание |
|---|---|---|---|
| POST | /api/batch |
✓ | До 25 подзапросов GET в одном HTTP-вызове |
Параметр fetch_all=true (query) на списках и отчётах возвращает все строки за один запрос (лимит 50 000). Поддерживается на:
/api/employees, /api/project-members, /api/time-entries, /api/work-report, /api/labor-summary, /api/project-report.
Общие правила работы с API
Даты
- В query и JSON:
YYYY-MM-DD(например2026-06-10). - В ответах поля
date,date_from,date_to— в том же формате. - Поля
datetimeиз БД — ISO-строка с пробелом:2026-06-10 14:30:00.
Пагинация
Большинство списков поддерживают limit и offset.
fetch_all=true — вернуть все строки одним ответом (без цикла offset). Максимум 50 000 строк; при превышении — 400 too_many_rows. Рекомендуется для /api/work-report за месяц/квартал.
GET /api/work-report?date_from=2026-01-01&date_to=2026-03-31&fetch_all=true
Классическая пагинация:
offset = 0
loop:
GET …?limit=L&offset=offset
обработать items
offset += count
пока offset < total
В ответе обычно:
| Поле | Смысл |
|---|---|
total |
Всего записей (до пагинации) |
limit, offset |
Эхо запроса |
count |
Размер массива items в текущем ответе (не у всех эндпоинтов) |
items |
Массив строк |
Часы и переработки
В агрегатах (work-report, labor-summary, project-report):
| Поле | Смысл |
|---|---|
hours |
Рабочие часы (is_over = 0) |
over1 |
Переработка в рабочий день |
over2 |
Переработка в выходной/праздник или день отсутствия |
over |
over1 + over2 |
total |
hours + over |
В сырых записях /api/time-entries: поле is_over — 0 или 1.
Стадия проекта
| Поле | Где | Смысл |
|---|---|---|
step |
/api/projects |
Числовой id стадии |
step_name |
Эндпоинты с project_name |
Краткое название: ПД, РД, К… |
status |
/api/projects, эндпоинты с project_name |
Числовой код статуса (0…3) |
status_name |
Эндпоинты с project_name |
Подпись: «В работе», «Завершён»… |
archive |
/api/projects, эндпоинты с project_name |
0 — активный, 1 — в архиве |
archive_date |
/api/projects, эндпоинты с project_name |
Дата архивации (YYYY-MM-DD) или null |
Коды status (порт eStatus.php):
status |
status_name |
|---|---|
0 |
Не определён |
1 |
В работе |
2 |
Завершён |
3 |
Пауза |
Секретные данные
Пароли и поля с password / secret / суффиксом _pass не возвращаются в API сотрудников.
1. Служебные эндпоинты
GET /api/health
Проверка живости и подключения к MySQL. Ключ не нужен.
Ответ 200:
{
"ok": true,
"db": true,
"database": "j7508239_tracker"
}
При ошибке БД: ok: false, db: false, поле error со строкой.
Использование: health-check в оркестраторе, перед массовой синхронизацией.
GET /api/meta
Метаданные таблицы сотрудников для клиента.
Query: нет.
Ответ 200 (основные поля):
| Поле | Описание |
|---|---|
schema_mode |
merakomis_emp или generic_user_table |
physical_table |
Имя таблицы в MySQL |
delta_field |
Имя поля в items для инкремента (updated и т.п.) |
selected_fields |
Список полей в /api/employees |
where_removed |
Колонка фильтра удалённых (если есть) |
Использование: один раз при старте интеграции — понять схему и поле для delta.
GET /api/labor/meta
Имена физических таблиц Merakomis (проекты, время, отсутствия).
Ответ 200:
{
"database": "…",
"tables": {
"project": "tmerakomisproject",
"section": "…",
"team_member": "…",
"time": "…",
"day": "…",
"time_absence": "…"
},
"overtime": { "is_over_field": "is_over", "over1": "…", "over2": "…" },
"section_source": "tMerakomisTeamMember.section через project.team"
}
2. Сотрудники
GET /api/employees
Постраничный список сотрудников.
| Query | По умолчанию | Ограничения |
|---|---|---|
limit |
100 |
1…500 |
offset |
0 |
≥ 0 |
Ответ 200:
| Поле | Описание |
|---|---|
total |
Всего записей (с учётом фильтра removed = 0 в Merakomis) |
items[] |
Объекты сотрудника |
Дополнительные поля в items (Merakomis):
| Поле | Описание |
|---|---|
id, name, login, email, … |
См. /api/meta → selected_fields |
department |
Коды отдела через запятую: ОВ, АР… |
department_codes |
Массив кодов |
departments |
{ department_id, name, short, code } |
staffing |
Сырое значение из БД: JSON-массив id должностей |
staffing_ids |
Массив id из tMerakomisDStaffing |
staffing_names |
Названия должностей: Архитектор, ГИП, ГАП… |
staffing_title |
Должности одной строкой через запятую |
staffings |
{ id, name, text } — text часто аббревиатура (ГИП, ГАП) |
Поле roles в Merakomis — это не должность, а служебный фильтр «подразделение» в UI портала; оргструктура в API — через departments / department.
GET /api/employees/delta
Инкрементальная выгрузка: только изменённые записи.
| Query | По умолчанию | Описание |
|---|---|---|
since_updated |
0 |
Unix timestamp (секунды); выбираются строки с updated > since_updated |
limit |
500 |
1…500 |
Ответ 200:
| Поле | Описание |
|---|---|
since_updated |
Эхо запроса |
max_updated |
Максимум updated среди возвращённых строк — передать в следующий запрос |
delta_column |
Имя поля времени в items |
count |
Число элементов |
items |
Те же поля, что в /api/employees |
Алгоритм синхронизации:
since = сохранённый_у_себя_маркер # 0 при первом запуске
повторять:
r = GET /api/employees/delta?since_updated={since}&limit=500
upsert каждого r.items по id
since = r.max_updated
пока r.count == 500
сохранить since
3. Трудозатраты (чтение)
GET /api/projects
Справочник проектов.
| Query | По умолчанию | Описание |
|---|---|---|
limit |
100 |
1…500 |
offset |
0 |
|
include_removed |
false |
Включать удалённые |
include_archive |
true |
Не скрывать архивные |
Поля items: id, code, name, director, step, status, status_name, team, archive, archive_date, removed, date, date_end.
GET /api/sections
Справочник разделов проекта.
| Query | Описание |
|---|---|
limit |
1…1000 (по умолчанию 500) |
offset |
Пагинация |
step |
Фильтр по полю step раздела |
Поля items: id, name, parent, step.
GET /api/project-members
Участники команд: сотрудник ↔ проект ↔ раздел.
| Query | По умолчанию | Описание |
|---|---|---|
project_id |
— | Фильтр |
emp_id |
— | Фильтр |
active_only |
true |
Только активные в команде |
limit |
100 |
1…500 |
offset |
0 |
Поля items:
| Поле | Описание |
|---|---|
member_id |
id TeamMember |
emp_id, emp_name |
Сотрудник |
project_id, project_code, project_name |
Проект |
step_name |
Стадия (ПД, РД…) |
status, status_name |
Статус проекта |
archive, archive_date |
Архив и дата архивации |
section_id, section_name |
Раздел в команде |
role, active |
Роль и активность |
Использование: список проектов сотрудника для UI, проверка членства перед записью часов.
GET /api/project-sections
Разделы, настроенные для проекта (tMerakomisProjectSection).
| Query | Обязательно |
|---|---|
project_id |
да |
Ответ 200: project_id, total, items[] → section_id, section_name.
Ошибки: 404 — проект не найден.
GET /api/member-roles
Справочник ролей в команде (ГИП, ГАП, Сотрудник…).
| Query | Описание |
|---|---|
role |
Включить архивную роль (для формы редактирования) |
Ответ: total, items[] → id, title.
GET /api/time-entries
Сырые строки табеля без агрегации.
| Query | По умолчанию | Описание |
|---|---|---|
date_from, date_to |
— | Период YYYY-MM-DD |
emp_id, project_id |
— | Фильтры |
is_over |
— | 0 / 1 |
limit |
500 |
1…2000 |
offset |
0 |
Поля items: id, emp, project, date, duration, is_over.
Ошибка 400, если date_from > date_to.
GET /api/work-report ⭐ рекомендуемый для интеграции
Агрегат за период: сотрудник × проект + отдел + раздел + стадия + часы.
| Query | Обязательно | По умолчанию | Описание |
|---|---|---|---|
date_from |
да | — | Начало периода |
date_to |
да | — | Конец периода (включительно) |
emp_id |
— | Фильтр | |
project_id |
— | Фильтр | |
limit |
500 |
1…2000 | |
offset |
0 |
Пагинация |
Поля items:
| Поле | Тип | Описание |
|---|---|---|
id |
int | emp_id |
employee |
string | ФИО |
department |
string | null | Орготдел |
staffing_title |
string | null | Должность (Архитектор, ГИП…) |
section |
string | null | Раздел в проекте |
project_code |
string | |
step_name |
string | Стадия |
status |
int | null | Код статуса проекта |
status_name |
string | null | Подпись статуса |
archive |
int | 0 / 1 |
archive_date |
string | null | Дата архивации |
project_name |
string | |
hours, over, over1, over2, total |
number | Часы |
Особенности:
- Нет delta — каждый запрос пересчитывает весь период.
- Раздел берётся из членства в команде, не из строки времени.
- При большом объёме обходите
offsetдоtotal.
GET /api/project-report
Агрегат: проект × отдел × раздел (сумма по всем сотрудникам).
| Query | Обязательно | Описание |
|---|---|---|
date_from, date_to |
да | Период |
project_id |
Фильтр | |
limit, offset |
1…2000 |
Поля items: id (project_id), project_code, step_name, status, status_name, archive, archive_date, project_name, department, section, hours, over, over1, over2, total.
GET /api/labor-summary
Тот же расчёт, что work-report, но с внутренними id Merakomis. Для внешних систем предпочтительнее /api/work-report.
| Query | Обязательно |
|---|---|
date_from, date_to |
да |
emp_id, project_id, limit, offset |
опционально |
Поля items: emp_id, emp_name, department, staffing_title, project_id, project_code, step_name, status, status_name, archive, archive_date, project_name, section_id, section_name, role, hours, over, over1, over2, total.
4. Табель (чтение)
Все эндпоинты ниже требуют X-Acting-Emp-Id и API-ключ.
GET /api/calendar-days
Производственный календарь (рабочие/выходные дни).
| Query | Обязательно |
|---|---|
date_from, date_to |
да |
Ответ: date_from, date_to, items[] — дни с типом и подписью.
GET /api/absence-types
Справочник типов отсутствий.
Ответ: items[] — всегда есть элемент { "id": 0, "title": "—", "code": "" } для снятия отсутствия.
GET /api/labor/permissions
Проверка прав до записи.
| Query | Обязательно | Описание |
|---|---|---|
target_emp_id |
да | За кого проверяем |
project_id |
Для can_write_time |
Ответ 200:
| Поле | Описание |
|---|---|
can_read |
Чтение табеля |
can_write_time |
Запись часов в project_id (false, если project_id не передан) |
can_write_absence |
Запись отсутствий |
can_write_member |
Запись состава проекта (PUT /api/project-members) |
is_admin |
Администратор |
is_delegate_writer |
Делегат на запись чужого табеля |
Дополнительные query: member_id (опционально) — для уточнения can_write_member при редактировании.
GET /api/time-calendar
Данные календаря табеля (формат, близкий к PHP getTimeTable).
| Query | По умолчанию | Описание |
|---|---|---|
emp_id |
acting | Чей табель |
project_id |
0 |
0 — сводный; иначе — по проекту |
Ответ (ключевые поля): can_edit, dates, days, absence, absence_options, project, total, acting_emp_id, target_emp_id.
Ошибки: 403 нет прав, 404 проект не найден.
GET /api/time-summary
Сводка часов за текущий месяц и текущий год для сотрудника.
| Query | По умолчанию |
|---|---|
emp_id |
acting |
Ответ: объекты month, year с блоками project, total, absence.
5. Табель (запись)
Требуются API-ключ и X-Acting-Emp-Id.
Тело запросов: Content-Type: application/json.
Детали бизнес-логики и портирования PHP: change-proposal-labor-api-write.md.
PUT /api/time-entries
Запись или обновление часов за один день по одному проекту.
Тело:
{
"emp_id": 46,
"project_id": 86,
"date": "2026-06-10",
"time": 8,
"over": 0
}
| Поле | Обязательно | Описание |
|---|---|---|
project_id |
да | id проекта |
date |
да | YYYY-MM-DD |
time |
да | Часы, 0…24 |
over |
0 — рабочие (по умолчанию), 1 — переработка |
|
emp_id |
Целевой сотрудник; иначе = acting |
time: 0 — удаляет запись за этот день и тип (over).
Ответ 200:
{
"ok": true,
"acting_emp_id": 46,
"target_emp_id": 46,
"id": 12345,
"duration": 8,
"is_over": 0,
"info": { "hours": 8, "over": 0 },
"limits": { },
"cache_rows_deleted": 0
}
Ошибки: 403 нет прав, 404 сотрудник не найден, 400 невалидная дата.
PUT /api/project-members
Добавление или обновление участника команды проекта (upsert по паре project_id + emp_id).
Тело:
{
"project_id": 86,
"emp_id": 46,
"section_id": 12,
"role": 22,
"active": true,
"text": ""
}
| Поле | Обязательно | Описание |
|---|---|---|
project_id |
да | id проекта |
emp_id |
да | id сотрудника |
section_id |
да | id из GET /api/project-sections |
role |
да | id из GET /api/member-roles |
active |
По умолчанию true; false — деактивация в команде |
|
text |
Комментарий |
Ответ 200: ok: true и поля как у элемента GET /api/project-members (member_id, emp_name, project_code, step_name, status, status_name, archive, archive_date, section_name, role, active).
Ошибки: 403 нет прав (Rules::isRwMember), 404 проект/сотрудник, 400 невалидный раздел, роль или архивный сотрудник.
Превью UI: GET /project-member
PUT /api/absences
Установка или снятие отсутствия по списку дат.
Тело:
{
"emp_id": 46,
"type": 3,
"dates": ["2026-06-10", "2026-06-11"]
}
| Поле | Описание |
|---|---|
type |
id из /api/absence-types; 0 — снять отсутствие |
dates |
Массив дат, минимум одна |
При type != 0 рабочие часы (is_over = 0) за эти дни удаляются.
Ответ 200: ok, results[] с date, absence_type_id, cache_rows_deleted.
PUT /api/absences/range
Массовая операция за диапазон дат.
Тело:
{
"emp_id": 46,
"absence_id": 3,
"begin": "2026-06-10",
"end": "2026-06-20"
}
| Поле | Описание |
|---|---|
absence_id |
0 — очистить диапазон (удалить отсутствия и рабочие часы) |
begin, end |
Границы включительно; end >= begin |
Ответ 200: ok, dates_count, m: "Успешно сохранено".
6. Пакетные запросы (POST /api/batch)
Один HTTP-вызов — несколько GET к /api/*. Снижает накладные расходы при выгрузке нескольких периодов или эндпоинтов.
Заголовки: X-Api-Key (обязателен, если настроен); X-Acting-Emp-Id пробрасывается в подзапросы.
Тело:
{
"requests": [
{
"id": "q1",
"method": "GET",
"path": "/api/work-report?date_from=2026-01-01&date_to=2026-01-31&fetch_all=true"
},
{
"id": "q2",
"method": "GET",
"path": "/api/work-report?date_from=2026-02-01&date_to=2026-02-28&fetch_all=true"
}
]
}
| Поле | Описание |
|---|---|
requests |
1…25 подзапросов |
id |
Произвольный идентификатор для сопоставления ответов |
method |
Только GET |
path |
Относительный путь /api/... с query |
Ответ 200:
{
"ok": true,
"count": 2,
"results": [
{ "id": "q1", "status": 200, "ok": true, "path": "…", "body": { } },
{ "id": "q2", "status": 200, "ok": true, "path": "…", "body": { } }
]
}
ok на верхнем уровне — true, только если все подзапросы успешны.
Ограничения: рекурсивный /api/batch запрещён; абсолютные URL запрещены.
Ошибки и коды ответа
| HTTP | Когда | Формат detail |
|---|---|---|
400 |
Невалидные параметры, даты, нет X-Acting-Emp-Id, delta без колонки времени |
Строка или { "code", "message" } |
401 |
Нет/неверный API-ключ | Строка |
403 |
Нет прав на табель | { "code": "forbidden", "message": "…" } |
404 |
Сотрудник/проект не найден | { "code": "…_not_found", "message": "…" } |
500 |
Ошибка БД, отсутствует таблица Merakomis | Строка или { "code": "db_error", "message" } |
Рекомендации клиенту:
- На
401— проверить ключ и переменнуюUSER_READER_API_KEYна сервере. - На
403— вызвать/api/labor/permissionsили проверитьX-Acting-Emp-Id. - На
500— повтор с backoff; приDEBUG=1на сервере в теле может быть traceback (не для прода).
Типовые сценарии
Проверка перед интеграцией
1. GET /api/health
2. GET /api/meta (с ключом)
3. GET /api/labor/meta (с ключом)
Ежедневная синхронизация сотрудников
GET /api/employees/delta?since_updated={stored}&limit=500
→ upsert по id
→ stored = max_updated
Отчёт за месяц в BI/другой сервис
GET /api/work-report?date_from=2026-05-01&date_to=2026-05-31&fetch_all=true
Или несколько месяцев одним HTTP:
POST /api/batch
→ work-report за январь, февраль, … с fetch_all=true
Запись часов из внешнего UI
1. GET /api/project-members?emp_id={acting}&active_only=true
2. GET /api/labor/permissions?target_emp_id={target}&project_id={pid}
→ если can_write_time
3. PUT /api/time-entries { project_id, date, time, over }
Сводка по проектам для руководства
GET /api/project-report?date_from=…&date_to=…&limit=2000
Управление составом проекта
1. GET /api/project-sections?project_id={pid}
2. GET /api/member-roles
3. GET /api/labor/permissions?target_emp_id={acting}&project_id={pid}
→ если can_write_member
4. PUT /api/project-members { project_id, emp_id, section_id, role, active }
Примеры запросов
Замените HOST и ключ.
# Без ключа
curl -s "http://HOST:8090/api/health"
# С ключом
export KEY="local-dev-key-change-in-prod"
export H="http://HOST:8090"
curl -s -H "X-Api-Key: $KEY" "$H/api/meta"
curl -s -H "X-Api-Key: $KEY" \
"$H/api/work-report?date_from=2026-05-01&date_to=2026-05-31&limit=100"
curl -s -H "X-Api-Key: $KEY" \
"$H/api/project-members?emp_id=46&active_only=true"
# Табель: чтение
curl -s -H "X-Api-Key: $KEY" -H "X-Acting-Emp-Id: 46" \
"$H/api/labor/permissions?target_emp_id=46&project_id=86"
# Табель: запись
curl -s -X PUT "$H/api/time-entries" \
-H "X-Api-Key: $KEY" \
-H "X-Acting-Emp-Id: 46" \
-H "Content-Type: application/json" \
-d '{"project_id":86,"date":"2026-06-10","time":8,"over":0}'
# Состав проекта: справочники и запись
curl -s -H "X-Api-Key: $KEY" "$H/api/project-sections?project_id=86"
curl -s -H "X-Api-Key: $KEY" "$H/api/member-roles"
curl -s -X PUT "$H/api/project-members" \
-H "X-Api-Key: $KEY" \
-H "X-Acting-Emp-Id: 1" \
-H "Content-Type: application/json" \
-d '{"project_id":86,"emp_id":46,"section_id":12,"role":22,"active":true}'
Пагинация work-report (Python)
import httpx
def fetch_all_work_report(base: str, key: str, date_from: str, date_to: str) -> list:
headers = {"X-Api-Key": key}
items, offset, limit = [], 0, 2000
while True:
r = httpx.get(
f"{base}/api/work-report",
headers=headers,
params={
"date_from": date_from,
"date_to": date_to,
"limit": limit,
"offset": offset,
},
timeout=120.0,
)
r.raise_for_status()
data = r.json()
items.extend(data["items"])
offset += data["count"]
if offset >= data["total"]:
break
return items
Связанные документы
| Документ | Содержание |
|---|---|
| DEVELOPERS.md | Развёртывание, модель данных, сборка Docker |
| change-proposal-labor-api-write.md | Детали write API и PHP-аналоги |
| services/user-reader/README.md | Быстрый старт контейнера |