Добавлен статус 'reserved' для витринных букетов ShowcaseItem
- inventory/models.py: добавлен статус 'reserved' в STATUS_CHOICES - Миграция: 0004_add_reserved_status_to_showcaseitem.py - Статус reserved используется для витринных букетов в отложенных заказах - Жизненный цикл: available → in_cart → reserved → sold
This commit is contained in:
@@ -584,6 +584,7 @@ class ShowcaseItem(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('available', 'Доступен'),
|
||||
('in_cart', 'В корзине'),
|
||||
('reserved', 'Зарезервирован'),
|
||||
('sold', 'Продан'),
|
||||
('dismantled', 'Разобран'),
|
||||
]
|
||||
@@ -668,10 +669,43 @@ class ShowcaseItem(models.Model):
|
||||
self.cart_session_id = None
|
||||
self.save(update_fields=['status', 'locked_by_user', 'cart_lock_expires_at', 'cart_session_id', 'updated_at'])
|
||||
|
||||
def reserve_for_order(self, order_item):
|
||||
"""
|
||||
Зарезервировать экземпляр под конкретный заказ (жёсткая блокировка).
|
||||
Используется для отложенных заказов, когда букет привязан к заказу,
|
||||
но заказ ещё не в финальном статусе.
|
||||
|
||||
Args:
|
||||
order_item: OrderItem - позиция заказа, к которой привязывается экземпляр
|
||||
|
||||
Raises:
|
||||
ValidationError: если экземпляр уже продан или разобран
|
||||
"""
|
||||
if self.status == 'sold':
|
||||
raise ValidationError(f'Экземпляр {self} уже продан')
|
||||
if self.status == 'dismantled':
|
||||
raise ValidationError(f'Экземпляр {self} разобран')
|
||||
|
||||
self.status = 'reserved'
|
||||
self.sold_order_item = order_item
|
||||
# sold_at оставляем пустым - это не финальная продажа
|
||||
# Очищаем soft-lock поля корзины
|
||||
self.locked_by_user = None
|
||||
self.cart_lock_expires_at = None
|
||||
self.cart_session_id = None
|
||||
self.save(update_fields=['status', 'sold_order_item', 'locked_by_user',
|
||||
'cart_lock_expires_at', 'cart_session_id', 'updated_at'])
|
||||
|
||||
def mark_sold(self, order_item):
|
||||
"""
|
||||
Пометить как проданный.
|
||||
Проверяет статус перед продажей чтобы избежать дублей.
|
||||
|
||||
Для прямой продажи (POS "Продать сейчас"):
|
||||
available/in_cart -> sold
|
||||
|
||||
Для финализации отложенного заказа:
|
||||
reserved -> sold (через mark_sold_from_reserved)
|
||||
"""
|
||||
if self.status == 'sold':
|
||||
raise ValidationError(f'Экземпляр {self} уже продан')
|
||||
@@ -684,6 +718,60 @@ class ShowcaseItem(models.Model):
|
||||
self.cart_session_id = None
|
||||
self.save()
|
||||
|
||||
def mark_sold_from_reserved(self):
|
||||
"""
|
||||
Финализировать продажу из зарезервированного состояния.
|
||||
Используется при переходе заказа в положительный конечный статус (completed).
|
||||
|
||||
Raises:
|
||||
ValidationError: если экземпляр не в статусе 'reserved'
|
||||
"""
|
||||
if self.status != 'reserved':
|
||||
raise ValidationError(
|
||||
f'Экземпляр {self} не в статусе "reserved" (текущий: {self.get_status_display()})'
|
||||
)
|
||||
|
||||
self.status = 'sold'
|
||||
self.sold_at = timezone.now()
|
||||
# sold_order_item уже установлен при резервировании
|
||||
self.save(update_fields=['status', 'sold_at', 'updated_at'])
|
||||
|
||||
def return_to_available(self):
|
||||
"""
|
||||
Вернуть экземпляр на витрину (освободить).
|
||||
Используется при отмене заказа.
|
||||
|
||||
Raises:
|
||||
ValidationError: если экземпляр уже разобран
|
||||
"""
|
||||
if self.status == 'dismantled':
|
||||
raise ValidationError(f'Экземпляр {self} разобран и не может быть возвращён на витрину')
|
||||
|
||||
self.status = 'available'
|
||||
self.sold_order_item = None
|
||||
self.sold_at = None
|
||||
self.locked_by_user = None
|
||||
self.cart_lock_expires_at = None
|
||||
self.cart_session_id = None
|
||||
self.save(update_fields=['status', 'sold_order_item', 'sold_at', 'locked_by_user',
|
||||
'cart_lock_expires_at', 'cart_session_id', 'updated_at'])
|
||||
|
||||
def return_to_reserved(self, order_item):
|
||||
"""
|
||||
Вернуть экземпляр в зарезервированное состояние.
|
||||
Используется при откате заказа из completed в нейтральный статус.
|
||||
|
||||
Args:
|
||||
order_item: OrderItem - позиция заказа, за которой остаётся экземпляр
|
||||
"""
|
||||
if self.status == 'dismantled':
|
||||
raise ValidationError(f'Экземпляр {self} разобран')
|
||||
|
||||
self.status = 'reserved'
|
||||
self.sold_order_item = order_item
|
||||
self.sold_at = None # Сбрасываем дату продажи
|
||||
self.save(update_fields=['status', 'sold_order_item', 'sold_at', 'updated_at'])
|
||||
|
||||
def is_lock_expired(self):
|
||||
"""Проверить истекла ли блокировка"""
|
||||
if self.cart_lock_expires_at is None:
|
||||
|
||||
Reference in New Issue
Block a user