Рефакторинг блока доставки: объединение с датой/временем, упрощение структуры адреса, вынос получателя, авто-выбор склада по умолчанию
This commit is contained in:
@@ -282,8 +282,23 @@ class OrderForm(forms.ModelForm):
|
||||
self.fields['recipient_from_history'].queryset = Recipient.objects.none()
|
||||
|
||||
# Инициализируем queryset для pickup_warehouse
|
||||
# Фильтруем только активные склады, доступные для самовывоза
|
||||
# Сортируем: сначала склад по умолчанию, потом по названию
|
||||
from inventory.models import Warehouse
|
||||
self.fields['pickup_warehouse'].queryset = Warehouse.objects.filter(is_active=True).order_by('name')
|
||||
self.fields['pickup_warehouse'].queryset = Warehouse.objects.filter(
|
||||
is_active=True,
|
||||
is_pickup_point=True
|
||||
).order_by('-is_default', 'name')
|
||||
|
||||
# Если это новый заказ и еще не выбран склад, выбираем склад по умолчанию
|
||||
if not self.instance.pk:
|
||||
default_warehouse = Warehouse.objects.filter(
|
||||
is_active=True,
|
||||
is_pickup_point=True,
|
||||
is_default=True
|
||||
).first()
|
||||
if default_warehouse:
|
||||
self.fields['pickup_warehouse'].initial = default_warehouse
|
||||
|
||||
# Инициализируем поля доставки из существующей Delivery
|
||||
if self.instance.pk and hasattr(self.instance, 'delivery'):
|
||||
@@ -294,6 +309,51 @@ class OrderForm(forms.ModelForm):
|
||||
self.fields['time_to'].initial = delivery.time_to
|
||||
self.fields['pickup_warehouse'].initial = delivery.pickup_warehouse
|
||||
self.fields['delivery_cost'].initial = delivery.cost
|
||||
|
||||
# Если выбран самовывоз, но склад не указан - выбираем склад по умолчанию
|
||||
if delivery.delivery_type == Delivery.DELIVERY_TYPE_PICKUP and not delivery.pickup_warehouse:
|
||||
default_warehouse = Warehouse.objects.filter(
|
||||
is_active=True,
|
||||
is_pickup_point=True,
|
||||
is_default=True
|
||||
).first()
|
||||
if default_warehouse:
|
||||
self.fields['pickup_warehouse'].initial = default_warehouse
|
||||
|
||||
# Инициализируем поля адреса, если есть адрес доставки
|
||||
if delivery.address:
|
||||
# Проверяем, есть ли этот адрес в истории клиента
|
||||
if self.instance.customer:
|
||||
customer_addresses = Address.objects.filter(
|
||||
deliveries__order__customer=self.instance.customer
|
||||
).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
|
||||
|
||||
def clean(self):
|
||||
"""Валидация формы заказа, включая обязательные поля доставки"""
|
||||
|
||||
@@ -195,50 +195,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дата и время доставки -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Дата и время доставки</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="delivery-datetime-wrapper">
|
||||
<!-- Поля ввода в один ряд -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.delivery_date.id_for_label }}" class="form-label">
|
||||
Дата <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.delivery_date }}
|
||||
{% if form.delivery_date.errors %}
|
||||
<div class="text-danger">{{ form.delivery_date.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.time_from.id_for_label }}" class="form-label">
|
||||
Время от
|
||||
</label>
|
||||
{{ form.time_from }}
|
||||
{% if form.time_from.errors %}
|
||||
<div class="text-danger">{{ form.time_from.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.time_to.id_for_label }}" class="form-label">
|
||||
Время до
|
||||
</label>
|
||||
{{ form.time_to }}
|
||||
{% if form.time_to.errors %}
|
||||
<div class="text-danger">{{ form.time_to.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки быстрого выбора ниже полей -->
|
||||
<div id="delivery-datetime-quick-buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Товары в заказе -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@@ -398,147 +354,262 @@
|
||||
<!-- Доставка -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Доставка</h5>
|
||||
<h5 class="mb-0"><i class="bi bi-truck"></i> Доставка</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Скрытое поле для передачи значения is_delivery -->
|
||||
<div style="display: none;">
|
||||
<input type="checkbox"
|
||||
id="{{ form.is_delivery.id_for_label }}"
|
||||
name="{{ form.is_delivery.name }}"
|
||||
value="on"
|
||||
{% if form.instance.is_delivery %}checked{% endif %}>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки выбора типа доставки -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label d-block mb-2">Тип доставки</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<input type="radio" class="btn-check" name="delivery-type" id="delivery-type-delivery" value="delivery" {% if form.instance.is_delivery %}checked{% endif %}>
|
||||
<label class="btn btn-outline-primary" for="delivery-type-delivery">
|
||||
<i class="bi bi-truck"></i> С ДОСТАВКОЙ
|
||||
</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="delivery-type" id="delivery-type-pickup" value="pickup" {% if not form.instance.is_delivery %}checked{% endif %}>
|
||||
<label class="btn btn-outline-primary" for="delivery-type-pickup">
|
||||
<i class="bi bi-shop"></i> САМОВЫВОЗ
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Поля доставки (показываются только если выбрана доставка) -->
|
||||
<div id="delivery-mode-fields" style="display: block;">
|
||||
<!-- Поля ввода адреса доставки -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_street.id_for_label }}" class="form-label">
|
||||
{{ form.address_street.label }}
|
||||
</label>
|
||||
{{ form.address_street }}
|
||||
{% if form.address_street.errors %}
|
||||
<div class="text-danger">{{ form.address_street.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_building_number.id_for_label }}" class="form-label">
|
||||
{{ form.address_building_number.label }}
|
||||
</label>
|
||||
{{ form.address_building_number }}
|
||||
{% if form.address_building_number.errors %}
|
||||
<div class="text-danger">{{ form.address_building_number.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_apartment_number.id_for_label }}" class="form-label">
|
||||
{{ form.address_apartment_number.label }}
|
||||
</label>
|
||||
{{ form.address_apartment_number }}
|
||||
{% if form.address_apartment_number.errors %}
|
||||
<div class="text-danger">{{ form.address_apartment_number.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_entrance.id_for_label }}" class="form-label">
|
||||
{{ form.address_entrance.label }}
|
||||
</label>
|
||||
{{ form.address_entrance }}
|
||||
{% if form.address_entrance.errors %}
|
||||
<div class="text-danger">{{ form.address_entrance.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_floor.id_for_label }}" class="form-label">
|
||||
{{ form.address_floor.label }}
|
||||
</label>
|
||||
{{ form.address_floor }}
|
||||
{% if form.address_floor.errors %}
|
||||
<div class="text-danger">{{ form.address_floor.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_intercom_code.id_for_label }}" class="form-label">
|
||||
{{ form.address_intercom_code.label }}
|
||||
</label>
|
||||
{{ form.address_intercom_code }}
|
||||
{% if form.address_intercom_code.errors %}
|
||||
<div class="text-danger">{{ form.address_intercom_code.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.address_delivery_instructions.id_for_label }}" class="form-label">
|
||||
{{ form.address_delivery_instructions.label }}
|
||||
</label>
|
||||
{{ form.address_delivery_instructions }}
|
||||
{% if form.address_delivery_instructions.errors %}
|
||||
<div class="text-danger">{{ form.address_delivery_instructions.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="mb-3">
|
||||
<!-- Крупный переключатель (switch) -->
|
||||
<div class="form-check form-switch" style="padding-left: 3.5em;">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="{{ form.address_confirm_with_recipient.id_for_label }}"
|
||||
name="{{ form.address_confirm_with_recipient.name }}"
|
||||
{% if form.address_confirm_with_recipient.value %}checked{% endif %}
|
||||
style="width: 3em; height: 1.5em; cursor: pointer;">
|
||||
<label class="form-check-label" for="{{ form.address_confirm_with_recipient.id_for_label }}"
|
||||
style="font-size: 1.1em; font-weight: 500; cursor: pointer; padding-left: 0.5em;">
|
||||
<i class="bi bi-telephone-fill text-primary"></i>
|
||||
{{ form.address_confirm_with_recipient.label }}
|
||||
<!-- Тип доставки -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label d-block mb-2 fw-bold">Тип доставки</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
{% for value, label in form.delivery_type.field.choices %}
|
||||
<input type="radio" class="btn-check" name="{{ form.delivery_type.name }}" id="id_delivery_type_{{ forloop.counter0 }}" value="{{ value }}" {% if form.delivery_type.value == value %}checked{% endif %}>
|
||||
<label class="btn btn-outline-primary" for="id_delivery_type_{{ forloop.counter0 }}">
|
||||
{% if value == 'courier' %}
|
||||
<i class="bi bi-truck"></i> Доставка курьером
|
||||
{% elif value == 'pickup' %}
|
||||
<i class="bi bi-shop"></i> Самовывоз
|
||||
{% else %}
|
||||
<i class="bi bi-truck"></i> {{ label }}
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if form.delivery_type.errors %}
|
||||
<div class="text-danger mt-2">{{ form.delivery_type.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Дата и время доставки -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label d-block mb-2 fw-bold">Дата и время доставки</label>
|
||||
<div class="delivery-datetime-wrapper">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.delivery_date.id_for_label }}" class="form-label">
|
||||
Дата <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.delivery_date }}
|
||||
{% if form.delivery_date.errors %}
|
||||
<div class="text-danger">{{ form.delivery_date.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.time_from.id_for_label }}" class="form-label">
|
||||
Время от
|
||||
</label>
|
||||
{{ form.time_from }}
|
||||
{% if form.time_from.errors %}
|
||||
<div class="text-danger">{{ form.time_from.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.time_to.id_for_label }}" class="form-label">
|
||||
Время до
|
||||
</label>
|
||||
{{ form.time_to }}
|
||||
{% if form.time_to.errors %}
|
||||
<div class="text-danger">{{ form.time_to.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Кнопки быстрого выбора -->
|
||||
<div id="delivery-datetime-quick-buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Адрес доставки (только для курьера) -->
|
||||
<div id="delivery-address-fields" class="mb-4" style="display: none;">
|
||||
<label class="form-label d-block mb-2 fw-bold">Адрес доставки</label>
|
||||
|
||||
<!-- Режим выбора адреса -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label d-block mb-2">Способ указания адреса</label>
|
||||
{% for choice in form.address_mode %}
|
||||
<div class="form-check form-check-inline">
|
||||
{{ choice.tag }}
|
||||
<label class="form-check-label" for="{{ choice.id_for_label }}">
|
||||
{{ choice.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if form.address_mode.errors %}
|
||||
<div class="text-danger">{{ form.address_mode.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Выбор адреса из истории -->
|
||||
<div class="mb-3" id="address-history-field" style="display: none;">
|
||||
<label for="{{ form.address_from_history.id_for_label }}" class="form-label">
|
||||
Адрес из истории
|
||||
</label>
|
||||
{{ form.address_from_history }}
|
||||
{% if form.address_from_history.errors %}
|
||||
<div class="text-danger">{{ form.address_from_history.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Ввод нового адреса -->
|
||||
<div id="address-new-fields" style="display: none;">
|
||||
<!-- Основные поля -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.address_street.id_for_label }}" class="form-label">
|
||||
Улица <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.address_street }}
|
||||
{% if form.address_street.errors %}
|
||||
<div class="text-danger">{{ form.address_street.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="{{ form.address_building_number.id_for_label }}" class="form-label">
|
||||
Дом <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.address_building_number }}
|
||||
{% if form.address_building_number.errors %}
|
||||
<div class="text-danger">{{ form.address_building_number.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="{{ form.address_apartment_number.id_for_label }}" class="form-label">
|
||||
Квартира/офис
|
||||
</label>
|
||||
{{ form.address_apartment_number }}
|
||||
{% if form.address_apartment_number.errors %}
|
||||
<div class="text-danger">{{ form.address_apartment_number.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Дополнительные поля (раскрывающийся блок) -->
|
||||
<div class="mb-3">
|
||||
<button type="button" class="btn btn-link p-0 text-decoration-none" data-bs-toggle="collapse" data-bs-target="#address-extra-fields" aria-expanded="false">
|
||||
<i class="bi bi-chevron-down"></i> Дополнительно
|
||||
</button>
|
||||
<div class="collapse mt-2" id="address-extra-fields">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.address_entrance.id_for_label }}" class="form-label">
|
||||
{{ form.address_entrance.label }}
|
||||
</label>
|
||||
{{ form.address_entrance }}
|
||||
{% if form.address_entrance.errors %}
|
||||
<div class="text-danger">{{ form.address_entrance.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.address_floor.id_for_label }}" class="form-label">
|
||||
{{ form.address_floor.label }}
|
||||
</label>
|
||||
{{ form.address_floor }}
|
||||
{% if form.address_floor.errors %}
|
||||
<div class="text-danger">{{ form.address_floor.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ form.address_intercom_code.id_for_label }}" class="form-label">
|
||||
{{ form.address_intercom_code.label }}
|
||||
</label>
|
||||
{{ form.address_intercom_code }}
|
||||
{% if form.address_intercom_code.errors %}
|
||||
<div class="text-danger">{{ form.address_intercom_code.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<label for="{{ form.address_delivery_instructions.id_for_label }}" class="form-label">
|
||||
{{ form.address_delivery_instructions.label }}
|
||||
</label>
|
||||
{{ form.address_delivery_instructions }}
|
||||
{% if form.address_delivery_instructions.errors %}
|
||||
<div class="text-danger">{{ form.address_delivery_instructions.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch" style="padding-left: 3.5em;">
|
||||
<input class="form-check-input" type="checkbox" role="switch"
|
||||
id="{{ form.address_confirm_with_recipient.id_for_label }}"
|
||||
name="{{ form.address_confirm_with_recipient.name }}"
|
||||
{% if form.address_confirm_with_recipient.value %}checked{% endif %}
|
||||
style="width: 3em; height: 1.5em; cursor: pointer;">
|
||||
<label class="form-check-label" for="{{ form.address_confirm_with_recipient.id_for_label }}"
|
||||
style="font-size: 1.1em; font-weight: 500; cursor: pointer; padding-left: 0.5em;">
|
||||
<i class="bi bi-telephone-fill text-primary"></i>
|
||||
{{ form.address_confirm_with_recipient.label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Склад самовывоза (только для самовывоза) -->
|
||||
<div id="pickup-fields" class="mb-4" style="display: none;">
|
||||
<label class="form-label d-block mb-2 fw-bold">Склад самовывоза</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ form.pickup_warehouse }}
|
||||
{% if form.pickup_warehouse.errors %}
|
||||
<div class="text-danger">{{ form.pickup_warehouse.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Стоимость доставки -->
|
||||
<div class="mb-0">
|
||||
<label for="{{ form.delivery_cost.id_for_label }}" class="form-label fw-bold">
|
||||
{{ form.delivery_cost.label }}
|
||||
</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
{{ form.delivery_cost }}
|
||||
<span class="input-group-text">руб.</span>
|
||||
</div>
|
||||
{% if form.delivery_cost.errors %}
|
||||
<div class="text-danger">{{ form.delivery_cost.errors }}</div>
|
||||
{% endif %}
|
||||
<small class="text-muted d-block mt-1">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Оставьте пустым для автоматического расчета стоимости доставки
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Получатель -->
|
||||
<div class="border-top pt-3 mt-3">
|
||||
<h6 class="mb-3">Получатель</h6>
|
||||
<!-- Дополнительно -->
|
||||
<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">
|
||||
<h5 class="mb-0"><i class="bi bi-person"></i> Получатель</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Чекбокс "Другой получатель" -->
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
@@ -547,7 +618,7 @@
|
||||
{{ form.other_recipient.label }}
|
||||
</label>
|
||||
{% if form.other_recipient.help_text %}
|
||||
<small class="form-text text-muted">{{ form.other_recipient.help_text }}</small>
|
||||
<small class="form-text text-muted d-block">{{ form.other_recipient.help_text }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if form.other_recipient.errors %}
|
||||
@@ -611,61 +682,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Стоимость доставки -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.delivery_cost.id_for_label }}" class="form-label">
|
||||
{{ form.delivery_cost.label }}
|
||||
</label>
|
||||
{{ form.delivery_cost }}
|
||||
{% if form.delivery_cost.errors %}
|
||||
<div class="text-danger">{{ form.delivery_cost.errors }}</div>
|
||||
{% endif %}
|
||||
<small class="d-block text-muted mt-1">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Оставьте пустым для автоматического расчета стоимости доставки
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Поля самовывоза (показываются только если выбран самовывоз) -->
|
||||
<div class="row" id="pickup-fields" style="display: none;">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.pickup_warehouse.id_for_label }}" class="form-label">
|
||||
Склад для самовывоза
|
||||
</label>
|
||||
{{ form.pickup_warehouse }}
|
||||
{% if form.pickup_warehouse.errors %}
|
||||
<div class="text-danger">{{ form.pickup_warehouse.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
@@ -954,62 +970,94 @@ if (typeof $ !== 'undefined') {
|
||||
});
|
||||
}
|
||||
|
||||
// === УПРАВЛЕНИЕ ТИПОМ ДОСТАВКИ (КНОПКИ) ===
|
||||
// ВАЖНО: Этот код должен быть ВНЕ jQuery document.ready,
|
||||
// чтобы выполниться после полной загрузки DOM
|
||||
// === УПРАВЛЕНИЕ ТИПОМ ДОСТАВКИ ===
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const deliveryTypeRadios = document.querySelectorAll('input[name="delivery-type"]');
|
||||
const isDeliveryCheckbox = document.getElementById('{{ form.is_delivery.id_for_label }}');
|
||||
const deliveryModeFields = document.getElementById('delivery-mode-fields');
|
||||
const deliveryTypeRadios = document.querySelectorAll('input[name="{{ form.delivery_type.name }}"]');
|
||||
const deliveryAddressFields = document.getElementById('delivery-address-fields');
|
||||
const pickupFields = document.getElementById('pickup-fields');
|
||||
const addressModeRadios = document.querySelectorAll('input[name="{{ form.address_mode.name }}"]');
|
||||
const addressHistoryField = document.getElementById('address-history-field');
|
||||
const addressNewFields = document.getElementById('address-new-fields');
|
||||
|
||||
function syncDeliveryTypeFromRadio() {
|
||||
const selectedRadio = document.querySelector('input[name="delivery-type"]:checked');
|
||||
if (!selectedRadio) return; // Защита от null
|
||||
// Функция переключения между доставкой и самовывозом
|
||||
function toggleDeliveryType() {
|
||||
const selectedRadio = document.querySelector('input[name="{{ form.delivery_type.name }}"]:checked');
|
||||
if (!selectedRadio) return;
|
||||
|
||||
const selectedType = selectedRadio.value;
|
||||
const deliveryType = selectedRadio.value;
|
||||
|
||||
if (selectedType === 'delivery') {
|
||||
isDeliveryCheckbox.checked = true;
|
||||
deliveryModeFields.style.display = 'block';
|
||||
pickupFields.style.display = 'none';
|
||||
} else {
|
||||
isDeliveryCheckbox.checked = false;
|
||||
deliveryModeFields.style.display = 'none';
|
||||
pickupFields.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function syncUIFromCheckbox() {
|
||||
if (!isDeliveryCheckbox) return; // Защита от null
|
||||
|
||||
if (isDeliveryCheckbox.checked) {
|
||||
const deliveryRadio = document.getElementById('delivery-type-delivery');
|
||||
if (deliveryRadio) {
|
||||
deliveryRadio.checked = true;
|
||||
}
|
||||
if (deliveryModeFields) deliveryModeFields.style.display = 'block';
|
||||
if (deliveryType === 'courier') {
|
||||
// Показываем поля адреса, скрываем склад
|
||||
if (deliveryAddressFields) deliveryAddressFields.style.display = 'block';
|
||||
if (pickupFields) pickupFields.style.display = 'none';
|
||||
} else {
|
||||
const pickupRadio = document.getElementById('delivery-type-pickup');
|
||||
if (pickupRadio) {
|
||||
pickupRadio.checked = true;
|
||||
}
|
||||
if (deliveryModeFields) deliveryModeFields.style.display = 'none';
|
||||
} else if (deliveryType === 'pickup') {
|
||||
// Скрываем поля адреса, показываем склад
|
||||
if (deliveryAddressFields) deliveryAddressFields.style.display = 'none';
|
||||
if (pickupFields) pickupFields.style.display = 'block';
|
||||
|
||||
// Автоматически выбираем склад по умолчанию, если он еще не выбран
|
||||
const pickupWarehouseSelect = document.getElementById('{{ form.pickup_warehouse.id_for_label }}');
|
||||
if (pickupWarehouseSelect && !pickupWarehouseSelect.value) {
|
||||
// Ищем первый option со значением (склад по умолчанию должен быть первым в списке)
|
||||
const options = pickupWarehouseSelect.querySelectorAll('option[value]:not([value=""])');
|
||||
if (options.length > 0) {
|
||||
// Первый option - это склад по умолчанию (благодаря сортировке -is_default)
|
||||
pickupWarehouseSelect.value = options[0].value;
|
||||
// Триггерим событие change для обновления UI
|
||||
pickupWarehouseSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчики для кнопок
|
||||
// Функция переключения режима адреса
|
||||
function toggleAddressMode() {
|
||||
const selectedMode = document.querySelector('input[name="{{ form.address_mode.name }}"]:checked');
|
||||
if (!selectedMode) return;
|
||||
|
||||
const mode = selectedMode.value;
|
||||
|
||||
// Скрываем все поля
|
||||
if (addressHistoryField) addressHistoryField.style.display = 'none';
|
||||
if (addressNewFields) addressNewFields.style.display = 'none';
|
||||
|
||||
// Показываем нужные поля
|
||||
if (mode === 'history') {
|
||||
if (addressHistoryField) addressHistoryField.style.display = 'block';
|
||||
} else if (mode === 'new') {
|
||||
if (addressNewFields) addressNewFields.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчики для типа доставки
|
||||
if (deliveryTypeRadios && deliveryTypeRadios.length > 0) {
|
||||
deliveryTypeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', syncDeliveryTypeFromRadio);
|
||||
radio.addEventListener('change', toggleDeliveryType);
|
||||
});
|
||||
}
|
||||
|
||||
// Обработчики для режима адреса
|
||||
if (addressModeRadios && addressModeRadios.length > 0) {
|
||||
addressModeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', toggleAddressMode);
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация при загрузке
|
||||
if (isDeliveryCheckbox) {
|
||||
syncUIFromCheckbox();
|
||||
toggleDeliveryType();
|
||||
toggleAddressMode();
|
||||
|
||||
// При загрузке страницы, если выбран самовывоз, но склад не выбран - выбираем по умолчанию
|
||||
const selectedDeliveryType = document.querySelector('input[name="{{ form.delivery_type.name }}"]:checked');
|
||||
if (selectedDeliveryType && selectedDeliveryType.value === 'pickup') {
|
||||
const pickupWarehouseSelect = document.getElementById('{{ form.pickup_warehouse.id_for_label }}');
|
||||
if (pickupWarehouseSelect && !pickupWarehouseSelect.value) {
|
||||
// Ищем первый option со значением (склад по умолчанию должен быть первым)
|
||||
const options = pickupWarehouseSelect.querySelectorAll('option[value]:not([value=""])');
|
||||
if (options.length > 0) {
|
||||
pickupWarehouseSelect.value = options[0].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Показ/скрытие полей получателя
|
||||
@@ -1058,7 +1106,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
recipientSourceRadios.forEach(radio => {
|
||||
radio.addEventListener('change', toggleRecipientSourceFields);
|
||||
});
|
||||
toggleRecipientFields();
|
||||
|
||||
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===
|
||||
// Вычисление суммы по всем видимым позициям
|
||||
@@ -1154,6 +1201,56 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const totalFormsInput = document.querySelector('#id_items-TOTAL_FORMS');
|
||||
const emptyFormTemplate = document.getElementById('empty-form-template');
|
||||
|
||||
/**
|
||||
* Безопасная инициализация Select2 для элемента формы заказа
|
||||
* Проверяет наличие всех зависимостей и повторяет попытки при необходимости
|
||||
* @param {Element} selectElement - DOM элемент select для инициализации
|
||||
* @param {number} retryCount - Количество оставшихся попыток (по умолчанию 10)
|
||||
* @param {number} delay - Задержка между попытками в миллисекундах (по умолчанию 100)
|
||||
*/
|
||||
window.safeInitOrderItemSelect2 = function(selectElement, retryCount = 10, delay = 100) {
|
||||
if (!selectElement) {
|
||||
console.warn('[safeInitOrderItemSelect2] Элемент не найден');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем наличие всех зависимостей
|
||||
if (typeof $ === 'undefined') {
|
||||
console.log('[safeInitOrderItemSelect2] jQuery еще не загружен, ожидание...');
|
||||
if (retryCount > 0) {
|
||||
setTimeout(() => window.safeInitOrderItemSelect2(selectElement, retryCount - 1, delay), delay);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof window.initOrderItemSelect2 !== 'function') {
|
||||
console.log('[safeInitOrderItemSelect2] initOrderItemSelect2 еще не загружена, ожидание...');
|
||||
if (retryCount > 0) {
|
||||
setTimeout(() => window.safeInitOrderItemSelect2(selectElement, retryCount - 1, delay), delay);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем, не инициализирован ли уже Select2
|
||||
if ($(selectElement).data('select2')) {
|
||||
console.log('[safeInitOrderItemSelect2] Select2 уже инициализирован для этого элемента');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Все зависимости готовы, инициализируем
|
||||
try {
|
||||
window.initOrderItemSelect2(selectElement);
|
||||
console.log('[safeInitOrderItemSelect2] Select2 успешно инициализирован');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[safeInitOrderItemSelect2] Ошибка при инициализации Select2:', error);
|
||||
if (retryCount > 0) {
|
||||
setTimeout(() => window.safeInitOrderItemSelect2(selectElement, retryCount - 1, delay), delay);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для добавления новой формы
|
||||
function addNewForm() {
|
||||
const formCount = parseInt(totalFormsInput.value);
|
||||
@@ -1169,19 +1266,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
totalFormsInput.value = formCount + 1;
|
||||
|
||||
// Инициализируем Select2 для новой формы
|
||||
// Инициализируем Select2 для новой формы с безопасной проверкой
|
||||
const select2Element = newForm.querySelector('.select2-order-item');
|
||||
select2Element.dataset.formIndex = formCount;
|
||||
if (typeof window.initOrderItemSelect2 === 'function') {
|
||||
window.initOrderItemSelect2(select2Element);
|
||||
if (select2Element) {
|
||||
select2Element.dataset.formIndex = formCount;
|
||||
// Используем безопасную инициализацию с повторными попытками
|
||||
window.safeInitOrderItemSelect2(select2Element);
|
||||
} else {
|
||||
console.error('[addNewForm] Элемент .select2-order-item не найден в новой форме');
|
||||
}
|
||||
|
||||
initPriceTracking(newForm);
|
||||
|
||||
const removeBtn = newForm.querySelector('.remove-item-btn');
|
||||
removeBtn.addEventListener('click', function() {
|
||||
removeForm(newForm);
|
||||
});
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', function() {
|
||||
removeForm(newForm);
|
||||
});
|
||||
}
|
||||
|
||||
updateTotalDisplay();
|
||||
|
||||
@@ -1263,10 +1365,35 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
// Если нет ни одной формы, добавляем одну автоматически
|
||||
if (container.querySelectorAll('.order-item-form').length === 0) {
|
||||
addNewForm();
|
||||
// Убеждаемся, что это происходит после загрузки всех скриптов
|
||||
function ensureInitialForm() {
|
||||
const existingForms = container.querySelectorAll('.order-item-form:not(.deleted)');
|
||||
if (existingForms.length === 0) {
|
||||
console.log('[Order Items] Нет форм, добавляем первую автоматически...');
|
||||
const newForm = addNewForm();
|
||||
|
||||
// Переинициализируем Select2 для добавленной формы после небольшой задержки
|
||||
// чтобы убедиться, что все скрипты загружены
|
||||
setTimeout(() => {
|
||||
const select2Element = newForm.querySelector('.select2-order-item');
|
||||
if (select2Element && (!window.$ || !$(select2Element).data('select2'))) {
|
||||
console.log('[Order Items] Переинициализация Select2 для автоматически добавленной формы...');
|
||||
if (window.safeInitOrderItemSelect2) {
|
||||
window.safeInitOrderItemSelect2(select2Element);
|
||||
} else if (window.initOrderItemSelect2 && typeof window.initOrderItemSelect2 === 'function') {
|
||||
window.initOrderItemSelect2(select2Element);
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// Пытаемся добавить форму сразу, но также проверяем после загрузки всех скриптов
|
||||
ensureInitialForm();
|
||||
|
||||
// Дополнительная проверка после небольшой задержки на случай, если скрипты загружаются асинхронно
|
||||
setTimeout(ensureInitialForm, 500);
|
||||
|
||||
// Инициализируем итоговую сумму
|
||||
updateTotalDisplay();
|
||||
|
||||
@@ -1643,12 +1770,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window.initOrderItemSelect2 !== 'function') {
|
||||
console.log('[Order Items] Ожидание инициализации initOrderItemSelect2...');
|
||||
setTimeout(initExistingOrderItems, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window.initOrderItemSelect2 !== 'function') {
|
||||
console.log('[Order Items] Ожидание загрузки initOrderItemSelect2 из select2-product-search.js...');
|
||||
setTimeout(initExistingOrderItems, 100);
|
||||
@@ -1660,12 +1781,31 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const items = document.querySelectorAll('.select2-order-item');
|
||||
console.log('[Order Items] Найдено элементов для инициализации:', items.length);
|
||||
|
||||
items.forEach((selectElement, index) => {
|
||||
console.log(`[Order Items] 📋 Инициализация элемента ${index + 1}/${items.length}`);
|
||||
// Функция для инициализации одного элемента
|
||||
function initOrderItemElement(selectElement, index) {
|
||||
console.log(`[Order Items] 📋 Инициализация элемента ${index + 1}`);
|
||||
|
||||
// Сначала инициализируем Select2
|
||||
window.initOrderItemSelect2(selectElement);
|
||||
console.log(`[Order Items] ✅ Select2 инициализирован для элемента ${index}`);
|
||||
// Используем безопасную инициализацию
|
||||
const initialized = window.safeInitOrderItemSelect2 ?
|
||||
window.safeInitOrderItemSelect2(selectElement) :
|
||||
(window.initOrderItemSelect2 ? (window.initOrderItemSelect2(selectElement), true) : false);
|
||||
|
||||
if (initialized) {
|
||||
console.log(`[Order Items] ✅ Select2 инициализирован для элемента ${index}`);
|
||||
} else {
|
||||
console.warn(`[Order Items] ⚠️ Не удалось инициализировать Select2 для элемента ${index}, будет повторная попытка`);
|
||||
// Повторная попытка через небольшую задержку
|
||||
setTimeout(() => {
|
||||
if (window.initOrderItemSelect2 && typeof window.initOrderItemSelect2 === 'function') {
|
||||
window.initOrderItemSelect2(selectElement);
|
||||
console.log(`[Order Items] ✅ Select2 инициализирован для элемента ${index} (повторная попытка)`);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach((selectElement, index) => {
|
||||
initOrderItemElement(selectElement, index);
|
||||
|
||||
// Затем проверяем, есть ли предзаполненные данные в скрытых полях
|
||||
const form = selectElement.closest('.order-item-form');
|
||||
@@ -1768,6 +1908,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
console.log('[Order Items] Инициализация всех существующих элементов завершена');
|
||||
|
||||
// Переинициализация для форм, которые могут быть добавлены позже
|
||||
// Используем MutationObserver для отслеживания новых форм
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if (node.nodeType === 1 && node.classList && node.classList.contains('order-item-form')) {
|
||||
const select2Element = node.querySelector('.select2-order-item');
|
||||
if (select2Element && !$(select2Element).data('select2')) {
|
||||
console.log('[Order Items] Обнаружена новая форма, инициализация Select2...');
|
||||
setTimeout(() => {
|
||||
if (window.initOrderItemSelect2 && typeof window.initOrderItemSelect2 === 'function') {
|
||||
window.initOrderItemSelect2(select2Element);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const container = document.getElementById('order-items-container');
|
||||
if (container) {
|
||||
observer.observe(container, { childList: true, subtree: true });
|
||||
console.log('[Order Items] MutationObserver установлен для отслеживания новых форм');
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user