feat(pos): добавлен полноценный интерфейс скидок в модальное окно продажи
- Добавлен API endpoint /pos/api/discounts/available/ для получения списка доступных скидок - Добавлен метод DiscountApplier.apply_manual_discount() для применения ручных скидок - Обновлен POS checkout для обработки manual_discount_id - Расширена секция скидок в модальном окне: * Отображение автоматических скидок (read-only) * Dropdown для выбора скидки вручную * Подробная детализация: подитог, общая скидка, скидки на позиции * Поле промокода с иконкой - Увеличен размер модального окна и изменено соотношение колонок (5/7) - Убрана вертикальная прокрутка из модального окна Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1402,6 +1402,7 @@ def pos_checkout(request):
|
||||
],
|
||||
"notes": str (optional),
|
||||
"promo_code": str (optional) - Промокод для скидки
|
||||
"manual_discount_id": int (optional) - ID выбранной вручную скидки
|
||||
}
|
||||
"""
|
||||
from orders.models import Order, OrderItem, OrderStatus
|
||||
@@ -1423,6 +1424,7 @@ def pos_checkout(request):
|
||||
payments_data = body.get('payments', [])
|
||||
order_notes = body.get('notes', '')
|
||||
promo_code = body.get('promo_code') # Промокод для скидки
|
||||
manual_discount_id = body.get('manual_discount_id') # ID выбранной вручную скидки
|
||||
|
||||
if not customer_id:
|
||||
return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400)
|
||||
@@ -1554,7 +1556,22 @@ def pos_checkout(request):
|
||||
order.calculate_total()
|
||||
|
||||
# 4. Применяем скидки
|
||||
if promo_code:
|
||||
if manual_discount_id:
|
||||
from discounts.services.applier import DiscountApplier
|
||||
from discounts.models import Discount
|
||||
|
||||
try:
|
||||
discount = Discount.objects.get(id=manual_discount_id, is_active=True)
|
||||
apply_result = DiscountApplier.apply_manual_discount(
|
||||
order=order,
|
||||
discount=discount,
|
||||
user=request.user
|
||||
)
|
||||
if not apply_result['success']:
|
||||
raise ValidationError(apply_result['error'])
|
||||
except Discount.DoesNotExist:
|
||||
pass
|
||||
elif promo_code:
|
||||
from discounts.services.applier import DiscountApplier
|
||||
apply_result = DiscountApplier.apply_promo_code(
|
||||
order=order,
|
||||
@@ -1815,3 +1832,85 @@ def calculate_cart_discounts(request):
|
||||
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(["GET"])
|
||||
def get_available_discounts(request):
|
||||
"""
|
||||
Возвращает список доступных скидок для ручного применения в POS.
|
||||
|
||||
Query params:
|
||||
- cart_total: сумма корзины (для фильтрации по min_order_amount)
|
||||
|
||||
Returns JSON:
|
||||
{
|
||||
'success': true,
|
||||
'order_discounts': [
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'Скидка 5%',
|
||||
'discount_type': 'percentage',
|
||||
'value': 5,
|
||||
'min_order_amount': 50
|
||||
},
|
||||
...
|
||||
],
|
||||
'auto_order_discount': {...} # если есть автоскидка на заказ
|
||||
}
|
||||
"""
|
||||
from discounts.services.calculator import DiscountCalculator
|
||||
|
||||
try:
|
||||
cart_total = request.GET.get('cart_total', '0')
|
||||
cart_total = Decimal(str(cart_total))
|
||||
|
||||
# Получаем НЕ автоматические скидки на заказ для ручного применения
|
||||
order_discounts = DiscountCalculator.get_available_discounts(
|
||||
scope='order',
|
||||
auto_only=False
|
||||
).filter(is_auto=False)
|
||||
|
||||
# Фильтруем по мин. сумме заказа
|
||||
result_discounts = []
|
||||
for d in order_discounts:
|
||||
if d.min_order_amount and cart_total < d.min_order_amount:
|
||||
continue
|
||||
result_discounts.append({
|
||||
'id': d.id,
|
||||
'name': d.name,
|
||||
'discount_type': d.discount_type,
|
||||
'value': float(d.value),
|
||||
'min_order_amount': float(d.min_order_amount) if d.min_order_amount else None
|
||||
})
|
||||
|
||||
# Получаем автоматическую скидку (только одну для отображения)
|
||||
auto_discounts = DiscountCalculator.get_available_discounts(
|
||||
scope='order',
|
||||
auto_only=True
|
||||
)
|
||||
|
||||
auto_discount_data = None
|
||||
for d in auto_discounts:
|
||||
if d.min_order_amount and cart_total < d.min_order_amount:
|
||||
continue
|
||||
# Рассчитываем сумму
|
||||
discount_amount = d.calculate_discount_amount(cart_total)
|
||||
auto_discount_data = {
|
||||
'id': d.id,
|
||||
'name': d.name,
|
||||
'discount_type': d.discount_type,
|
||||
'value': float(d.value),
|
||||
'discount_amount': float(discount_amount)
|
||||
}
|
||||
break
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'order_discounts': result_discounts,
|
||||
'auto_order_discount': auto_discount_data
|
||||
})
|
||||
|
||||
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