482 lines
18 KiB
Python
482 lines
18 KiB
Python
"""Представления документов: списки и формы создания/редактирования."""
|
||
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"
|