diff --git a/myproject/orders/static/orders/js/unified_transaction_form.js b/myproject/orders/static/orders/js/unified_transaction_form.js new file mode 100644 index 0000000..4a710c2 --- /dev/null +++ b/myproject/orders/static/orders/js/unified_transaction_form.js @@ -0,0 +1,218 @@ +/** + * Unified Transaction Form Module + * Переиспользуемый модуль для формы платежей/возвратов + */ + +(function(window) { + 'use strict'; + + /** + * Инициализирует унифицированную форму транзакций + * @param {Element|string} formElement - DOM элемент формы или селектор + * @param {Object} options - Настройки + * @param {string} options.paymentUrl - URL для платежей + * @param {string} options.refundUrl - URL для возвратов + * @param {number} options.amountDue - Сумма к оплате + * @param {number} options.amountPaid - Сумма оплаченного + * @param {number} options.walletBalance - Баланс кошелька клиента + */ + function initUnifiedTransactionForm(formElement, options) { + var form = typeof formElement === 'string' + ? document.querySelector(formElement) + : formElement; + + if (!form) { + console.error('[UnifiedTransactionForm] Form not found'); + return; + } + + options = options || {}; + + // URLs из опций или data-атрибутов + var paymentUrl = options.paymentUrl || form.dataset.paymentUrl; + var refundUrl = options.refundUrl || form.dataset.refundUrl; + + // Данные из опций или data-атрибутов + var amountDue = options.amountDue !== undefined + ? parseFloat(options.amountDue) + : parseFloat(form.dataset.amountDue || 0); + + var amountPaid = options.amountPaid !== undefined + ? parseFloat(options.amountPaid) + : parseFloat(form.dataset.amountPaid || 0); + + var walletBalance = options.walletBalance !== undefined + ? parseFloat(options.walletBalance) + : parseFloat(form.dataset.walletBalance || 0); + + // Элементы формы + var modeButtons = form.querySelectorAll('#mode-toggle [data-mode]'); + var methodButtons = form.querySelectorAll('.method-btn'); + var methodHidden = form.querySelector('#unified-method-id'); + + var amountInput = form.querySelector('#unified-amount'); + var amountHint = form.querySelector('#amount-hint'); + var amountMaxSpan = form.querySelector('#amount-max'); + var walletEmptyHint = form.querySelector('#wallet-empty-hint'); + + var refundReasonWrap = form.querySelector('#refund-reason-wrap'); + var refundReasonInput = form.querySelector('#refund-reason'); + var notesInput = form.querySelector('#unified-notes'); + var submitBtn = form.querySelector('#unified-submit'); + + // Состояние + var currentMode = 'payment'; // режим по умолчанию — Платёж + var currentMethodCode = null; + + // Применить режим + function applyMode() { + if (currentMode === 'payment') { + form.action = paymentUrl; + methodHidden.name = 'payment_method'; + amountInput.name = 'amount'; + notesInput.name = 'notes'; + refundReasonWrap.style.display = 'none'; + refundReasonInput.removeAttribute('name'); + refundReasonInput.removeAttribute('required'); + + submitBtn.classList.remove('btn-warning', 'text-dark'); + submitBtn.classList.add('btn-success'); + submitBtn.innerHTML = ' Оплатить'; + + updateLimitsForPayment(); + } else { + form.action = refundUrl; + methodHidden.name = 'refund_payment_method'; + amountInput.name = 'refund_amount'; + notesInput.name = 'refund_notes'; + refundReasonWrap.style.display = 'block'; + refundReasonInput.name = 'refund_reason'; + refundReasonInput.setAttribute('required', 'required'); + + submitBtn.classList.remove('btn-success'); + submitBtn.classList.add('btn-warning', 'text-dark'); + submitBtn.innerHTML = ' Вернуть средства'; + + updateLimitsForRefund(); + } + } + + // Лимиты для платежа (кошелёк ограничивается балансом и суммой к оплате) + function updateLimitsForPayment() { + if (currentMethodCode === 'account_balance') { + var max = Math.min(amountDue, walletBalance); + amountInput.max = max; + amountHint.style.display = 'inline'; + amountMaxSpan.textContent = (max || 0).toFixed(2); + } else { + amountInput.removeAttribute('max'); + amountHint.style.display = 'none'; + } + } + + // Лимиты для возврата (всегда ограничиваем суммой оплаченного) + function updateLimitsForRefund() { + amountInput.max = amountPaid || 0; + amountHint.style.display = 'inline'; + amountMaxSpan.textContent = (amountPaid || 0).toFixed(2); + } + + // Выбор режима + modeButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + modeButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + currentMode = btn.dataset.mode; + + // Сбрасываем выбор способа и сумму при смене режима + methodButtons.forEach(function(b) { b.classList.remove('active'); }); + methodHidden.value = ''; + currentMethodCode = null; + amountInput.value = currentMode === 'payment' ? (amountDue || '') : ''; + + applyMode(); + }); + }); + + // Выбор способа + methodButtons.forEach(function(btn) { + btn.addEventListener('click', function() { + var methodCode = btn.dataset.code; + + // Скрываем подсказку + walletEmptyHint.style.display = 'none'; + + // Проверяем баланс кошелька + if (currentMode === 'payment' && methodCode === 'account_balance' && walletBalance === 0) { + walletEmptyHint.style.display = 'block'; + return; + } + + // Активируем выбранный способ + methodButtons.forEach(function(b) { b.classList.remove('active'); }); + btn.classList.add('active'); + methodHidden.value = btn.dataset.id; + currentMethodCode = methodCode; + + // Для кошелька подставляем максимальную сумму + if (currentMode === 'payment' && methodCode === 'account_balance') { + amountInput.value = Math.min(amountDue, walletBalance).toFixed(2); + } + + // Обновляем лимиты + currentMode === 'payment' ? updateLimitsForPayment() : updateLimitsForRefund(); + }); + }); + + // Инициализация начального режима + if (modeButtons.length > 0) { + modeButtons[0].classList.add('active'); + } + amountInput.value = amountDue || ''; + applyMode(); + + // Валидация перед отправкой формы + form.addEventListener('submit', function(e) { + // Проверяем выбран ли способ оплаты + if (!methodHidden.value) { + e.preventDefault(); + + // Подсвечиваем блок со способами оплаты + var methodsContainer = form.querySelector('#unified-methods'); + methodsContainer.classList.add('border', 'border-danger', 'rounded', 'p-2'); + + // Показываем подсказку + var existingHint = form.querySelector('#method-required-hint'); + if (existingHint) { + existingHint.remove(); + } + + var hint = document.createElement('small'); + hint.id = 'method-required-hint'; + hint.className = 'text-danger d-block mt-1'; + var messageText = currentMode === 'payment' ? 'способ оплаты' : 'способ возврата'; + hint.innerHTML = ' Пожалуйста, выберите ' + messageText; + methodsContainer.parentElement.appendChild(hint); + + // Убираем подсветку при выборе + var removeValidation = function() { + methodsContainer.classList.remove('border', 'border-danger', 'rounded', 'p-2'); + var hintEl = form.querySelector('#method-required-hint'); + if (hintEl) hintEl.remove(); + }; + + methodButtons.forEach(function(btn) { + btn.addEventListener('click', removeValidation, { once: true }); + }); + + return false; + } + }); + + console.log('[UnifiedTransactionForm] Initialized for form:', form.id || form); + } + + // Экспорт + window.initUnifiedTransactionForm = initUnifiedTransactionForm; + +})(window); diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html index d39c305..7e8a861 100644 --- a/myproject/orders/templates/orders/order_form.html +++ b/myproject/orders/templates/orders/order_form.html @@ -883,186 +883,21 @@ document.addEventListener('DOMContentLoaded', function() { }); + + +{% if order.pk %} +{% endif %}