Corp/PROCUREMENT_IMPLEMENTATION_PLAN.md
Игорь Надеин 3cd161d83c feat: add equipment management with QR codes and transfers
Stage 5 (Inventory) implementation:
- Add EquipmentTransferLog model for tracking equipment movements
- Add EquipmentViewSet actions:
  - transfer: move equipment between departments/users
  - write_off: decommission equipment
  - add_maintenance: record maintenance activities
  - qr_code: generate QR code for equipment
  - transfer_history: view equipment transfer history
- Use existing QRCodeGenerator and InventoryNumberGenerator services
- Fix EquipmentStatus constants usage (IN_USE, RETIRED)
- Add 14 new tests for inventory functionality

Test results: 66/66 PASSED
- 20/20 Model tests
- 12/12 API tests
- 9/9 Workflow tests
- 11/11 Budget tests
- 14/14 Inventory tests (NEW)
2025-12-09 10:42:07 +03:00

62 KiB
Raw Blame History

План Разработки Модуля "Закупки и Инвентарь" (Procurement)

Дата начала: 8 декабря 2025 Статус: 🚀 В разработке Версия: 1.0


📋 Содержание

  1. Обзор Проекта
  2. Архитектурные Решения
  3. Архитектура и Модели
  4. Права Доступа
  5. Workflow Согласования
  6. API Endpoints
  7. UI/UX Компоненты
  8. Этапы Реализации
  9. Технологический Стек
  10. Чеклист Реализации

Архитектурные Решения

🔔 Система Уведомлений: NotificationService вместо Celery

Решение: Использовать встроенную систему уведомлений (notifications.services.NotificationService) вместо внедрения Celery.

Обоснование:

За использование NotificationService:

  1. ✅ Уже существует и протестирована - используется во всех модулях (communications, documents, feed, calendar_app, requests_app)
  2. ✅ Полнофункциональная - поддерживает Web (WebSocket), Email (HTML), Telegram, настройки пользователя
  3. ✅ Простота - не требует дополнительных процессов и сервисов
  4. ✅ Масштаб проекта - 50-200 сотрудников, <10 заявок/день - синхронная отправка приемлема
  5. ✅ Меньше точек отказа - нет дополнительных worker'ов для мониторинга
  6. ✅ Легче debugging - ошибки в том же процессе и логах

Против Celery:

  1. ❌ Усложнение - +2 процесса в production (worker + beat)
  2. ❌ Дублирование - NotificationService уже отправляет email
  3. ❌ Overkill - избыточно для текущего масштаба проекта
  4. ❌ DevOps overhead - настройка supervisor/systemd, мониторинг workers

Производительность:

  • SMTP отправка: ~0.5-2 секунды
  • Согласование заявки: редкое действие (1-5 раз/день на отдел)
  • Задержка 2 секунды приемлема для редких critical операций

Когда пересмотреть:

  • 500 сотрудников и >50 заявок/день

  • Генерация тяжелых отчётов (PDF >10MB)
  • Нестабильный email-провайдер требует retry логики

Периодические задачи: Используется APScheduler (уже в requirements.txt) для:

  • Дайджесты уведомлений
  • Напоминания о просроченных заявках
  • Очистка старых данных

Обзор Проекта

Цель

Создать систему управления внутренними закупками и инвентарем с автоматизированным workflow согласования заявок и контролем бюджетов отделов.

Основные Возможности

Для сотрудников:

  • ✅ Создание заявок на закупку
  • ✅ Отслеживание статуса своих заявок
  • ✅ Просмотр доступного оборудования

Для руководителей отделов:

  • ✅ Согласование/отклонение заявок отдела
  • ✅ Просмотр бюджета отдела
  • ✅ Управление инвентарем отдела

Для финансового отдела:

  • ✅ Контроль бюджетов всех отделов
  • ✅ Финансовое согласование заявок
  • ✅ Формирование отчетов

Для менеджера закупок:

  • ✅ Исполнение одобренных заявок
  • ✅ Работа с поставщиками
  • ✅ Оприходование закупленного оборудования

Для завхоза:

  • ✅ Учет всего оборудования
  • ✅ Назначение ответственных
  • ✅ Контроль технического обслуживания
  • ✅ Списание оборудования

Метрики Успеха

Метрика Целевое Значение
Среднее время согласования < 3 рабочих дня
Процент заявок без доработок > 90%
Точность бюджетирования 95-105%
Удовлетворенность пользователей > 4.0/5.0
Критических багов в production 0

Архитектура и Модели

Диаграмма Связей

┌─────────────────┐       ┌──────────────────┐
│ User            │       │ Department       │
│ (встроенный)    │◄──────┤ (employees)      │
└────────┬────────┘       └────────┬─────────┘
         │                         │
         │ requestor               │ department
         │                         │
         ▼                         ▼
┌─────────────────────────────────────────────┐
│ ProcurementRequest                          │
│ ─────────────────────────────────────────── │
│ - title: str                                │
│ - description: text                         │
│ - status: FSMField (DRAFT→PENDING→APPROVED) │
│ - urgency: choice                           │
│ - estimated_cost: decimal                   │
│ - actual_cost: decimal (nullable)           │
│ - created_at, updated_at, completed_at      │
└────────┬────────────────────┬───────────────┘
         │                    │
         │ request            │ request
         ▼                    ▼
┌──────────────────┐    ┌─────────────────┐
│ ProcurementItem  │    │ Approval        │
│ ──────────────── │    │ ─────────────── │
│ - name           │    │ - approver      │
│ - quantity       │    │ - role          │
│ - unit           │    │ - status        │
│ - unit_price     │    │ - comment       │
│ - supplier_info  │    │ - created_at    │
└────────┬─────────┘    └─────────────────┘
         │
         │ procurement_item (nullable)
         ▼
