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

@@ -0,0 +1,220 @@
"""
Сервис для управления статусами заказов.
Содержит бизнес-логику для работы со статусами и их переходами.
"""
from django.db import transaction
from orders.models import OrderStatus, Order
class OrderStatusService:
"""Сервис для работы со статусами заказов"""
@staticmethod
def get_default_status():
"""
Возвращает статус по умолчанию для новых заказов ('new')
"""
try:
return OrderStatus.objects.get(code='new', is_system=True)
except OrderStatus.DoesNotExist:
return None
@staticmethod
def get_draft_status():
"""Возвращает системный статус 'draft' (черновик)"""
try:
return OrderStatus.objects.get(code='draft', is_system=True)
except OrderStatus.DoesNotExist:
return None
@staticmethod
def get_system_status(code):
"""Получить системный статус по коду"""
try:
return OrderStatus.objects.get(code=code, is_system=True)
except OrderStatus.DoesNotExist:
return None
@staticmethod
def create_default_statuses():
"""
Создает системные статусы для тенанта.
Вызывается при первом использовании или миграции.
"""
default_statuses = [
{
'code': 'draft',
'name': 'Черновик',
'label': 'Черновик',
'is_system': True,
'order': 0,
'color': '#9E9E9E',
'description': 'Заказ находится в процессе создания/редактирования'
},
{
'code': 'new',
'name': 'Новый',
'label': 'Новый',
'is_system': True,
'order': 10,
'color': '#2196F3',
'description': 'Новый заказ, ожидающий обработки'
},
{
'code': 'confirmed',
'name': 'Подтвержден',
'label': 'Подтвержден',
'is_system': True,
'order': 20,
'color': '#FF9800',
'description': 'Заказ подтвержден и одобрен'
},
{
'code': 'in_assembly',
'name': 'В сборке',
'label': 'В сборке',
'is_system': True,
'order': 30,
'color': '#FF9800',
'description': 'Заказ находится в процессе сборки/подготовки'
},
{
'code': 'in_delivery',
'name': 'В доставке',
'label': 'В доставке',
'is_system': True,
'order': 40,
'color': '#9C27B0',
'description': 'Заказ в пути к клиенту'
},
{
'code': 'completed',
'name': 'Выполнен',
'label': 'Выполнен',
'is_system': True,
'is_positive_end': True,
'order': 50,
'color': '#4CAF50',
'description': 'Заказ успешно доставлен/выполнен'
},
{
'code': 'return',
'name': 'Возврат',
'label': 'Возврат',
'is_system': True,
'order': 60,
'color': '#FF5722',
'description': 'Заказ возвращен клиентом'
},
{
'code': 'cancelled',
'name': 'Отменен',
'label': 'Отменен',
'is_system': True,
'is_negative_end': True,
'order': 70,
'color': '#F44336',
'description': 'Заказ отменен'
},
]
for status_data in default_statuses:
OrderStatus.objects.get_or_create(
code=status_data['code'],
defaults=status_data
)
@staticmethod
@transaction.atomic
def change_order_status(order, new_status, user, notes=""):
"""
Меняет статус заказа и выполняет соответствующую бизнес-логику.
Args:
order (Order): Экземпляр заказа
new_status (OrderStatus): Новый статус
user (CustomUser): Пользователь, делающий изменение
notes (str): Опциональные заметки
Returns:
Order: Обновленный экземпляр заказа
Raises:
ValueError: Если статус не может быть применен
"""
old_status = order.status
order.status = new_status
order.modified_by = user
order.save()
# Запустить бизнес-логику в зависимости от нового статуса
if new_status.code == 'completed':
_handle_order_completion(order, user)
elif new_status.code == 'cancelled':
_handle_order_cancellation(order, old_status, user)
elif new_status.code == 'return':
_handle_order_return(order, user)
return order
@staticmethod
def get_all_statuses():
"""Возвращает все статусы, отсортированные по порядку"""
return OrderStatus.objects.all().order_by('order', 'name')
@staticmethod
def get_system_statuses():
"""Возвращает только системные статусы"""
return OrderStatus.objects.filter(is_system=True).order_by('order')
@staticmethod
def get_custom_statuses():
"""Возвращает только пользовательские статусы"""
return OrderStatus.objects.filter(is_system=False).order_by('order', 'name')
def _handle_order_completion(order, user):
"""
Обработка при переводе в статус 'Выполнен'.
Здесь происходит списание товаров со склада.
TODO: Интеграция с inventory приложением
"""
# from inventory.services import InventoryService
# InventoryService.process_order_completion(order)
pass
def _handle_order_cancellation(order, old_status, user):
"""
Обработка при переводе в статус 'Отменен'.
Если заказ был выполнен - возвращаем товары и деньги.
"""
if old_status and old_status.code == 'completed':
# Заказ был выполнен - нужно вернуть товары и деньги
order.is_returned = True
order.save()
# TODO: Интеграция с inventory - возврат товаров
# InventoryService.process_order_return(order)
# TODO: Интеграция с платежами - создать возврат
# PaymentService.create_refund(order)
else:
# Заказ отменен до выполнения - просто отменить резервы
# TODO: InventoryService.cancel_order_reservation(order)
pass
def _handle_order_return(order, user):
"""
Обработка при переводе в статус 'Возврат'.
Это промежуточный статус перед окончательной отменой.
"""
order.is_returned = True
order.save()
# TODO: Интеграция с inventory - вернуть товары на склад
# InventoryService.process_order_return(order)