Исправлена проблема с сохранением платежей и автоматический пересчёт статуса оплаты
- Добавлен префикс 'payments' для PaymentFormSet во всех представлениях - Добавлен атрибут form='order-form' для динамически создаваемых полей платежей - Убрано переопределение has_changed() в PaymentForm (использует стандартную логику Django) - Автоматическая установка created_by для новых платежей - Автоматический пересчёт payment_status при изменении суммы заказа - Автоматическая обработка переплаты с возвратом в кошелёк клиента - Убран весь отладочный код
This commit is contained in:
@@ -510,22 +510,6 @@ class PaymentForm(forms.ModelForm):
|
|||||||
# Делаем notes опциональным
|
# Делаем notes опциональным
|
||||||
self.fields['notes'].required = False
|
self.fields['notes'].required = False
|
||||||
|
|
||||||
def has_changed(self):
|
|
||||||
"""
|
|
||||||
Переопределяем has_changed() чтобы formset не считал форму пустой.
|
|
||||||
Форма считается заполненной если указан payment_method ИЛИ amount.
|
|
||||||
"""
|
|
||||||
# Если есть ID - значит форма существует в БД, проверяем изменения стандартно
|
|
||||||
if self.instance and self.instance.pk:
|
|
||||||
return super().has_changed()
|
|
||||||
|
|
||||||
# Для новых форм: считаем заполненной если есть payment_method или amount
|
|
||||||
payment_method = self.cleaned_data.get('payment_method') if hasattr(self, 'cleaned_data') else self.data.get(self.add_prefix('payment_method'))
|
|
||||||
amount = self.cleaned_data.get('amount') if hasattr(self, 'cleaned_data') else self.data.get(self.add_prefix('amount'))
|
|
||||||
|
|
||||||
# Форма изменена если заполнено хотя бы одно из ключевых полей
|
|
||||||
return bool(payment_method or amount)
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Валидация платежа, особенно для оплаты из кошелька"""
|
"""Валидация платежа, особенно для оплаты из кошелька"""
|
||||||
cleaned = super().clean()
|
cleaned = super().clean()
|
||||||
|
|||||||
@@ -670,14 +670,14 @@
|
|||||||
<!-- Скрытый шаблон для новых платежей -->
|
<!-- Скрытый шаблон для новых платежей -->
|
||||||
<template id="empty-payment-form-template">
|
<template id="empty-payment-form-template">
|
||||||
<div class="payment-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
<div class="payment-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
||||||
<input type="hidden" name="payments-__prefix__-id" id="id_payments-__prefix__-id">
|
<input type="hidden" name="payments-__prefix__-id" id="id_payments-__prefix__-id" form="order-form">
|
||||||
<input type="checkbox" name="payments-__prefix__-DELETE" id="id_payments-__prefix__-DELETE" style="display: none;">
|
<input type="checkbox" name="payments-__prefix__-DELETE" id="id_payments-__prefix__-DELETE" form="order-form" style="display: none;">
|
||||||
|
|
||||||
<div class="row align-items-end">
|
<div class="row align-items-end">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Способ оплаты</label>
|
<label class="form-label">Способ оплаты</label>
|
||||||
<select name="payments-__prefix__-payment_method" class="form-select" id="id_payments-__prefix__-payment_method">
|
<select name="payments-__prefix__-payment_method" class="form-select" id="id_payments-__prefix__-payment_method" form="order-form">
|
||||||
<option value="">---------</option>
|
<option value="">---------</option>
|
||||||
{% for pm in payment_formset.forms.0.fields.payment_method.queryset %}
|
{% for pm in payment_formset.forms.0.fields.payment_method.queryset %}
|
||||||
<option value="{{ pm.id }}">{{ pm.name }}</option>
|
<option value="{{ pm.id }}">{{ pm.name }}</option>
|
||||||
@@ -688,13 +688,13 @@
|
|||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Сумма</label>
|
<label class="form-label">Сумма</label>
|
||||||
<input type="number" name="payments-__prefix__-amount" step="0.01" min="0" class="form-control" placeholder="Сумма платежа" id="id_payments-__prefix__-amount">
|
<input type="number" name="payments-__prefix__-amount" step="0.01" min="0" class="form-control" placeholder="Сумма платежа" id="id_payments-__prefix__-amount" form="order-form">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Примечания</label>
|
<label class="form-label">Примечания</label>
|
||||||
<textarea name="payments-__prefix__-notes" class="form-control" rows="1" placeholder="Примечания к платежу (опционально)" id="id_payments-__prefix__-notes"></textarea>
|
<textarea name="payments-__prefix__-notes" class="form-control" rows="1" placeholder="Примечания к платежу (опционально)" id="id_payments-__prefix__-notes" form="order-form"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
@@ -1745,11 +1745,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Функция для добавления нового платежа
|
// Функция для добавления нового платежа
|
||||||
function addNewPayment() {
|
function addNewPayment() {
|
||||||
|
console.log('[addNewPayment] START, paymentFormCount:', paymentFormCount);
|
||||||
|
|
||||||
// ВАЖНО: Получаем HTML из template.content и заменяем __prefix__
|
// ВАЖНО: Получаем HTML из template.content и заменяем __prefix__
|
||||||
const tempContainer = document.createElement('div');
|
const tempContainer = document.createElement('div');
|
||||||
tempContainer.appendChild(paymentFormTemplate.content.cloneNode(true));
|
tempContainer.appendChild(paymentFormTemplate.content.cloneNode(true));
|
||||||
const templateHtml = tempContainer.innerHTML;
|
const templateHtml = tempContainer.innerHTML;
|
||||||
|
console.log('[addNewPayment] templateHtml (first 200 chars):', templateHtml.substring(0, 200));
|
||||||
|
|
||||||
const replacedHtml = templateHtml.replace(/__prefix__/g, paymentFormCount);
|
const replacedHtml = templateHtml.replace(/__prefix__/g, paymentFormCount);
|
||||||
|
console.log('[addNewPayment] replacedHtml (first 200 chars):', replacedHtml.substring(0, 200));
|
||||||
|
|
||||||
// Создаем элемент из обработанного HTML
|
// Создаем элемент из обработанного HTML
|
||||||
const tempDiv = document.createElement('div');
|
const tempDiv = document.createElement('div');
|
||||||
@@ -1760,6 +1765,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Добавляем в контейнер
|
// Добавляем в контейнер
|
||||||
paymentsContainer.appendChild(newPaymentDiv);
|
paymentsContainer.appendChild(newPaymentDiv);
|
||||||
|
console.log('[addNewPayment] Added payment form, index:', paymentFormCount);
|
||||||
|
|
||||||
|
// DEBUG: Проверяем что поля действительно в DOM
|
||||||
|
const allInputs = newPaymentDiv.querySelectorAll('input, select, textarea');
|
||||||
|
console.log('[addNewPayment] Total inputs in new form:', allInputs.length);
|
||||||
|
allInputs.forEach((inp, idx) => {
|
||||||
|
console.log(` [${idx}] name="${inp.name}", type="${inp.type || inp.tagName}"`);
|
||||||
|
});
|
||||||
|
|
||||||
// Обновляем счетчик форм
|
// Обновляем счетчик форм
|
||||||
paymentFormCount++;
|
paymentFormCount++;
|
||||||
@@ -2222,7 +2235,46 @@ if (!document.getElementById('notification-styles')) {
|
|||||||
// При отправке формы - отключаем предупреждение
|
// При отправке формы - отключаем предупреждение
|
||||||
const form = document.getElementById('order-form');
|
const form = document.getElementById('order-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function() {
|
form.addEventListener('submit', function(e) {
|
||||||
|
console.log('[FORM SUBMIT] Form is being submitted');
|
||||||
|
console.log('[FORM SUBMIT] TOTAL_FORMS:', document.querySelector('[name="payments-TOTAL_FORMS"]')?.value);
|
||||||
|
|
||||||
|
// Проверяем payment поля в DOM ПЕРЕД submit
|
||||||
|
console.log('[FORM SUBMIT] Checking payments container...');
|
||||||
|
const paymentsContainer = document.getElementById('payments-container');
|
||||||
|
console.log('[FORM SUBMIT] payments-container exists:', !!paymentsContainer);
|
||||||
|
console.log('[FORM SUBMIT] payments-container childElementCount:', paymentsContainer?.childElementCount);
|
||||||
|
console.log('[FORM SUBMIT] form.contains(paymentsContainer):', form.contains(paymentsContainer));
|
||||||
|
|
||||||
|
// Проверяем что внутри контейнера
|
||||||
|
if (paymentsContainer && paymentsContainer.childElementCount > 0) {
|
||||||
|
const firstChild = paymentsContainer.firstElementChild;
|
||||||
|
console.log('[FORM SUBMIT] First child of container:', firstChild);
|
||||||
|
const inputs = firstChild.querySelectorAll('input, select, textarea');
|
||||||
|
console.log('[FORM SUBMIT] Inputs in first child:', inputs.length);
|
||||||
|
inputs.forEach((inp, idx) => {
|
||||||
|
console.log(` [${idx}] name="${inp.name}", value="${inp.value}", form="${inp.form?.id}"`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[FORM SUBMIT] Payment fields in DOM (via form.querySelectorAll):');
|
||||||
|
const paymentInputs = form.querySelectorAll('[name^="payments-"]');
|
||||||
|
console.log('[FORM SUBMIT] Found payment inputs:', paymentInputs.length);
|
||||||
|
paymentInputs.forEach(inp => {
|
||||||
|
const isDisabled = inp.disabled ? ' [DISABLED]' : '';
|
||||||
|
const val = inp.type === 'checkbox' ? inp.checked : inp.value;
|
||||||
|
console.log(` ${inp.name} = "${val}"${isDisabled}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Логируем все payment поля в FormData
|
||||||
|
const formData = new FormData(form);
|
||||||
|
console.log('[FORM SUBMIT] Payment fields in FormData:');
|
||||||
|
for (let [key, value] of formData.entries()) {
|
||||||
|
if (key.startsWith('payments-')) {
|
||||||
|
console.log(` ${key} = ${value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSubmitting = true;
|
isSubmitting = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ def order_create(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = OrderForm(request.POST)
|
form = OrderForm(request.POST)
|
||||||
formset = OrderItemFormSet(request.POST)
|
formset = OrderItemFormSet(request.POST)
|
||||||
payment_formset = PaymentFormSet(request.POST)
|
payment_formset = PaymentFormSet(request.POST, prefix='payments')
|
||||||
|
|
||||||
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
||||||
# Сохраняем форму БЕЗ commit, чтобы не вызывать reset_delivery_cost() до сохранения items
|
# Сохраняем форму БЕЗ commit, чтобы не вызывать reset_delivery_cost() до сохранения items
|
||||||
@@ -97,18 +97,31 @@ def order_create(request):
|
|||||||
formset.instance = order
|
formset.instance = order
|
||||||
formset.save()
|
formset.save()
|
||||||
|
|
||||||
# Сохраняем платежи
|
# Сохраняем платежи (устанавливаем created_by)
|
||||||
payment_formset.instance = order
|
payment_formset.instance = order
|
||||||
payment_formset.save()
|
unsaved_payments = payment_formset.save(commit=False)
|
||||||
|
|
||||||
|
for p in unsaved_payments:
|
||||||
|
if p.created_by_id is None:
|
||||||
|
p.created_by = request.user
|
||||||
|
p.order = order
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
for obj in payment_formset.deleted_objects:
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
# Пересчитываем стоимость доставки если она не установлена вручную
|
# Пересчитываем стоимость доставки если она не установлена вручную
|
||||||
delivery_cost = form.cleaned_data.get('delivery_cost')
|
delivery_cost = form.cleaned_data.get('delivery_cost')
|
||||||
if not delivery_cost or delivery_cost <= 0:
|
if not delivery_cost or delivery_cost <= 0:
|
||||||
order.reset_delivery_cost()
|
order.reset_delivery_cost()
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму
|
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
||||||
order.calculate_total()
|
order.calculate_total()
|
||||||
order.save()
|
order.update_payment_status()
|
||||||
|
|
||||||
|
# Обрабатываем переплату (если amount_paid > total_amount)
|
||||||
|
from customers.services.wallet_service import WalletService
|
||||||
|
WalletService.add_overpayment(order, request.user)
|
||||||
|
|
||||||
if order.is_draft():
|
if order.is_draft():
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно создан!')
|
messages.success(request, f'Черновик #{order.order_number} успешно создан!')
|
||||||
@@ -132,7 +145,7 @@ def order_create(request):
|
|||||||
|
|
||||||
form = OrderForm(initial=initial_data)
|
form = OrderForm(initial=initial_data)
|
||||||
formset = OrderItemFormSet()
|
formset = OrderItemFormSet()
|
||||||
payment_formset = PaymentFormSet()
|
payment_formset = PaymentFormSet(prefix='payments')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'form': form,
|
'form': form,
|
||||||
@@ -154,7 +167,7 @@ def order_update(request, order_number):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = OrderForm(request.POST, instance=order)
|
form = OrderForm(request.POST, instance=order)
|
||||||
formset = OrderItemFormSet(request.POST, instance=order)
|
formset = OrderItemFormSet(request.POST, instance=order)
|
||||||
payment_formset = PaymentFormSet(request.POST, instance=order)
|
payment_formset = PaymentFormSet(request.POST, instance=order, prefix='payments')
|
||||||
|
|
||||||
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
||||||
order = form.save(commit=False)
|
order = form.save(commit=False)
|
||||||
@@ -176,11 +189,26 @@ def order_update(request, order_number):
|
|||||||
|
|
||||||
order.save()
|
order.save()
|
||||||
formset.save()
|
formset.save()
|
||||||
payment_formset.save()
|
# Сохраняем платежи (устанавливаем created_by)
|
||||||
|
payment_formset.instance = order
|
||||||
|
unsaved_payments = payment_formset.save(commit=False)
|
||||||
|
|
||||||
|
for p in unsaved_payments:
|
||||||
|
if p.created_by_id is None:
|
||||||
|
p.created_by = request.user
|
||||||
|
p.order = order
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
for obj in payment_formset.deleted_objects:
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму
|
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
||||||
order.calculate_total()
|
order.calculate_total()
|
||||||
order.save()
|
order.update_payment_status()
|
||||||
|
|
||||||
|
# Обрабатываем переплату (если amount_paid > total_amount)
|
||||||
|
from customers.services.wallet_service import WalletService
|
||||||
|
WalletService.add_overpayment(order, request.user)
|
||||||
|
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно завершен и переведен в статус "Новый"!')
|
messages.success(request, f'Черновик #{order.order_number} успешно завершен и переведен в статус "Новый"!')
|
||||||
return redirect('orders:order-detail', order_number=order.order_number)
|
return redirect('orders:order-detail', order_number=order.order_number)
|
||||||
@@ -214,12 +242,26 @@ def order_update(request, order_number):
|
|||||||
order.save()
|
order.save()
|
||||||
formset.save()
|
formset.save()
|
||||||
|
|
||||||
# Сохраняем платежи
|
# Сохраняем платежи (устанавливаем created_by)
|
||||||
payment_formset.save()
|
payment_formset.instance = order
|
||||||
|
unsaved_payments = payment_formset.save(commit=False)
|
||||||
|
|
||||||
|
for p in unsaved_payments:
|
||||||
|
if p.created_by_id is None:
|
||||||
|
p.created_by = request.user
|
||||||
|
p.order = order
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
for obj in payment_formset.deleted_objects:
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму
|
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
||||||
order.calculate_total()
|
order.calculate_total()
|
||||||
order.save()
|
order.update_payment_status()
|
||||||
|
|
||||||
|
# Обрабатываем переплату (если amount_paid > total_amount)
|
||||||
|
from customers.services.wallet_service import WalletService
|
||||||
|
WalletService.add_overpayment(order, request.user)
|
||||||
|
|
||||||
if order.is_draft():
|
if order.is_draft():
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно обновлен!')
|
messages.success(request, f'Черновик #{order.order_number} успешно обновлен!')
|
||||||
@@ -245,7 +287,7 @@ def order_update(request, order_number):
|
|||||||
else:
|
else:
|
||||||
form = OrderForm(instance=order)
|
form = OrderForm(instance=order)
|
||||||
formset = OrderItemFormSet(instance=order)
|
formset = OrderItemFormSet(instance=order)
|
||||||
payment_formset = PaymentFormSet(instance=order)
|
payment_formset = PaymentFormSet(instance=order, prefix='payments')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'form': form,
|
'form': form,
|
||||||
@@ -481,7 +523,7 @@ def apply_wallet_payment(request, order_number):
|
|||||||
amount = Decimal(str(raw_amount).replace(',', '.'))
|
amount = Decimal(str(raw_amount).replace(',', '.'))
|
||||||
except (ValueError, TypeError, ArithmeticError):
|
except (ValueError, TypeError, ArithmeticError):
|
||||||
messages.error(request, 'Некорректная сумма для списания из кошелька.')
|
messages.error(request, 'Некорректная сумма для списания из кошелька.')
|
||||||
return redirect('orders:order-detail', pk=pk)
|
return redirect('orders:order-detail', order_number=order.order_number)
|
||||||
|
|
||||||
# Вызываем сервис для оплаты из кошелька
|
# Вызываем сервис для оплаты из кошелька
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user