Implement flexible order status management system

Features:
- Created OrderStatus model for managing statuses per tenant
- Added system-level statuses: draft, new, confirmed, in_assembly, in_delivery, completed, return, cancelled
- Implemented CRUD views for managing order statuses
- Created OrderStatusService with status transitions and business logic hooks
- Updated Order model to use ForeignKey to OrderStatus
- Added is_returned flag for tracking returned orders
- Updated filters to work with new OrderStatus model
- Created management command for status initialization
- Added HTML templates for status list, form, and confirmation
- Fixed views.py to use OrderStatus instead of removed STATUS_CHOICES

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-13 16:29:50 +03:00
parent 0d5f0d2015
commit c7875f147c
28 changed files with 1337 additions and 390 deletions

View File

@@ -7,6 +7,99 @@ from shops.models import Shop
from simple_history.models import HistoricalRecords
class OrderStatus(models.Model):
"""
Статус заказа, управляется отдельно для каждого тенанта.
Благодаря django-tenants в TENANT_APPS, данные изолированы по схемам.
"""
name = models.CharField(
max_length=100,
verbose_name="Название статуса"
)
code = models.SlugField(
unique=True,
verbose_name="Код статуса",
help_text="Уникальный идентификатор (например: 'completed', 'cancelled')"
)
label = models.CharField(
max_length=100,
verbose_name="Метка для отображения",
blank=True
)
is_system = models.BooleanField(
default=False,
verbose_name="Системный статус",
help_text="True для встроенных статусов (draft, completed, cancelled)"
)
is_positive_end = models.BooleanField(
default=False,
verbose_name="Положительный конец",
help_text="True если это финальный успешный статус (Выполнен)"
)
is_negative_end = models.BooleanField(
default=False,
verbose_name="Отрицательный конец",
help_text="True если это финальный отрицательный статус (Отменен)"
)
order = models.PositiveIntegerField(
default=0,
verbose_name="Порядок отображения"
)
color = models.CharField(
max_length=7,
blank=True,
default='#808080',
verbose_name="Цвет (hex)",
help_text="Например: #FF5733"
)
description = models.TextField(
blank=True,
verbose_name="Описание"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(
CustomUser,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='created_order_statuses',
verbose_name="Создано"
)
updated_by = models.ForeignKey(
CustomUser,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='updated_order_statuses',
verbose_name="Последнее изменение"
)
class Meta:
verbose_name = "Статус заказа"
verbose_name_plural = "Статусы заказов"
ordering = ['order', 'name']
indexes = [
models.Index(fields=['code']),
models.Index(fields=['is_system']),
models.Index(fields=['order']),
]
def __str__(self):
return self.name
class Address(models.Model):
"""
Модель адреса доставки для заказа цветочного магазина в Минске.
@@ -233,23 +326,22 @@ class Order(models.Model):
)
# Статус заказа
STATUS_CHOICES = [
('draft', 'Черновик'),
('new', 'Новый'),
('confirmed', 'Подтвержден'),
('in_assembly', 'В сборке'),
('in_delivery', 'В доставке'),
('delivered', 'Доставлен'),
('cancelled', 'Отменен'),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='new',
status = models.ForeignKey(
'OrderStatus',
on_delete=models.PROTECT,
related_name='orders',
null=True,
blank=True,
verbose_name="Статус заказа"
)
# Флаг для отслеживания возвратов
is_returned = models.BooleanField(
default=False,
verbose_name="Возвращен",
help_text="True если заказ был выполнен, но потом отменен или возвращен клиентом"
)
# Автосохранение (для черновиков)
last_autosave_at = models.DateTimeField(
null=True,
@@ -496,7 +588,7 @@ class Order(models.Model):
def is_draft(self):
"""Проверяет, является ли заказ черновиком"""
return self.status == 'draft'
return self.status and self.status.code == 'draft'
@property
def amount_due(self):