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.
Обоснование
- Single Responsibility — сериализатор отвечает за формат ответа, сервис — за бизнес-логику анализа.
- Тестируемость — сервис покрывается unit-тестами без поднятия DRF.
- Feature-Based / Screaming Architecture — сервис явно принадлежит домену «анализ плана закупок».
- Переиспользование — те же методы можно вызывать из отчётов или других API.
Consequences
- (+) Чистое разделение слоёв, проще поддерживать и расширять.
- (+) Тесты в
app.document.tests.test_procurement_plan_analysisпроверяют cap 100% и структуру analize. - (-) Дополнительный модуль и зависимость сериализатора от сервиса.
Источник: backend/narmak_v2/app/document/ADR.md (ADR-004)