Рефакторинг блока доставки: объединение с датой/временем, упрощение структуры адреса, вынос получателя, авто-выбор склада по умолчанию

This commit is contained in:
2025-12-24 22:51:14 +03:00
parent 61ce3f550d
commit 98470c83af
2 changed files with 522 additions and 294 deletions

View File

@@ -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'):
@@ -295,6 +310,51 @@ class OrderForm(forms.ModelForm):
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):
"""Валидация формы заказа, включая обязательные поля доставки"""
cleaned_data = super().clean()

View File

@@ -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,64 +354,125 @@
<!-- Доставка -->
<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="mb-4">
<label class="form-label d-block mb-2 fw-bold">Тип доставки</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> С ДОСТАВКОЙ
{% 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>
<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> САМОВЫВОЗ
<!-- Дата и время доставки -->
<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-mode-fields" style="display: block;">
<!-- Поля ввода адреса доставки -->
<div class="row">
<div class="col-md-6">
<!-- Адрес доставки (только для курьера) -->
<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">
{{ form.address_street.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>
<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 }}
Дом <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>
<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 %}
@@ -463,11 +480,15 @@
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<!-- Дополнительные поля (раскрывающийся блок) -->
<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>
@@ -476,9 +497,7 @@
<div class="text-danger">{{ form.address_entrance.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<div class="col-md-4">
<label for="{{ form.address_floor.id_for_label }}" class="form-label">
{{ form.address_floor.label }}
</label>
@@ -487,9 +506,7 @@
<div class="text-danger">{{ form.address_floor.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<div class="col-md-4">
<label for="{{ form.address_intercom_code.id_for_label }}" class="form-label">
{{ form.address_intercom_code.label }}
</label>
@@ -499,11 +516,8 @@
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="row mt-3">
<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>
@@ -513,12 +527,8 @@
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="row mt-3">
<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 }}"
@@ -534,11 +544,72 @@
</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="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="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-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');
if (select2Element) {
select2Element.dataset.formIndex = formCount;
if (typeof window.initOrderItemSelect2 === 'function') {
window.initOrderItemSelect2(select2Element);
// Используем безопасную инициализацию с повторными попытками
window.safeInitOrderItemSelect2(select2Element);
} else {
console.error('[addNewForm] Элемент .select2-order-item не найден в новой форме');
}
initPriceTracking(newForm);
const removeBtn = newForm.querySelector('.remove-item-btn');
if (removeBtn) {
removeBtn.addEventListener('click', function() {
removeForm(newForm);
});
}
updateTotalDisplay();
@@ -1263,9 +1365,34 @@ 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);
// Используем безопасную инициализацию
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>