Исправление проблем с сохранением адреса, получателя и даты доставки
- Исправлено: адрес теперь сохраняется для черновиков заказов - Исправлено: получатель корректно предзаполняется при редактировании заказа - Исправлено: адрес при редактировании отображается в режиме 'новый' для возможности редактирования - Исправлено: дата доставки корректно предзаполняется при редактировании заказа - Исправлено: при редактировании получателя обновляется существующий объект вместо создания нового - Улучшена логика обработки Delivery для черновиков (создание с опциональными полями) - Улучшена логика обновления получателя через загрузку заказа из БД с select_related
This commit is contained in:
@@ -303,6 +303,8 @@ class OrderForm(forms.ModelForm):
|
||||
# Инициализируем поля доставки из существующей Delivery
|
||||
if self.instance.pk and hasattr(self.instance, 'delivery'):
|
||||
delivery = self.instance.delivery
|
||||
|
||||
# Устанавливаем значения через fields.initial для правильной работы виджетов
|
||||
self.fields['delivery_type'].initial = delivery.delivery_type
|
||||
self.fields['delivery_date'].initial = delivery.delivery_date
|
||||
self.fields['time_from'].initial = delivery.time_from
|
||||
@@ -310,6 +312,19 @@ class OrderForm(forms.ModelForm):
|
||||
self.fields['pickup_warehouse'].initial = delivery.pickup_warehouse
|
||||
self.fields['delivery_cost'].initial = delivery.cost
|
||||
|
||||
# Также устанавливаем через self.initial для совместимости
|
||||
self.initial['delivery_type'] = delivery.delivery_type
|
||||
self.initial['delivery_date'] = delivery.delivery_date
|
||||
self.initial['time_from'] = delivery.time_from
|
||||
self.initial['time_to'] = delivery.time_to
|
||||
self.initial['pickup_warehouse'] = delivery.pickup_warehouse
|
||||
self.initial['delivery_cost'] = delivery.cost
|
||||
|
||||
# Для DateInput с type='date' нужно установить значение в формате YYYY-MM-DD
|
||||
if delivery.delivery_date:
|
||||
# Устанавливаем значение напрямую в виджете
|
||||
self.fields['delivery_date'].widget.attrs['value'] = delivery.delivery_date.strftime('%Y-%m-%d')
|
||||
|
||||
# Если выбран самовывоз, но склад не указан - выбираем склад по умолчанию
|
||||
if delivery.delivery_type == Delivery.DELIVERY_TYPE_PICKUP and not delivery.pickup_warehouse:
|
||||
default_warehouse = Warehouse.objects.filter(
|
||||
@@ -322,38 +337,26 @@ class OrderForm(forms.ModelForm):
|
||||
|
||||
# Инициализируем поля адреса, если есть адрес доставки
|
||||
if delivery.address:
|
||||
# Проверяем, есть ли этот адрес в истории клиента
|
||||
# При редактировании всегда используем режим "новый", чтобы можно было редактировать адрес
|
||||
# Инициализируем queryset для истории адресов (для выбора из истории, если нужно)
|
||||
if self.instance.customer:
|
||||
customer_addresses = Address.objects.filter(
|
||||
deliveries__order__customer=self.instance.customer
|
||||
).exclude(
|
||||
deliveries__order=self.instance # Исключаем адрес текущего заказа
|
||||
).distinct()
|
||||
if delivery.address in customer_addresses:
|
||||
# Адрес есть в истории - используем режим "история"
|
||||
self.fields['address_mode'].initial = 'history'
|
||||
self.fields['address_from_history'].queryset = customer_addresses
|
||||
self.fields['address_from_history'].initial = delivery.address.pk
|
||||
else:
|
||||
# Адреса нет в истории - используем режим "новый" и заполняем поля
|
||||
self.fields['address_mode'].initial = 'new'
|
||||
self.fields['address_street'].initial = delivery.address.street
|
||||
self.fields['address_building_number'].initial = delivery.address.building_number
|
||||
self.fields['address_apartment_number'].initial = delivery.address.apartment_number
|
||||
self.fields['address_entrance'].initial = delivery.address.entrance
|
||||
self.fields['address_floor'].initial = delivery.address.floor
|
||||
self.fields['address_intercom_code'].initial = delivery.address.intercom_code
|
||||
self.fields['address_delivery_instructions'].initial = delivery.address.delivery_instructions
|
||||
self.fields['address_confirm_with_recipient'].initial = delivery.address.confirm_address_with_recipient
|
||||
else:
|
||||
# Нет клиента - просто заполняем поля
|
||||
self.fields['address_mode'].initial = 'new'
|
||||
self.fields['address_street'].initial = delivery.address.street
|
||||
self.fields['address_building_number'].initial = delivery.address.building_number
|
||||
self.fields['address_apartment_number'].initial = delivery.address.apartment_number
|
||||
self.fields['address_entrance'].initial = delivery.address.entrance
|
||||
self.fields['address_floor'].initial = delivery.address.floor
|
||||
self.fields['address_intercom_code'].initial = delivery.address.intercom_code
|
||||
self.fields['address_delivery_instructions'].initial = delivery.address.delivery_instructions
|
||||
self.fields['address_confirm_with_recipient'].initial = delivery.address.confirm_address_with_recipient
|
||||
self.fields['address_from_history'].queryset = customer_addresses
|
||||
|
||||
# Всегда используем режим "новый" для редактирования, чтобы можно было редактировать
|
||||
self.initial['address_mode'] = 'new'
|
||||
self.initial['address_street'] = delivery.address.street
|
||||
self.initial['address_building_number'] = delivery.address.building_number
|
||||
self.initial['address_apartment_number'] = delivery.address.apartment_number
|
||||
self.initial['address_entrance'] = delivery.address.entrance
|
||||
self.initial['address_floor'] = delivery.address.floor
|
||||
self.initial['address_intercom_code'] = delivery.address.intercom_code
|
||||
self.initial['address_delivery_instructions'] = delivery.address.delivery_instructions
|
||||
self.initial['address_confirm_with_recipient'] = delivery.address.confirm_address_with_recipient
|
||||
|
||||
def clean(self):
|
||||
"""Валидация формы заказа, включая обязательные поля доставки"""
|
||||
|
||||
@@ -121,6 +121,16 @@ class Delivery(models.Model):
|
||||
"""Валидация модели"""
|
||||
super().clean()
|
||||
|
||||
# Для черновиков пропускаем строгую валидацию
|
||||
if self.order and self.order.status and hasattr(self.order.status, 'code') and self.order.status.code == 'draft':
|
||||
# Для черновиков только проверяем время, если оно указано
|
||||
if self.time_from and self.time_to and self.time_from >= self.time_to:
|
||||
raise ValidationError({
|
||||
'time_to': 'Время окончания доставки должно быть позже времени начала'
|
||||
})
|
||||
return
|
||||
|
||||
# Для не-черновиков полная валидация
|
||||
# Проверка: для курьерской доставки должен быть адрес
|
||||
if self.delivery_type == self.DELIVERY_TYPE_COURIER:
|
||||
if not self.address:
|
||||
|
||||
@@ -95,7 +95,22 @@ class AddressService:
|
||||
if not name or not phone:
|
||||
return None
|
||||
|
||||
# Проверяем, есть ли уже такой получатель в БД
|
||||
# Если у заказа уже есть получатель - обновляем его вместо создания нового
|
||||
# Загружаем заказ из БД, чтобы получить актуального получателя
|
||||
if order.pk:
|
||||
try:
|
||||
# Order уже импортирован в начале файла
|
||||
db_order = Order.objects.select_related('recipient').get(pk=order.pk)
|
||||
if db_order.recipient:
|
||||
# Обновляем существующего получателя
|
||||
db_order.recipient.name = name
|
||||
db_order.recipient.phone = phone
|
||||
# Сохранять будем в views.py, здесь просто возвращаем объект
|
||||
return db_order.recipient
|
||||
except Order.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Проверяем, есть ли уже такой получатель в БД (по имени и телефону)
|
||||
existing_recipient = Recipient.objects.filter(
|
||||
name=name,
|
||||
phone=phone
|
||||
|
||||
@@ -585,25 +585,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дополнительно -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-three-dots"></i> Дополнительно</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3 form-check">
|
||||
{{ form.is_anonymous }}
|
||||
<label class="form-check-label" for="{{ form.is_anonymous.id_for_label }}">
|
||||
Анонимная доставка
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.special_instructions.id_for_label }}" class="form-label">Особые пожелания</label>
|
||||
{{ form.special_instructions }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Получатель -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@@ -613,7 +594,10 @@
|
||||
<!-- Чекбокс "Другой получатель" -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
{{ form.other_recipient }}
|
||||
<input type="checkbox" class="form-check-input"
|
||||
id="{{ form.other_recipient.id_for_label }}"
|
||||
name="{{ form.other_recipient.html_name }}"
|
||||
{% if form.other_recipient.value or form.other_recipient.field.initial %}checked{% endif %}>
|
||||
<label class="form-check-label" for="{{ form.other_recipient.id_for_label }}">
|
||||
{{ form.other_recipient.label }}
|
||||
</label>
|
||||
@@ -683,6 +667,25 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дополнительно -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-three-dots"></i> Дополнительно</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3 form-check">
|
||||
{{ form.is_anonymous }}
|
||||
<label class="form-check-label" for="{{ form.is_anonymous.id_for_label }}">
|
||||
Анонимная доставка
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.special_instructions.id_for_label }}" class="form-label">Особые пожелания</label>
|
||||
{{ form.special_instructions }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Конец левой колонки -->
|
||||
@@ -935,6 +938,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
<!-- Delivery Date/Time Widget -->
|
||||
<script src="{% static 'orders/js/delivery_datetime.js' %}"></script>
|
||||
<script>
|
||||
// Убеждаемся, что дата доставки правильно отображается при редактировании
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const deliveryDateField = document.getElementById('{{ form.delivery_date.id_for_label }}');
|
||||
{% if form.delivery_date.initial %}
|
||||
if (deliveryDateField && !deliveryDateField.value) {
|
||||
// Если поле пустое, но есть initial значение, устанавливаем его
|
||||
const initialDate = '{{ form.delivery_date.initial|date:"Y-m-d" }}';
|
||||
if (initialDate && initialDate !== 'None') {
|
||||
deliveryDateField.value = initialDate;
|
||||
console.log('[Delivery Date] Установлена дата из initial:', initialDate);
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Unified Transaction Form -->
|
||||
<script src="{% static 'orders/js/unified_transaction_form.js' %}"></script>
|
||||
@@ -1099,8 +1118,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Обработчики событий
|
||||
if (otherRecipientCheckbox) {
|
||||
otherRecipientCheckbox.addEventListener('change', toggleOtherRecipientBlock);
|
||||
// Инициализация при загрузке
|
||||
toggleOtherRecipientBlock();
|
||||
// Инициализация при загрузке - проверяем checked состояние
|
||||
// Используем DOMContentLoaded чтобы убедиться, что DOM полностью загружен
|
||||
// Но также проверяем сразу, если DOM уже загружен
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
toggleOtherRecipientBlock();
|
||||
});
|
||||
} else {
|
||||
// DOM уже загружен
|
||||
toggleOtherRecipientBlock();
|
||||
}
|
||||
}
|
||||
|
||||
recipientSourceRadios.forEach(radio => {
|
||||
|
||||
@@ -121,30 +121,53 @@ def order_create(request):
|
||||
# Проверяем, является ли заказ черновиком
|
||||
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')
|
||||
|
||||
# Проверяем наличие обязательных полей (время необязательно)
|
||||
# Получаем данные из формы (уже провалидированы)
|
||||
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')
|
||||
|
||||
# Обрабатываем адрес для курьерской доставки (даже для черновиков, если указан)
|
||||
address = None
|
||||
if delivery_type == Delivery.DELIVERY_TYPE_COURIER:
|
||||
# Для курьерской доставки обрабатываем адрес, если он указан
|
||||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
||||
if address and not address.pk:
|
||||
address.save()
|
||||
|
||||
# Создаем или обновляем Delivery
|
||||
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
||||
if is_draft:
|
||||
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
||||
if address or delivery_type or pickup_warehouse:
|
||||
# Для черновиков используем значения по умолчанию, если не указаны
|
||||
from django.utils import timezone
|
||||
draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER
|
||||
draft_delivery_date = delivery_date or timezone.now().date()
|
||||
|
||||
Delivery.objects.update_or_create(
|
||||
order=order,
|
||||
defaults={
|
||||
'delivery_type': draft_delivery_type,
|
||||
'delivery_date': draft_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')
|
||||
}
|
||||
)
|
||||
else:
|
||||
# Для не-черновиков проверяем обязательные поля
|
||||
if not delivery_type or not delivery_date:
|
||||
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:
|
||||
@@ -259,9 +282,8 @@ def order_update(request, order_number):
|
||||
# Обрабатываем получателя
|
||||
recipient = AddressService.process_recipient_from_form(order, form.cleaned_data)
|
||||
if recipient:
|
||||
# Если получатель не существует в БД, сохраняем его
|
||||
if not recipient.pk:
|
||||
recipient.save()
|
||||
# Сохраняем получателя: если новый - создаем, если существующий - обновляем
|
||||
recipient.save() # Django автоматически определит create или update
|
||||
order.recipient = recipient
|
||||
else:
|
||||
# Если покупатель является получателем
|
||||
@@ -274,30 +296,56 @@ def order_update(request, order_number):
|
||||
# Проверяем, является ли заказ черновиком
|
||||
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')
|
||||
|
||||
# Проверяем наличие обязательных полей (время необязательно)
|
||||
# Получаем данные из формы (уже провалидированы)
|
||||
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')
|
||||
|
||||
# Обрабатываем адрес для курьерской доставки (даже для черновиков, если указан)
|
||||
address = None
|
||||
if delivery_type == Delivery.DELIVERY_TYPE_COURIER:
|
||||
# Для курьерской доставки обрабатываем адрес, если он указан
|
||||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
||||
if address and not address.pk:
|
||||
address.save()
|
||||
|
||||
# Создаем или обновляем Delivery
|
||||
# Для черновиков создаем Delivery без обязательных проверок, чтобы сохранить адрес
|
||||
if is_draft:
|
||||
# Для черновиков создаем Delivery, если есть хотя бы адрес или данные доставки
|
||||
if address or delivery_type or pickup_warehouse:
|
||||
# Для черновиков используем значения по умолчанию, если не указаны
|
||||
from django.utils import timezone
|
||||
draft_delivery_type = delivery_type or Delivery.DELIVERY_TYPE_COURIER
|
||||
draft_delivery_date = delivery_date or timezone.now().date()
|
||||
|
||||
Delivery.objects.update_or_create(
|
||||
order=order,
|
||||
defaults={
|
||||
'delivery_type': draft_delivery_type,
|
||||
'delivery_date': draft_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()
|
||||
else:
|
||||
# Для не-черновиков проверяем обязательные поля
|
||||
if not delivery_type or not delivery_date:
|
||||
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:
|
||||
@@ -316,9 +364,6 @@ def order_update(request, order_number):
|
||||
'cost': delivery_cost if delivery_cost else Decimal('0')
|
||||
}
|
||||
)
|
||||
elif hasattr(order, 'delivery'):
|
||||
# Если заказ стал черновиком, удаляем Delivery
|
||||
order.delivery.delete()
|
||||
|
||||
# Пересчитываем итоговую стоимость
|
||||
order.calculate_total()
|
||||
|
||||
Reference in New Issue
Block a user