diff --git a/myproject/orders/views.py b/myproject/orders/views.py index d502432..5ef9bb6 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -118,6 +118,59 @@ def order_create(request): formset.instance = order formset.save() + # === Обработка витринных комплектов из POS === + # Если заказ создан из POS с showcase_kit, обрабатываем ShowcaseItem + draft_token = request.GET.get('draft') + if draft_token: + from django.core.cache import cache + cache_key = f'pos_draft:{draft_token}' + draft_data = cache.get(cache_key) + + if draft_data and draft_data.get('items'): + from inventory.models import ShowcaseItem + from products.models import ProductKit + + # Проверяем статус заказа для выбора стратегии + is_final_positive = order.status and order.status.is_positive_end + + for draft_item in draft_data['items']: + if draft_item.get('type') == 'showcase_kit': + showcase_item_ids = draft_item.get('showcase_item_ids', []) + kit_id = draft_item.get('id') + + if not showcase_item_ids or not kit_id: + continue + + # Находим соответствующий OrderItem + kit = ProductKit.objects.get(id=kit_id) + order_item = order.items.filter( + product_kit=kit, + is_from_showcase=True + ).first() + + if not order_item: + continue + + # Загружаем ShowcaseItem + showcase_items = list(ShowcaseItem.objects.filter( + id__in=showcase_item_ids + )) + + if not showcase_items: + continue + + # Выбираем стратегию в зависимости от статуса заказа + if is_final_positive: + # Прямая продажа (заказ сразу в completed - редкий случай) + from inventory.services.showcase_manager import ShowcaseManager + result = ShowcaseManager.sell_showcase_items(showcase_items, order_item) + if not result['success']: + raise ValidationError(result['message']) + else: + # Резервирование под отложенный заказ + for showcase_item in showcase_items: + showcase_item.reserve_for_order(order_item) + # Проверяем, является ли заказ черновиком is_draft = order.status and order.status.code == 'draft' @@ -141,17 +194,12 @@ def order_create(request): # Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес if is_draft: # Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки - if address or delivery_type or pickup_warehouse: - # Для черновиков используем значения по умолчанию, если не указаны - from django.utils import timezone - draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER - draft_delivery_date = delivery_date or timezone.now().date() - + if address or delivery_type or pickup_warehouse or delivery_date: Delivery.objects.update_or_create( order=order, defaults={ - 'delivery_type': draft_delivery_type, - 'delivery_date': draft_delivery_date, + 'delivery_type': delivery_type or Delivery.DELIVERY_TYPE_COURIER, + 'delivery_date': delivery_date, # Может быть None для черновиков 'time_from': time_from, 'time_to': time_to, 'address': address, @@ -316,17 +364,12 @@ def order_update(request, order_number): # Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес if is_draft: # Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки - if address or delivery_type or pickup_warehouse: - # Для черновиков используем значения по умолчанию, если не указаны - from django.utils import timezone - draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER - draft_delivery_date = delivery_date or timezone.now().date() - + if address or delivery_type or pickup_warehouse or delivery_date: Delivery.objects.update_or_create( order=order, defaults={ - 'delivery_type': draft_delivery_type, - 'delivery_date': draft_delivery_date, + 'delivery_type': delivery_type or Delivery.DELIVERY_TYPE_COURIER, + 'delivery_date': delivery_date, # Может быть None для черновиков 'time_from': time_from, 'time_to': time_to, 'address': address, @@ -913,3 +956,155 @@ def set_order_status(request, order_number): return JsonResponse({'success': False, 'error': str(e)}, status=400) except Exception as e: return JsonResponse({'success': False, 'error': f'Server error: {str(e)}'}, status=500) + + +@login_required +@require_http_methods(["POST"]) +def create_order_from_pos(request): + """ + Создаёт отложенный заказ (черновик) из POS. + Сразу создаёт Order со статусом 'draft' и резервирует ShowcaseItem. + + Payload (JSON): + { + "customer_id": int, + "items": [ + { + "type": "product"|"kit"|"showcase_kit", + "id": int, + "quantity": float, + "price": float, + "sales_unit_id": int, // для product + "showcase_item_ids": [int, ...] // для showcase_kit + } + ] + } + + Response: + { + "success": true, + "order_number": int, + "message": "Заказ #123 создан (черновик)" + } + """ + from .services.order_status_service import OrderStatusService + from customers.models import Customer + from products.models import Product, ProductKit + from inventory.models import ShowcaseItem + import logging + + logger = logging.getLogger(__name__) + + try: + data = json.loads(request.body) + customer_id = data.get('customer_id') + items = data.get('items', []) + + if not customer_id: + return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400) + + if not items: + return JsonResponse({'success': False, 'error': 'Корзина пуста'}, status=400) + + with transaction.atomic(): + # 1. Получаем клиента + try: + customer = Customer.objects.get(id=customer_id) + except Customer.DoesNotExist: + return JsonResponse({'success': False, 'error': 'Клиент не найден'}, status=404) + + # 2. Получаем статус 'Черновик' + draft_status = OrderStatusService.get_draft_status() + if not draft_status: + return JsonResponse({ + 'success': False, + 'error': 'Статус "Черновик" не найден. Выполните миграции.' + }, status=500) + + # 3. Создаём заказ со статусом 'draft' + order = Order.objects.create( + customer=customer, + status=draft_status, + modified_by=request.user + ) + + # 4. Создаём OrderItem и резервируем ShowcaseItem + for item_data in items: + item_type = item_data.get('type') + item_id = item_data.get('id') + quantity = item_data.get('quantity', 1) + price = item_data.get('price', 0) + + if item_type == 'product': + # Обычный товар + product = Product.objects.get(id=item_id) + sales_unit_id = item_data.get('sales_unit_id') + + OrderItem.objects.create( + order=order, + product=product, + sales_unit_id=sales_unit_id, + quantity=quantity, + price=price, + is_custom_price=False + ) + + elif item_type == 'kit': + # Обычный комплект + kit = ProductKit.objects.get(id=item_id) + + OrderItem.objects.create( + order=order, + product_kit=kit, + quantity=quantity, + price=price, + is_custom_price=False + ) + + elif item_type == 'showcase_kit': + # Витринный комплект - сразу резервируем ShowcaseItem + kit = ProductKit.objects.get(id=item_id) + showcase_item_ids = item_data.get('showcase_item_ids', []) + + if not showcase_item_ids: + continue + + # Создаём OrderItem с флагом is_from_showcase + order_item = OrderItem.objects.create( + order=order, + product_kit=kit, + quantity=len(showcase_item_ids), + price=price, + is_custom_price=False, + is_from_showcase=True + ) + + # Резервируем ShowcaseItem: in_cart → reserved + showcase_items = ShowcaseItem.objects.filter( + id__in=showcase_item_ids, + status='in_cart', + locked_by_user=request.user + ) + + for showcase_item in showcase_items: + showcase_item.reserve_for_order(order_item) + + # 5. Пересчитываем стоимость заказа + order.calculate_total() + order.update_payment_status() + + return JsonResponse({ + 'success': True, + 'order_number': order.order_number, + 'message': f'Заказ #{order.order_number} создан (черновик)' + }) + + except json.JSONDecodeError: + return JsonResponse({'success': False, 'error': 'Неверный формат JSON'}, status=400) + except Product.DoesNotExist: + return JsonResponse({'success': False, 'error': 'Товар не найден'}, status=404) + except ProductKit.DoesNotExist: + return JsonResponse({'success': False, 'error': 'Комплект не найден'}, status=404) + except Exception as e: + logger.error(f'Ошибка при создании отложенного заказа из POS: {str(e)}', exc_info=True) + return JsonResponse({'success': False, 'error': f'Ошибка: {str(e)}'}, status=500)