Рефакторинг: отделение Delivery от Order, обязательные поля доставки, исправление доменов

- Отделена модель Delivery от Order (OneToOne связь)
- Добавлены обязательные поля delivery_date, time_from, time_to в Delivery
- Delivery обязательна при создании заказа (кроме черновиков)
- Добавлены методы calculate_total() и reset_delivery_cost() в Order
- Добавлена валидация полей доставки в OrderForm
- Исправлено создание доменов - убран порт из домена в БД
- Исправлен редирект после установки пароля (правильный формат URL)
- Исправлена ошибка NoReverseMatch в navbar для public схемы
- Удалены все старые миграции (база создается с нуля)
- Обновлены views для работы с новой моделью Delivery
This commit is contained in:
2025-12-23 23:52:59 +03:00
parent d29c736252
commit 94fe363cb1
61 changed files with 1342 additions and 2189 deletions

View File

@@ -8,7 +8,7 @@ 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 .models import Order, OrderItem, Address, OrderStatus, Transaction, PaymentMethod, Delivery
from .forms import OrderForm, OrderItemFormSet, OrderItemForm, OrderStatusForm, TransactionForm
from .filters import OrderFilter
from .services.address_service import AddressService
@@ -22,7 +22,7 @@ def order_list(request):
"""
# Базовый queryset с оптимизацией запросов
orders = Order.objects.select_related(
'customer', 'delivery_address', 'pickup_warehouse', 'status' # Добавлен 'status' для избежания N+1
'customer', 'delivery', 'delivery__address', 'delivery__pickup_warehouse', 'status' # Добавлен 'status' для избежания N+1
).all()
# Применяем фильтры через django-filter
@@ -48,7 +48,7 @@ def order_list(request):
def order_detail(request, order_number):
"""Детальная информация о заказе"""
order = get_object_or_404(
Order.objects.select_related('customer', 'delivery_address', 'pickup_warehouse', 'modified_by', 'status')
Order.objects.select_related('customer', 'delivery', 'delivery__address', 'delivery__pickup_warehouse', 'modified_by', 'status')
.prefetch_related('items__product', 'items__product_kit', 'transactions__created_by', 'transactions__payment_method'),
order_number=order_number
)
@@ -108,15 +108,6 @@ def order_create(request):
# Если покупатель является получателем
order.recipient = None
# Обрабатываем адрес доставки
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
@@ -127,10 +118,53 @@ def order_create(request):
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()
# Проверяем, является ли заказ черновиком
is_draft = order.status and order.status.code == 'draft'
# Создаем Delivery (обязательно, кроме черновиков)
if not is_draft:
# Получаем данные из формы (уже провалидированы)
delivery_type = form.cleaned_data.get('delivery_type')
delivery_date = form.cleaned_data.get('delivery_date')
time_from = form.cleaned_data.get('time_from')
time_to = form.cleaned_data.get('time_to')
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
# Проверяем наличие обязательных полей
if not all([delivery_type, delivery_date, time_from, time_to]):
raise ValidationError('Необходимо заполнить все поля доставки')
# Обрабатываем адрес для курьерской доставки
address = None
if delivery_type == Delivery.DELIVERY_TYPE_COURIER:
# Для курьерской доставки нужен адрес
address = AddressService.process_address_from_form(order, form.cleaned_data)
if not address:
raise ValidationError('Для курьерской доставки необходимо указать адрес')
if not address.pk:
address.save()
elif delivery_type == Delivery.DELIVERY_TYPE_PICKUP:
# Для самовывоза нужен склад
if not pickup_warehouse:
raise ValidationError('Для самовывоза необходимо выбрать склад')
# Создаем Delivery
delivery = Delivery.objects.create(
order=order,
delivery_type=delivery_type,
delivery_date=delivery_date,
time_from=time_from,
time_to=time_to,
address=address,
pickup_warehouse=pickup_warehouse,
cost=delivery_cost if delivery_cost else Decimal('0')
)
# Пересчитываем стоимость доставки если она не установлена вручную
if not delivery.cost or delivery.cost <= 0:
order.reset_delivery_cost()
# Пересчитываем итоговую стоимость
order.calculate_total()
@@ -233,25 +267,59 @@ def order_update(request, order_number):
# Если покупатель является получателем
order.recipient = None
# Обрабатываем адрес доставки
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:
# Если режим "без адреса", очищаем адрес
order.delivery_address = None
else:
# Если не доставка, очищаем адрес
order.delivery_address = None
order.modified_by = request.user
order.save()
formset.save()
# Проверяем, является ли заказ черновиком
is_draft = order.status and order.status.code == 'draft'
# Создаем или обновляем Delivery (обязательно, кроме черновиков)
if not is_draft:
# Получаем данные из формы (уже провалидированы)
delivery_type = form.cleaned_data.get('delivery_type')
delivery_date = form.cleaned_data.get('delivery_date')
time_from = form.cleaned_data.get('time_from')
time_to = form.cleaned_data.get('time_to')
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
# Проверяем наличие обязательных полей
if not all([delivery_type, delivery_date, time_from, time_to]):
raise ValidationError('Необходимо заполнить все поля доставки')
# Обрабатываем адрес для курьерской доставки
address = None
if delivery_type == Delivery.DELIVERY_TYPE_COURIER:
# Для курьерской доставки нужен адрес
address = AddressService.process_address_from_form(order, form.cleaned_data)
if not address:
raise ValidationError('Для курьерской доставки необходимо указать адрес')
if not address.pk:
address.save()
elif delivery_type == Delivery.DELIVERY_TYPE_PICKUP:
# Для самовывоза нужен склад
if not pickup_warehouse:
raise ValidationError('Для самовывоза необходимо выбрать склад')
# Создаем или обновляем Delivery
delivery, created = Delivery.objects.update_or_create(
order=order,
defaults={
'delivery_type': delivery_type,
'delivery_date': delivery_date,
'time_from': time_from,
'time_to': time_to,
'address': address,
'pickup_warehouse': pickup_warehouse,
'cost': delivery_cost if delivery_cost else Decimal('0')
}
)
elif hasattr(order, 'delivery'):
# Если заказ стал черновиком, удаляем Delivery
order.delivery.delete()
# Пересчитываем итоговую стоимость
order.calculate_total()
order.update_payment_status()