"""Представления документов: списки и формы создания/редактирования.""" import logging from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView from config.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.shortcuts import redirect, get_object_or_404 from django.contrib import messages from django.http import HttpResponseRedirect from users.utils import get_author_employee from references.models import Employee, OrderStatus from .models import ( CustomerOrder, SupplierOrder, CashInflow, CashTransfer, CashExpense, ) from .forms import ( CustomerOrderForm, CustomerOrderItemFormSet, CustomerOrderItemFormSetUpdate, SupplierOrderForm, SupplierOrderItemFormSet, SupplierOrderItemFormSetUpdate, CashInflowForm, CashTransferForm, CashExpenseForm, ) from .services import next_number logger = logging.getLogger(__name__) class SortableListMixin: """Миксин для сортировки списка по GET-параметрам sort и order.""" sort_param = "sort" order_param = "order" sort_fields = {} # ключ (из GET) -> поле order_by, например "date", "-date" не нужен, order добавляется в get_queryset default_sort = None # например "date" default_order = "desc" # asc или desc def get_sort_fields(self): return self.sort_fields def get_queryset(self): qs = super().get_queryset() fields = self.get_sort_fields() if not fields: return qs sort_key = self.request.GET.get(self.sort_param) order = (self.request.GET.get(self.order_param) or "").lower() if order not in ("asc", "desc"): order = self.default_order or "asc" if sort_key and sort_key in fields: order_by_field = fields[sort_key] if order == "desc": order_by_field = "-" + order_by_field return qs.order_by(order_by_field) if self.default_sort and self.default_sort in fields: order_by_field = fields[self.default_sort] if (self.default_order or "asc") == "desc": order_by_field = "-" + order_by_field return qs.order_by(order_by_field) return qs def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["sort_key"] = self.request.GET.get(self.sort_param) or self.default_sort ctx["sort_order"] = (self.request.GET.get(self.order_param) or self.default_order or "asc").lower() if ctx["sort_order"] not in ("asc", "desc"): ctx["sort_order"] = "asc" ctx["sort_param"] = self.sort_param ctx["order_param"] = self.order_param return ctx def set_author(form, request): """Подставить автора при создании документа: из профиля пользователя (UserProfile.employee) или сотрудник по имени пользователя.""" if form.instance.pk: return # при редактировании автора не меняем author = _get_author_for_request(request) if author: form.instance.author = author def _get_author_for_request(request): """Сотрудник-автор: из профиля пользователя или по ФИО/логину.""" if not request.user.is_authenticated: return None author = get_author_employee(request.user) if author: return author name = (request.user.get_full_name() or "").strip() or request.user.username author = Employee.objects.filter(name=name).first() if not author: author = Employee.objects.create(name=name) return author # --- Заказы клиентов --- class CustomerOrderList(LoginRequiredMixin, SortableListMixin, ListView): model = CustomerOrder template_name = "documents/customer_order_list.html" context_object_name = "object_list" default_sort = "date" default_order = "desc" sort_fields = { "date": "date", "number": "number", "status": "status__name", "order_kind": "order_kind__name", "organization": "organization__name", "client": "client__name", "total_amount": "total_amount", } class CustomerOrderCreate(LoginRequiredMixin, CreateView): model = CustomerOrder form_class = CustomerOrderForm template_name = "documents/order_form.html" success_url = reverse_lazy("documents:customer_order_list") def get_initial(self): initial = super().get_initial() if not initial.get("status"): in_progress = OrderStatus.objects.filter(name="В работе").first() if in_progress: initial["status"] = in_progress return initial def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["formset"] = CustomerOrderItemFormSet(instance=self.object) if self.object and self.object.pk else CustomerOrderItemFormSet() ctx["title"] = "Заказ клиента" ctx["author_display"] = _get_author_for_request(self.request) if not self.object or not self.object.pk else self.object.author return ctx def form_valid(self, form): set_author(form, self.request) self.object = form.save() formset = CustomerOrderItemFormSet(self.request.POST, instance=self.object) if formset.is_valid(): formset.save() self.object.recalc_total() self.object.save() logger.info("Создан заказ клиента %s", self.object) messages.success(self.request, "Заказ создан.") return redirect(self.success_url) return self.render_to_response(self.get_context_data(form=form, formset=formset)) def get(self, request, *args, **kwargs): self.object = None return super().get(request, *args, **kwargs) class CustomerOrderUpdate(LoginRequiredMixin, UpdateView): model = CustomerOrder form_class = CustomerOrderForm template_name = "documents/order_form.html" context_object_name = "object" success_url = reverse_lazy("documents:customer_order_list") def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["formset"] = CustomerOrderItemFormSetUpdate(instance=self.object) ctx["title"] = "Заказ клиента" ctx["author_display"] = self.object.author return ctx def form_valid(self, form): formset = CustomerOrderItemFormSetUpdate(self.request.POST, instance=self.object) if formset.is_valid(): form.save() formset.save() self.object.recalc_total() self.object.save() logger.info("Обновлён заказ клиента %s", self.object) messages.success(self.request, "Заказ сохранён.") return redirect(self.success_url) return self.render_to_response(self.get_context_data(form=form, formset=formset)) class CustomerOrderDelete(LoginRequiredMixin, DeleteView): model = CustomerOrder template_name = "documents/confirm_delete.html" success_url = reverse_lazy("documents:customer_order_list") context_object_name = "object" class CustomerOrderDetail(LoginRequiredMixin, DetailView): model = CustomerOrder template_name = "documents/customer_order_detail.html" context_object_name = "object" def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["economics"] = self.object.get_economics() return ctx # --- Заказы поставщику --- class SupplierOrderList(LoginRequiredMixin, SortableListMixin, ListView): model = SupplierOrder template_name = "documents/supplier_order_list.html" context_object_name = "object_list" default_sort = "date" default_order = "desc" sort_fields = { "date": "date", "number": "number", "organization": "organization__name", "supplier": "supplier__name", "total_in_currency": "total_in_currency", "total_amount": "total_amount", } class SupplierOrderCreate(LoginRequiredMixin, CreateView): model = SupplierOrder form_class = SupplierOrderForm template_name = "documents/supplier_order_form.html" success_url = reverse_lazy("documents:supplier_order_list") def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["formset"] = SupplierOrderItemFormSet(instance=self.object) if self.object and self.object.pk else SupplierOrderItemFormSet() ctx["title"] = "Заказ поставщику" ctx["author_display"] = _get_author_for_request(self.request) if not self.object or not self.object.pk else self.object.author return ctx def form_valid(self, form): set_author(form, self.request) self.object = form.save() formset = SupplierOrderItemFormSet(self.request.POST, instance=self.object) if formset.is_valid(): formset.save() self.object.recalc_totals() self.object.save() logger.info("Создан заказ поставщику %s", self.object) messages.success(self.request, "Заказ создан.") return redirect(self.success_url) return self.render_to_response(self.get_context_data(form=form, formset=formset)) def get(self, request, *args, **kwargs): self.object = None return super().get(request, *args, **kwargs) class SupplierOrderUpdate(LoginRequiredMixin, UpdateView): model = SupplierOrder form_class = SupplierOrderForm template_name = "documents/supplier_order_form.html" context_object_name = "object" success_url = reverse_lazy("documents:supplier_order_list") def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["formset"] = SupplierOrderItemFormSetUpdate(instance=self.object) ctx["title"] = "Заказ поставщику" ctx["author_display"] = self.object.author return ctx def form_valid(self, form): formset = SupplierOrderItemFormSetUpdate(self.request.POST, instance=self.object) if formset.is_valid(): form.save() formset.save() self.object.recalc_totals() self.object.save() logger.info("Обновлён заказ поставщику %s", self.object) messages.success(self.request, "Заказ сохранён.") return redirect(self.success_url) return self.render_to_response(self.get_context_data(form=form, formset=formset)) class SupplierOrderDelete(LoginRequiredMixin, DeleteView): model = SupplierOrder template_name = "documents/confirm_delete.html" success_url = reverse_lazy("documents:supplier_order_list") context_object_name = "object" # --- Поступление денежных средств --- class CashInflowList(LoginRequiredMixin, SortableListMixin, ListView): model = CashInflow template_name = "documents/cash_inflow_list.html" context_object_name = "object_list" default_sort = "date" default_order = "desc" sort_fields = { "date": "date", "number": "number", "recipient": "recipient__name", "amount": "amount", "customer_order": "customer_order__number", } class CashInflowCreate(LoginRequiredMixin, CreateView): model = CashInflow form_class = CashInflowForm template_name = "documents/cash_doc_form.html" success_url = reverse_lazy("documents:cash_inflow_list") def get_initial(self): initial = super().get_initial() initial["number"] = next_number(CashInflow) return initial def form_valid(self, form): set_author(form, self.request) form.save() logger.info("Создано поступление %s", form.instance) messages.success(self.request, "Поступление создано.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Поступление денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_inflow_list") return ctx class CashInflowUpdate(LoginRequiredMixin, UpdateView): model = CashInflow form_class = CashInflowForm template_name = "documents/cash_doc_form.html" context_object_name = "object" success_url = reverse_lazy("documents:cash_inflow_list") def form_valid(self, form): form.save() logger.info("Обновлено поступление %s", self.object) messages.success(self.request, "Поступление сохранено.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Поступление денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_inflow_list") return ctx class CashInflowDelete(LoginRequiredMixin, DeleteView): model = CashInflow template_name = "documents/confirm_delete.html" success_url = reverse_lazy("documents:cash_inflow_list") context_object_name = "object" # --- Перемещение денежных средств --- class CashTransferList(LoginRequiredMixin, SortableListMixin, ListView): model = CashTransfer template_name = "documents/cash_transfer_list.html" context_object_name = "object_list" default_sort = "date" default_order = "desc" sort_fields = { "date": "date", "number": "number", "sender": "sender__name", "recipient": "recipient__name", "amount": "amount", } class CashTransferCreate(LoginRequiredMixin, CreateView): model = CashTransfer form_class = CashTransferForm template_name = "documents/cash_doc_form.html" success_url = reverse_lazy("documents:cash_transfer_list") def get_initial(self): initial = super().get_initial() initial["number"] = next_number(CashTransfer) return initial def form_valid(self, form): set_author(form, self.request) form.save() logger.info("Создано перемещение %s", form.instance) messages.success(self.request, "Перемещение создано.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Перемещение денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_transfer_list") return ctx class CashTransferUpdate(LoginRequiredMixin, UpdateView): model = CashTransfer form_class = CashTransferForm template_name = "documents/cash_doc_form.html" context_object_name = "object" success_url = reverse_lazy("documents:cash_transfer_list") def form_valid(self, form): form.save() logger.info("Обновлено перемещение %s", self.object) messages.success(self.request, "Перемещение сохранено.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Перемещение денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_transfer_list") return ctx class CashTransferDelete(LoginRequiredMixin, DeleteView): model = CashTransfer template_name = "documents/confirm_delete.html" success_url = reverse_lazy("documents:cash_transfer_list") context_object_name = "object" # --- Расход денежных средств --- class CashExpenseList(LoginRequiredMixin, SortableListMixin, ListView): model = CashExpense template_name = "documents/cash_expense_list.html" context_object_name = "object_list" default_sort = "date" default_order = "desc" sort_fields = { "date": "date", "number": "number", "sender": "sender__name", "amount": "amount", "supplier_order": "supplier_order__number", } class CashExpenseCreate(LoginRequiredMixin, CreateView): model = CashExpense form_class = CashExpenseForm template_name = "documents/cash_doc_form.html" success_url = reverse_lazy("documents:cash_expense_list") def get_initial(self): initial = super().get_initial() initial["number"] = next_number(CashExpense) return initial def form_valid(self, form): set_author(form, self.request) form.save() logger.info("Создан расход %s", form.instance) messages.success(self.request, "Расход создан.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Расход денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_expense_list") return ctx class CashExpenseUpdate(LoginRequiredMixin, UpdateView): model = CashExpense form_class = CashExpenseForm template_name = "documents/cash_doc_form.html" context_object_name = "object" success_url = reverse_lazy("documents:cash_expense_list") def form_valid(self, form): form.save() logger.info("Обновлён расход %s", self.object) messages.success(self.request, "Расход сохранён.") return redirect(self.success_url) def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx["title"] = "Расход денежных средств" ctx["cancel_url"] = reverse_lazy("documents:cash_expense_list") return ctx class CashExpenseDelete(LoginRequiredMixin, DeleteView): model = CashExpense template_name = "documents/confirm_delete.html" success_url = reverse_lazy("documents:cash_expense_list") context_object_name = "object"