Skip to content

19. Анализ плана закупок — сервисный слой ProcurementPlanAnalysisService

Date: 2026-02-08

Status

Accepted

Context

Расчёт analize включает несколько запросов к БД (позиции, офферы, supply, даты поставки, контрагенты). Размещение всей логики в сериализаторе нарушает SRP и усложняет тестирование.

Decision

Ввести отдельный сервис ProcurementPlanAnalysisService в app.document.service.procurement_plan_analysis с методами:

  • get_positions_fulfillment(plan_document) — выполнение по позициям (cap 100%)
  • get_plan_fulfillment_summary(plan_document, positions_fulfillment) — сводка
  • get_savings_analysis(plan_document) — экономия (2 supply + план vs 3 we_buy)
  • get_metrics(plan_document) — бюджет, просрочки, концентрация, конверсия офферов
  • get_recommendations(metrics, fulfillment_summary, savings_analysis) — рекомендации по порогам
  • get_full_analize(plan_document) — объединённая структура для API

Сериализатор только проверяет type.key == 'procurement_plan' и вызывает service.get_full_analize(obj); иначе возвращает None.

Обоснование

  1. Single Responsibility — сериализатор отвечает за формат ответа, сервис — за бизнес-логику анализа.
  2. Тестируемость — сервис покрывается unit-тестами без поднятия DRF.
  3. Feature-Based / Screaming Architecture — сервис явно принадлежит домену «анализ плана закупок».
  4. Переиспользование — те же методы можно вызывать из отчётов или других API.

Consequences

  • (+) Чистое разделение слоёв, проще поддерживать и расширять.
  • (+) Тесты в app.document.tests.test_procurement_plan_analysis проверяют cap 100% и структуру analize.
  • (-) Дополнительный модуль и зависимость сериализатора от сервиса.

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