# -*- coding: utf-8 -*- from django.shortcuts import render from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.views.decorators.http import require_http_methods from django.db import transaction from django.utils import timezone from decimal import Decimal import json from products.models import Product, ProductCategory, ProductKit, KitItem from inventory.models import Showcase, Reservation, Warehouse from inventory.services import ShowcaseManager def get_showcase_kits_for_pos(): """ Получает витринные комплекты для отображения в POS. Возвращает список временных комплектов, которые зарезервированы на витринах. """ # Получаем все уникальные комплекты, у которых есть резервы на витринах showcase_reservations = Reservation.objects.filter( showcase__isnull=False, showcase__is_active=True, status='reserved' ).select_related('showcase', 'product').values( 'showcase_id', 'showcase__name' ).distinct() # Собираем все kit_items, связанные с этими резервами showcase_kits = [] # Получаем все временные комплекты с резервами на витринах reserved_products = Reservation.objects.filter( showcase__isnull=False, showcase__is_active=True, status='reserved' ).values_list('product_id', flat=True).distinct() # Находим комплекты, в которых есть эти товары kits_with_showcase_items = ProductKit.objects.filter( is_temporary=True, status='active', kit_items__product_id__in=reserved_products ).prefetch_related('photos', 'kit_items__product').distinct() for kit in kits_with_showcase_items: # Проверяем что все компоненты этого комплекта зарезервированы на одной витрине kit_product_ids = set(kit.kit_items.values_list('product_id', flat=True)) # Находим резервы для этих товаров kit_reservations = Reservation.objects.filter( product_id__in=kit_product_ids, showcase__isnull=False, showcase__is_active=True, status='reserved' ).select_related('showcase') if kit_reservations.exists(): # Берём первую витрину (обычно комплект резервируется на одной) showcase = kit_reservations.first().showcase showcase_kits.append({ 'id': kit.id, 'name': kit.name, 'price': str(kit.actual_price), 'category_ids': [], # Временные комплекты обычно без категорий 'in_stock': True, # На витрине = в наличии 'sku': kit.sku or '', 'image': kit.photos.first().get_thumbnail_url() if kit.photos.exists() else None, 'type': 'showcase_kit', 'showcase_name': showcase.name, 'showcase_id': showcase.id }) return showcase_kits @login_required def pos_terminal(request): """ Tablet-friendly POS screen prototype. Shows categories and all items (products + kits) for quick tap-to-add. """ categories_qs = ProductCategory.objects.filter(is_active=True) # Показываем все товары, не только in_stock products_qs = Product.objects.all().prefetch_related('categories', 'photos') # Показываем все комплекты (кроме временных) kits_qs = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos') categories = [{'id': c.id, 'name': c.name} for c in categories_qs] # Сериализация товаров products = [{ 'id': p.id, 'name': p.name, 'price': str(p.actual_price), 'category_ids': [c.id for c in p.categories.all()], 'in_stock': p.in_stock, 'sku': p.sku or '', 'image': p.photos.first().get_thumbnail_url() if p.photos.exists() else None, 'type': 'product' } for p in products_qs] # Сериализация комплектов kits = [{ 'id': k.id, 'name': k.name, 'price': str(k.actual_price), 'category_ids': [c.id for c in k.categories.all()], 'in_stock': False, # Комплекты всегда "Под заказ" (пока не интегрируем проверку наличия) 'sku': k.sku or '', 'image': k.photos.first().get_thumbnail_url() if k.photos.exists() else None, 'type': 'kit' } for k in kits_qs] # Получаем витринные комплекты (временные комплекты с резервами на витринах) showcase_kits_data = get_showcase_kits_for_pos() # Объединяем все позиции all_items = products + kits context = { 'categories_json': json.dumps(categories), 'items_json': json.dumps(all_items), 'showcase_kits_json': json.dumps(showcase_kits_data), 'title': 'POS Terminal', } return render(request, 'pos/terminal.html', context) @login_required @require_http_methods(["GET"]) def showcase_items_api(request): """ API endpoint для получения витринных букетов. Возвращает комплекты, зарезервированные на активных витринах. """ # Получаем все активные резервы на витринах showcase_reservations = Reservation.objects.filter( showcase__isnull=False, showcase__is_active=True, status='reserved' ).select_related('showcase', 'product').prefetch_related('product__photos') # Группируем по витринам showcases_dict = {} for res in showcase_reservations: showcase_id = res.showcase.id if showcase_id not in showcases_dict: showcases_dict[showcase_id] = { 'id': showcase_id, 'name': res.showcase.name, 'warehouse': res.showcase.warehouse.name, 'items': [] } # Добавляем товар в список showcases_dict[showcase_id]['items'].append({ 'product_id': res.product.id, 'product_name': res.product.name, 'quantity': str(res.quantity), 'image': res.product.photos.first().get_thumbnail_url() if res.product.photos.exists() else None, }) showcases_list = list(showcases_dict.values()) return JsonResponse({ 'success': True, 'showcases': showcases_list }) @login_required @require_http_methods(["GET"]) def get_showcases_api(request): """ API endpoint для получения списка активных витрин. Используется для выбора витрины при создании временного комплекта. """ showcases = Showcase.objects.filter(is_active=True).select_related('warehouse') showcases_data = [{ 'id': s.id, 'name': s.name, 'warehouse_name': s.warehouse.name, 'warehouse_id': s.warehouse.id } for s in showcases] return JsonResponse({ 'success': True, 'showcases': showcases_data }) @login_required @require_http_methods(["POST"]) def create_temp_kit_to_showcase(request): """ API endpoint для создания временного комплекта из корзины POS и резервирования его на витрину. Ожидаемый payload: { "kit_name": "Название комплекта", "showcase_id": 1, "items": [ {"product_id": 1, "quantity": 2.0}, {"product_id": 3, "quantity": 1.0} ], "price_adjustment_type": "none", // optional "price_adjustment_value": 0 // optional } """ try: data = json.loads(request.body) kit_name = data.get('kit_name', '').strip() showcase_id = data.get('showcase_id') items = data.get('items', []) price_adjustment_type = data.get('price_adjustment_type', 'none') price_adjustment_value = Decimal(str(data.get('price_adjustment_value', 0))) # Валидация if not kit_name: return JsonResponse({ 'success': False, 'error': 'Необходимо указать название комплекта' }, status=400) if not showcase_id: return JsonResponse({ 'success': False, 'error': 'Необходимо выбрать витрину' }, status=400) if not items or len(items) == 0: return JsonResponse({ 'success': False, 'error': 'Корзина пуста. Добавьте товары для создания комплекта' }, status=400) # Проверяем что витрина существует и активна try: showcase = Showcase.objects.select_related('warehouse').get(id=showcase_id, is_active=True) except Showcase.DoesNotExist: return JsonResponse({ 'success': False, 'error': 'Витрина не найдена или неактивна' }, status=404) # Проверяем что все товары из корзины - это Product (не Kit) product_ids = [item['product_id'] for item in items] products = Product.objects.in_bulk(product_ids) if len(products) != len(product_ids): return JsonResponse({ 'success': False, 'error': 'Некоторые товары не найдены' }, status=400) # Агрегируем дубликаты (если один товар добавлен несколько раз) aggregated_items = {} for item in items: product_id = item['product_id'] quantity = Decimal(str(item['quantity'])) if product_id in aggregated_items: aggregated_items[product_id] += quantity else: aggregated_items[product_id] = quantity # Создаём временный комплект и резервируем на витрину with transaction.atomic(): # 1. Создаём ProductKit (is_temporary=True) kit = ProductKit.objects.create( name=kit_name, is_temporary=True, status='active', price_adjustment_type=price_adjustment_type, price_adjustment_value=price_adjustment_value ) # 2. Создаём KitItem для каждого товара из корзины for product_id, quantity in aggregated_items.items(): KitItem.objects.create( kit=kit, product=products[product_id], quantity=quantity ) # 3. Пересчитываем цену комплекта kit.recalculate_base_price() # 4. Резервируем комплект на витрину result = ShowcaseManager.reserve_kit_to_showcase( product_kit=kit, showcase=showcase, quantity=1 ) if not result['success']: # Откатываем транзакцию через raise raise Exception(result['message']) return JsonResponse({ 'success': True, 'message': f'Временный комплект "{kit_name}" создан и зарезервирован на витрине "{showcase.name}"', 'kit_id': kit.id, 'kit_name': kit.name, 'kit_price': str(kit.actual_price), 'reservations_count': len(result['reservations']) }) except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Неверный формат данных' }, status=400) except Exception as e: return JsonResponse({ 'success': False, 'error': f'Ошибка при создании комплекта: {str(e)}' }, status=500)