feat(pos): интеграция системы скидок в POS терминал
API endpoints: - POST /api/discounts/validate-promo/: валидация промокода - POST /api/discounts/calculate/: расчёт скидок для корзины Обновлён pos_checkout: - добавлен параметр promo_code в payload - автоматическое применение скидок к заказу UI (terminal.html): - секция скидок в модальном окне оплаты - поле ввода промокода - отображение автоматических скидок - кнопки применения/удаления промокода JavaScript (terminal.js): - переменные состояния скидок - функции applyPromoCode, removePromoCode - checkAutoDiscounts: проверка автоматических скидок - updateCheckoutTotalWithDiscounts: пересчёт итога - обработчики кнопок промокода Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1400,7 +1400,8 @@ def pos_checkout(request):
|
||||
{"payment_method": "cash"|"card"|"online"|"account_balance", "amount": float, "notes": str},
|
||||
...
|
||||
],
|
||||
"notes": str (optional)
|
||||
"notes": str (optional),
|
||||
"promo_code": str (optional) - Промокод для скидки
|
||||
}
|
||||
"""
|
||||
from orders.models import Order, OrderItem, OrderStatus
|
||||
@@ -1421,6 +1422,7 @@ def pos_checkout(request):
|
||||
items_data = body.get('items', [])
|
||||
payments_data = body.get('payments', [])
|
||||
order_notes = body.get('notes', '')
|
||||
promo_code = body.get('promo_code') # Промокод для скидки
|
||||
|
||||
if not customer_id:
|
||||
return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400)
|
||||
@@ -1551,7 +1553,22 @@ def pos_checkout(request):
|
||||
order.refresh_from_db()
|
||||
order.calculate_total()
|
||||
|
||||
# 4. Проводим платежи
|
||||
# 4. Применяем скидки
|
||||
if promo_code:
|
||||
from discounts.services.applier import DiscountApplier
|
||||
apply_result = DiscountApplier.apply_promo_code(
|
||||
order=order,
|
||||
promo_code=promo_code,
|
||||
user=request.user
|
||||
)
|
||||
if not apply_result['success']:
|
||||
raise ValidationError(apply_result['error'])
|
||||
else:
|
||||
# Применяем автоматические скидки
|
||||
from discounts.services.applier import DiscountApplier
|
||||
DiscountApplier.apply_auto_discounts(order, user=request.user)
|
||||
|
||||
# 5. Проводим платежи
|
||||
payments_list = []
|
||||
for payment_data in payments_data:
|
||||
payments_list.append({
|
||||
@@ -1652,3 +1669,149 @@ def create_order_draft(request):
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при создании черновика заказа: {str(e)}', exc_info=True)
|
||||
return JsonResponse({'success': False, 'error': f'Ошибка: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
# ============================================
|
||||
# DISCOUNT API
|
||||
# ============================================
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def validate_promo_code(request):
|
||||
"""
|
||||
Валидировать промокод.
|
||||
|
||||
Payload JSON:
|
||||
{
|
||||
'promo_code': str,
|
||||
'cart_total': decimal (optional)
|
||||
}
|
||||
|
||||
Returns JSON:
|
||||
{
|
||||
'success': true/false,
|
||||
'promo_code': {...},
|
||||
'error': str
|
||||
}
|
||||
"""
|
||||
from discounts.services.validator import DiscountValidator
|
||||
from customers.models import Customer
|
||||
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
promo_code = data.get('promo_code', '')
|
||||
cart_total = data.get('cart_total', '0')
|
||||
|
||||
# Получаем текущего клиента из Redis
|
||||
from django.core.cache import cache
|
||||
cart_key = f'pos:customer:{request.user.id}'
|
||||
customer_id = cache.get(cart_key)
|
||||
|
||||
customer = None
|
||||
if customer_id:
|
||||
try:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
except Customer.DoesNotExist:
|
||||
pass
|
||||
|
||||
is_valid, promo, error = DiscountValidator.validate_promo_code(
|
||||
promo_code, customer, Decimal(str(cart_total)) if cart_total else None
|
||||
)
|
||||
|
||||
if is_valid:
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'promo_code': {
|
||||
'code': promo.code,
|
||||
'discount_id': promo.discount.id,
|
||||
'discount_name': promo.discount.name,
|
||||
'discount_type': promo.discount.discount_type,
|
||||
'discount_value': float(promo.discount.value),
|
||||
'scope': promo.discount.scope,
|
||||
}
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'error': error})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'success': False, 'error': 'Неверный формат JSON'}, status=400)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при валидации промокода: {str(e)}', exc_info=True)
|
||||
return JsonResponse({'success': False, 'error': f'Ошибка: {str(e)}'}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def calculate_cart_discounts(request):
|
||||
"""
|
||||
Рассчитать скидки для корзины POS.
|
||||
|
||||
Payload JSON:
|
||||
{
|
||||
'items': [...],
|
||||
'promo_code': str (optional),
|
||||
'customer_id': int (optional)
|
||||
}
|
||||
|
||||
Returns JSON:
|
||||
{
|
||||
'success': true,
|
||||
'cart_subtotal': float,
|
||||
'order_discount': {...},
|
||||
'item_discounts': [...],
|
||||
'total_discount': float,
|
||||
'final_total': float
|
||||
}
|
||||
"""
|
||||
from discounts.services.calculator import DiscountCalculator
|
||||
from customers.models import Customer
|
||||
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
items_data = data.get('items', [])
|
||||
promo_code = data.get('promo_code')
|
||||
customer_id = data.get('customer_id')
|
||||
|
||||
customer = None
|
||||
if customer_id:
|
||||
try:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
except Customer.DoesNotExist:
|
||||
pass
|
||||
|
||||
result = DiscountCalculator.calculate_cart_discounts(
|
||||
items_data, promo_code, customer
|
||||
)
|
||||
|
||||
cart_subtotal = Decimal('0')
|
||||
for item in items_data:
|
||||
cart_subtotal += Decimal(str(item['price'])) * Decimal(str(item['quantity']))
|
||||
|
||||
response_data = {
|
||||
'success': True,
|
||||
'cart_subtotal': float(cart_subtotal),
|
||||
'order_discount': {
|
||||
'discount_id': result['order_discount']['discount'].id if result['order_discount'].get('discount') else None,
|
||||
'discount_name': result['order_discount']['discount'].name if result['order_discount'].get('discount') else None,
|
||||
'discount_amount': float(result['order_discount']['discount_amount']),
|
||||
'error': result['order_discount'].get('error'),
|
||||
} if result['order_discount'] else None,
|
||||
'item_discounts': [
|
||||
{
|
||||
'cart_index': i['cart_index'],
|
||||
'discount_id': i['discount'].id,
|
||||
'discount_name': i['discount'].name,
|
||||
'discount_amount': float(i['discount_amount']),
|
||||
} for i in result['item_discounts']
|
||||
],
|
||||
'total_discount': float(result['total_discount']),
|
||||
'final_total': float(result['final_total']),
|
||||
}
|
||||
|
||||
return JsonResponse(response_data)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'success': False, 'error': 'Неверный формат JSON'}, status=400)
|
||||
except Exception as e:
|
||||
logger.error(f'Ошибка при расчете скидок: {str(e)}', exc_info=True)
|
||||
return JsonResponse({'success': False, 'error': f'Ошибка: {str(e)}'}, status=500)
|
||||
|
||||
Reference in New Issue
Block a user