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:
2026-01-11 01:59:43 +03:00
parent 6313b8f6e7
commit 42d8c34e8c
5 changed files with 389 additions and 30 deletions

View File

@@ -214,6 +214,69 @@ class DiscountApplier:
# Пересчитываем # Пересчитываем
order.calculate_total() order.calculate_total()
@staticmethod
@transaction.atomic
def apply_manual_discount(order, discount, user=None):
"""
Применить скидку вручную к заказу.
Args:
order: Order
discount: Discount
user: CustomUser (применивший скидку)
Returns:
dict: {
'success': bool,
'discount_amount': Decimal,
'error': str
}
"""
from discounts.models import DiscountApplication
# Проверяем scope скидки
if discount.scope != 'order':
return {'success': False, 'error': 'Эта скидка не применяется к заказу'}
# Проверяем мин. сумму
if discount.min_order_amount and order.subtotal < discount.min_order_amount:
return {'success': False, 'error': f'Мин. сумма заказа: {discount.min_order_amount} руб.'}
# Удаляем предыдущую скидку на заказ
DiscountApplier._remove_order_discount_only(order)
# Рассчитываем сумму
discount_amount = discount.calculate_discount_amount(Decimal(order.subtotal))
# Применяем к заказу
order.applied_discount = discount
order.discount_amount = discount_amount
order.save(update_fields=['applied_discount', 'discount_amount'])
# Пересчитываем total_amount
order.calculate_total()
# Создаем запись о применении
DiscountApplication.objects.create(
order=order,
discount=discount,
target='order',
base_amount=order.subtotal,
discount_amount=discount_amount,
final_amount=order.subtotal - discount_amount,
customer=order.customer,
applied_by=user
)
# Увеличиваем счетчик использований скидки
discount.current_usage_count += 1
discount.save(update_fields=['current_usage_count'])
return {
'success': True,
'discount_amount': discount_amount
}
@staticmethod @staticmethod
def _remove_order_discount_only(order): def _remove_order_discount_only(order):
""" """

View File