┌─────────────────────────────────────────────┐
│ Equipment                                   │
│ ─────────────────────────────────────────── │
│ - name: str                                 │
│ - inventory_number: str (unique)            │
│ - serial_number: str                        │
│ - category: FK                              │
│ - status: choice (Available/In_Use/...)     │
│ - department: FK                            │
│ - responsible_person: FK (nullable)         │
│ - purchase_date: date                       │
│ - warranty_until: date (nullable)           │
│ - purchase_cost: decimal                    │
└────────┬────────────────────────────────────┘
         │
         │ equipment
         ▼
┌──────────────────────┐
│ MaintenanceRecord    │
│ ──────────────────── │
│ - date: date         │
│ - type: choice       │
│ - description: text  │
│ - cost: decimal      │
│ - performed_by: FK   │
└──────────────────────┘

┌──────────────────────┐       ┌─────────────────┐
│ EquipmentCategory    │       │ Budget          │
│ ──────────────────── │       │ ─────────────── │
│ - name: str          │       │ - department    │
│ - parent: FK (self)  │       │ - year: int     │
│ - description: text  │       │ - quarter: int  │
└──────────────────────┘       │ - allocated: $  │
                               │ - spent: $      │
                               └─────────────────┘

┌──────────────────────┐
│ Supplier             │
│ ──────────────────── │
│ - name: str          │
│ - contact_person     │
│ - phone, email       │
│ - address: text      │
│ - rating: decimal    │
└──────────────────────┘

Модель 1: ProcurementRequest (Заявка на закупку)

Поля:

title = CharField(max_length=255)              # Название заявки
description = TextField()                      # Описание/обоснование
department = ForeignKey(Department)            # Отдел-заявитель
requestor = ForeignKey(User)                   # Автор заявки
status = FSMField(default='draft')             # Статус (state machine)
urgency = CharField(choices=UrgencyLevel)      # Срочность (low/medium/high/critical)
estimated_cost = DecimalField()                # Предполагаемая стоимость
actual_cost = DecimalField(null=True)          # Фактическая (после закупки)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
completed_at = DateTimeField(null=True)        # Когда завершена

Статусы:

  • DRAFT - Черновик (редактируется)
  • PENDING - На согласовании
  • APPROVED - Одобрено (все согласования пройдены)
  • REJECTED - Отклонено
  • IN_PROGRESS - В работе (закупка выполняется)
  • COMPLETED - Завершено (товар получен)
  • CANCELLED - Отменено заявителем

Методы:

  • submit() - отправить на согласование (DRAFT → PENDING)
  • approve(approver) - согласовать (PENDING → APPROVED если все ОК)
  • reject(approver, reason) - отклонить (PENDING → REJECTED)
  • start_execution() - начать закупку (APPROVED → IN_PROGRESS)
  • complete(actual_cost) - завершить (IN_PROGRESS → COMPLETED)
  • cancel() - отменить (любой → CANCELLED)
  • get_required_approvals() - кто должен согласовать (зависит от суммы)
  • check_budget_available() - есть ли бюджет в отделе

Permissions:

  • procurement.add_procurementrequest - создавать заявки
  • procurement.change_procurementrequest - редактировать свои
  • procurement.view_procurementrequest - просматривать
  • procurement.approve_procurementrequest - согласовывать (custom)
  • procurement.view_all_requests - видеть заявки всех отделов (custom)

Модель 2: ProcurementItem (Позиция заявки)

Поля:

request = ForeignKey(ProcurementRequest, related_name='items')
name = CharField(max_length=255)               # Название товара/услуги
description = TextField(blank=True)            # Подробное описание
quantity = PositiveIntegerField(default=1)     # Количество
unit = CharField(max_length=50)                # Единица измерения (шт, кг, л)
estimated_unit_price = DecimalField()          # Цена за единицу
supplier_info = TextField(blank=True)          # Инфо о поставщике/ссылки
equipment = ForeignKey(Equipment, null=True)   # Связь с оборудованием после закупки
created_at = DateTimeField(auto_now_add=True)

Свойства:

  • total_price - quantity × estimated_unit_price

Модель 3: Approval (Согласование)

Поля:

request = ForeignKey(ProcurementRequest, related_name='approvals')
approver = ForeignKey(User)                    # Кто согласовывает
role = CharField(choices=ApprovalRole)         # Роль согласующего
status = CharField(choices=ApprovalStatus)     # approved/rejected/pending
comment = TextField(blank=True)                # Комментарий
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)

Роли согласующих:

  • DEPARTMENT_HEAD - Руководитель отдела
  • FINANCE_MANAGER - Финансовый менеджер
  • DIRECTOR - Генеральный директор

Статусы:

  • PENDING - Ожидает решения
  • APPROVED - Одобрено
  • REJECTED - Отклонено

Логика согласования:

if estimated_cost < 10_000:
    # Только руководитель отдела
    required = [DEPARTMENT_HEAD]
elif estimated_cost < 100_000:
    # Руководитель + финансовый менеджер
    required = [DEPARTMENT_HEAD, FINANCE_MANAGER]
else:
    # Все три уровня
    required = [DEPARTMENT_HEAD, FINANCE_MANAGER, DIRECTOR]

Модель 4: Equipment (Оборудование)

Поля:

