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

1569 lines
62 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План Разработки Модуля "Закупки и Инвентарь" (Procurement)
**Дата начала:** 8 декабря 2025
**Статус:** 🚀 В разработке
**Версия:** 1.0
---
## 📋 Содержание
1. [Обзор Проекта](#обзор-проекта)
2. [Архитектурные Решения](#архитектурные-решения)
3. [Архитектура и Модели](#архитектура-и-модели)
4. [Права Доступа](#права-доступа)
5. [Workflow Согласования](#workflow-согласования)
6. [API Endpoints](#api-endpoints)
7. [UI/UX Компоненты](#uiux-компоненты)
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 (Заявка на закупку)
**Поля:**
```python
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 (Позиция заявки)
**Поля:**
```python
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 (Согласование)
**Поля:**
```python
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` - Отклонено
**Логика согласования:**
```python
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 (Оборудование)
**Поля:**
```python
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 (Категория оборудования)
**Поля:**
```python
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 (Запись обслуживания)
**Поля:**
```python
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 (Бюджет отдела)
**Поля:**
```python
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 (Поставщик)
**Поля:**
```python
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`
**Кастомные (добавляем вручную):**
```python
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)
```python
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
```python
# 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 Согласования
### Схема Переходов Статусов
```mermaid
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)
#### Список и создание
```http
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:**
```json
{
"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
}
]
}
]
}
```
```http
POST /api/v1/procurement/requests/
```
**Request Body:**
```json
{
"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/..."
}
]
}
```
#### Детали, обновление, удаление
```http
GET /api/v1/procurement/requests/{id}/
PATCH /api/v1/procurement/requests/{id}/
DELETE /api/v1/procurement/requests/{id}/
```
#### Actions (Действия над заявкой)
**Отправить на согласование:**
```http
POST /api/v1/procurement/requests/{id}/submit/
```
**Согласовать:**
```http
POST /api/v1/procurement/requests/{id}/approve/
```
```json
{
"comment": "Одобрено. Необходимо для работы."
}
```
**Отклонить:**
```http
POST /api/v1/procurement/requests/{id}/reject/
```
```json
{
"reason": "Недостаточно бюджета в этом квартале",
"comment": "Предлагаю перенести на следующий квартал"
}
```
**Начать исполнение:**
```http
POST /api/v1/procurement/requests/{id}/start_execution/
```
**Завершить:**
```http
POST /api/v1/procurement/requests/{id}/complete/
```
```json
{
"actual_cost": "740000.00",
"completion_notes": "Все товары получены и оприходованы"
}
```
**Отменить:**
```http
POST /api/v1/procurement/requests/{id}/cancel/
```
```json
{
"reason": "Больше не актуально"
}
```
**Добавить позицию:**
```http
POST /api/v1/procurement/requests/{id}/add_item/
```
```json
{
"name": "Клавиатура механическая",
"quantity": 3,
"unit": "шт",
"estimated_unit_price": "8000.00"
}
```
### Оборудование (Equipment)
#### Список и создание
```http
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:**
```json
{
"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
**Назначить ответственного:**
```http
POST /api/v1/procurement/equipment/{id}/assign/
```
```json
{
"responsible_person": 15,
"location": "Офис 2.10"
}
```
**Добавить запись обслуживания:**
```http
POST /api/v1/procurement/equipment/{id}/maintenance/
```
```json
{
"date": "2025-12-08",
"type": "inspection",
"description": "Плановая проверка, все в норме",
"cost": "0.00"
}
```
**Списать:**
```http
POST /api/v1/procurement/equipment/{id}/retire/
```
```json
{
"reason": "Устарело морально и физически",
"date": "2025-12-08"
}
```
**История:**
```http
GET /api/v1/procurement/equipment/{id}/history/
```
```json
{
"equipment": {...},
"maintenance_records": [...],
"assignments": [
{
"date": "2024-03-15",
"person": "Иванов И.И.",
"location": "Офис 3.14"
}
]
}
```
### Бюджеты (Budgets)
```http
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):**
```json
{
"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)
```http
GET /api/v1/procurement/stats/overview/
```
```json
{
"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
}
}
```
```http
GET /api/v1/procurement/stats/by_department/
```
```json
[
{
"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 недели)
**Цель:** Создать приложение и определить все модели без сложной логики
**Задачи:**
- [X] Создать 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 при согласовании
- [X] Интеграция с NotificationService:
- [X] Создание типов уведомлений (procurement_*)
- [X] Signals для автоматических уведомлений
- [X] 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 автоопределение
- [X] Интеграция с системой уведомлений (NotificationService):
- [X] Web уведомления через WebSocket
- [X] Email уведомления с HTML шаблонами
- [X] Telegram уведомления через Bot API
- [X] Настройки пользователя для каналов доставки
- [ ] Периодические задачи (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 | Автоматизация |
---
## Чеклист Реализации
### Подготовка
- [X] Создать ветку `feature/procurement-module`
- [X] Настроить окружение (установить зависимости)
- [X] Создать документ с требованиями
### Этап 1: Модели ✅ ЗАВЕРШЕН
- [X] Создать app: `python manage.py startapp procurement`
- [X] Добавить в `INSTALLED_APPS`
- [X] Создать `procurement/constants.py`
- [X] Создать модели:
- [X] ProcurementRequest (с FSM)
- [X] ProcurementItem
- [X] Approval
- [X] Equipment
- [X] EquipmentCategory
- [X] MaintenanceRecord
- [X] Budget
- [X] Supplier
- [X] Создать миграции: `python manage.py makemigrations`
- [X] Применить: `python manage.py migrate`
- [X] Настроить admin.py для всех моделей
- [X] Создать тестовые данные через админку
- [X] Написать тесты моделей (20/20 PASSED)
- [X] Коммит: "feat: add procurement app with basic models"
### Этап 2: API ✅ ЗАВЕРШЕН (95%)
- [X] Создать `procurement/` структуру (без api/v1)
- [X] Написать сериализаторы (9 сериализаторов)
- [X] Создать ViewSets (7 ViewSets)
- [X] Настроить URL routing
- [X] Создать permission classes (9 классов)
- [X] Добавить департаментные права
- [X] Написать API тесты (12/12 PASSED)
- [X] Протестировать endpoints (50+ endpoints)
- [X] Коммит: "feat: add procurement API endpoints"
- [ ] ⚠️ Исправить: баг items_count (property vs annotate) - ИСПРАВЛЕН
### Этап 3: Workflow ✅ ЗАВЕРШЕН (100%)
- [X] Установить django-fsm: `pip install django-fsm==3.0.1`
- [X] Добавить FSM в ProcurementRequest
- [X] Реализовать transitions (submit, approve, reject)
- [X] Создать actions в API (@action decorators)
- [X] Добавить проверки бюджета (check_budget_available)
- [X] Настроить signals (post_save для уведомлений)
- [X] Интеграция с NotificationService (вместо Celery)
- [X] Написать workflow тесты (9/9 PASSED)
- [X] Коммит: "feat: add procurement approval workflow"
- [X] Исправить queryset для approvers (41/41 тестов PASSED)
### Этап 4: Бюджеты ✅ ЗАВЕРШЕН (100%)
- [X] Реализовать методы Budget (reserved_amount, available_amount)
- [X] Добавить проверки (is_staff/headed_departments)
- [X] Создать алерты (_check_budget_alert при < 20% и < 10%)
- [X] Добавить endpoints статистики (/stats/overview/, /stats/by-department/)
- [X] Добавить my-department endpoint
- [X] Написать тесты (11/11 PASSED)
- [X] Коммит: "feat: add budget control and alerts"
### Этап 5: Инвентарь
- [X] Установить barcode библиотеки (qrcode[pil])
- [X] Реализовать генерацию номеров (InventoryNumberGenerator)
- [X] Добавить EquipmentTransferLog модель
- [X] Создать actions в API (transfer, write_off, add_maintenance, qr_code, transfer_history)
- [X] Реализовать QR-коды (QRCodeGenerator)
- [X] Написать тесты (14/14 PASSED)
- [ ] Коммит: "feat: add equipment management with QR codes"
### Этап 6: Frontend
- [ ] Создать templates структуру
- [ ] Создать static файлы структуру
- [ ] Дашборд
- [ ] Форма создания
- [ ] Детальная страница
- [ ] Каталог оборудования
- [ ] Мобильная адаптация
- [ ] Коммит: "feat: add procurement UI components"
### Этап 7: Интеграции
- [X] Интеграция с 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)!