@@ -2294,6 +2294,8 @@ let paymentWidget = null;
// Переменные состояния скидок // Переменные состояния скидок
let appliedPromoCode = null; // примененный промокод let appliedPromoCode = null; // примененный промокод
let appliedManualDiscount = null; // выбранная вручную скидка
let availableDiscounts = []; // список доступных скидок
let cartDiscounts = { let cartDiscounts = {
orderDiscount: null, // скидка на заказ orderDiscount: null, // скидка на заказ
itemDiscounts: [], // скидки на позиции itemDiscounts: [], // скидки на позиции
@@ -2326,6 +2328,9 @@ document.getElementById('checkoutModal').addEventListener('show.bs.modal', async
// Проверяем автоматические скидки // Проверяем автоматические скидки
await checkAutoDiscounts(); await checkAutoDiscounts();
// Загружаем доступные скидки для ручного выбора
await loadAvailableDiscounts();
// Применяем скидки к итоговой сумме // Применяем скидки к итоговой сумме
const finalTotal = Math.max(0, totalAmount - cartDiscounts.totalDiscount); const finalTotal = Math.max(0, totalAmount - cartDiscounts.totalDiscount);
document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalTotal) + ' руб.'; document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalTotal) + ' руб.';
@@ -2366,6 +2371,8 @@ function reinitPaymentWidget(mode) {
// Сброс скидок // Сброс скидок
function resetDiscounts() { function resetDiscounts() {
appliedPromoCode = null; appliedPromoCode = null;
appliedManualDiscount = null;
availableDiscounts = [];
cartDiscounts = { cartDiscounts = {
orderDiscount: null, orderDiscount: null,
itemDiscounts: [], itemDiscounts: [],
@@ -2379,8 +2386,13 @@ function resetDiscounts() {
document.getElementById('promoCodeSuccess').style.display = 'none'; document.getElementById('promoCodeSuccess').style.display = 'none';
document.getElementById('promoCodeSuccess').textContent = ''; document.getElementById('promoCodeSuccess').textContent = '';
document.getElementById('removePromoBtn').style.display = 'none'; document.getElementById('removePromoBtn').style.display = 'none';
document.getElementById('autoDiscounts').style.display = 'none';
document.getElementById('autoDiscountsText').textContent = ''; // Новые элементы UI
document.getElementById('autoDiscountsContainer').style.display = 'none';
document.getElementById('autoDiscountsList').innerHTML = '';
document.getElementById('manualDiscountContainer').style.display = 'none';
document.getElementById('discountsSummary').style.display = 'none';
document.getElementById('itemDiscountsBreakdown').innerHTML = '';
} }
// Проверить автоматические скидки // Проверить автоматические скидки
@@ -2403,29 +2415,168 @@ async function checkAutoDiscounts() {
}, },
body: JSON.stringify({ body: JSON.stringify({
items: items, items: items,
customer_id: customer.id !== SYSTEM_CUSTOMER.id ? customer.id : null customer_id: customer.id !== SYSTEM_CUSTOMER.id ? customer.id : null,
manual_discount_id: appliedManualDiscount?.id || null
}) })
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
// Сохраняем скидки
cartDiscounts.totalDiscount = result.total_discount || 0; cartDiscounts.totalDiscount = result.total_discount || 0;
cartDiscounts.orderDiscount = result.order_discount; cartDiscounts.orderDiscount = result.order_discount;
cartDiscounts.itemDiscounts = result.item_discounts || [];
// Показываем автоматические скидки updateDiscountsUI(result);
if (result.order_discount && result.order_discount.discount_id) {
document.getElementById('autoDiscounts').style.display = 'block';
document.getElementById('autoDiscountsText').textContent =
`${result.order_discount.discount_name}: -${result.order_discount.discount_amount.toFixed(2)} руб.`;
}
} }
} catch (error) { } catch (error) {
console.error('Ошибка при проверке автоматических скидок:', error); console.error('Ошибка при проверке автоматических скидок:', error);
} }
} }
// Обновить UI скидок
function updateDiscountsUI(result) {
const autoContainer = document.getElementById('autoDiscountsContainer');
const autoList = document.getElementById('autoDiscountsList');
const summaryDiv = document.getElementById('discountsSummary');
const itemBreakdown = document.getElementById('itemDiscountsBreakdown');
// Очистка
autoList.innerHTML = '';
itemBreakdown.innerHTML = '';
let hasDiscounts = false;
// 1. Скидка на заказ (автоматическая)
if (result.order_discount && result.order_discount.discount_id) {
hasDiscounts = true;
autoContainer.style.display = 'block';
const div = document.createElement('div');
div.className = 'd-flex justify-content-between';
div.innerHTML = `
<span>${result.order_discount.discount_name}</span>
<span class="text-success">-${result.order_discount.discount_amount.toFixed(2)} руб.</span>
`;
autoList.appendChild(div);
} else {
autoContainer.style.display = 'none';
}
// 2. Скидки на позиции
if (result.item_discounts && result.item_discounts.length > 0) {
hasDiscounts = true;
result.item_discounts.forEach(item => {
const div = document.createElement('div');
div.className = 'text-muted';
div.innerHTML = `${item.discount_name}: -${item.discount_amount.toFixed(2)} руб.`;
itemBreakdown.appendChild(div);
});
}
// 3. Ручная скидка
if (appliedManualDiscount) {
hasDiscounts = true;
document.getElementById('manualDiscountContainer').style.display = 'block';
document.getElementById('manualDiscountName').textContent = appliedManualDiscount.name;
document.getElementById('manualDiscountAmount').textContent =
`-${appliedManualDiscount.amount.toFixed(2)} руб.`;
} else {
document.getElementById('manualDiscountContainer').style.display = 'none';
}
// Показываем/скрываем summary
if (hasDiscounts) {
summaryDiv.style.display = 'block';
document.getElementById('discountsSubtotal').textContent =
(result.cart_subtotal || 0).toFixed(2) + ' руб.';
document.getElementById('discountsTotalDiscount').textContent =
'-' + (result.total_discount || 0).toFixed(2) + ' руб.';
} else {
summaryDiv.style.display = 'none';
}
// Обновляем итоговую цену
const finalTotal = Math.max(0, (result.cart_subtotal || 0) - (result.total_discount || 0));
document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalTotal) + ' руб.';
// Пересчитываем виджет оплаты
reinitPaymentWidget(document.getElementById('singlePaymentMode').classList.contains('active') ? 'single' : 'mixed');
}
// Загрузить доступные скидки
async function loadAvailableDiscounts() {
try {
let cartTotal = 0;
cart.forEach((item) => {
cartTotal += item.qty * item.price;
});
const response = await fetch(`/pos/api/discounts/available/?cart_total=${cartTotal}`);
const result = await response.json();
if (result.success) {
availableDiscounts = result.order_discounts;
renderDiscountsDropdown(result.order_discounts);
}
} catch (error) {
console.error('Ошибка загрузки скидок:', error);
}
}
// Отобразить список скидок в dropdown
function renderDiscountsDropdown(discounts) {
const list = document.getElementById('discountsDropdownList');
list.innerHTML = '';
if (discounts.length === 0) {
list.innerHTML = '<li><span class="dropdown-item-text small text-muted">Нет доступных скидок</span></li>';
return;
}
discounts.forEach(d => {
const li = document.createElement('li');
const valueText = d.discount_type === 'percentage' ? `${d.value}%` : `${d.value} руб.`;
const minText = d.min_order_amount ? `(от ${d.min_order_amount} руб.)` : '';
const a = document.createElement('a');
a.href = '#';
a.className = 'dropdown-item d-flex justify-content-between';
a.innerHTML = `
<span>${d.name}</span>
<span class="text-success">${valueText} ${minText}</span>
`;
a.onclick = (e) => {
e.preventDefault();
applyManualDiscount(d);
};
li.appendChild(a);
list.appendChild(li);
});
}
// Применить скидку вручную
async function applyManualDiscount(discount) {
// Рассчитываем сумму скидки на клиенте (для отображения до ответа сервера)
let cartTotal = 0;
cart.forEach((item) => {
cartTotal += item.qty * item.price;
});
let discountAmount = discount.discount_type === 'percentage'
? cartTotal * (discount.value / 100)
: discount.value;
appliedManualDiscount = { ...discount, amount: discountAmount };
await checkAutoDiscounts();
await loadAvailableDiscounts();
}
// Удалить ручную скидку
document.getElementById('removeManualDiscountBtn').addEventListener('click', async () => {
appliedManualDiscount = null;
await checkAutoDiscounts();
await loadAvailableDiscounts();
});
// Применить промокод // Применить промокод
async function applyPromoCode() { async function applyPromoCode() {
const code = document.getElementById('promoCodeInput').value.trim().toUpperCase(); const code = document.getElementById('promoCodeInput').value.trim().toUpperCase();
@@ -2455,7 +2606,7 @@ async function applyPromoCode() {
if (result.success) { if (result.success) {
appliedPromoCode = result.promo_code; appliedPromoCode = result.promo_code;
// Пересчитываем скидки с промокодом // Пересчитываем скидки с промокодом (updateDiscountsUI обновит UI)
await recalculateDiscountsWithPromo(code); await recalculateDiscountsWithPromo(code);
// Показываем успех // Показываем успех
@@ -2464,9 +2615,6 @@ async function applyPromoCode() {
document.getElementById('promoCodeSuccess').style.display = 'block'; document.getElementById('promoCodeSuccess').style.display = 'block';
document.getElementById('promoCodeError').style.display = 'none'; document.getElementById('promoCodeError').style.display = 'none';
document.getElementById('removePromoBtn').style.display = 'inline-block'; document.getElementById('removePromoBtn').style.display = 'inline-block';
// Обновляем итоговую сумму
updateCheckoutTotalWithDiscounts();
} else { } else {
// Показываем ошибку // Показываем ошибку
document.getElementById('promoCodeError').textContent = result.error || 'Неверный промокод'; document.getElementById('promoCodeError').textContent = result.error || 'Неверный промокод';
@@ -2511,6 +2659,7 @@ async function recalculateDiscountsWithPromo(promoCode) {
cartDiscounts.totalDiscount = result.total_discount || 0; cartDiscounts.totalDiscount = result.total_discount || 0;
cartDiscounts.orderDiscount = result.order_discount; cartDiscounts.orderDiscount = result.order_discount;
cartDiscounts.itemDiscounts = result.item_discounts || []; cartDiscounts.itemDiscounts = result.item_discounts || [];
updateDiscountsUI(result);
} }
} catch (error) { } catch (error) {
console.error('Ошибка при пересчёте скидок:', error); console.error('Ошибка при пересчёте скидок:', error);
@@ -2539,12 +2688,11 @@ function updateCheckoutTotalWithDiscounts() {
function removePromoCode() { function removePromoCode() {
appliedPromoCode = null; appliedPromoCode = null;
// Пересчитываем без промокода // Пересчитываем без промокода (updateDiscountsUI обновит UI)
recalculateDiscountsWithPromo(null).then(() => { recalculateDiscountsWithPromo(null).then(() => {
document.getElementById('promoCodeInput').value = ''; document.getElementById('promoCodeInput').value = '';
document.getElementById('removePromoBtn').style.display = 'none'; document.getElementById('removePromoBtn').style.display = 'none';
document.getElementById('promoCodeSuccess').style.display = 'none'; document.getElementById('promoCodeSuccess').style.display = 'none';
updateCheckoutTotalWithDiscounts();
}); });
} }
@@ -2629,7 +2777,8 @@ async function handleCheckoutSubmit(paymentsData) {
}), }),
payments: paymentsData, payments: paymentsData,
notes: document.getElementById('orderNote').value.trim(), notes: document.getElementById('orderNote').value.trim(),
promo_code: appliedPromoCode || null promo_code: appliedPromoCode?.code || null,
manual_discount_id: appliedManualDiscount?.id || null
}; };
// Отправляем на сервер // Отправляем на сервер

View File

@@ -282,18 +282,18 @@
<!-- Модалка: Продажа --> <!-- Модалка: Продажа -->
<div class="modal fade" id="checkoutModal" tabindex="-1" aria-labelledby="checkoutModalLabel" aria-hidden="true"> <div class="modal fade" id="checkoutModal" tabindex="-1" aria-labelledby="checkoutModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" style="max-width: 70vw;"> <div class="modal-dialog modal-dialog-centered modal-xl" style="max-width: 85vw;">
<div class="modal-content" style="max-height: 90vh; overflow: hidden;"> <div class="modal-content">
<div class="modal-header py-2"> <div class="modal-header py-2">
<h5 class="modal-title" id="checkoutModalLabel"> <h5 class="modal-title" id="checkoutModalLabel">
<i class="bi bi-cash-stack"></i> Продажа <i class="bi bi-cash-stack"></i> Продажа
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body py-2" style="overflow-y: auto;"> <div class="modal-body py-2">
<div class="row g-3"> <div class="row g-3">
<!-- Левая колонка: состав заказа --> <!-- Левая колонка: состав заказа -->
<div class="col-md-7"> <div class="col-md-5">
<!-- Информация о клиенте и баланс в одной строке --> <!-- Информация о клиенте и баланс в одной строке -->
<div class="row g-2 mb-2"> <div class="row g-2 mb-2">
<div class="col-auto"> <div class="col-auto">
@@ -320,7 +320,7 @@
<!-- Состав заказа --> <!-- Состав заказа -->
<div class="mb-2"> <div class="mb-2">
<strong class="small">Состав заказа</strong> <strong class="small">Состав заказа</strong>
<div class="border rounded p-2 mt-1" id="checkoutItems" style="max-height: 180px; overflow-y: auto; background: #f8f9fa; font-size: 0.9rem;"> <div class="border rounded p-2 mt-1" id="checkoutItems" style="background: #f8f9fa; font-size: 0.9rem;">
<!-- Заполняется из JS --> <!-- Заполняется из JS -->
</div> </div>
</div> </div>
@@ -333,7 +333,7 @@
</div> </div>
<!-- Правая колонка: оплата --> <!-- Правая колонка: оплата -->
<div class="col-md-5"> <div class="col-md-7">
<div class="card mb-0"> <div class="card mb-0">
<div class="card-header bg-light py-2"> <div class="card-header bg-light py-2">
<strong class="small">Оплата</strong> <strong class="small">Оплата</strong>
@@ -347,14 +347,45 @@
<!-- Скидки --> <!-- Скидки -->
<div class="mb-2 pb-2 border-bottom"> <div class="mb-2 pb-2 border-bottom">
<small class="text-muted">Скидки:</small> <small class="text-muted d-block mb-1">Скидки</small>
<!-- Автоматические скидки -->
<div id="autoDiscounts" class="small text-success mb-1" style="display: none;"> <!-- Автоматические скидки (read-only) -->
<i class="bi bi-tag-fill"></i> <div id="autoDiscountsContainer" class="mb-2" style="display: none;">
<span id="autoDiscountsText"></span> <div class="alert alert-success py-1 px-2 mb-1" style="font-size: 0.85rem;">
<i class="bi bi-magic me-1"></i>
<strong>Автоматически:</strong>
<div id="autoDiscountsList"></div>
</div> </div>
</div>
<!-- Выбранная скидка вручную -->
<div id="manualDiscountContainer" class="mb-2" style="display: none;">
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-info text-dark">
<i class="bi bi-tag me-1"></i>
<span id="manualDiscountName"></span>
<span id="manualDiscountAmount" class="ms-1"></span>
</span>
<button type="button" class="btn btn-sm btn-link text-danger p-0" id="removeManualDiscountBtn">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<!-- Dropdown для выбора скидки -->
<div class="dropdown mb-2">
<button class="btn btn-sm btn-outline-primary w-100 dropdown-toggle" type="button"
id="selectDiscountBtn" data-bs-toggle="dropdown">
<i class="bi bi-plus-circle me-1"></i> Добавить скидку
</button>
<ul class="dropdown-menu w-100" id="discountsDropdownList">
<li><span class="dropdown-item-text small text-muted">Загрузка...</span></li>
</ul>
</div>
<!-- Промокод --> <!-- Промокод -->
<div class="input-group input-group-sm mt-1"> <div class="input-group input-group-sm">
<span class="input-group-text"><i class="bi bi-ticket-perforated"></i></span>
<input type="text" class="form-control" id="promoCodeInput" <input type="text" class="form-control" id="promoCodeInput"
placeholder="Промокод" style="text-transform: uppercase;"> placeholder="Промокод" style="text-transform: uppercase;">
<button class="btn btn-outline-secondary" type="button" id="applyPromoBtn"> <button class="btn btn-outline-secondary" type="button" id="applyPromoBtn">
@@ -366,6 +397,21 @@
</div> </div>
<div id="promoCodeError" class="text-danger small mt-1" style="display: none;"></div> <div id="promoCodeError" class="text-danger small mt-1" style="display: none;"></div>
<div id="promoCodeSuccess" class="text-success small mt-1" style="display: none;"></div> <div id="promoCodeSuccess" class="text-success small mt-1" style="display: none;"></div>
<!-- Итоговая информация по скидкам -->
<div id="discountsSummary" class="mt-2 pt-2 border-top" style="display: none;">
<div class="d-flex justify-content-between small text-muted">
<span>Подитог:</span>
<span id="discountsSubtotal">0.00 руб.</span>
</div>
<div class="d-flex justify-content-between small text-success">
<span>Общая скидка:</span>
<span id="discountsTotalDiscount">-0.00 руб.</span>
</div>
<div id="itemDiscountsBreakdown" class="mt-1 small text-muted">
<!-- Скидки на позиции -->
</div>
</div>
</div> </div>
<!-- Переключатель режима --> <!-- Переключатель режима -->

View File

@@ -44,4 +44,6 @@ urlpatterns = [
path('api/discounts/validate-promo/', views.validate_promo_code, name='validate-promo'), path('api/discounts/validate-promo/', views.validate_promo_code, name='validate-promo'),
# Рассчитать скидки для корзины [POST] # Рассчитать скидки для корзины [POST]
path('api/discounts/calculate/', views.calculate_cart_discounts, name='calculate-discounts'), path('api/discounts/calculate/', views.calculate_cart_discounts, name='calculate-discounts'),
# Получить список доступных скидок [GET]
path('api/discounts/available/', views.get_available_discounts, name='available-discounts'),
] ]

