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 appliedPromoCode = null; // примененный промокод
|
||||
let cartDiscounts = {
|
||||
orderDiscount: null, // скидка на заказ
|
||||
itemDiscounts: [], // скидки на позиции
|
||||
totalDiscount: 0 // общая сумма скидки
|
||||
};
|
||||
|
||||
// При открытии модалки checkout
|
||||
document.getElementById('checkoutModal').addEventListener('show.bs.modal', () => {
|
||||
document.getElementById('checkoutModal').addEventListener('show.bs.modal', async () => {
|
||||
const customer = selectedCustomer || SYSTEM_CUSTOMER;
|
||||
const walletBalance = customer.wallet_balance || 0;
|
||||
|
||||
// Сбрасываем скидки
|
||||
resetDiscounts();
|
||||
|
||||
// Показываем баланс кошелька
|
||||
const walletDiv = document.getElementById('checkoutWalletBalance');
|
||||
if (customer.id !== SYSTEM_CUSTOMER.id) {
|
||||
@@ -2312,11 +2323,16 @@ document.getElementById('checkoutModal').addEventListener('show.bs.modal', () =>
|
||||
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
|
||||
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 }
|
||||
});
|
||||
});
|
||||
@@ -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) {
|
||||
const paymentMethods = [
|
||||
{ id: 1, code: 'account_balance', name: 'С баланса счёта' },
|
||||
@@ -2413,7 +2628,8 @@ async function handleCheckoutSubmit(paymentsData) {
|
||||
return itemData;
|
||||
}),
|
||||
payments: paymentsData,
|
||||
notes: document.getElementById('orderNote').value.trim()
|
||||
notes: document.getElementById('orderNote').value.trim(),
|
||||
promo_code: appliedPromoCode || null
|
||||
};
|
||||
|
||||
// Отправляем на сервер
|
||||
|
||||
Reference in New Issue
Block a user