/** * Модуль автоматического создания черновика заказа. * * При первом изменении формы создания заказа автоматически создаёт черновик * и перенаправляет пользователя на страницу редактирования черновика, * где уже работает обычное автосохранение. */ (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 = `