View File

@@ -1402,6 +1402,7 @@ def pos_checkout(request):
], ],
"notes": str (optional), "notes": str (optional),
"promo_code": str (optional) - Промокод для скидки "promo_code": str (optional) - Промокод для скидки
"manual_discount_id": int (optional) - ID выбранной вручную скидки
} }
""" """
from orders.models import Order, OrderItem, OrderStatus from orders.models import Order, OrderItem, OrderStatus
@@ -1423,6 +1424,7 @@ def pos_checkout(request):
payments_data = body.get('payments', []) payments_data = body.get('payments', [])
order_notes = body.get('notes', '') order_notes = body.get('notes', '')
promo_code = body.get('promo_code') # Промокод для скидки promo_code = body.get('promo_code') # Промокод для скидки
manual_discount_id = body.get('manual_discount_id') # ID выбранной вручную скидки
if not customer_id: if not customer_id:
return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400) return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400)
@@ -1554,7 +1556,22 @@ def pos_checkout(request):
order.calculate_total() order.calculate_total()
# 4. Применяем скидки # 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 from discounts.services.applier import DiscountApplier
apply_result = DiscountApplier.apply_promo_code( apply_result = DiscountApplier.apply_promo_code(
order=order, order=order,
@@ -1815,3 +1832,85 @@ def calculate_cart_discounts(request):
except Exception as e: except Exception as e:
logger.error(f'Ошибка при расчете скидок: {str(e)}', exc_info=True) logger.error(f'Ошибка при расчете скидок: {str(e)}', exc_info=True)
return JsonResponse({'success': False, 'error': f'Ошибка: {str(e)}'}, status=500) 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)