name = CharField(max_length=255)               # Название
inventory_number = CharField(unique=True)      # Инвентарный номер (INV-2024-0001)
serial_number = CharField(blank=True)          # Серийный номер производителя
category = ForeignKey(EquipmentCategory)       # Категория
status = CharField(choices=EquipmentStatus)    # Статус
department = ForeignKey(Department)            # Отдел
responsible_person = ForeignKey(User, null=True) # Ответственный
location = CharField(max_length=255)           # Расположение (кабинет, стеллаж)
purchase_date = DateField()                    # Дата покупки
warranty_until = DateField(null=True)          # Гарантия до
purchase_cost = DecimalField()                 # Стоимость покупки
procurement_item = ForeignKey(ProcurementItem, null=True) # Из какой заявки
notes = TextField(blank=True)                  # Примечания
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)

Статусы:

  • AVAILABLE - Доступно (на складе)
  • IN_USE - В использовании
  • MAINTENANCE - На обслуживании
  • REPAIR - В ремонте
  • RETIRED - Списано
  • LOST - Утеряно

Методы:

  • assign_to(user, location) - назначить ответственного
  • send_to_maintenance(description) - отправить на ТО
  • retire(reason) - списать
  • generate_qr_code() - генерировать QR-код с инв. номером

Модель 5: EquipmentCategory (Категория оборудования)

Поля:

name = CharField(max_length=100, unique=True)
parent = ForeignKey('self', null=True)         # Родительская категория
description = TextField(blank=True)
icon = CharField(max_length=50, blank=True)    # Bootstrap icon класс
created_at = DateTimeField(auto_now_add=True)

Примеры иерархии:

- Компьютерная техника
  ├── Ноутбуки
  ├── Настольные ПК
  ├── Мониторы
  └── Периферия
      ├── Клавиатуры
      ├── Мыши
      └── Веб-камеры
- Офисная мебель
  ├── Столы
  ├── Кресла
  └── Шкафы
- Оргтехника
  ├── Принтеры
  ├── Сканеры
  └── МФУ

Модель 6: MaintenanceRecord (Запись обслуживания)

Поля:

equipment = ForeignKey(Equipment, related_name='maintenance_history')
date = DateField()                             # Дата обслуживания
type = CharField(choices=MaintenanceType)      # Тип обслуживания
description = TextField()                      # Что сделано
cost = DecimalField(default=0)                 # Стоимость (если платно)
performed_by = ForeignKey(User)                # Кто выполнил
next_maintenance_date = DateField(null=True)   # Следующее ТО
created_at = DateTimeField(auto_now_add=True)

Типы обслуживания:

  • INSPECTION - Осмотр/проверка
  • MAINTENANCE - Плановое ТО
  • REPAIR - Ремонт
  • UPGRADE - Модернизация
  • CLEANING - Чистка

Модель 7: Budget (Бюджет отдела)

Поля:

department = ForeignKey(Department)
year = PositiveIntegerField()                  # Год
quarter = PositiveIntegerField(choices=[(1,1),(2,2),(3,3),(4,4)])
allocated_amount = DecimalField()              # Выделено
spent_amount = DecimalField(default=0)         # Потрачено
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)

class Meta:
    unique_together = [('department', 'year', 'quarter')]

Методы:

  • remaining_amount - остаток бюджета
  • utilization_percentage - процент использования
  • can_spend(amount) - можно ли потратить сумму
  • reserve_amount(amount, request) - зарезервировать под заявку
  • commit_spending(amount, request) - зафиксировать расход

Модель 8: Supplier (Поставщик)

Поля:

name = CharField(max_length=255)
contact_person = CharField(max_length=255, blank=True)
phone = CharField(max_length=50, blank=True)
email = EmailField(blank=True)
address = TextField(blank=True)
website = URLField(blank=True)
inn = CharField(max_length=12, blank=True)     # ИНН
rating = DecimalField(default=0)               # Оценка (0-5)
is_active = BooleanField(default=True)
notes = TextField(blank=True)
created_at = DateTimeField(auto_now_add=True)

Права Доступа

Django Model Permissions

Автоматические (создаются Django):

  • procurement.add_procurementrequest
  • procurement.change_procurementrequest
  • procurement.delete_procurementrequest
  • procurement.view_procurementrequest
  • procurement.add_equipment
  • procurement.change_equipment
  • procurement.delete_equipment
  • procurement.view_equipment

Кастомные (добавляем вручную):

class ProcurementRequest(models.Model):
    class Meta:
        permissions = [
            ('approve_procurementrequest', 'Can approve procurement requests'),
            ('view_all_requests', 'Can view all department requests'),
            ('manage_budget', 'Can manage department budgets'),
            ('execute_procurement', 'Can execute approved requests'),
        ]

Department Permissions (добавить в employees.constants.DeptPerm)

class DeptPerm:
    # ... существующие права ...
  
    # Новые права для Procurement
    CREATE_PROCUREMENT = "create_procurement_request"
    APPROVE_PROCUREMENT = "approve_procurement_request"
    MANAGE_EQUIPMENT = "manage_department_equipment"
    VIEW_BUDGET = "view_department_budget"

Матрица Прав Доступа

Действие Сотрудник Рук. Отдела Финансы Закупки Завхоз Admin
Создать заявку ✅ ✅ ✅ ✅ ✅ ✅
Просмотреть свои заявки ✅ ✅ ✅ ✅ ✅ ✅
Просмотреть заявки отдела ❌ ✅ ❌ ❌ ❌ ✅
Просмотреть все заявки ❌ ❌ ✅ ✅ ❌ ✅
Согласовать (1 уровень) ❌ ✅ ❌ ❌ ❌ ✅
Согласовать (финансы) ❌ ❌ ✅ ❌ ❌ ✅
Согласовать (директор) ❌ ❌ ❌ ❌ ❌ ✅
Исполнить заявку ❌ ❌ ❌ ✅ ❌ ✅
Просмотр оборудования ✅ ✅ ✅ ✅ ✅ ✅
Добавить оборудование ❌ ❌ ❌ ✅ ✅ ✅
Управление инвентарем отдела ❌ ✅ ❌ ❌ ✅ ✅
Управление всем инвентарем ❌ ❌ ❌ ❌ ✅ ✅
Списание оборудования ❌ ❌ ❌ ❌ ✅ ✅
Просмотр бюджета отдела ❌ ✅ ✅ ❌ ❌ ✅
Управление бюджетами ❌ ❌ ✅ ❌ ❌ ✅

