Feature: справочник Статусы заказов, поле Статус в заказе клиента, оформление в списке (зелёный/песочный)

Made-with: Cursor
This commit is contained in:
2026-02-26 16:44:39 +00:00
parent 9d1bfadb96
commit 29cf44e278
15 changed files with 151 additions and 4 deletions

View File

@@ -1,5 +1,15 @@
# История изменений ERP WaterSurf
## 2025-02-26 17:00 UTC Справочник «Статусы заказов» и поле «Статус заказа» в заказе клиента
**Задача**: Справочник статусов заказов, поле в заказе клиента, отображение в списке с цветовой подложкой («Выполнено» — зелёная, «В работе» — песочная).
**Решение**: В справочниках добавлена модель **OrderStatus** (название). Начальные данные: «В работе», «Выполнено». В модель **CustomerOrder** добавлено поле **status** (FK на OrderStatus, null/blank). В форме заказа клиента добавлено поле выбора статуса (по умолчанию при создании — «В работе»). В списке заказов клиентов добавлена колонка «Статус заказа» после «Номер»; значение выводится бейджем с классом `ws-status-done` (зелёная подложка) для «Выполнено» и `ws-status-inprogress` (песочная подложка) для «В работе».
**Изменения**: references/models.py (OrderStatus), references/admin.py, references/views.py, references/urls.py, base.html (меню «Статусы заказов»), references/migrations 0002, 0003 (данные); documents/models.py (status у CustomerOrder), documents/migrations 0004; documents/forms.py, views.py (форма, get_initial); order_form.html (поле статус); customer_order_list.html (колонка и бейдж); theme.css (ws-status-badge, ws-status-done, ws-status-inprogress).
---
## 2025-02-26 16:50 UTC Общая сумма над блоком Товары; клик по строке списка открывает документ
**Изменения**: Блок «Общая сумма заказа» перенесён над всей табличной частью «Товары» — сразу под полями Клиент/Автор. В списках заказов клиентов и заказов поставщику документ открывается при клике по строке (в обоих случаях — форма редактирования); клик по ссылкам «Изменить»/«Удалить» не переходит по строке. Добавлены класс строки `ws-row-clickable`, атрибут `data-href` и скрипт перехода.

View File

@@ -82,7 +82,7 @@ SupplierOrderItemFormSetUpdate = inlineformset_factory(
class CustomerOrderForm(forms.ModelForm):
class Meta:
model = CustomerOrder
fields = ("date", "number", "order_kind", "organization", "client")
fields = ("date", "number", "order_kind", "organization", "client", "status")
widgets = {
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
"number": forms.TextInput(attrs={"size": 15, "maxlength": 15}),

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.11 on 2026-02-26 16:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0003_quantity_integer_0_99'),
('references', '0002_order_status'),
]
operations = [
migrations.AddField(
model_name='customerorder',
name='status',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='customer_orders', to='references.orderstatus', verbose_name='Статус заказа'),
),
]

View File

@@ -4,7 +4,7 @@ from decimal import Decimal
from django.db import models
from django.db.models import Sum
from django.core.validators import MinValueValidator, MaxValueValidator
from references.models import Currency, OrderKind, Client, Organization, Supplier, Employee, CashAccount, Product
from references.models import Currency, OrderKind, Client, Organization, Supplier, Employee, CashAccount, Product, OrderStatus
class CustomerOrder(models.Model):
date = models.DateField("Дата")
@@ -12,6 +12,14 @@ class CustomerOrder(models.Model):
order_kind = models.ForeignKey(OrderKind, on_delete=models.PROTECT, verbose_name="Вид заказа")
organization = models.ForeignKey(Organization, on_delete=models.PROTECT, verbose_name="Организация")
client = models.ForeignKey(Client, on_delete=models.PROTECT, verbose_name="Клиент")
status = models.ForeignKey(
OrderStatus,
on_delete=models.PROTECT,
verbose_name="Статус заказа",
null=True,
blank=True,
related_name="customer_orders",
)
total_amount = models.DecimalField("Стоимость заказа", max_digits=18, decimal_places=2, default=Decimal("0"), editable=False)
author = models.ForeignKey(Employee, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Автор", related_name="customer_orders")
class Meta:

View File

@@ -8,7 +8,7 @@ from django.contrib import messages
from django.http import HttpResponseRedirect
from users.utils import get_author_employee
from references.models import Employee
from references.models import Employee, OrderStatus
from .models import (
CustomerOrder,
SupplierOrder,
@@ -67,6 +67,14 @@ class CustomerOrderCreate(LoginRequiredMixin, CreateView):
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()

View File

@@ -8,6 +8,7 @@ from .models import (
Employee,
CashAccount,
Product,
OrderStatus,
)
admin.site.register(Currency)
@@ -18,3 +19,4 @@ admin.site.register(Supplier)
admin.site.register(Employee)
admin.site.register(CashAccount)
admin.site.register(Product)
admin.site.register(OrderStatus)

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.11 on 2026-02-26 16:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('references', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='OrderStatus',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Название')),
],
options={
'verbose_name': 'Статус заказа',
'verbose_name_plural': 'Статусы заказов',
},
),
]

