From a784212ea162bb33137cf4ea83ca12d9f75616ab Mon Sep 17 00:00:00 2001 From: lizerk Date: Tue, 10 Mar 2026 12:38:39 +0300 Subject: [PATCH] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20=D0=BB?= =?UTF-8?q?=D0=B8=D1=88=D0=BD=D0=B5=D0=B3=D0=BE=20=D0=B8=D0=B7=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=8F=D0=B2=D0=BE=D0=BA=20=D0=B8=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D1=83=D0=B4=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/eusrr_backend/urls.py | 4 - backend/procurement/forms.py | 163 --------------- backend/procurement/tests.py | 3 - backend/procurement/urls_frontend.py | 39 ---- backend/procurement/views_frontend.py | 284 -------------------------- 5 files changed, 493 deletions(-) delete mode 100644 backend/procurement/forms.py delete mode 100644 backend/procurement/tests.py delete mode 100644 backend/procurement/urls_frontend.py delete mode 100644 backend/procurement/views_frontend.py diff --git a/backend/eusrr_backend/urls.py b/backend/eusrr_backend/urls.py index 0789daf9..f6fb0a75 100644 --- a/backend/eusrr_backend/urls.py +++ b/backend/eusrr_backend/urls.py @@ -34,10 +34,6 @@ urlpatterns = [ path("notifications/", include("notifications.urls", namespace="notifications")), path("search/", include("search.urls", namespace="search")), path("finance/", include("finance.urls", namespace="finance")), - path( - "procurement/", - include("procurement.urls_frontend", namespace="procurement_frontend") - ), path("api/", include(("api.urls", "api"), namespace="api")), # Service Worker должен быть в корне для правильного scope path("sw.js", serve_service_worker, name="sw"), diff --git a/backend/procurement/forms.py b/backend/procurement/forms.py deleted file mode 100644 index be5c887c..00000000 --- a/backend/procurement/forms.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -Django Forms для модуля закупок. -""" -from datetime import date - -from django import forms - -from .models import ProcurementRequest, ProcurementItem, Budget - - -class ProcurementRequestForm(forms.ModelForm): - """Форма создания/редактирования заявки на закупку.""" - - class Meta: - model = ProcurementRequest - fields = ['title', 'description', 'department', 'urgency'] - widgets = { - 'title': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Название заявки', - }), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 4, - 'placeholder': 'Описание и обоснование закупки', - }), - 'department': forms.Select(attrs={ - 'class': 'form-select', - }), - 'urgency': forms.Select(attrs={ - 'class': 'form-select', - }), - } - - def __init__(self, *args, user=None, **kwargs): - """Инициализация формы с фильтрацией отделов пользователя.""" - super().__init__(*args, **kwargs) - if user: - # Ограничиваем выбор отделов теми, в которых состоит юзер - user_departments = user.departments.filter( - employeedepartment__is_active=True - ) - self.fields['department'].queryset = user_departments - - # Если у пользователя один отдел - выбираем его по умолчанию - if user_departments.count() == 1: - self.fields['department'].initial = user_departments.first() - - -class ProcurementItemForm(forms.ModelForm): - """Форма добавления позиции в заявку.""" - - class Meta: - model = ProcurementItem - fields = [ - 'name', 'description', 'quantity', 'unit', - 'estimated_unit_price', 'supplier_info' - ] - widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Наименование', - }), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 2, - 'placeholder': 'Описание позиции', - }), - 'quantity': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 1, - }), - 'unit': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'шт.', - }), - 'estimated_unit_price': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 0.01, - 'step': '0.01', - }), - 'supplier_info': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 2, - 'placeholder': 'Ссылки на товар, контакты поставщика', - }), - } - - -class BudgetForm(forms.ModelForm): - """Форма создания/редактирования бюджета отдела.""" - - QUARTER_CHOICES = [ - (1, '1 квартал'), - (2, '2 квартал'), - (3, '3 квартал'), - (4, '4 квартал'), - ] - - class Meta: - model = Budget - fields = ['department', 'year', 'quarter', 'allocated_amount'] - widgets = { - 'department': forms.Select(attrs={ - 'class': 'form-select', - }), - 'year': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 2020, - 'max': 2100, - }), - 'quarter': forms.Select(attrs={ - 'class': 'form-select', - }), - 'allocated_amount': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 0, - 'step': '0.01', - 'placeholder': '0.00', - }), - } - labels = { - 'department': 'Отдел', - 'year': 'Год', - 'quarter': 'Квартал', - 'allocated_amount': 'Выделенная сумма (₽)', - } - - def __init__(self, *args, **kwargs): - """Инициализация формы с текущим годом/кварталом.""" - super().__init__(*args, **kwargs) - - # Устанавливаем текущий год и квартал по умолчанию для новых бюджетов - if not self.instance.pk: - today = date.today() - self.fields['year'].initial = today.year - self.fields['quarter'].initial = (today.month - 1) // 3 + 1 - - def clean(self): - """Проверка уникальности бюджета.""" - cleaned_data = super().clean() - department = cleaned_data.get('department') - year = cleaned_data.get('year') - quarter = cleaned_data.get('quarter') - - if department and year and quarter: - # Проверяем, нет ли уже такого бюджета - existing = Budget.objects.filter( - department=department, - year=year, - quarter=quarter - ) - # Исключаем текущий объект при редактировании - if self.instance.pk: - existing = existing.exclude(pk=self.instance.pk) - - if existing.exists(): - raise forms.ValidationError( - f'Бюджет для отдела "{department}" на {year} год, ' - f'{quarter} квартал уже существует.' - ) - - return cleaned_data diff --git a/backend/procurement/tests.py b/backend/procurement/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/backend/procurement/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/backend/procurement/urls_frontend.py b/backend/procurement/urls_frontend.py deleted file mode 100644 index f78f6049..00000000 --- a/backend/procurement/urls_frontend.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -URL конфигурация для HTML страниц модуля закупок (Frontend). -""" - -from django.urls import path - -from . import views_frontend as views - -app_name = 'procurement_frontend' - -urlpatterns = [ - # Дашборд - path('', views.dashboard, name='dashboard'), - - # Заявки на закупку (orders - чтобы не путать с заявлениями requests) - path('orders/', views.request_list, name='request_list'), - path('orders/my/', views.my_requests, name='my_requests'), - path( - 'orders/pending/', - views.pending_approvals, - name='pending_approvals' - ), - path('orders/create/', views.request_create, name='request_create'), - path('orders//', views.request_detail, name='request_detail'), - path('orders//edit/', views.request_edit, name='request_edit'), - - # Оборудование - path('equipment/', views.equipment_list, name='equipment_list'), - path( - 'equipment//', - views.equipment_detail, - name='equipment_detail' - ), - - # Бюджеты - path('budgets/', views.budget_list, name='budget_list'), - path('budgets/create/', views.budget_create, name='budget_create'), - path('budgets//edit/', views.budget_edit, name='budget_edit'), -] diff --git a/backend/procurement/views_frontend.py b/backend/procurement/views_frontend.py deleted file mode 100644 index d12ff69b..00000000 --- a/backend/procurement/views_frontend.py +++ /dev/null @@ -1,284 +0,0 @@ -""" -HTML Views для модуля закупок (Frontend). - -Все данные загружаются через API на клиенте. -Views только рендерят layout и передают минимальный контекст. -""" - -from django.contrib.auth.decorators import login_required -from django.shortcuts import render - - -@login_required -def dashboard(request): - """Главная страница модуля закупок. - - Данные загружаются через API: - - /api/procurement/stats/overview/ — статистика - - /api/procurement/budgets/my-department/ — бюджет отдела - - /api/procurement/requests/?ordering=-created_at&page_size=5 — последние заявки - """ - return render(request, 'procurement/dashboard.html', {}) - - -@login_required -def request_list(request): - """Список заявок на закупку. - - Данные загружаются через API: - - /api/procurement/requests/ — с фильтрами - - /api/v1/departments/ — для фильтра по отделам - """ - scope = request.GET.get('scope', 'mine') - user = request.user - - # Читаем фильтры из GET параметров - filters = { - 'status': request.GET.get('status', ''), - 'urgency': request.GET.get('urgency', ''), - 'period': request.GET.get('period', ''), - } - - # Определяем доступные представления - can_view_department = user.departments.exists() - can_view_all = ( - user.is_staff or user.is_superuser or - user.has_perm('procurement.view_procurementrequest') or - user.has_perm('procurement.change_procurementrequest') - ) - - return render(request, 'procurement/request_list.html', { - 'scope': scope, - 'show_tabs': True, - 'can_view_department': can_view_department, - 'can_view_all': can_view_all, - 'filters': filters, - }) -@login_required -def my_requests(request): - """Мои заявки — редирект на request_list.""" - from django.shortcuts import redirect - return redirect('procurement_frontend:request_list') - - -@login_required -def pending_approvals(request): - """Заявки на согласовании. - - Данные загружаются через API: - - /api/procurement/requests/pending_approvals/ - """ - user = request.user - - # Определяем доступные представления - can_view_department = user.departments.exists() - can_view_all = ( - user.is_staff or user.is_superuser or - user.has_perm('procurement.view_procurementrequest') or - user.has_perm('procurement.change_procurementrequest') - ) - - return render(request, 'procurement/request_list.html', { - 'scope': 'pending', - 'page_title': 'На согласовании', - 'show_tabs': True, - 'can_view_department': can_view_department, - 'can_view_all': can_view_all, - }) - - -@login_required -@login_required -def request_detail(request, pk): - """Детальная страница заявки. - - Загружает данные из БД и передаёт в шаблон. - - Права на редактирование (can_edit): - - Админы/Staff → любая заявка в DRAFT - - Модельные права (change_procurementrequest) → любая заявка в DRAFT - - Автор заявки → своя заявка в DRAFT - - Начальник отдела → заявки своего отдела в DRAFT - - Права на согласование (can_approve): - - Пользователи с активным approval для этой заявки - """ - from django.shortcuts import get_object_or_404 - from procurement.models import ProcurementRequest - - request_obj = get_object_or_404( - ProcurementRequest.objects.select_related( - 'department', 'requestor', 'executor' - ).prefetch_related('items', 'approvals__approver'), - pk=pk - ) - - user = request.user - - # Проверяем права на редактирование (синхронизировано с API) - can_edit = False - if request_obj.is_editable: # Только DRAFT - # Админы могут редактировать - if user.is_superuser or user.is_staff: - can_edit = True - # Модельные права - elif user.has_perm('procurement.change_procurementrequest'): - can_edit = True - # Автор заявки - elif request_obj.requestor == user: - can_edit = True - # Начальник отдела - elif request_obj.department.head == user: - can_edit = True - - # Проверяем права на согласование - can_approve = ( - request_obj.approvals.filter( - approver=user, - status='pending' - ).exists() - ) - - return render(request, 'procurement/request_detail.html', { - 'request_id': pk, - 'request_obj': request_obj, - 'can_edit': can_edit, - 'can_approve': can_approve, - }) - - -@login_required -def request_create(request): - """Страница создания заявки. - - Данные загружаются через API: - - /api/v1/departments/ — список отделов - Создание через: - - POST /api/procurement/requests/ - """ - return render(request, 'procurement/request_form.html', { - 'page_title': 'Новая заявка', - 'request_id': None, - }) - - -@login_required -def request_edit(request, pk): - """Страница редактирования заявки. - - Данные загружаются через API: - - /api/v1/procurement/requests/{pk}/ — данные заявки - - /api/v1/departments/ — список отделов - Обновление через: - - PATCH /api/v1/procurement/requests/{pk}/ - """ - return render(request, 'procurement/request_form.html', { - 'page_title': 'Редактирование заявки', - 'request_id': pk, - }) - - -@login_required -def equipment_list(request): - """Список оборудования. - - Данные загружаются через API: - - /api/procurement/equipment/ — список с фильтрами - - /api/procurement/equipment-categories/ — категории для фильтра - - /api/v1/departments/ — отделы для фильтра - - /api/procurement/equipment/create-options/ — при создании - """ - return render(request, 'procurement/equipment_list.html', {}) - - -@login_required -def equipment_detail(request, pk): - """Детальная страница оборудования. - - Загружает данные из БД и передаёт в шаблон. - - Права на управление (can_manage): - - Админы/Staff → любое оборудование - - Начальник отдела → оборудование своего отдела - - Ответственный за оборудование → своё оборудование - """ - from django.shortcuts import get_object_or_404 - from procurement.models import Equipment - - equipment = get_object_or_404( - Equipment.objects.select_related( - 'department', - 'department__head', - 'category', - 'responsible_person', - 'procurement_item', - 'procurement_item__request' - ).prefetch_related( - 'maintenance_history', - 'transfer_logs__from_department', - 'transfer_logs__to_department', - 'transfer_logs__from_person', - 'transfer_logs__to_person' - ), - pk=pk - ) - - user = request.user - - # Проверяем права на управление - can_manage = False - if user.is_superuser or user.is_staff: - can_manage = True - elif equipment.department and equipment.department.head == user: - can_manage = True - elif equipment.responsible_person == user: - can_manage = True - - return render(request, 'procurement/equipment_detail.html', { - 'equipment': equipment, - 'can_manage': can_manage, - }) - - -# ============================================================================= -# БЮДЖЕТЫ -# ============================================================================= - -@login_required -def budget_list(request): - """Список бюджетов. - - Данные загружаются через API: - - /api/procurement/budgets/ — список с фильтрами - - /api/v1/departments/ — отделы для фильтра - """ - return render(request, 'procurement/budget_list.html', {}) - - -@login_required -def budget_create(request): - """Создание бюджета. - - Данные загружаются через API: - - /api/v1/departments/ — список отделов - Создание через: - - POST /api/procurement/budgets/ - """ - return render(request, 'procurement/budget_form.html', { - 'budget_id': None, - }) - - -@login_required -def budget_edit(request, pk): - """Редактирование бюджета. - - Данные загружаются через API: - - /api/procurement/budgets/{pk}/ — текущие данные - - /api/v1/departments/ — список отделов - Обновление через: - - PATCH /api/procurement/budgets/{pk}/ - """ - return render(request, 'procurement/budget_form.html', { - 'budget_id': pk, - })