Permission Classes для API

# procurement/permissions.py

class IsRequestorOrDeptHead(BasePermission):
    """Автор заявки или руководитель его отдела"""
    def has_object_permission(self, request, view, obj):
        if request.user == obj.requestor:
            return True
        return user_is_dept_head(request.user, obj.department)

class CanApproveProcurement(BasePermission):
    """Может согласовывать заявки (зависит от суммы и роли)"""
    def has_object_permission(self, request, view, obj):
        if request.user.is_staff:
            return True
        # Логика проверки роли и суммы
        required_approvals = obj.get_required_approvals()
        # ... проверка текущей роли пользователя
      
class CanManageEquipment(BasePermission):
    """Может управлять оборудованием"""
    def has_permission(self, request, view):
        return (
            request.user.is_staff or
            request.user.has_perm('procurement.manage_equipment')
        )

Workflow Согласования

Схема Переходов Статусов

stateDiagram-v2
    [*] --> DRAFT
    DRAFT --> PENDING: submit()
    DRAFT --> CANCELLED: cancel()
  
    PENDING --> APPROVED: approve()
    PENDING --> REJECTED: reject()
    PENDING --> CANCELLED: cancel()
  
    APPROVED --> IN_PROGRESS: start_execution()
    APPROVED --> CANCELLED: cancel()
  
    IN_PROGRESS --> COMPLETED: complete()
    IN_PROGRESS --> CANCELLED: cancel()
  
    COMPLETED --> [*]
    REJECTED --> [*]
    CANCELLED --> [*]

Правила Согласования по Сумме

Сумма заявки Необходимые согласования Примерный срок
< 10,000₽ Руководитель отдела 1-2 дня
10,000 - 100,000₽ Руководитель отдела + Финансы 2-3 дня
> 100,000₽ Руководитель отдела + Финансы + Директор 3-5 дней

Уведомления

При создании заявки:

  • ✉️ Email руководителю отдела: "Новая заявка требует вашего согласования"

При согласовании:

  • ✉️ Email следующему согласующему (если требуется)
  • ✉️ Email автору заявки: "Ваша заявка согласована [Роль]"

При полном согласовании:

  • ✉️ Email менеджеру закупок: "Заявка одобрена, можно приступать к исполнению"
  • ✉️ Email автору: "Ваша заявка полностью одобрена и передана в работу"

При отклонении:

  • ✉️ Email автору: "Ваша заявка отклонена. Причина: ..."

При завершении:

  • ✉️ Email автору: "Ваша заявка выполнена"
  • ✉️ Email руководителю отдела: "Заявка выполнена, оборудование оприходовано"

Автоматические Проверки

Перед отправкой на согласование (submit):

  • ✅ Есть хотя бы одна позиция в заявке
  • ✅ Указана предполагаемая стоимость
  • ✅ Заполнены обязательные поля

Перед финансовым согласованием:

  • ✅ В бюджете отдела достаточно средств
  • ✅ Если бюджета нет → алерт финансовому директору

Перед завершением:

  • ✅ Указана фактическая стоимость
  • ✅ Все позиции оприходованы (если оборудование)

API Endpoints

Базовый URL

/api/v1/procurement/

Заявки (Requests)

Список и создание

GET /api/v1/procurement/requests/

Query Parameters:

  • status - фильтр по статусу (draft, pending, approved, etc.)
  • department - ID отдела
  • requestor - ID заявителя
  • urgency - уровень срочности
  • created_at__gte - с даты (ISO format)
  • created_at__lte - до даты
  • min_cost - минимальная стоимость
  • max_cost - максимальная стоимость
  • search - полнотекстовый поиск (title, description)
  • ordering - сортировка (created_at, estimated_cost, -created_at)

Response:

{
  "count": 42,
  "next": "http://api/v1/procurement/requests/?page=2",
  "previous": null,
  "results": [
    {
      "id": 1,
      "title": "Ноутбуки для разработчиков",
      "description": "Требуется 3 ноутбука MacBook Pro",
      "department": {
        "id": 5,
        "name": "IT отдел"
      },
      "requestor": {
        "id": 10,
        "full_name": "Иванов Иван Иванович"
      },
      "status": "pending",
      "urgency": "medium",
      "estimated_cost": "750000.00",
      "actual_cost": null,
      "items_count": 3,
      "created_at": "2025-12-01T10:00:00Z",
      "updated_at": "2025-12-03T15:30:00Z",
      "completed_at": null,
      "pending_approvals": [
        {
          "role": "finance_manager",
          "approver": null
        }
      ]
    }
  ]
}
POST /api/v1/procurement/requests/

Request Body:

{
  "title": "Новые мониторы",
  "description": "Необходимо заменить устаревшие мониторы",
  "department": 5,
  "urgency": "medium",
  "items": [
    {
      "name": "Монитор Dell UltraSharp 27\"",
      "description": "4K, IPS, USB-C",
      "quantity": 5,
      "unit": "шт",
      "estimated_unit_price": "45000.00",
      "supplier_info": "https://dell.com/..."
    }
  ]
}

Детали, обновление, удаление

GET /api/v1/procurement/requests/{id}/
PATCH /api/v1/procurement/requests/{id}/
DELETE /api/v1/procurement/requests/{id}/

Actions (Действия над заявкой)