View File

@@ -0,0 +1,24 @@
# Data migration: начальные статусы заказов
from django.db import migrations
def create_default_statuses(apps, schema_editor):
OrderStatus = apps.get_model("references", "OrderStatus")
for name in ["В работе", "Выполнено"]:
OrderStatus.objects.get_or_create(name=name)
def noop(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("references", "0002_order_status"),
]
operations = [
migrations.RunPython(create_default_statuses, noop),
]

View File

@@ -99,3 +99,15 @@ class Product(models.Model):
def __str__(self):
return self.name
class OrderStatus(models.Model):
"""Статусы заказов."""
name = models.CharField("Название", max_length=100)
class Meta:
verbose_name = "Статус заказа"
verbose_name_plural = "Статусы заказов"
def __str__(self):
return self.name

View File

@@ -36,4 +36,8 @@ urlpatterns = [
path("products/create/", views.ProductCreate.as_view(), name="product_create"),
path("products/<int:pk>/edit/", views.ProductUpdate.as_view(), name="product_update"),
path("products/<int:pk>/delete/", views.ProductDelete.as_view(), name="product_delete"),
path("order-statuses/", views.OrderStatusList.as_view(), name="orderstatus_list"),
path("order-statuses/create/", views.OrderStatusCreate.as_view(), name="orderstatus_create"),
path("order-statuses/<int:pk>/edit/", views.OrderStatusUpdate.as_view(), name="orderstatus_update"),
path("order-statuses/<int:pk>/delete/", views.OrderStatusDelete.as_view(), name="orderstatus_delete"),
]

View File

@@ -17,6 +17,7 @@ from .models import (
Employee,
CashAccount,
Product,
OrderStatus,
)
logger = logging.getLogger(__name__)
@@ -129,3 +130,10 @@ ProductList, ProductCreate, ProductUpdate, ProductDelete = _ref_view(
"references:product_update", "references:product_delete",
"Товары", "Товар",
)
# Статусы заказов
OrderStatusList, OrderStatusCreate, OrderStatusUpdate, OrderStatusDelete = _ref_view(
OrderStatus, "references:orderstatus_list", "references:orderstatus_create",
"references:orderstatus_update", "references:orderstatus_delete",
"Статусы заказов", "Статус заказа",
)

View File

@@ -260,6 +260,23 @@ body {
color: var(--ws-danger-dark);
}
/* Бейдж статуса заказа в списке */
.ws-status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-size: 0.875em;
font-weight: 500;
}
.ws-status-done {
background: rgba(76, 175, 80, 0.35);
color: #a5d6a7;
}
.ws-status-inprogress {
background: rgba(255, 193, 7, 0.35);
color: #ffe082;
}
.ws-table .ws-num {
text-align: right;
font-variant-numeric: tabular-nums;

View File

@@ -34,6 +34,7 @@
<li><a class="dropdown-item" href="{% url 'references:employee_list' %}">Сотрудники</a></li>
<li><a class="dropdown-item" href="{% url 'references:cashaccount_list' %}">Счета денежных средств</a></li>
<li><a class="dropdown-item" href="{% url 'references:product_list' %}">Товары</a></li>
<li><a class="dropdown-item" href="{% url 'references:orderstatus_list' %}">Статусы заказов</a></li>
</ul>
</li>
<li class="nav-item dropdown">

View File

@@ -13,6 +13,7 @@
<tr>
<th>Дата</th>
<th>Номер</th>
<th>Статус заказа</th>
<th>Вид заказа</th>
<th>Организация</th>
<th>Клиент</th>
@@ -25,6 +26,7 @@
<tr class="ws-row-clickable" data-href="{% url 'documents:customer_order_edit' obj.pk %}">
<td>{{ obj.date }}</td>
<td><a href="{% url 'documents:customer_order_edit' obj.pk %}" class="ws-link">{{ obj.number }}</a></td>
<td>{% if obj.status %}<span class="ws-status-badge {% if obj.status.name == 'Выполнено' %}ws-status-done{% elif obj.status.name == 'В работе' %}ws-status-inprogress{% endif %}">{{ obj.status.name }}</span>{% else %}—{% endif %}</td>
<td>{{ obj.order_kind }}</td>
<td>{{ obj.organization }}</td>
<td>{{ obj.client }}</td>
@@ -36,7 +38,7 @@
</td>
</tr>
{% empty %}
<tr><td colspan="7" class="ws-empty">Нет заказов.</td></tr>
<tr><td colspan="8" class="ws-empty">Нет заказов.</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -35,6 +35,13 @@
{{ form.client }}
{% if form.client.errors %}<small class="ws-text-danger">{{ form.client.errors.0 }}</small>{% endif %}
</div>
<div class="ws-form-group">
<label for="{{ form.status.id_for_label }}">{{ form.status.label }}</label>
{{ form.status }}
{% if form.status.errors %}<small class="ws-text-danger">{{ form.status.errors.0 }}</small>{% endif %}
</div>
</div>
<div class="ws-form-row ws-form-row-2">
<div class="ws-form-group">
<label>Автор</label>
<span class="ws-readonly">{{ author_display|default:"—" }}</span>