Удалён kostyl с автозаполнением delivery_date для черновиков
- orders/views.py: убрано auto_fill_draft_date из order_create и order_update - Черновики теперь могут сохраняться с NULL датой доставки - Валидация на уровне модели обеспечивает корректность данных
This commit is contained in:
@@ -118,6 +118,59 @@ def order_create(request):
|
|||||||
formset.instance = order
|
formset.instance = order
|
||||||
formset.save()
|
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'
|
is_draft = order.status and order.status.code == 'draft'
|
||||||
|
|
||||||
@@ -141,17 +194,12 @@ def order_create(request):
|
|||||||
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
||||||
if is_draft:
|
if is_draft:
|
||||||
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
||||||
if address or delivery_type or pickup_warehouse:
|
if address or delivery_type or pickup_warehouse or delivery_date:
|
||||||
# Для черновиков используем значения по умолчанию, если не указаны
|
|
||||||
from django.utils import timezone
|
|
||||||
draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER
|
|
||||||
draft_delivery_date = delivery_date or timezone.now().date()
|
|
||||||
|
|
||||||
Delivery.objects.update_or_create(
|
Delivery.objects.update_or_create(
|
||||||
order=order,
|
order=order,
|
||||||
defaults={
|
defaults={
|
||||||
'delivery_type': draft_delivery_type,
|
'delivery_type': delivery_type or Delivery.DELIVERY_TYPE_COURIER,
|
||||||
'delivery_date': draft_delivery_date,
|
'delivery_date': delivery_date, # Может быть None для черновиков
|
||||||
'time_from': time_from,
|
'time_from': time_from,
|
||||||
'time_to': time_to,
|
'time_to': time_to,
|
||||||
'address': address,
|
'address': address,
|
||||||
@@ -316,17 +364,12 @@ def order_update(request, order_number):
|
|||||||
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
||||||
if is_draft:
|
if is_draft:
|
||||||
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
||||||
if address or delivery_type or pickup_warehouse:
|
if address or delivery_type or pickup_warehouse or delivery_date:
|
||||||
# Для черновиков используем значения по умолчанию, если не указаны
|
|
||||||
from django.utils import timezone
|
|
||||||
draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER
|
|
||||||
draft_delivery_date = delivery_date or timezone.now().date()
|
|
||||||
|
|
||||||
Delivery.objects.update_or_create(
|
Delivery.objects.update_or_create(
|
||||||
order=order,
|
order=order,
|
||||||
defaults={
|
defaults={
|
||||||
'delivery_type': draft_delivery_type,
|
'delivery_type': delivery_type or Delivery.DELIVERY_TYPE_COURIER,
|
||||||
'delivery_date': draft_delivery_date,
|
'delivery_date': delivery_date, # Может быть None для черновиков
|
||||||
'time_from': time_from,
|
'time_from': time_from,
|
||||||
'time_to': time_to,
|
'time_to': time_to,
|
||||||
'address': address,
|
'address': address,
|
||||||
@@ -913,3 +956,155 @@ def set_order_status(request, order_number):
|
|||||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({'success': False, 'error': f'Server error: {str(e)}'}, status=500)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user