Отправить на согласование:

POST /api/v1/procurement/requests/{id}/submit/

Согласовать:

POST /api/v1/procurement/requests/{id}/approve/
{
  "comment": "Одобрено. Необходимо для работы."
}

Отклонить:

POST /api/v1/procurement/requests/{id}/reject/
{
  "reason": "Недостаточно бюджета в этом квартале",
  "comment": "Предлагаю перенести на следующий квартал"
}

Начать исполнение:

POST /api/v1/procurement/requests/{id}/start_execution/

Завершить:

POST /api/v1/procurement/requests/{id}/complete/
{
  "actual_cost": "740000.00",
  "completion_notes": "Все товары получены и оприходованы"
}

Отменить:

POST /api/v1/procurement/requests/{id}/cancel/
{
  "reason": "Больше не актуально"
}

Добавить позицию:

POST /api/v1/procurement/requests/{id}/add_item/
{
  "name": "Клавиатура механическая",
  "quantity": 3,
  "unit": "шт",
  "estimated_unit_price": "8000.00"
}

Оборудование (Equipment)

Список и создание

GET /api/v1/procurement/equipment/
POST /api/v1/procurement/equipment/

Query Parameters:

  • status - статус оборудования
  • category - ID категории
  • department - ID отдела
  • responsible_person - ID ответственного
  • search - поиск по названию, инв. номеру, серийному номеру
  • warranty_expires_soon - булев, гарантия истекает < 30 дней
  • ordering - сортировка

Response:

{
  "count": 156,
  "results": [
    {
      "id": 1,
      "name": "MacBook Pro M3 16\"",
      "inventory_number": "INV-2024-0142",
      "serial_number": "C02XK0AHJG5H",
      "category": {
        "id": 1,
        "name": "Ноутбуки",
        "parent_name": "Компьютерная техника"
      },
      "status": "in_use",
      "department": {
        "id": 5,
        "name": "IT отдел"
      },
      "responsible_person": {
        "id": 10,
        "full_name": "Иванов Иван"
      },
      "location": "Офис 3.14, Рабочее место №5",
      "purchase_date": "2024-03-15",
      "warranty_until": "2027-03-15",
      "purchase_cost": "250000.00",
      "qr_code_url": "/media/qr_codes/INV-2024-0142.png"
    }
  ]
}

Actions

Назначить ответственного:

POST /api/v1/procurement/equipment/{id}/assign/
{
  "responsible_person": 15,
  "location": "Офис 2.10"
}

Добавить запись обслуживания:

POST /api/v1/procurement/equipment/{id}/maintenance/
{
  "date": "2025-12-08",
  "type": "inspection",
  "description": "Плановая проверка, все в норме",
  "cost": "0.00"
}

Списать:

POST /api/v1/procurement/equipment/{id}/retire/
{
  "reason": "Устарело морально и физически",
  "date": "2025-12-08"
}

История:

GET /api/v1/procurement/equipment/{id}/history/
{
  "equipment": {...},
  "maintenance_records": [...],
  "assignments": [
    {
      "date": "2024-03-15",
      "person": "Иванов И.И.",
      "location": "Офис 3.14"
    }
  ]
}

Бюджеты (Budgets)

GET /api/v1/procurement/budgets/
GET /api/v1/procurement/budgets/my_department/
POST /api/v1/procurement/budgets/
PATCH /api/v1/procurement/budgets/{id}/

Response (my_department):

{
  "department": {
    "id": 5,
    "name": "IT отдел"
  },
  "year": 2025,
  "quarter": 4,
  "allocated_amount": "2000000.00",
  "spent_amount": "845000.00",
  "remaining_amount": "1155000.00",
  "utilization_percentage": 42.25,
  "reserved_amount": "150000.00",
  "pending_requests": [
    {
      "id": 15,
      "title": "Серверное оборудование",
      "estimated_cost": "150000.00",
      "status": "pending"
    }
  ]
}

Статистика (Stats)

GET /api/v1/procurement/stats/overview/
{
  "total_requests": 124,
  "pending_requests": 15,
  "approved_this_month": 8,
  "completed_this_month": 12,
  "total_spent_this_year": "12500000.00",
  "by_status": {
    "draft": 5,
    "pending": 15,
    "approved": 3,
    "in_progress": 8,
    "completed": 89,
    "rejected": 4
  },
  "by_urgency": {
    "low": 30,
    "medium": 70,
    "high": 20,
    "critical": 4
  }
}
GET /api/v1/procurement/stats/by_department/
[
  {
    "department": {"id": 5, "name": "IT отдел"},
    "total_requests": 42,
    "total_spent": "3200000.00",
    "budget_utilization": 85.5
  }
]

UI/UX Компоненты

1. Дашборд (/procurement/)

Элементы:

  • 📊 Карточки со статистикой (всего заявок, ожидают согласования, одобрено, потрачено)
  • 📈 График расходов по месяцам (Chart.js)
  • 🔍 Быстрые фильтры (статус, отдел, период)
  • 📋 Список последних заявок
  • 🎯 Кнопка "Создать заявку" (если есть права)

Макет:

┌─────────────────────────────────────────────────────────┐
│ 📊 Дашборд Закупок                         [+ Создать]  │
├─────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Всего    │ │ Ожидают  │ │ Одобрено │ │ Потрачено   │ │
│ │   24     │ │    8     │ │    5     │ │ 450,000₽    │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
│                                                          │
│ 📈 [График расходов - Chart.js line chart]              │
│                                                          │
│ 🔍 [Все статусы ▼] [Мой отдел ▼] [Этот месяц ▼]        │
│                                                          │
│ 📋 Последние заявки                                     │
│ ┌──────────────────────────────────────────────────────┐│
│ │ #142 Ноутбуки для разработки | 85,000₽ | Ожидает    ││
│ │ [Детали] [Согласовать] [Отклонить]                   ││
│ └──────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘

