Feature: экономика заказа клиента — связи с поставщиком и расходами, отчёт по марже и рентабельности
Made-with: Cursor
This commit is contained in:
@@ -82,7 +82,7 @@ class CustomerOrderForm(forms.ModelForm):
|
||||
class SupplierOrderForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = SupplierOrder
|
||||
fields = ("date", "number", "organization", "supplier", "currency", "rate", "author")
|
||||
fields = ("date", "number", "organization", "supplier", "customer_order", "currency", "rate", "author")
|
||||
widgets = {
|
||||
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
|
||||
"number": forms.TextInput(attrs={"size": 15, "maxlength": 15}),
|
||||
@@ -112,7 +112,7 @@ class CashTransferForm(forms.ModelForm):
|
||||
class CashExpenseForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = CashExpense
|
||||
fields = ("date", "number", "sender", "amount", "supplier_order", "comment", "author")
|
||||
fields = ("date", "number", "sender", "amount", "supplier_order", "customer_order", "comment", "author")
|
||||
widgets = {
|
||||
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
|
||||
"number": forms.TextInput(attrs={"size": 15, "maxlength": 15}),
|
||||
|
||||
24
app/documents/migrations/0002_add_customer_order_links.py
Normal file
24
app/documents/migrations/0002_add_customer_order_links.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='supplierorder',
|
||||
name='customer_order',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_orders', to='documents.customerorder', verbose_name='Заказ клиента'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cashexpense',
|
||||
name='customer_order',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cash_expenses', to='documents.customerorder', verbose_name='Заказ клиента'),
|
||||
),
|
||||
]
|
||||
@@ -2,6 +2,7 @@
|
||||
"""Документы ERP WaterSurf."""
|
||||
from decimal import Decimal
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from references.models import Currency, OrderKind, Client, Organization, Supplier, Employee, CashAccount, Product
|
||||
|
||||
class CustomerOrder(models.Model):
|
||||
@@ -18,6 +19,25 @@ class CustomerOrder(models.Model):
|
||||
def __str__(self): return f"{self.number} от {self.date}"
|
||||
def recalc_total(self): self.total_amount = sum((i.price * i.quantity for i in self.items.all()), Decimal("0"))
|
||||
|
||||
def get_economics(self):
|
||||
"""Сводка по поступлениям и расходам по заказу: поступления, расходы (через заказы поставщику + прямые), маржа, маржинальность, рентабельность."""
|
||||
total_inflows = self.cash_inflows.aggregate(s=Sum("amount"))["s"] or Decimal("0")
|
||||
expenses_via_supplier = CashExpense.objects.filter(supplier_order__customer_order=self).aggregate(s=Sum("amount"))["s"] or Decimal("0")
|
||||
expenses_direct = self.cash_expenses.aggregate(s=Sum("amount"))["s"] or Decimal("0")
|
||||
total_expenses = expenses_via_supplier + expenses_direct
|
||||
margin = total_inflows - total_expenses
|
||||
margin_pct = (margin / total_inflows * 100) if total_inflows else None
|
||||
profitability_pct = (margin / total_expenses * 100) if total_expenses else None
|
||||
return {
|
||||
"total_inflows": total_inflows,
|
||||
"expenses_via_supplier": expenses_via_supplier,
|
||||
"expenses_direct": expenses_direct,
|
||||
"total_expenses": total_expenses,
|
||||
"margin": margin,
|
||||
"margin_pct": margin_pct,
|
||||
"profitability_pct": profitability_pct,
|
||||
}
|
||||
|
||||
class CustomerOrderItem(models.Model):
|
||||
document = models.ForeignKey(CustomerOrder, on_delete=models.CASCADE, related_name="items", verbose_name="Заказ")
|
||||
product = models.ForeignKey(Product, on_delete=models.PROTECT, verbose_name="Товар")
|
||||
@@ -38,6 +58,7 @@ class SupplierOrder(models.Model):
|
||||
number = models.CharField("Номер", max_length=50)
|
||||
organization = models.ForeignKey(Organization, on_delete=models.PROTECT, verbose_name="Организация")
|
||||
supplier = models.ForeignKey(Supplier, on_delete=models.PROTECT, verbose_name="Поставщик")
|
||||
customer_order = models.ForeignKey(CustomerOrder, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Заказ клиента", related_name="supplier_orders")
|
||||
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, verbose_name="Валюта")
|
||||
rate = models.DecimalField("Курс валюты", max_digits=18, decimal_places=6, default=Decimal("1"))
|
||||
total_in_currency = models.DecimalField("Стоимость в валюте", max_digits=18, decimal_places=2, default=Decimal("0"), editable=False)
|
||||
@@ -97,6 +118,7 @@ class CashExpense(models.Model):
|
||||
sender = models.ForeignKey(CashAccount, on_delete=models.PROTECT, verbose_name="Отправитель", related_name="expenses")
|
||||
amount = models.DecimalField("Сумма", max_digits=18, decimal_places=2, default=Decimal("0"))
|
||||
supplier_order = models.ForeignKey(SupplierOrder, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Заказ поставщику", related_name="cash_expenses")
|
||||
customer_order = models.ForeignKey(CustomerOrder, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Заказ клиента", related_name="cash_expenses")
|
||||
comment = models.CharField("Комментарий", max_length=500, blank=True)
|
||||
author = models.ForeignKey(Employee, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Автор", related_name="cash_expenses")
|
||||
class Meta:
|
||||
|
||||
@@ -8,6 +8,7 @@ urlpatterns = [
|
||||
path("customer-orders/create/", views.CustomerOrderCreate.as_view(), name="customer_order_create"),
|
||||
path("customer-orders/<int:pk>/edit/", views.CustomerOrderUpdate.as_view(), name="customer_order_edit"),
|
||||
path("customer-orders/<int:pk>/delete/", views.CustomerOrderDelete.as_view(), name="customer_order_delete"),
|
||||
path("customer-orders/<int:pk>/", views.CustomerOrderDetail.as_view(), name="customer_order_detail"),
|
||||
path("supplier-orders/", views.SupplierOrderList.as_view(), name="supplier_order_list"),
|
||||
path("supplier-orders/create/", views.SupplierOrderCreate.as_view(), name="supplier_order_create"),
|
||||
path("supplier-orders/<int:pk>/edit/", views.SupplierOrderUpdate.as_view(), name="supplier_order_edit"),
|
||||
|
||||
@@ -107,6 +107,17 @@ class CustomerOrderDelete(LoginRequiredMixin, DeleteView):
|
||||
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, ListView):
|
||||
model = SupplierOrder
|
||||
|
||||
Reference in New Issue
Block a user