POS: улучшения работы с витринными букетами
- Упрощено добавление в корзину: 1 клик = 1 шт (без prompt) - API показывает все букеты (available + in_cart), не только доступные - Карточка показывает available/total и сколько в корзине - Корзина показывает реальное количество витринных букетов - Кнопка "Очистить" сбрасывает блокировки и обновляет отображение - API release-all-my-locks для сброса зависших блокировок - Автоочистка истёкших блокировок при загрузке витрины 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,16 +57,17 @@ def get_showcase_kits_for_pos():
|
||||
|
||||
НОВАЯ АРХИТЕКТУРА с ShowcaseItem:
|
||||
- Группирует экземпляры по (product_kit, showcase)
|
||||
- Возвращает available_count и showcase_item_ids для каждой группы
|
||||
- Позволяет продавать несколько экземпляров одного букета
|
||||
- Показывает ВСЕ букеты (available + in_cart), не только доступные
|
||||
- Возвращает available_count (сколько можно добавить) и total_count (всего)
|
||||
"""
|
||||
from products.models import ProductKitPhoto
|
||||
from inventory.models import ShowcaseItem
|
||||
from django.db.models import Count, Prefetch as DjangoPrefetch
|
||||
from django.db.models import Count, Q
|
||||
|
||||
# Группируем доступные ShowcaseItem по (product_kit, showcase)
|
||||
available_items = ShowcaseItem.objects.filter(
|
||||
status='available',
|
||||
# Группируем ShowcaseItem по (product_kit, showcase)
|
||||
# Включаем и available, и in_cart (чтобы видеть букеты в корзине)
|
||||
all_items = ShowcaseItem.objects.filter(
|
||||
status__in=['available', 'in_cart'],
|
||||
showcase__is_active=True
|
||||
).select_related(
|
||||
'product_kit',
|
||||
@@ -80,14 +81,15 @@ def get_showcase_kits_for_pos():
|
||||
'showcase_id',
|
||||
'showcase__name'
|
||||
).annotate(
|
||||
available_count=Count('id')
|
||||
total_count=Count('id'),
|
||||
available_count=Count('id', filter=Q(status='available'))
|
||||
).order_by('showcase__name', 'product_kit__name')
|
||||
|
||||
if not available_items:
|
||||
if not all_items:
|
||||
return []
|
||||
|
||||
# Получаем ID всех комплектов для загрузки фото
|
||||
kit_ids = list(set(item['product_kit_id'] for item in available_items))
|
||||
kit_ids = list(set(item['product_kit_id'] for item in all_items))
|
||||
|
||||
# Загружаем первые фото для комплектов
|
||||
kit_photos = {}
|
||||
@@ -101,12 +103,12 @@ def get_showcase_kits_for_pos():
|
||||
|
||||
# Формируем результат
|
||||
showcase_kits = []
|
||||
for item in available_items:
|
||||
for item in all_items:
|
||||
kit_id = item['product_kit_id']
|
||||
showcase_id = item['showcase_id']
|
||||
|
||||
# Получаем IDs всех доступных экземпляров этой группы
|
||||
item_ids = list(ShowcaseItem.objects.filter(
|
||||
# Получаем IDs только ДОСТУПНЫХ экземпляров этой группы
|
||||
available_item_ids = list(ShowcaseItem.objects.filter(
|
||||
product_kit_id=kit_id,
|
||||
showcase_id=showcase_id,
|
||||
status='available'
|
||||
@@ -120,15 +122,16 @@ def get_showcase_kits_for_pos():
|
||||
'name': item['product_kit__name'],
|
||||
'price': str(price),
|
||||
'category_ids': [],
|
||||
'in_stock': True,
|
||||
'in_stock': item['available_count'] > 0, # Есть ли доступные
|
||||
'sku': item['product_kit__sku'] or '',
|
||||
'image': kit_photos.get(kit_id),
|
||||
'type': 'showcase_kit',
|
||||
'showcase_name': item['showcase__name'],
|
||||
'showcase_id': showcase_id,
|
||||
# НОВЫЕ ПОЛЯ для поддержки количества
|
||||
'available_count': item['available_count'],
|
||||
'showcase_item_ids': item_ids
|
||||
# Количества
|
||||
'available_count': item['available_count'], # Сколько можно добавить
|
||||
'total_count': item['total_count'], # Всего на витрине (включая в корзине)
|
||||
'showcase_item_ids': available_item_ids # IDs только доступных
|
||||
})
|
||||
|
||||
return showcase_kits
|
||||
@@ -404,6 +407,21 @@ def get_showcase_kits_api(request):
|
||||
Включает информацию о блокировках в корзинах.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
from inventory.models import ShowcaseItem
|
||||
|
||||
# Очищаем только ИСТЁКШИЕ блокировки (cart_lock_expires_at < now)
|
||||
expired_locks = ShowcaseItem.objects.filter(
|
||||
status='in_cart',
|
||||
cart_lock_expires_at__lt=timezone.now()
|
||||
)
|
||||
expired_count = expired_locks.update(
|
||||
status='available',
|
||||
locked_by_user=None,
|
||||
cart_lock_expires_at=None,
|
||||
cart_session_id=None
|
||||
)
|
||||
if expired_count > 0:
|
||||
logger.info(f'Очищено {expired_count} истёкших блокировок ShowcaseItem')
|
||||
|
||||
showcase_kits_data = get_showcase_kits_for_pos()
|
||||
|
||||
@@ -630,6 +648,41 @@ def remove_showcase_kit_from_cart(request, kit_id):
|
||||
}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def release_all_my_showcase_locks(request):
|
||||
"""
|
||||
API endpoint для сброса ВСЕХ блокировок витринных букетов текущего пользователя.
|
||||
Используется при загрузке POS если корзина пустая, чтобы освободить зависшие блокировки.
|
||||
"""
|
||||
from inventory.models import ShowcaseItem
|
||||
|
||||
try:
|
||||
# Снимаем ВСЕ блокировки текущего пользователя
|
||||
updated_count = ShowcaseItem.objects.filter(
|
||||
status='in_cart',
|
||||
locked_by_user=request.user
|
||||
).update(
|
||||
status='available',
|
||||
locked_by_user=None,
|
||||
cart_lock_expires_at=None,
|
||||
cart_session_id=None
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'Освобождено {updated_count} блокировок',
|
||||
'released_count': updated_count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка сброса блокировок: {str(e)}', exc_info=True)
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def get_items_api(request):
|
||||
|
||||
Reference in New Issue
Block a user