2. Форма Создания Заявки (/procurement/requests/create/)

Шаги:

  1. Основная информация (название, описание, срочность)
  2. Добавление позиций (таблица с возможностью добавления строк)
  3. Проверка и отправка

JavaScript функционал:

  • Динамическое добавление/удаление позиций
  • Автоматический расчет общей суммы
  • Проверка остатка бюджета (AJAX запрос)
  • Валидация перед отправкой

3. Детальная Страница Заявки (/procurement/requests/{id}/)

Секции:

  • Заголовок с бейджем статуса
  • Основная информация (автор, отдел, даты)
  • Таблица позиций с итоговой суммой
  • Timeline согласований (кто, когда, решение)
  • Кнопки действий (согласовать/отклонить/отменить) - зависит от прав
  • Комментарии/история изменений

4. Каталог Оборудования (/procurement/equipment/)

Элементы:

  • Карточки оборудования с фото (если есть)
  • Фильтры: статус, категория, отдел, ответственный
  • Поиск по названию/инв. номеру
  • Сортировка (дата покупки, стоимость, название)
  • Массовые действия (экспорт, печать QR-кодов)

5. Карточка Оборудования (/procurement/equipment/{id}/)

Секции:

  • QR-код (для сканирования)
  • Основная информация
  • Текущий статус и местоположение
  • Ответственный
  • История обслуживания (таблица)
  • История перемещений
  • Кнопки действий

6. Управление Бюджетом (/procurement/budgets/)

Элементы:

  • Таблица бюджетов по отделам
  • Визуализация использования (progress bars)
  • Алерты при превышении 90%
  • Форма добавления/редактирования бюджета
  • График использования по кварталам

Этапы Реализации

✅ Этап 1: Базовая Структура (1-2 недели)

Цель: Создать приложение и определить все модели без сложной логики

Задачи:

  • Создать Django app procurement
  • Определить все модели в models.py
    • ProcurementRequest
    • ProcurementItem
    • Approval
    • Equipment
    • EquipmentCategory
    • MaintenanceRecord
    • Budget
    • Supplier
  • Создать файл constants.py с choices
  • Создать миграции и применить
  • Настроить admin.py для всех моделей
  • Добавить __str__ методы
  • Написать базовые тесты моделей

Результат: Все модели созданы, можно добавлять данные через админку

🔄 Этап 2: API и Права (1 неделя)

Цель: Создать REST API со всеми CRUD операциями и правами доступа

Задачи:

  • Создать api/v1/procurement/serializers.py
    • ProcurementRequestSerializer (read/write versions)
    • EquipmentSerializer
    • BudgetSerializer
    • Остальные сериализаторы
  • Создать api/v1/procurement/views.py
    • ProcurementRequestViewSet (базовый CRUD)
    • EquipmentViewSet
    • BudgetViewSet
    • Остальные ViewSets
  • Создать api/v1/procurement/permissions.py
    • IsRequestorOrDeptHead
    • CanApproveProcurement
    • CanManageEquipment
  • Добавить департаментные права в employees.constants.DeptPerm
  • Настроить URL routing
  • Написать API тесты (pytest)

Результат: API работает, можно создавать/редактировать через Postman

🔄 Этап 3: Workflow Согласования (1-2 недели)

Цель: Реализовать state machine и автоматическую логику согласования

Задачи:

  • Установить django-fsm
  • Добавить FSM transitions в ProcurementRequest:
    • submit() - отправка на согласование
    • approve() - согласование
    • reject() - отклонение
    • complete() - завершение
    • cancel() - отмена
  • Реализовать get_required_approvals() - логика по сумме
  • Создать actions в ViewSet:
    • /requests/{id}/submit/
    • /requests/{id}/approve/
    • /requests/{id}/reject/
    • /requests/{id}/complete/
    • /requests/{id}/cancel/
  • Добавить проверки budget при согласовании
  • Интеграция с NotificationService:
    • Создание типов уведомлений (procurement_*)
    • Signals для автоматических уведомлений
    • Web + Email + Telegram каналы
  • Написать тесты workflow

Результат: Заявки проходят полный цикл согласования с автоматическими проверками и уведомлениями

🔄 Этап 4: Бюджеты и Контроль (1 неделя)

Цель: Контроль бюджетов и автоматические алерты

Задачи:

  • Реализовать методы модели Budget:
    • remaining_amount
    • utilization_percentage
    • can_spend(amount)
    • reserve_amount(request)
    • commit_spending(request)
  • Добавить проверку бюджета перед финансовым согласованием
  • Создать алерты при превышении 90% бюджета
  • Реализовать quarterly/yearly отчеты
  • Добавить endpoint для статистики по бюджетам
  • Написать тесты для бюджетной логики

Результат: Система контролирует бюджеты и не дает перерасход

🔄 Этап 5: Инвентарь (1 неделя)

Цель: Полноценный учет оборудования с QR-кодами

Задачи:

  • Реализовать генерацию inventory_number (INV-YYYY-NNNN)
  • Добавить методы Equipment:
    • assign_to(user, location)
    • send_to_maintenance()
    • retire(reason)
    • generate_qr_code() - python-barcode/qrcode
  • Создать actions в EquipmentViewSet:
    • /equipment/{id}/assign/
    • /equipment/{id}/maintenance/
    • /equipment/{id}/retire/
    • /equipment/{id}/history/
  • Реализовать иерархию категорий (recursive)
  • Добавить фильтры (warranty_expires_soon, etc.)
  • Написать тесты

