diff --git a/myproject/customers/views.py b/myproject/customers/views.py index 505b422..c2e6ced 100644 --- a/myproject/customers/views.py +++ b/myproject/customers/views.py @@ -441,4 +441,47 @@ def api_create_customer(request): return JsonResponse({ 'success': False, 'error': f'Ошибка сервера: {str(e)}' - }, status=500) \ No newline at end of file + }, status=500) + + +@require_http_methods(["POST"]) +def api_create_system_customer(request): + """ + Создать или получить системного анонимного клиента для POS. + + Идентификаторы системного клиента: + - email: system@pos.customer + - name: АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS) + - loyalty_tier: 'no_discount' + - notes: 'SYSTEM_CUSTOMER' + + Поведение: + - Если клиент уже существует (по уникальному email), новый не создаётся. + - Если не существует — создаётся с указанными полями. + - Возвращает JSON с признаком, был ли создан новый клиент. + + Возвращаемый JSON: + { + "success": true, + "created": false, # или true, если впервые создан + "id": 123, + "name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)", + "email": "system@pos.customer" + } + """ + customer, created = Customer.objects.get_or_create( + email="system@pos.customer", + defaults={ + "name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)", + "loyalty_tier": "no_discount", + "notes": "SYSTEM_CUSTOMER", + }, + ) + + return JsonResponse({ + "success": True, + "created": created, + "id": customer.pk, + "name": customer.name, + "email": customer.email, + }) \ No newline at end of file diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index 3137985..69a1efe 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -934,9 +934,160 @@ document.getElementById('createTempKitModal').addEventListener('hidden.bs.modal' } }); -// Заглушки для функционала (будет реализовано позже) -document.getElementById('checkoutNow').onclick = async () => { - alert('Функционал будет подключен позже: создание заказа и списание со склада.'); +// Открытие модалки "Продажа" и рендер сводки корзины +document.getElementById('checkoutNow').onclick = () => { + if (cart.size === 0) { + alert('Корзина пуста. Добавьте товары перед продажей.'); + return; + } + renderCheckoutModal(); + const modal = new bootstrap.Modal(document.getElementById('checkoutModal')); + modal.show(); +}; + +// Рендер позиций корзины и итога в модалке продажи +function renderCheckoutModal() { + const container = document.getElementById('checkoutItems'); + container.innerHTML = ''; + let total = 0; + + cart.forEach((item) => { + const row = document.createElement('div'); + row.className = 'd-flex justify-content-between align-items-center mb-2 pb-2 border-bottom'; + + // Иконка для комплектов + let typeIcon = ''; + if (item.type === 'kit' || item.type === 'showcase_kit') { + typeIcon = ''; + } else { + typeIcon = ''; + } + + row.innerHTML = ` +
+
${typeIcon}${item.name}
+ ${item.qty} шт × ${formatMoney(item.price)} руб. +
+
${formatMoney(item.qty * item.price)} руб.
+ `; + container.appendChild(row); + total += item.qty * item.price; + }); + + // Обновляем базовую цену и пересчитываем + updateCheckoutPricing(total); +} + +// Пересчёт цен в модалке продажи +function updateCheckoutPricing(basePrice = null) { + // Если basePrice не передан, пересчитываем из корзины + if (basePrice === null) { + basePrice = 0; + cart.forEach((item) => { + basePrice += item.qty * item.price; + }); + } + + // Базовая сумма + document.getElementById('checkoutBasePrice').textContent = formatMoney(basePrice) + ' руб.'; + + // Скидка + const discountType = document.getElementById('discountType').value; + const discountValue = parseFloat(document.getElementById('discountValue').value) || 0; + + let discountedPrice = basePrice; + if (discountType !== 'none' && discountValue > 0) { + if (discountType === 'percent') { + discountedPrice = basePrice - (basePrice * discountValue / 100); + } else if (discountType === 'amount') { + discountedPrice = Math.max(0, basePrice - discountValue); + } + } + + document.getElementById('checkoutDiscountedPrice').textContent = formatMoney(discountedPrice) + ' руб.'; + + // Финальная цена (с учётом ручной суммы если задана) + const useManualPrice = document.getElementById('useManualPrice').checked; + const manualPrice = parseFloat(document.getElementById('manualPrice').value) || 0; + + let finalPrice = discountedPrice; + if (useManualPrice && manualPrice > 0) { + finalPrice = manualPrice; + } + + document.getElementById('checkoutFinalPrice').textContent = formatMoney(finalPrice); +} + +// Обработчики для полей скидки и цены +document.getElementById('discountType').addEventListener('change', function() { + const discountBlock = document.getElementById('discountValueBlock'); + if (this.value === 'none') { + discountBlock.style.display = 'none'; + document.getElementById('discountValue').value = '0'; + } else { + discountBlock.style.display = 'block'; + } + updateCheckoutPricing(); +}); + +document.getElementById('discountValue').addEventListener('input', function() { + updateCheckoutPricing(); +}); + +document.getElementById('useManualPrice').addEventListener('change', function() { + const manualPriceBlock = document.getElementById('manualPriceBlock'); + if (this.checked) { + manualPriceBlock.style.display = 'block'; + } else { + manualPriceBlock.style.display = 'none'; + document.getElementById('manualPrice').value = ''; + } + updateCheckoutPricing(); +}); + +document.getElementById('manualPrice').addEventListener('input', function() { + updateCheckoutPricing(); +}); + +// Подтверждение продажи (пока заглушка) +document.getElementById('confirmCheckoutBtn').onclick = () => { + const payment = document.getElementById('paymentMethod').value; + const note = document.getElementById('orderNote').value.trim(); + + const paymentText = { + 'cash': 'Наличные', + 'card': 'Карта', + 'mixed': 'Смешанная оплата' + }[payment] || payment; + + // Получаем данные о ценах и скидке + const basePrice = document.getElementById('checkoutBasePrice').textContent; + const discountType = document.getElementById('discountType').value; + const discountValue = document.getElementById('discountValue').value; + const finalPrice = document.getElementById('checkoutFinalPrice').textContent; + + let discountInfo = 'нет скидки'; + if (discountType === 'percent' && discountValue > 0) { + discountInfo = `скидка ${discountValue}%`; + } else if (discountType === 'amount' && discountValue > 0) { + discountInfo = `скидка ${discountValue} руб.`; + } + + const useManual = document.getElementById('useManualPrice').checked; + if (useManual) { + discountInfo += ' (установлена своя сумма)'; + } + + alert(`Функция проведения продажи будет подключена позже. + +Базовая сумма: ${basePrice} +Скидка: ${discountInfo} +Итого к оплате: ${finalPrice} руб. +Оплата: ${paymentText} +Комментарий: ${note || '—'}`); + + const modal = bootstrap.Modal.getInstance(document.getElementById('checkoutModal')); + modal.hide(); }; document.getElementById('scheduleLater').onclick = async () => { diff --git a/myproject/pos/templates/pos/terminal.html b/myproject/pos/templates/pos/terminal.html index e4dba33..cebf626 100644 --- a/myproject/pos/templates/pos/terminal.html +++ b/myproject/pos/templates/pos/terminal.html @@ -252,6 +252,113 @@ + + +