Исправлена форма заказа: две колонки и корректная работа кнопки сохранения

- Разделен экран на две колонки: заказ слева, оплата справа
- Форма оплаты вынесена за пределы основной формы заказа (устранена проблема вложенных форм)
- Исправлен метод calculate_total() для сохранения итоговой суммы в БД
- Добавлена модель Transaction для учета платежей и возвратов
- Добавлена модель PaymentMethod для методов оплаты
- Удалена старая модель Payment, заменена на Transaction
- Добавлен TransactionService для управления транзакциями
- Обновлен интерфейс форм оплаты для правой колонки
- Кнопка 'Сохранить изменения' теперь работает корректно
This commit is contained in:
2025-11-29 14:33:23 +03:00
parent 438ca5d515
commit c1351e1f49
14 changed files with 1188 additions and 548 deletions

View File

@@ -8,8 +8,8 @@ from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.db import models
from decimal import Decimal
from .models import Order, OrderItem, Address, OrderStatus, Payment, PaymentMethod
from .forms import OrderForm, OrderItemFormSet, OrderStatusForm, PaymentForm
from .models import Order, OrderItem, Address, OrderStatus, Transaction, PaymentMethod
from .forms import OrderForm, OrderItemFormSet, OrderStatusForm, TransactionForm
from .filters import OrderFilter
from .services.address_service import AddressService
import json
@@ -49,7 +49,7 @@ def order_detail(request, order_number):
"""Детальная информация о заказе"""
order = get_object_or_404(
Order.objects.select_related('customer', 'delivery_address', 'pickup_warehouse', 'modified_by')
.prefetch_related('items__product', 'items__product_kit', 'payments__created_by'),
.prefetch_related('items__product', 'items__product_kit', 'transactions__created_by'),
order_number=order_number
)
@@ -133,6 +133,9 @@ def order_create(request):
def order_update(request, order_number):
"""Редактирование заказа"""
order = get_object_or_404(Order, order_number=order_number)
# Пересчитываем amount_paid на основе транзакций (на случай миграции)
order.recalculate_amount_paid()
if request.method == 'POST':
form = OrderForm(request.POST, instance=order)
@@ -221,37 +224,37 @@ def order_delete(request, order_number):
return render(request, 'orders/order_confirm_delete.html', context)
# === УПРАВЛЕНИЕ ПЛАТЕЖАМИ ===
# === УПРАВЛЕНИЕ ТРАНЗАКЦИЯМИ ===
@login_required
@require_http_methods(["POST"])
def payment_add(request, order_number):
def transaction_add_payment(request, order_number):
"""
Добавление нового платежа к заказу.
Отдельный endpoint для чистоты архитектуры.
"""
from orders.services.transaction_service import TransactionService
order = get_object_or_404(Order, order_number=order_number)
form = PaymentForm(request.POST)
form = TransactionForm(request.POST)
if form.is_valid():
payment = form.save(commit=False)
payment.order = order
payment.created_by = request.user
try:
# save() вызовет Payment.save() который обработает:
# - Списание из кошелька (если account_balance)
# - Обработку переплаты
# - Обновление amount_paid и payment_status
payment.save()
# Создаём транзакцию платежа
transaction = TransactionService.create_payment(
order=order,
amount=form.cleaned_data['amount'],
payment_method=form.cleaned_data['payment_method'],
user=request.user,
notes=form.cleaned_data.get('notes')
)
messages.success(
request,
f'Платеж на сумму {payment.amount} руб. '
f'({payment.payment_method.name}) успешно добавлен.'
f'Платёж на сумму {transaction.amount} руб. '
f'({transaction.payment_method.name}) успешно добавлен.'
)
except ValidationError as e:
except (ValidationError, ValueError) as e:
messages.error(request, f'Ошибка при добавлении платежа: {e}')
else:
# Показываем ошибки валидации
@@ -265,29 +268,68 @@ def payment_add(request, order_number):
@login_required
@require_http_methods(["POST"])
def payment_delete(request, order_number, payment_id):
def transaction_add_refund(request, order_number):
"""
Удаление платежа.
Возвращает средства в кошелек, если платеж был из кошелька.
Добавление возврата по заказу.
"""
from orders.services.transaction_service import TransactionService
order = get_object_or_404(Order, order_number=order_number)
amount = request.POST.get('refund_amount')
payment_method_id = request.POST.get('refund_payment_method')
reason = request.POST.get('refund_reason')
notes = request.POST.get('refund_notes')
try:
amount = Decimal(amount)
payment_method = get_object_or_404(PaymentMethod, pk=payment_method_id)
# Создаём транзакцию возврата
transaction = TransactionService.create_refund(
order=order,
amount=amount,
payment_method=payment_method,
user=request.user,
reason=reason,
notes=notes
)
messages.success(
request,
f'Возврат на сумму {transaction.amount} руб. '
f'({transaction.payment_method.name}) успешно создан.'
)
except (ValidationError, ValueError) as e:
messages.error(request, f'Ошибка при создании возврата: {e}')
return redirect('orders:order-update', order_number=order.order_number)
@login_required
@require_http_methods(["POST"])
def transaction_delete(request, order_number, transaction_id):
"""
Удаление транзакции (не рекомендуется, лучше использовать refund).
Оставлено для совместимости.
"""
order = get_object_or_404(Order, order_number=order_number)
payment = get_object_or_404(Payment, pk=payment_id, order=order)
transaction_obj = get_object_or_404(Transaction, pk=transaction_id, order=order)
# Сохраняем данные для сообщения
payment_info = f'{payment.payment_method.name} на сумму {payment.amount} руб.'
transaction_info = f'{transaction_obj.get_transaction_type_display()} {transaction_obj.payment_method.name} на сумму {transaction_obj.amount} руб.'
# Если это платеж из кошелька - возвращаем средства
if payment.payment_method.code == 'account_balance':
from customers.services.wallet_service import WalletService
WalletService.refund_wallet_payment(order, payment.amount, request.user)
# Предупреждение: удаление транзакций нарушает историю
transaction_obj.delete()
payment.delete()
# Пересчитываем баланс
order.recalculate_amount_paid()
# Пересчитываем сумму оплаты
order.amount_paid = sum(p.amount for p in order.payments.all())
order.update_payment_status()
messages.success(request, f'Платеж {payment_info} успешно удален.')
messages.warning(
request,
f'Транзакция {transaction_info} удалена. '
f'Рекомендуем использовать "Возврат" вместо удаления.'
)
return redirect('orders:order-update', order_number=order.order_number)