Результат: Полный учет оборудования с историей и QR-кодами

🔄 Этап 6: Frontend (2-3 недели)

Цель: Создать все UI компоненты

Подэтап 6.1: Дашборд и Списки

  • Создать templates/procurement/dashboard.html
  • Создать static/js/procurement/dashboard.js (загрузка stats)
  • Интегрировать Chart.js для графиков
  • Создать templates/procurement/request_list.html
  • Создать static/js/procurement/requestList.js (фильтры, пагинация)

Подэтап 6.2: Форма Создания Заявки

  • Создать templates/procurement/request_create.html
  • Создать static/js/procurement/requestForm.js:
    • Динамическое добавление позиций
    • Расчет общей суммы
    • AJAX проверка бюджета
    • Валидация перед отправкой

Подэтап 6.3: Детальная Страница Заявки

  • Создать templates/procurement/request_detail.html
  • Создать static/js/procurement/approvalFlow.js:
    • Кнопки approve/reject с модальными окнами
    • Timeline согласований
    • Real-time обновления (WebSocket)

Подэтап 6.4: Инвентарь

  • Создать templates/procurement/equipment_list.html
  • Создать templates/procurement/equipment_detail.html
  • Создать static/js/procurement/equipmentList.js
  • Добавить QR code scanner (jsQR или quagga.js)

Подэтап 6.5: Мобильная Адаптация

  • Адаптировать все страницы под мобильные
  • Тестировать на разных разрешениях

Результат: Полноценный UI со всеми функциями

🔄 Этап 7: Интеграции (1 неделя)

Цель: Интеграция с другими модулями и внешними системами

Задачи:

  • Email уведомления (django.core.mail):
    • При создании заявки
    • При согласовании/отклонении
    • При завершении
  • WebSocket для real-time обновлений (channels):
    • Обновление статуса заявки
    • Новые уведомления
  • Экспорт отчетов:
    • PDF (reportlab/WeasyPrint)
    • Excel (openpyxl/xlsxwriter)
  • Интеграция с модулем сотрудников:
    • Использование Position для прав
    • Department head автоопределение
  • Интеграция с системой уведомлений (NotificationService):
    • Web уведомления через WebSocket
    • Email уведомления с HTML шаблонами
    • Telegram уведомления через Bot API
    • Настройки пользователя для каналов доставки
  • Периодические задачи (APScheduler):
    • Напоминания о просроченных заявках
    • Дайджесты уведомлений
    • Очистка старых данных

Результат: Полная интеграция со всеми системами

🔄 Этап 8: Тестирование и Документация (1 неделя)

Цель: Достичь высокого покрытия тестами и создать документацию

Задачи:

  • Unit тесты (coverage > 80%):
    • Модели
    • Сериализаторы
    • ViewSets
    • Permissions
    • Workflow
  • Integration тесты:
    • Полный цикл создания и согласования заявки
    • Работа с бюджетами
    • Управление оборудованием
  • E2E тесты (Playwright/Selenium):
    • Создание заявки через UI
    • Согласование заявки
    • Добавление оборудования
  • API документация (drf-spectacular):
    • OpenAPI schema
    • Swagger UI
  • Пользовательская документация:
    • Инструкция для сотрудников
    • Инструкция для руководителей
    • Инструкция для завхоза

Результат: Стабильное приложение с полной документацией


Технологический Стек

Backend

Компонент Технология Версия Назначение
Framework Django 5.2+ Основной фреймворк
API Django REST Framework 3.14+ REST API
Database PostgreSQL 14+ Основная БД
State Machine django-fsm 2.8+ Workflow статусов
Notifications NotificationService - Встроенная система уведомлений
Scheduler APScheduler 3.11+ Периодические задачи
Message Broker Redis 7+ Cache + Channels
WebSocket Django Channels 4+ Real-time updates
Export reportlab 4+ PDF генерация
Barcodes python-barcode 0.15+ Штрихкоды
QR Codes qrcode 7.4+ QR-коды

Frontend

Компонент Технология Версия Назначение
Framework Vanilla JS ES6+ Логика
CSS Framework Bootstrap 5.3+ Стили
Charts Chart.js 4+ Графики
QR Scanner jsQR 1.4+ Сканирование QR
Icons Bootstrap Icons 1.11+ Иконки
HTTP Client Fetch API - AJAX запросы

DevOps

Компонент Технология Назначение
Testing pytest + pytest-django Unit/Integration тесты
E2E Testing Playwright End-to-end тесты
Coverage coverage.py Покрытие кода
Code Quality flake8, black Линтинг, форматирование
CI/CD GitHub Actions Автоматизация

Чеклист Реализации

Подготовка

  • Создать ветку feature/procurement-module
  • Настроить окружение (установить зависимости)
  • Создать документ с требованиями

Этап 1: Модели ✅ ЗАВЕРШЕН

  • Создать app: python manage.py startapp procurement
  • Добавить в INSTALLED_APPS
  • Создать procurement/constants.py
  • Создать модели:
    • ProcurementRequest (с FSM)
    • ProcurementItem
    • Approval
    • Equipment
    • EquipmentCategory
    • MaintenanceRecord
    • Budget
    • Supplier
  • Создать миграции: python manage.py makemigrations
  • Применить: python manage.py migrate
  • Настроить admin.py для всех моделей
  • Создать тестовые данные через админку
  • Написать тесты моделей (20/20 PASSED)
  • Коммит: "feat: add procurement app with basic models"

