User Service — BR 1.4.1

Контракты

  • API — 29 новых эндпоинтов (24 public + 4 internal + 1 CSV export)
  • Data Model — 7 новых таблиц
  • Events — 2 новых события

Фаза 1: Инфраструктура

Миграции

  • Таблица employee_legal_details (1:1 с employees)
  • Таблица shift_templates
  • Таблица shift_schedules
  • Таблица shift_records
  • Таблица shift_corrections
  • Таблица salary_formulas
  • Таблица payroll_records

JPA Entities + Repositories

  • EmployeeLegalDetails entity + repository
  • ShiftTemplate entity + repository
  • ShiftSchedule entity + repository
  • ShiftRecord entity + repository
  • ShiftCorrection entity + repository
  • SalaryFormula entity + repository
  • PayrollRecord entity + repository

Фаза 2: Юридические детали + Шаблоны + Расписание

Юридические детали

  • GET /employees/{id}/legal-details — получить юр. детали
  • PUT /employees/{id}/legal-details — upsert юр. деталей
  • Ролевой доступ: franchise — все, franchisee — свои, manager/cashier — 403
  • Валидация ИНН (12 цифр), СНИЛС формат

Шаблоны смен

  • GET /shift-templates?store_id= — список шаблонов ТТ
  • POST /shift-templates — создание (валидация: макс 4 на store_id)
  • PATCH /shift-templates/{id} — обновление
  • DELETE /shift-templates/{id} — удаление
  • Ролевой доступ: franchise/franchisee (свои) — CRUD, manager — read, cashier — 403

Расписание (плановые смены)

  • GET /schedules?store_id=&date_from=&date_to= — расписание за период
  • POST /schedules — batch создание плановых смен (валидация: только будущие дни)
  • PATCH /schedules/{id} — обновление (только будущие)
  • DELETE /schedules/{id} — удаление (только будущие)
  • Уникальность: один сотрудник — одна смена на (employee_id, store_id, date)

Фаза 3: Фактические смены + Корректировки + Дашборд

Фактические смены (public)

  • GET /shift-records?store_id=&employee_id=&date_from=&date_to= — записи за период
  • POST /shift-records — ручной ввод (source=manual)
  • PATCH /shift-records/{id} — обновление ручной записи
  • Логика расчёта break_duration_minutes = break_end − break_start
  • Логика расчёта status — сопоставление с shift_schedules (±30 мин правило)
  • Бизнес-день: date = дата clock_in (ночные смены)

Корректировки

  • POST /shift-records/{id}/corrections — добавить корректировку
  • DELETE /shift-records/{id}/corrections/{correction_id} — сбросить корректировку
  • Валидация: нельзя корректировать смены со статусом missed
  • Валидация: comment обязателен

Дашборд активности

  • GET /dashboard/activity?store_id=&date= — агрегация за дату
  • Агрегация: join shift_records + (будущее: order data)
  • Ролевой доступ: franchise — все, franchisee — свои, manager — своя ТТ

Фаза 4: Зарплата

Формулы

  • GET /salary-formulas?store_id= — список формул
  • POST /salary-formulas — создание (по роли или индивидуальная)
  • PATCH /salary-formulas/{id} — обновление
  • DELETE /salary-formulas/{id} — удаление (только индивидуальные)
  • Логика иерархии: individual → role+store → нет формулы
  • Валидация по formula_type: hourly → hourly_rate обязателен, fixed → monthly_salary, mixed → все три

Ведомости

  • GET /payroll?store_id=&period= — список ведомостей за период
  • POST /payroll/calculate — расчёт ведомости (создание/пересчёт)
  • POST /payroll/{id}/confirm — подтверждение (calculated → confirmed)
  • POST /payroll/{id}/mark-paid — отметка о выплате (confirmed → paid)
  • GET /payroll/{id}/export — CSV экспорт (UTF-8 BOM)
  • Логика расчёта: net_hours × hourly_rate OR monthly_salary OR mixed
  • formula_snapshot — сохранение снимка формулы при расчёте
  • Статус-машина: calculated → confirmed → paid (только вперёд)

Фаза 5: Internal API + Scheduled Jobs

Internal API для POS

  • POST /internal/shift-records/clock-in — POS: начало смены
  • POST /internal/shift-records/clock-out — POS: конец смены
  • POST /internal/shift-records/break-start — POS: начало перерыва
  • POST /internal/shift-records/break-end — POS: конец перерыва
  • Auth: Service token (X-Service-Token)

Scheduled Jobs

  • Автозакрытие смен: job каждые 15 мин, закрывает shift_records где clock_out IS NULL AND clock_in < NOW() - 24h
  • Ставит auto_closed = true, clock_out = clock_in + 24h
  • Публикует событие user.shift.auto-closed (Kafka не подключён — заглушка в логах)
  • Статус missed: вычисляется динамически при запросе расписания, job не нужен

Kafka Events

  • user.shift.auto-closed — при автозакрытии (отложено до подключения Kafka)
  • user.payroll.confirmed — при подтверждении ведомости (отложено до подключения Kafka)