Поля с Select2 не генерируют стандартные события 'change', поэтому добавлено прослушивание событий 'select2:select'. Также добавлено логирование для отладки. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
471 lines
18 KiB
JavaScript
471 lines
18 KiB
JavaScript
/**
|
||
* Модуль автоматического создания черновика заказа.
|
||
*
|
||
* При первом изменении формы создания заказа автоматически создаёт черновик
|
||
* и перенаправляет пользователя на страницу редактирования черновика,
|
||
* где уже работает обычное автосохранение.
|
||
*/
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Конфигурация
|
||
const CONFIG = {
|
||
CREATE_DRAFT_URL: '/orders/create-draft/',
|
||
DEBOUNCE_DELAY: 2000, // Задержка перед созданием черновика (мс)
|
||
};
|
||
|
||
// Состояние модуля
|
||
let createDraftTimer = null;
|
||
let isCreatingDraft = false;
|
||
let draftCreated = false;
|
||
|
||
/**
|
||
* Инициализация модуля
|
||
*/
|
||
function init() {
|
||
// Проверяем, что мы на странице создания заказа
|
||
const isCreatePage = window.location.pathname.includes('/orders/create/');
|
||
if (!isCreatePage) {
|
||
console.log('[DraftCreator] Not on create page, exiting');
|
||
return;
|
||
}
|
||
|
||
const orderForm = document.getElementById('order-form');
|
||
if (!orderForm) {
|
||
console.log('[DraftCreator] Order form not found, exiting');
|
||
return;
|
||
}
|
||
|
||
// Проверяем, что это не черновик (для черновиков есть autosave.js)
|
||
if (orderForm.dataset.isDraft === 'true') {
|
||
console.log('[DraftCreator] This is a draft, exiting (autosave.js will handle it)');
|
||
return;
|
||
}
|
||
|
||
console.log('[DraftCreator] Initialized on order create page');
|
||
|
||
// Добавляем обработчики событий
|
||
attachEventListeners();
|
||
}
|
||
|
||
/**
|
||
* Прикрепляет обработчики событий к полям формы
|
||
*/
|
||
function attachEventListeners() {
|
||
const form = document.getElementById('order-form');
|
||
if (!form) {
|
||
return;
|
||
}
|
||
|
||
// Слушаем изменения в поле клиента (обязательное поле)
|
||
const customerField = form.querySelector('select[name="customer"]');
|
||
if (customerField) {
|
||
// Обычное событие change
|
||
customerField.addEventListener('change', function() {
|
||
console.log('[DraftCreator] Customer changed (native event):', this.value);
|
||
if (this.value && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
|
||
// Событие Select2
|
||
if (window.jQuery && jQuery(customerField).data('select2')) {
|
||
jQuery(customerField).on('select2:select', function(e) {
|
||
console.log('[DraftCreator] Customer changed (select2 event):', e.params.data.id);
|
||
if (e.params.data.id && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Слушаем изменения в других основных полях
|
||
const fieldsToWatch = [
|
||
'input[name="delivery_date"]',
|
||
'input[name="delivery_time_start"]',
|
||
'input[name="delivery_time_end"]',
|
||
'select[name="payment_method"]',
|
||
'textarea[name="special_instructions"]',
|
||
'input[type="checkbox"]',
|
||
'select[name="delivery_address"]',
|
||
'select[name="pickup_shop"]',
|
||
];
|
||
|
||
fieldsToWatch.forEach(selector => {
|
||
const fields = form.querySelectorAll(selector);
|
||
fields.forEach(field => {
|
||
const eventType = (field.tagName === 'SELECT' || field.type === 'checkbox') ? 'change' : 'input';
|
||
|
||
// Обычное событие
|
||
field.addEventListener(eventType, function() {
|
||
console.log('[DraftCreator] Field changed:', field.name);
|
||
// Создаём черновик только если выбран клиент
|
||
const customer = form.querySelector('select[name="customer"]');
|
||
if (customer && customer.value && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
|
||
// Для select полей также слушаем Select2
|
||
if (field.tagName === 'SELECT' && window.jQuery && jQuery(field).data('select2')) {
|
||
jQuery(field).on('select2:select', function() {
|
||
console.log('[DraftCreator] Field changed (select2):', field.name);
|
||
const customer = form.querySelector('select[name="customer"]');
|
||
if (customer && customer.value && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// Слушаем изменения в формах товаров
|
||
observeFormsetChanges();
|
||
}
|
||
|
||
/**
|
||
* Наблюдает за изменениями в формсете товаров
|
||
*/
|
||
function observeFormsetChanges() {
|
||
const formsetContainer = document.getElementById('order-items-formset');
|
||
if (!formsetContainer) {
|
||
return;
|
||
}
|
||
|
||
// Наблюдаем за добавлением новых форм
|
||
const observer = new MutationObserver(() => {
|
||
attachFormsetEventListeners();
|
||
});
|
||
|
||
observer.observe(formsetContainer, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
|
||
// Прикрепляем обработчики к существующим формам
|
||
attachFormsetEventListeners();
|
||
}
|
||
|
||
/**
|
||
* Прикрепляет обработчики к полям в формах товаров
|
||
*/
|
||
function attachFormsetEventListeners() {
|
||
const itemForms = document.querySelectorAll('.order-item-form');
|
||
|
||
itemForms.forEach(form => {
|
||
// Если уже прикреплены обработчики, пропускаем
|
||
if (form.dataset.draftCreatorAttached === 'true') {
|
||
return;
|
||
}
|
||
|
||
const fields = form.querySelectorAll('select, input[type="number"]');
|
||
fields.forEach(field => {
|
||
const eventType = field.tagName === 'SELECT' ? 'change' : 'input';
|
||
|
||
// Обычное событие
|
||
field.addEventListener(eventType, function() {
|
||
console.log('[DraftCreator] Item field changed:', field.name);
|
||
const customerField = document.querySelector('select[name="customer"]');
|
||
if (customerField && customerField.value && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
|
||
// Для select полей товаров также слушаем Select2
|
||
if (field.tagName === 'SELECT' && window.jQuery && jQuery(field).data('select2')) {
|
||
jQuery(field).on('select2:select', function() {
|
||
console.log('[DraftCreator] Item field changed (select2):', field.name);
|
||
const customerField = document.querySelector('select[name="customer"]');
|
||
if (customerField && customerField.value && !draftCreated) {
|
||
scheduleCreateDraft();
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
form.dataset.draftCreatorAttached = 'true';
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Планирует создание черновика с задержкой (debouncing)
|
||
*/
|
||
function scheduleCreateDraft() {
|
||
// Отменяем предыдущий таймер
|
||
if (createDraftTimer) {
|
||
clearTimeout(createDraftTimer);
|
||
}
|
||
|
||
// Устанавливаем новый таймер
|
||
createDraftTimer = setTimeout(() => {
|
||
createDraft();
|
||
}, CONFIG.DEBOUNCE_DELAY);
|
||
|
||
console.log('[DraftCreator] Scheduled draft creation in ' + CONFIG.DEBOUNCE_DELAY + 'ms');
|
||
}
|
||
|
||
/**
|
||
* Создаёт черновик заказа
|
||
*/
|
||
async function createDraft() {
|
||
if (isCreatingDraft || draftCreated) {
|
||
console.log('[DraftCreator] Already creating or created, skipping');
|
||
return;
|
||
}
|
||
|
||
isCreatingDraft = true;
|
||
console.log('[DraftCreator] Creating draft...');
|
||
|
||
try {
|
||
// Собираем данные формы
|
||
const formData = collectFormData();
|
||
|
||
// Проверяем наличие клиента
|
||
if (!formData.customer) {
|
||
console.log('[DraftCreator] No customer selected, skipping');
|
||
isCreatingDraft = false;
|
||
return;
|
||
}
|
||
|
||
// Отправляем AJAX запрос
|
||
const response = await fetch(CONFIG.CREATE_DRAFT_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': getCsrfToken(),
|
||
},
|
||
body: JSON.stringify(formData)
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok && data.success) {
|
||
console.log('[DraftCreator] Draft created successfully:', data);
|
||
draftCreated = true;
|
||
|
||
// Показываем уведомление
|
||
showNotification('Черновик создан. Перенаправление...');
|
||
|
||
// Перенаправляем на страницу редактирования черновика
|
||
setTimeout(() => {
|
||
window.location.href = data.redirect_url;
|
||
}, 500);
|
||
|
||
} else {
|
||
console.error('[DraftCreator] Error creating draft:', data);
|
||
|
||
// Если ошибка не критичная (например, клиент не выбран), не показываем
|
||
if (response.status !== 400) {
|
||
showNotification('Ошибка создания черновика: ' + (data.error || 'Неизвестная ошибка'), 'error');
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('[DraftCreator] Exception:', error);
|
||
showNotification('Ошибка соединения с сервером', 'error');
|
||
} finally {
|
||
isCreatingDraft = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Собирает данные формы для отправки
|
||
*/
|
||
function collectFormData() {
|
||
const form = document.getElementById('order-form');
|
||
const data = {};
|
||
|
||
// Основные поля заказа
|
||
const customerField = form.querySelector('select[name="customer"]');
|
||
if (customerField && customerField.value) {
|
||
data.customer = parseInt(customerField.value);
|
||
}
|
||
|
||
const deliveryDateField = form.querySelector('input[name="delivery_date"]');
|
||
if (deliveryDateField && deliveryDateField.value) {
|
||
data.delivery_date = deliveryDateField.value;
|
||
}
|
||
|
||
const deliveryTimeStartField = form.querySelector('input[name="delivery_time_start"]');
|
||
if (deliveryTimeStartField && deliveryTimeStartField.value) {
|
||
data.delivery_time_start = deliveryTimeStartField.value;
|
||
}
|
||
|
||
const deliveryTimeEndField = form.querySelector('input[name="delivery_time_end"]');
|
||
if (deliveryTimeEndField && deliveryTimeEndField.value) {
|
||
data.delivery_time_end = deliveryTimeEndField.value;
|
||
}
|
||
|
||
const deliveryCostField = form.querySelector('input[name="delivery_cost"]');
|
||
if (deliveryCostField && deliveryCostField.value) {
|
||
data.delivery_cost = deliveryCostField.value;
|
||
}
|
||
|
||
const paymentMethodField = form.querySelector('select[name="payment_method"]');
|
||
if (paymentMethodField && paymentMethodField.value) {
|
||
data.payment_method = paymentMethodField.value;
|
||
}
|
||
|
||
const specialInstructionsField = form.querySelector('textarea[name="special_instructions"]');
|
||
if (specialInstructionsField) {
|
||
data.special_instructions = specialInstructionsField.value;
|
||
}
|
||
|
||
const discountAmountField = form.querySelector('input[name="discount_amount"]');
|
||
if (discountAmountField && discountAmountField.value) {
|
||
data.discount_amount = discountAmountField.value;
|
||
}
|
||
|
||
// Checkbox поля
|
||
const isDeliveryField = form.querySelector('input[name="is_delivery"]');
|
||
if (isDeliveryField) {
|
||
data.is_delivery = isDeliveryField.checked;
|
||
}
|
||
|
||
const customerIsRecipientField = form.querySelector('input[name="customer_is_recipient"]');
|
||
if (customerIsRecipientField) {
|
||
data.customer_is_recipient = customerIsRecipientField.checked;
|
||
}
|
||
|
||
const isAnonymousField = form.querySelector('input[name="is_anonymous"]');
|
||
if (isAnonymousField) {
|
||
data.is_anonymous = isAnonymousField.checked;
|
||
}
|
||
|
||
// Адрес доставки или точка самовывоза
|
||
const deliveryAddressField = form.querySelector('select[name="delivery_address"]');
|
||
if (deliveryAddressField && deliveryAddressField.value) {
|
||
data.delivery_address = parseInt(deliveryAddressField.value);
|
||
}
|
||
|
||
const pickupShopField = form.querySelector('select[name="pickup_shop"]');
|
||
if (pickupShopField && pickupShopField.value) {
|
||
data.pickup_shop = parseInt(pickupShopField.value);
|
||
}
|
||
|
||
// Собираем позиции заказа
|
||
const items = collectOrderItems();
|
||
if (items.length > 0) {
|
||
data.items = items;
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Собирает данные о позициях заказа
|
||
*/
|
||
function collectOrderItems() {
|
||
const items = [];
|
||
const itemForms = document.querySelectorAll('.order-item-form');
|
||
|
||
itemForms.forEach((form, index) => {
|
||
// Пропускаем удаленные формы
|
||
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
||
if (deleteCheckbox && deleteCheckbox.checked) {
|
||
return;
|
||
}
|
||
|
||
// Получаем выбранный товар/комплект
|
||
const itemSelect = form.querySelector('.select2-order-item');
|
||
if (!itemSelect || !itemSelect.value) {
|
||
return;
|
||
}
|
||
|
||
const itemValue = itemSelect.value;
|
||
const quantityInput = form.querySelector('input[name$="-quantity"]');
|
||
const priceInput = form.querySelector('input[name$="-price"]');
|
||
|
||
if (!quantityInput || !priceInput) {
|
||
return;
|
||
}
|
||
|
||
const item = {
|
||
quantity: quantityInput.value || '1',
|
||
price: priceInput.value || '0'
|
||
};
|
||
|
||
// Определяем тип: товар или комплект
|
||
if (itemValue.startsWith('product_')) {
|
||
item.product_id = parseInt(itemValue.replace('product_', ''));
|
||
} else if (itemValue.startsWith('kit_')) {
|
||
item.product_kit_id = parseInt(itemValue.replace('kit_', ''));
|
||
}
|
||
|
||
items.push(item);
|
||
});
|
||
|
||
return items;
|
||
}
|
||
|
||
/**
|
||
* Получает CSRF токен
|
||
*/
|
||
function getCsrfToken() {
|
||
// Пробуем получить из cookie
|
||
const name = 'csrftoken';
|
||
let cookieValue = null;
|
||
if (document.cookie && document.cookie !== '') {
|
||
const cookies = document.cookie.split(';');
|
||
for (let i = 0; i < cookies.length; i++) {
|
||
const cookie = cookies[i].trim();
|
||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Если не нашли в cookie, пробуем в input поле
|
||
if (!cookieValue) {
|
||
const csrfInput = document.querySelector('input[name="csrfmiddlewaretoken"]');
|
||
if (csrfInput) {
|
||
cookieValue = csrfInput.value;
|
||
}
|
||
}
|
||
|
||
return cookieValue;
|
||
}
|
||
|
||
/**
|
||
* Показывает уведомление пользователю
|
||
*/
|
||
function showNotification(message, type = 'info') {
|
||
// Создаём простое уведомление
|
||
const notification = document.createElement('div');
|
||
notification.className = `alert alert-${type === 'error' ? 'danger' : 'info'}`;
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 70px;
|
||
right: 20px;
|
||
z-index: 1050;
|
||
min-width: 250px;
|
||
padding: 10px 15px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||
`;
|
||
notification.innerHTML = `
|
||
<div class="d-flex align-items-center">
|
||
<span class="me-2">${type === 'error' ? '⚠️' : 'ℹ️'}</span>
|
||
<span>${message}</span>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// Автоматически удаляем через 3 секунды
|
||
setTimeout(() => {
|
||
notification.remove();
|
||
}, 3000);
|
||
}
|
||
|
||
// Инициализация при загрузке DOM
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
|
||
})();
|