Skip to content

4. Сервисный слой и адаптер производственного календаря (Adapter, фасады, кэш)

Date: 2026-02-03

Status

Accepted

Context

Модели (Salary, ActualWorkShedule) не должны выполнять HTTP-запросы и парсить ответы API. Нужен слой, изолирующий внешний API и предоставляющий данные календаря и расчёта стоимости дня/часа.

Decision

  1. Adapter — клиент к isdayoff.ru в app/manufacture/service/production_calendar.py:

    • get_work_days_and_hours(year, month) -> tuple[int, int] — рабочие дни и рабочие часы в месяце (по символам 0, 2, 4; часы: 8 для 0/4, 7 для 2).
    • is_working_day(year, month, day) -> bool — является ли дата рабочим днём (False для кода 1). Реализация через тот же ответ за месяц (символ в позиции day−1).
    • Кэширование ответа за месяц в Django cache (ключ вида production_calendar:ru:{year}:{month}, TTL не менее 1 дня).
  2. Сервис расчёта — функция/класс: по monthly_salary, year, month и зависимости «календарь» вычисляет cost_per_day и cost_per_hour (оклад / рабочие дни, оклад / рабочие часы). Защита от деления на ноль и от отсутствия оклада.

  3. Фасады для вызова из кода (в т.ч. из модели/расчёта смен):

    • get_cost_per_hour_for_month(salary, year, month) — возвращает стоимость часа за месяц (или None/0 при ошибке или type≠4).
    • is_working_day(year, month, day) — доступен через адаптер/сервис. Модели вызывают фасады, а не HTTP.

Обоснование

  1. Разделение ответственности — модель не знает об HTTP; тесты могут подменять фасад/адаптер через mock.patch.
  2. Кэширование — один запрос к API на месяц, повторные расчёты используют кэш.
  3. Единая точка входа — фасады упрощают вызов из ActualWorkShedule и сериализатора.

Consequences

  • (+) Модели остаются «чистыми», тестируемость высокая.
  • (+) Меньше нагрузки на isdayoff.ru за счёт кэша.
  • (-) Необходимость обработки ошибок API и граничных случаев (нет даты, пустой оклад) в сервисе/фасаде.

Источник: backend/narmak_v2/app/manufacture/ADR.md (ADR-003)