Этап 2: API ✅ ЗАВЕРШЕН (95%)

  • Создать procurement/ структуру (без api/v1)
  • Написать сериализаторы (9 сериализаторов)
  • Создать ViewSets (7 ViewSets)
  • Настроить URL routing
  • Создать permission classes (9 классов)
  • Добавить департаментные права
  • Написать API тесты (12/12 PASSED)
  • Протестировать endpoints (50+ endpoints)
  • Коммит: "feat: add procurement API endpoints"
  • ⚠️ Исправить: баг items_count (property vs annotate) - ИСПРАВЛЕН

Этап 3: Workflow ✅ ЗАВЕРШЕН (100%)

  • Установить django-fsm: pip install django-fsm==3.0.1
  • Добавить FSM в ProcurementRequest
  • Реализовать transitions (submit, approve, reject)
  • Создать actions в API (@action decorators)
  • Добавить проверки бюджета (check_budget_available)
  • Настроить signals (post_save для уведомлений)
  • Интеграция с NotificationService (вместо Celery)
  • Написать workflow тесты (9/9 PASSED)
  • Коммит: "feat: add procurement approval workflow"
  • Исправить queryset для approvers (41/41 тестов PASSED)

Этап 4: Бюджеты ✅ ЗАВЕРШЕН (100%)

  • Реализовать методы Budget (reserved_amount, available_amount)
  • Добавить проверки (is_staff/headed_departments)
  • Создать алерты (_check_budget_alert при < 20% и < 10%)
  • Добавить endpoints статистики (/stats/overview/, /stats/by-department/)
  • Добавить my-department endpoint
  • Написать тесты (11/11 PASSED)
  • Коммит: "feat: add budget control and alerts"

Этап 5: Инвентарь

  • Установить barcode библиотеки (qrcode[pil])
  • Реализовать генерацию номеров (InventoryNumberGenerator)
  • Добавить EquipmentTransferLog модель
  • Создать actions в API (transfer, write_off, add_maintenance, qr_code, transfer_history)
  • Реализовать QR-коды (QRCodeGenerator)
  • Написать тесты (14/14 PASSED)
  • Коммит: "feat: add equipment management with QR codes"

Этап 6: Frontend

  • Создать templates структуру
  • Создать static файлы структуру
  • Дашборд
  • Форма создания
  • Детальная страница
  • Каталог оборудования
  • Мобильная адаптация
  • Коммит: "feat: add procurement UI components"

Этап 7: Интеграции

  • Интеграция с NotificationService (система уведомлений)
  • WebSocket обновления для real-time статусов
  • Экспорт отчетов (PDF/Excel)
  • APScheduler для периодических задач
  • Коммит: "feat: add integrations and notifications"

Этап 8: Тесты и Документация

  • Довести coverage до 80%+
  • E2E тесты
  • API документация
  • Пользовательская документация
  • Коммит: "docs: add comprehensive documentation"

Финализация

  • Code review
  • Исправить замечания
  • Merge в master
  • Deploy на production
  • Провести обучение пользователей

Риски и Митигация

Риск Вероятность Влияние Митигация
Сложность workflow Высокая Высокое Начать с простого (2 уровня), расширять постепенно
Производительность при большой нагрузке Средняя Среднее Индексы БД, кэширование, пагинация
Конфликты бюджетов Средняя Высокое Pessimistic locking, транзакции
Миграция данных Низкая Среднее Хорошо протестировать скрипты миграции
Недостаточное покрытие тестами Средняя Высокое CI/CD с проверкой coverage, code review
Сопротивление пользователей Средняя Среднее Обучение, простой UI, постепенное внедрение

🎯 Результаты Тестирования

Последнее тестирование: 9 декабря 2025 Статус: ✅ ВСЕ ТЕСТЫ ПРОХОДЯТ

Итоговая статистика:

✅ 66/66 PASSED (100%)
  ├─ 20/20 Model tests ✅
  ├─ 12/12 API tests ✅
  ├─ 9/9 Workflow tests ✅
  ├─ 11/11 Budget tests ✅
  └─ 14/14 Inventory tests ✅ (NEW)

Основные исправления:

  1. ✅ Исправлен led_departments → headed_departments
  2. ✅ Удален конфликт items_count (annotate vs @property)
  3. ✅ Создан settings_test.py с InMemoryChannelLayer
  4. ✅ Добавлены поля в сериализаторы (purchase_cost, requestor)
  5. ✅ Исправлен Finance Manager lookup (groups + permissions)
  6. ✅ Обновлены фикстуры бюджета (2025 Q4)
  7. ✅ Расширен queryset для approvers - ключевое исправление!

Техническая инфраструктура:

  • Git cleanup: localcerts_backup.zip удален из истории (279 коммитов)
  • Test settings: InMemoryChannelLayer вместо Redis
  • Notifications: NotificationService интегрирован (вместо Celery)

Следующие Шаги

Завершено:

  1. ✅ Создать данную документацию
  2. ✅ Создать ветку feature/procurement-module
  3. ✅ Завершить Этап 1: Базовая Структура (100%)
  4. ✅ Завершить Этап 2: API и Права (100%)
  5. ✅ Завершить Этап 3: Workflow (100%)
  6. ✅ Завершить Этап 4: Бюджеты (100%)

Следующий шаг:

  • ⏭️ Начать Этап 5: Инвентарь - управление оборудованием с QR-кодами

Через 1 месяц:

  • Завершить Этапы 5-6 (Инвентарь, Frontend)
  • Иметь полнофункциональный модуль с UI

Через 2 месяца:

  • Завершить все 8 этапов
  • Готовый модуль к production deploy

Контакты и Вопросы

Ответственный за разработку: [Ваше имя] Дата последнего обновления: 9 декабря 2025 Версия документа: 1.3

Вопросы и предложения:

  • Создавайте issues в репозитории
  • Обсуждайте в команде

Статус: 🎉 Этапы 1-5 завершены! Готовы к Этапу 6 (Frontend)!