Проблема: ValidationError из сигналов отображался как: 'Server error: [\'Заказ 134 был отменён...\']' со служебными элементами (Server error, квадратные скобки). Решение: В order_update добавлена обработка ValidationError перед ValueError: - Извлекаем чистое сообщение из исключения (e.messages[0] или str(e)) - Показываем через messages.error() — Django автоматически отобразит красивым Bootstrap alert-danger - Транзакция откатывается, изменения не сохраняются Теперь пользователь видит: [красный Bootstrap alert] 'Заказ 134 был отменён, товары проданы в другом заказе. Невозможно изменить статус. Для новой продажи создайте новый заказ.' Без технических префиксов и форматирования - user-friendly.
693 lines
28 KiB
Python
693 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
||
from django.shortcuts import render, redirect, get_object_or_404
|
||
from django.contrib import messages
|
||
from django.core.paginator import Paginator
|
||
from django.http import JsonResponse
|
||
from django.views.decorators.http import require_http_methods
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.core.exceptions import ValidationError
|
||
from django.db import models, transaction
|
||
from decimal import Decimal
|
||
from .models import Order, OrderItem, Address, OrderStatus, Transaction, PaymentMethod
|
||
from .forms import OrderForm, OrderItemFormSet, OrderItemForm, OrderStatusForm, TransactionForm
|
||
from .filters import OrderFilter
|
||
from .services.address_service import AddressService
|
||
import json
|
||
|
||
|
||
def order_list(request):
|
||
"""
|
||
Список всех заказов с фильтрацией и поиском
|
||
Использует django-filter для фильтрации данных
|
||
"""
|
||
# Базовый queryset с оптимизацией запросов
|
||
orders = Order.objects.select_related(
|
||
'customer', 'delivery_address', 'pickup_warehouse'
|
||
).all()
|
||
|
||
# Применяем фильтры через django-filter
|
||
order_filter = OrderFilter(request.GET, queryset=orders)
|
||
|
||
# Сортировка
|
||
filtered_orders = order_filter.qs.order_by('-created_at')
|
||
|
||
# Пагинация
|
||
paginator = Paginator(filtered_orders, 25)
|
||
page_number = request.GET.get('page')
|
||
page_obj = paginator.get_page(page_number)
|
||
|
||
context = {
|
||
'filter': order_filter,
|
||
'page_obj': page_obj,
|
||
'status_choices': OrderStatus.objects.all().order_by('order'),
|
||
}
|
||
|
||
return render(request, 'orders/order_list.html', context)
|
||
|
||
|
||
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', 'transactions__created_by'),
|
||
order_number=order_number
|
||
)
|
||
|
||
context = {
|
||
'order': order,
|
||
}
|
||
|
||
return render(request, 'orders/order_detail.html', context)
|
||
|
||
|
||
def order_create(request):
|
||
"""Создание нового заказа"""
|
||
# Инициализация переменных для контекста
|
||
preselected_customer = None
|
||
draft_items = []
|
||
|
||
if request.method == 'POST':
|
||
# Логирование POST-данных для отладки
|
||
print("\n=== POST DATA ===")
|
||
print(f"items-TOTAL_FORMS: {request.POST.get('items-TOTAL_FORMS')}")
|
||
print(f"items-INITIAL_FORMS: {request.POST.get('items-INITIAL_FORMS')}")
|
||
print(f"items-MIN_NUM_FORMS: {request.POST.get('items-MIN_NUM_FORMS')}")
|
||
print(f"items-MAX_NUM_FORMS: {request.POST.get('items-MAX_NUM_FORMS')}")
|
||
|
||
# Показываем все формы товаров
|
||
total_forms = int(request.POST.get('items-TOTAL_FORMS', 0))
|
||
for i in range(total_forms):
|
||
product = request.POST.get(f'items-{i}-product', '')
|
||
kit = request.POST.get(f'items-{i}-product_kit', '')
|
||
quantity = request.POST.get(f'items-{i}-quantity', '')
|
||
price = request.POST.get(f'items-{i}-price', '')
|
||
print(f"\nForm {i}:")
|
||
print(f" product: {product or '(пусто)'}")
|
||
print(f" kit: {kit or '(пусто)'}")
|
||
print(f" quantity: {quantity or '(пусто)'}")
|
||
print(f" price: {price or '(пусто)'}")
|
||
print("=== END POST DATA ===\n")
|
||
|
||
form = OrderForm(request.POST)
|
||
formset = OrderItemFormSet(request.POST)
|
||
|
||
if form.is_valid() and formset.is_valid():
|
||
try:
|
||
with transaction.atomic():
|
||
# Сохраняем форму БЕЗ commit, чтобы не вызывать reset_delivery_cost() до сохранения items
|
||
order = form.save(commit=False)
|
||
|
||
# Обрабатываем адрес доставки
|
||
if order.is_delivery:
|
||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
||
if address:
|
||
# Если адрес не существует в БД, сохраняем его
|
||
if not address.pk:
|
||
address.save()
|
||
order.delivery_address = address
|
||
|
||
# Статус берём из формы (в том числе может быть "Черновик")
|
||
order.modified_by = request.user
|
||
|
||
# Сохраняем заказ в БД (теперь у него есть pk)
|
||
order.save()
|
||
|
||
# Сохраняем позиции заказа
|
||
formset.instance = order
|
||
formset.save()
|
||
|
||
# Пересчитываем стоимость доставки если она не установлена вручную
|
||
delivery_cost = form.cleaned_data.get('delivery_cost')
|
||
if not delivery_cost or delivery_cost <= 0:
|
||
order.reset_delivery_cost()
|
||
|
||
# Пересчитываем итоговую стоимость
|
||
order.calculate_total()
|
||
order.update_payment_status()
|
||
|
||
messages.success(request, f'Заказ #{order.order_number} успешно создан!')
|
||
return redirect('orders:order-detail', order_number=order.order_number)
|
||
except ValueError as e:
|
||
# Ошибка в сигналах (например, не удалось создать Sale)
|
||
# Транзакция откатилась, заказ НЕ создан
|
||
messages.error(request, f'Ошибка при создании заказа: {e}')
|
||
else:
|
||
messages.error(request, 'Пожалуйста, исправьте ошибки в форме.')
|
||
else:
|
||
# Предзаполнение клиента из GET параметра
|
||
initial_data = {}
|
||
customer_id = request.GET.get('customer')
|
||
|
||
# Проверяем, есть ли черновик из POS
|
||
draft_token = request.GET.get('draft')
|
||
if draft_token:
|
||
from django.core.cache import cache
|
||
cache_key = f'pos_draft:{draft_token}'
|
||
draft_data = cache.get(cache_key)
|
||
|
||
if draft_data:
|
||
# Загружаем клиента из черновика
|
||
customer_id = draft_data.get('customer_id')
|
||
draft_items = draft_data.get('items', [])
|
||
|
||
# Удаляем черновик из кэша (одноразовый токен)
|
||
cache.delete(cache_key)
|
||
|
||
if customer_id:
|
||
try:
|
||
from customers.models import Customer
|
||
preselected_customer = Customer.objects.get(pk=customer_id)
|
||
initial_data['customer'] = preselected_customer.pk
|
||
except (Customer.DoesNotExist, ValueError):
|
||
pass
|
||
|
||
form = OrderForm(initial=initial_data)
|
||
|
||
# Создаем formset с предзаполненными товарами из черновика
|
||
if draft_items:
|
||
from django.forms import inlineformset_factory
|
||
from products.models import Product, ProductKit
|
||
|
||
# Создаём формсет с нужным количеством extra форм
|
||
DraftOrderItemFormSet = inlineformset_factory(
|
||
Order,
|
||
OrderItem,
|
||
form=OrderItemForm,
|
||
extra=len(draft_items), # Создаём столько форм, сколько товаров
|
||
can_delete=True,
|
||
min_num=0,
|
||
validate_min=False,
|
||
)
|
||
formset = DraftOrderItemFormSet()
|
||
else:
|
||
formset = OrderItemFormSet()
|
||
|
||
context = {
|
||
'form': form,
|
||
'formset': formset,
|
||
'preselected_customer': preselected_customer,
|
||
'draft_items_json': json.dumps(draft_items) if draft_items else '[]', # Передаём JSON товары из черновика
|
||
'title': 'Создание заказа',
|
||
'button_text': 'Создать заказ',
|
||
'is_create_page': True,
|
||
}
|
||
|
||
return render(request, 'orders/order_form.html', context)
|
||
|
||
|
||
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)
|
||
formset = OrderItemFormSet(request.POST, instance=order)
|
||
|
||
if form.is_valid() and formset.is_valid():
|
||
try:
|
||
with transaction.atomic():
|
||
order = form.save(commit=False)
|
||
|
||
# Обрабатываем адрес доставки
|
||
if order.is_delivery:
|
||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
||
if address:
|
||
# Если адрес не существует в БД, сохраняем его
|
||
if not address.pk:
|
||
address.save()
|
||
order.delivery_address = address
|
||
else:
|
||
# Если режим "без адреса", удаляем существующий адрес
|
||
if order.delivery_address:
|
||
old_address = order.delivery_address
|
||
order.delivery_address = None
|
||
# Удаляем старый адрес, если он больше не используется
|
||
if old_address and not old_address.order:
|
||
old_address.delete()
|
||
else:
|
||
# Если не доставка, удаляем адрес если он был
|
||
if order.delivery_address:
|
||
old_address = order.delivery_address
|
||
order.delivery_address = None
|
||
# Удаляем старый адрес
|
||
if old_address and not old_address.order:
|
||
old_address.delete()
|
||
|
||
order.modified_by = request.user
|
||
order.save()
|
||
formset.save()
|
||
|
||
# Пересчитываем итоговую стоимость
|
||
order.calculate_total()
|
||
order.update_payment_status()
|
||
|
||
messages.success(request, f'Заказ #{order.order_number} успешно обновлен!')
|
||
return redirect('orders:order-detail', order_number=order.order_number)
|
||
except ValidationError as e:
|
||
# Ошибка валидации (например, запрет смены статуса для возвращённого заказа)
|
||
# Транзакция откатилась, заказ НЕ изменился
|
||
# Показываем сообщение из исключения без служебных элементов
|
||
error_message = str(e.message) if hasattr(e, 'message') else str(e)
|
||
# Если это список ошибок, берём первую
|
||
if hasattr(e, 'messages'):
|
||
error_message = e.messages[0] if e.messages else str(e)
|
||
messages.error(request, error_message)
|
||
except ValueError as e:
|
||
# Ошибка в сигналах (например, не удалось создать Sale)
|
||
# Транзакция откатилась, статус НЕ изменился
|
||
messages.error(request, f'Ошибка при сохранении заказа: {e}')
|
||
else:
|
||
# Логируем ошибки для отладки
|
||
print("\n=== ОШИБКИ ВАЛИДАЦИИ ФОРМЫ ===")
|
||
if not form.is_valid():
|
||
print(f"OrderForm errors: {form.errors}")
|
||
if not formset.is_valid():
|
||
print(f"OrderItemFormSet errors: {formset.errors}")
|
||
print(f"OrderItemFormSet non_form_errors: {formset.non_form_errors()}")
|
||
for i, item_form in enumerate(formset):
|
||
if item_form.errors:
|
||
print(f" Item form {i} errors: {item_form.errors}")
|
||
print("=== КОНЕЦ ОШИБОК ===\n")
|
||
messages.error(request, 'Пожалуйста, исправьте ошибки в форме.')
|
||
else:
|
||
form = OrderForm(instance=order)
|
||
formset = OrderItemFormSet(instance=order)
|
||
|
||
context = {
|
||
'form': form,
|
||
'formset': formset,
|
||
'order': order,
|
||
'title': f'Редактирование заказа #{order.order_number}',
|
||
'button_text': 'Сохранить изменения',
|
||
}
|
||
|
||
return render(request, 'orders/order_form.html', context)
|
||
|
||
|
||
def order_delete(request, order_number):
|
||
"""Удаление заказа с подтверждением"""
|
||
order = get_object_or_404(Order, order_number=order_number)
|
||
|
||
if request.method == 'POST':
|
||
order_number = order.order_number
|
||
order.delete()
|
||
messages.success(request, f'Заказ #{order_number} успешно удален.')
|
||
return redirect('orders:order-list')
|
||
|
||
context = {
|
||
'order': order,
|
||
}
|
||
|
||
return render(request, 'orders/order_confirm_delete.html', context)
|
||
|
||
|
||
# === УПРАВЛЕНИЕ ТРАНЗАКЦИЯМИ ===
|
||
|
||
@login_required
|
||
@require_http_methods(["POST"])
|
||
def transaction_add_payment(request, order_number):
|
||
"""
|
||
Добавление нового платежа к заказу.
|
||
"""
|
||
from orders.services.transaction_service import TransactionService
|
||
|
||
order = get_object_or_404(Order, order_number=order_number)
|
||
|
||
form = TransactionForm(request.POST)
|
||
|
||
if form.is_valid():
|
||
try:
|
||
# Создаём транзакцию платежа
|
||
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'Платёж на сумму {transaction.amount} руб. '
|
||
f'({transaction.payment_method.name}) успешно добавлен.'
|
||
)
|
||
except (ValidationError, ValueError) as e:
|
||
messages.error(request, f'Ошибка при добавлении платежа: {e}')
|
||
else:
|
||
# Показываем ошибки валидации
|
||
for field, errors in form.errors.items():
|
||
for error in errors:
|
||
messages.error(request, f'{field}: {error}')
|
||
|
||
# Возвращаемся на страницу редактирования
|
||
return redirect('orders:order-update', order_number=order.order_number)
|
||
|
||
|
||
@login_required
|
||
@require_http_methods(["POST"])
|
||
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)
|
||
transaction_obj = get_object_or_404(Transaction, pk=transaction_id, order=order)
|
||
|
||
# Сохраняем данные для сообщения
|
||
transaction_info = f'{transaction_obj.get_transaction_type_display()} {transaction_obj.payment_method.name} на сумму {transaction_obj.amount} руб.'
|
||
|
||
# Предупреждение: удаление транзакций нарушает историю
|
||
transaction_obj.delete()
|
||
|
||
# Пересчитываем баланс
|
||
order.recalculate_amount_paid()
|
||
|
||
messages.warning(
|
||
request,
|
||
f'Транзакция {transaction_info} удалена. '
|
||
f'Рекомендуем использовать "Возврат" вместо удаления.'
|
||
)
|
||
return redirect('orders:order-update', order_number=order.order_number)
|
||
|
||
|
||
# === AJAX ENDPOINTS ===
|
||
|
||
@require_http_methods(["POST"])
|
||
@require_http_methods(["GET"])
|
||
@login_required
|
||
def get_customer_address_history(request):
|
||
"""
|
||
AJAX endpoint для получения истории адресов клиента.
|
||
|
||
GET параметры:
|
||
- customer_id: ID клиента
|
||
|
||
Возвращает JSON со списком адресов из истории заказов клиента.
|
||
"""
|
||
try:
|
||
customer_id = request.GET.get('customer_id')
|
||
|
||
if not customer_id:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'customer_id не указан'
|
||
}, status=400)
|
||
|
||
from customers.models import Customer
|
||
|
||
try:
|
||
customer = Customer.objects.get(pk=customer_id)
|
||
except Customer.DoesNotExist:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'Клиент не найден'
|
||
}, status=404)
|
||
|
||
# Получаем адреса из истории заказов
|
||
addresses = AddressService.get_customer_address_history(customer)
|
||
|
||
# Форматируем для отправки клиенту
|
||
addresses_data = [
|
||
{
|
||
'id': addr.id,
|
||
'display': AddressService.format_address_for_display(addr),
|
||
'street': addr.street,
|
||
'building_number': addr.building_number,
|
||
'apartment_number': addr.apartment_number,
|
||
'entrance': addr.entrance,
|
||
'floor': addr.floor,
|
||
'intercom_code': addr.intercom_code,
|
||
'recipient_name': addr.recipient_name,
|
||
'recipient_phone': addr.recipient_phone,
|
||
}
|
||
for addr in addresses
|
||
]
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'addresses': addresses_data,
|
||
'count': len(addresses_data)
|
||
})
|
||
|
||
except Exception as e:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': f'Ошибка сервера: {str(e)}'
|
||
}, status=500)
|
||
|
||
|
||
# === УПРАВЛЕНИЕ СТАТУСАМИ ЗАКАЗОВ ===
|
||
|
||
@login_required
|
||
def order_status_list(request):
|
||
"""Список всех статусов заказов"""
|
||
statuses = OrderStatus.objects.all().order_by('order', 'name')
|
||
|
||
# orders_count уже доступен как property в модели OrderStatus
|
||
# Не нужно вручную добавлять атрибут
|
||
|
||
context = {
|
||
'statuses': statuses,
|
||
'title': 'Статусы заказов'
|
||
}
|
||
|
||
return render(request, 'orders/status_list.html', context)
|
||
|
||
|
||
@login_required
|
||
def order_status_create(request):
|
||
"""Создание нового статуса"""
|
||
if request.method == 'POST':
|
||
form = OrderStatusForm(request.POST)
|
||
|
||
if form.is_valid():
|
||
status = form.save(commit=False)
|
||
status.created_by = request.user
|
||
status.updated_by = request.user
|
||
|
||
# Если не указан порядок - делаем его последним
|
||
if not status.order:
|
||
max_order = OrderStatus.objects.aggregate(models.Max('order'))['order__max'] or 0
|
||
status.order = max_order + 10
|
||
|
||
status.save()
|
||
messages.success(request, f'Статус "{status.name}" успешно создан')
|
||
return redirect('orders:status_list')
|
||
else:
|
||
form = OrderStatusForm()
|
||
|
||
context = {
|
||
'form': form,
|
||
'title': 'Создать новый статус',
|
||
'button_text': 'Создать'
|
||
}
|
||
|
||
return render(request, 'orders/status_form.html', context)
|
||
|
||
|
||
@login_required
|
||
def order_status_update(request, pk):
|
||
"""Редактирование статуса"""
|
||
status = get_object_or_404(OrderStatus, pk=pk)
|
||
|
||
if request.method == 'POST':
|
||
form = OrderStatusForm(request.POST, instance=status)
|
||
|
||
if form.is_valid():
|
||
status = form.save(commit=False)
|
||
status.updated_by = request.user
|
||
status.save()
|
||
messages.success(request, f'Статус "{status.name}" успешно обновлен')
|
||
return redirect('orders:status_list')
|
||
else:
|
||
form = OrderStatusForm(instance=status)
|
||
|
||
context = {
|
||
'form': form,
|
||
'status': status,
|
||
'title': f'Редактировать статус: {status.name}',
|
||
'button_text': 'Сохранить',
|
||
'is_system': status.is_system
|
||
}
|
||
|
||
return render(request, 'orders/status_form.html', context)
|
||
|
||
|
||
@login_required
|
||
def order_status_delete(request, pk):
|
||
"""Удаление статуса"""
|
||
status = get_object_or_404(OrderStatus, pk=pk)
|
||
|
||
if status.is_system:
|
||
messages.error(request, f'Нельзя удалить системный статус "{status.name}"')
|
||
return redirect('orders:status_list')
|
||
|
||
if request.method == 'POST':
|
||
# Проверяем, что статус не используется в заказах
|
||
orders_count = Order.objects.filter(status=status).count()
|
||
|
||
if orders_count > 0:
|
||
messages.error(
|
||
request,
|
||
f'Невозможно удалить статус. Есть {orders_count} заказов с этим статусом.'
|
||
)
|
||
return redirect('orders:status_list')
|
||
|
||
status_name = status.name
|
||
status.delete()
|
||
messages.success(request, f'Статус "{status_name}" успешно удален')
|
||
return redirect('orders:status_list')
|
||
|
||
# Информация для подтверждения удаления
|
||
orders_count = Order.objects.filter(status=status).count()
|
||
|
||
context = {
|
||
'status': status,
|
||
'orders_count': orders_count,
|
||
'title': 'Удалить статус'
|
||
}
|
||
|
||
return render(request, 'orders/status_confirm_delete.html', context)
|
||
|
||
|
||
# === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
||
# УДАЛЕНО: Логика создания временных комплектов перенесена в products.services.kit_service
|
||
# Используйте API endpoint: products:api-temporary-kit-create
|
||
|
||
|
||
# === КОШЕЛЁК КЛИЕНТА ===
|
||
|
||
@login_required
|
||
def apply_wallet_payment(request, order_number):
|
||
"""
|
||
Применение оплаты из кошелька клиента к заказу.
|
||
Вызывается через POST-запрос с суммой для списания.
|
||
"""
|
||
if request.method != 'POST':
|
||
return redirect('orders:order-detail', order_number=order_number)
|
||
|
||
order = get_object_or_404(Order, order_number=order_number)
|
||
|
||
# Получаем запрашиваемую сумму из формы
|
||
try:
|
||
raw_amount = request.POST.get('wallet_amount', '0')
|
||
amount = Decimal(str(raw_amount).replace(',', '.'))
|
||
except (ValueError, TypeError, ArithmeticError):
|
||
messages.error(request, 'Некорректная сумма для списания из кошелька.')
|
||
return redirect('orders:order-detail', order_number=order.order_number)
|
||
|
||
# Вызываем сервис для оплаты из кошелька
|
||
try:
|
||
from customers.services.wallet_service import WalletService
|
||
paid_amount = WalletService.pay_with_wallet(order, amount, request.user)
|
||
|
||
if paid_amount and paid_amount > 0:
|
||
messages.success(
|
||
request,
|
||
f'Из кошелька клиента списано {paid_amount} руб. для оплаты заказа #{order.order_number}.'
|
||
)
|
||
else:
|
||
messages.warning(
|
||
request,
|
||
'Не удалось списать средства из кошелька. Проверьте баланс и сумму заказа.'
|
||
)
|
||
except ValueError as e:
|
||
messages.error(request, str(e))
|
||
except Exception as e:
|
||
messages.error(request, f'Ошибка при оплате из кошелька: {str(e)}')
|
||
|
||
return redirect('orders:order-detail', order_number=order.order_number)
|
||
|
||
|
||
@require_http_methods(["POST"])
|
||
@login_required
|
||
def set_order_status(request, order_number):
|
||
"""
|
||
Update order status via AJAX.
|
||
Accepts POST with 'status_id' (can be empty to clear).
|
||
Returns JSON with the resulting status info.
|
||
|
||
Использует транзакцию, чтобы при ошибке в сигналах (например, при создании Sale)
|
||
статус откатился вместе со всеми связанными изменениями.
|
||
"""
|
||
try:
|
||
order = get_object_or_404(Order, order_number=order_number)
|
||
status_id = request.POST.get('status_id', '').strip()
|
||
|
||
# Allow clearing status if empty
|
||
if status_id == '':
|
||
with transaction.atomic():
|
||
order.status = None
|
||
order.modified_by = request.user
|
||
order.save(update_fields=['status', 'modified_by', 'updated_at'])
|
||
return JsonResponse({'success': True, 'status': None})
|
||
|
||
try:
|
||
status = OrderStatus.objects.get(pk=status_id)
|
||
except OrderStatus.DoesNotExist:
|
||
return JsonResponse({'success': False, 'error': 'Status not found'}, status=404)
|
||
|
||
# Оборачиваем в транзакцию, чтобы при ошибке в сигналах статус откатился
|
||
with transaction.atomic():
|
||
order.status = status
|
||
order.modified_by = request.user
|
||
order.save(update_fields=['status', 'modified_by', 'updated_at'])
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'status': {
|
||
'id': status.pk,
|
||
'name': status.label or status.name,
|
||
'color': status.color
|
||
}
|
||
})
|
||
except ValueError as e:
|
||
# Ошибка в сигналах (например, не удалось создать Sale)
|
||
# Транзакция откатилась, статус НЕ изменился
|
||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||
except Exception as e:
|
||
return JsonResponse({'success': False, 'error': f'Server error: {str(e)}'}, status=500)
|