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:
@@ -2292,11 +2292,22 @@ function renderCheckoutModal() {
|
|||||||
|
|
||||||
let paymentWidget = null;
|
let paymentWidget = null;
|
||||||
|
|
||||||
|
// Переменные состояния скидок
|
||||||
|
let appliedPromoCode = null; // примененный промокод
|
||||||
|
let cartDiscounts = {
|
||||||
|
orderDiscount: null, // скидка на заказ
|
||||||
|
itemDiscounts: [], // скидки на позиции
|
||||||
|
totalDiscount: 0 // общая сумма скидки
|
||||||
|
};
|
||||||
|
|
||||||
// При открытии модалки checkout
|
// При открытии модалки checkout
|
||||||
document.getElementById('checkoutModal').addEventListener('show.bs.modal', () => {
|
document.getElementById('checkoutModal').addEventListener('show.bs.modal', async () => {
|
||||||
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
||||||
const walletBalance = customer.wallet_balance || 0;
|
const walletBalance = customer.wallet_balance || 0;
|
||||||
|
|
||||||
|
// Сбрасываем скидки
|
||||||
|
resetDiscounts();
|
||||||
|
|
||||||
// Показываем баланс кошелька
|
// Показываем баланс кошелька
|
||||||
const walletDiv = document.getElementById('checkoutWalletBalance');
|
const walletDiv = document.getElementById('checkoutWalletBalance');
|
||||||
if (customer.id !== SYSTEM_CUSTOMER.id) {
|
if (customer.id !== SYSTEM_CUSTOMER.id) {
|
||||||
@@ -2312,11 +2323,16 @@ document.getElementById('checkoutModal').addEventListener('show.bs.modal', () =>
|
|||||||
totalAmount += item.qty * item.price;
|
totalAmount += item.qty * item.price;
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('checkoutFinalPrice').textContent = formatMoney(totalAmount) + ' руб.';
|
// Проверяем автоматические скидки
|
||||||
|
await checkAutoDiscounts();
|
||||||
|
|
||||||
|
// Применяем скидки к итоговой сумме
|
||||||
|
const finalTotal = Math.max(0, totalAmount - cartDiscounts.totalDiscount);
|
||||||
|
document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalTotal) + ' руб.';
|
||||||
|
|
||||||
// Инициализируем виджет в single mode
|
// Инициализируем виджет в single mode
|
||||||
initPaymentWidget('single', {
|
initPaymentWidget('single', {
|
||||||
order: { total: totalAmount, amount_due: totalAmount, amount_paid: 0 },
|
order: { total: finalTotal, amount_due: finalTotal, amount_paid: 0 },
|
||||||
customer: { id: customer.id, name: customer.name, wallet_balance: walletBalance }
|
customer: { id: customer.id, name: customer.name, wallet_balance: walletBalance }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2345,6 +2361,205 @@ function reinitPaymentWidget(mode) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== ФУНКЦИИ ДЛЯ РАБОТЫ СО СКИДКАМИ =====
|
||||||
|
|
||||||
|
// Сброс скидок
|
||||||
|
function resetDiscounts() {
|
||||||
|
appliedPromoCode = null;
|
||||||
|
cartDiscounts = {
|
||||||
|
orderDiscount: null,
|
||||||
|
itemDiscounts: [],
|
||||||
|
totalDiscount: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сбрасываем UI
|
||||||
|
document.getElementById('promoCodeInput').value = '';
|
||||||
|
document.getElementById('promoCodeError').style.display = 'none';
|
||||||
|
document.getElementById('promoCodeError').textContent = '';
|
||||||
|
document.getElementById('promoCodeSuccess').style.display = 'none';
|
||||||
|
document.getElementById('promoCodeSuccess').textContent = '';
|
||||||
|
document.getElementById('removePromoBtn').style.display = 'none';
|
||||||
|
document.getElementById('autoDiscounts').style.display = 'none';
|
||||||
|
document.getElementById('autoDiscountsText').textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить автоматические скидки
|
||||||
|
async function checkAutoDiscounts() {
|
||||||
|
try {
|
||||||
|
const items = Array.from(cart.values()).map(item => ({
|
||||||
|
type: item.type,
|
||||||
|
id: item.id,
|
||||||
|
quantity: item.qty,
|
||||||
|
price: item.price
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
||||||
|
|
||||||
|
const response = await fetch('/pos/api/discounts/calculate/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
items: items,
|
||||||
|
customer_id: customer.id !== SYSTEM_CUSTOMER.id ? customer.id : null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Сохраняем скидки
|
||||||
|
cartDiscounts.totalDiscount = result.total_discount || 0;
|
||||||
|
cartDiscounts.orderDiscount = result.order_discount;
|
||||||
|
|
||||||
|
// Показываем автоматические скидки
|
||||||
|
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) {
|
||||||
|
console.error('Ошибка при проверке автоматических скидок:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применить промокод
|
||||||
|
async function applyPromoCode() {
|
||||||
|
const code = document.getElementById('promoCodeInput').value.trim().toUpperCase();
|
||||||
|
if (!code) return;
|
||||||
|
|
||||||
|
// Вычисляем сумму корзины
|
||||||
|
let cartTotal = 0;
|
||||||
|
cart.forEach((item) => {
|
||||||
|
cartTotal += item.qty * item.price;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/pos/api/discounts/validate-promo/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
promo_code: code,
|
||||||
|
cart_total: cartTotal
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
appliedPromoCode = result.promo_code;
|
||||||
|
|
||||||
|
// Пересчитываем скидки с промокодом
|
||||||
|
await recalculateDiscountsWithPromo(code);
|
||||||
|
|
||||||
|
// Показываем успех
|
||||||
|
document.getElementById('promoCodeSuccess').textContent =
|
||||||
|
`Скидка: ${result.promo_code.discount_name} (${result.promo_code.discount_type === 'percentage' ? result.promo_code.discount_value + '%' : result.promo_code.discount_value + ' руб.'})`;
|
||||||
|
document.getElementById('promoCodeSuccess').style.display = 'block';
|
||||||
|
document.getElementById('promoCodeError').style.display = 'none';
|
||||||
|
document.getElementById('removePromoBtn').style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Обновляем итоговую сумму
|
||||||
|
updateCheckoutTotalWithDiscounts();
|
||||||
|
} else {
|
||||||
|
// Показываем ошибку
|
||||||
|
document.getElementById('promoCodeError').textContent = result.error || 'Неверный промокод';
|
||||||
|
document.getElementById('promoCodeError').style.display = 'block';
|
||||||
|
document.getElementById('promoCodeSuccess').style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при применении промокода:', error);
|
||||||
|
document.getElementById('promoCodeError').textContent = 'Ошибка при проверке промокода';
|
||||||
|
document.getElementById('promoCodeError').style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пересчитать скидки с промокодом
|
||||||
|
async function recalculateDiscountsWithPromo(promoCode) {
|
||||||
|
try {
|
||||||
|
const items = Array.from(cart.values()).map(item => ({
|
||||||
|
type: item.type,
|
||||||
|
id: item.id,
|
||||||
|
quantity: item.qty,
|
||||||
|
price: item.price
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
||||||
|
|
||||||
|
const response = await fetch('/pos/api/discounts/calculate/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
items: items,
|
||||||
|
promo_code: promoCode,
|
||||||
|
customer_id: customer.id !== SYSTEM_CUSTOMER.id ? customer.id : null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
cartDiscounts.totalDiscount = result.total_discount || 0;
|
||||||
|
cartDiscounts.orderDiscount = result.order_discount;
|
||||||
|
cartDiscounts.itemDiscounts = result.item_discounts || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при пересчёте скидок:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновить итоговую сумму с учётом скидок
|
||||||
|
function updateCheckoutTotalWithDiscounts() {
|
||||||
|
let cartTotal = 0;
|
||||||
|
cart.forEach((item) => {
|
||||||
|
cartTotal += item.qty * item.price;
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalTotal = Math.max(0, cartTotal - cartDiscounts.totalDiscount);
|
||||||
|
document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalTotal) + ' руб.';
|
||||||
|
|
||||||
|
// Пересоздаём платёжный виджет с новой суммой
|
||||||
|
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
||||||
|
initPaymentWidget('single', {
|
||||||
|
order: { total: finalTotal, amount_due: finalTotal, amount_paid: 0 },
|
||||||
|
customer: { id: customer.id, name: customer.name, wallet_balance: customer.wallet_balance || 0 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удалить промокод
|
||||||
|
function removePromoCode() {
|
||||||
|
appliedPromoCode = null;
|
||||||
|
|
||||||
|
// Пересчитываем без промокода
|
||||||
|
recalculateDiscountsWithPromo(null).then(() => {
|
||||||
|
document.getElementById('promoCodeInput').value = '';
|
||||||
|
document.getElementById('removePromoBtn').style.display = 'none';
|
||||||
|
document.getElementById('promoCodeSuccess').style.display = 'none';
|
||||||
|
updateCheckoutTotalWithDiscounts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики кнопок промокода
|
||||||
|
document.getElementById('applyPromoBtn').addEventListener('click', applyPromoCode);
|
||||||
|
|
||||||
|
document.getElementById('removePromoBtn').addEventListener('click', removePromoCode);
|
||||||
|
|
||||||
|
document.getElementById('promoCodeInput').addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
applyPromoCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function initPaymentWidget(mode, data) {
|
async function initPaymentWidget(mode, data) {
|
||||||
const paymentMethods = [
|
const paymentMethods = [
|
||||||
{ id: 1, code: 'account_balance', name: 'С баланса счёта' },
|
{ id: 1, code: 'account_balance', name: 'С баланса счёта' },
|
||||||
@@ -2413,7 +2628,8 @@ async function handleCheckoutSubmit(paymentsData) {
|
|||||||
return itemData;
|
return itemData;
|
||||||
}),
|
}),
|
||||||
payments: paymentsData,
|
payments: paymentsData,
|
||||||
notes: document.getElementById('orderNote').value.trim()
|
notes: document.getElementById('orderNote').value.trim(),
|
||||||
|
promo_code: appliedPromoCode || null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Отправляем на сервер
|
// Отправляем на сервер
|
||||||
|
|||||||
@@ -345,6 +345,29 @@
|
|||||||
<div class="fw-bold text-success fs-5" id="checkoutFinalPrice">0.00 руб.</div>
|
<div class="fw-bold text-success fs-5" id="checkoutFinalPrice">0.00 руб.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Скидки -->
|
||||||
|
<div class="mb-2 pb-2 border-bottom">
|
||||||
|
<small class="text-muted">Скидки:</small>
|
||||||
|
<!-- Автоматические скидки -->
|
||||||
|
<div id="autoDiscounts" class="small text-success mb-1" style="display: none;">
|
||||||
|
<i class="bi bi-tag-fill"></i>
|
||||||
|
<span id="autoDiscountsText"></span>
|
||||||
|
</div>
|
||||||
|
<!-- Промокод -->
|
||||||
|
<div class="input-group input-group-sm mt-1">
|
||||||
|
<input type="text" class="form-control" id="promoCodeInput"
|
||||||
|
placeholder="Промокод" style="text-transform: uppercase;">
|
||||||
|
<button class="btn btn-outline-secondary" type="button" id="applyPromoBtn">
|
||||||
|
<i class="bi bi-check-lg"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger" type="button" id="removePromoBtn" style="display: none;">
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
|
|
||||||
<!-- Переключатель режима -->
|
<!-- Переключатель режима -->
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label fw-semibold small mb-1">Режим оплаты</label>
|
<label class="form-label fw-semibold small mb-1">Режим оплаты</label>
|
||||||
|
|||||||
@@ -37,4 +37,11 @@ urlpatterns = [
|
|||||||
path('api/create-temp-kit/', views.create_temp_kit_to_showcase, name='create-temp-kit-api'),
|
path('api/create-temp-kit/', views.create_temp_kit_to_showcase, name='create-temp-kit-api'),
|
||||||
# Создать заказ и провести оплату в POS [POST]
|
# Создать заказ и провести оплату в POS [POST]
|
||||||
path('api/checkout/', views.pos_checkout, name='pos-checkout'),
|
path('api/checkout/', views.pos_checkout, name='pos-checkout'),
|
||||||
|
# ============================================
|
||||||
|
# DISCOUNT API
|
||||||
|
# ============================================
|
||||||
|
# Валидировать промокод [POST]
|
||||||
|
path('api/discounts/validate-promo/', views.validate_promo_code, name='validate-promo'),
|
||||||
|
# Рассчитать скидки для корзины [POST]
|
||||||
|
path('api/discounts/calculate/', views.calculate_cart_discounts, name='calculate-discounts'),
|
||||||
]
|
]
|
||||||
@@ -1400,7 +1400,8 @@ def pos_checkout(request):
|
|||||||
{"payment_method": "cash"|"card"|"online"|"account_balance", "amount": float, "notes": str},
|
{"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
|
from orders.models import Order, OrderItem, OrderStatus
|
||||||
@@ -1421,6 +1422,7 @@ def pos_checkout(request):
|
|||||||
items_data = body.get('items', [])
|
items_data = body.get('items', [])
|
||||||
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') # Промокод для скидки
|
||||||
|
|
||||||
if not customer_id:
|
if not customer_id:
|
||||||
return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400)
|
return JsonResponse({'success': False, 'error': 'Не указан клиент'}, status=400)
|
||||||
@@ -1551,7 +1553,22 @@ def pos_checkout(request):
|
|||||||
order.refresh_from_db()
|
order.refresh_from_db()
|
||||||
order.calculate_total()
|
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 = []
|
payments_list = []
|
||||||
for payment_data in payments_data:
|
for payment_data in payments_data:
|
||||||
payments_list.append({
|
payments_list.append({
|
||||||
@@ -1652,3 +1669,149 @@ def create_order_draft(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)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 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