Files
octopus/myproject/orders/static/orders/js/draft-creator.js
Andrey Smakotin be79730ee3 Добавлена поддержка событий Select2 в draft-creator.js
Поля с Select2 не генерируют стандартные события 'change',
поэтому добавлено прослушивание событий 'select2:select'.
Также добавлено логирование для отладки.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 23:18:49 +03:00

471 lines
18 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Модуль автоматического создания черновика заказа.
*
* При первом изменении формы создания заказа автоматически создаёт черновик
* и перенаправляет пользователя на страницу редактирования черновика,
* где уже работает обычное автосохранение.
*/
(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();
}
})();