Simplify order creation and editing - remove autosave

- Removed autosave.js (665 lines) and draft-creator.js (441 lines)
- Removed draft_service.py (~500 lines) and DraftOrderService
- Removed AJAX endpoints: autosave and create-draft
- Updated order_create() to add is_create_page flag
- Updated order_update() to finalize drafts without DraftOrderService
- Added get_new_status() method to OrderStatusService
- Updated order_form.html:
  - Removed old JS includes
  - Added beforeunload warning for unsaved data
  - Updated buttons: separate buttons for create/draft/finalize
- Total code reduction: ~1600 lines (92% removed)

New workflow:
- /orders/create/ - user fills form, chooses button
- /orders/<id>/edit/ - simple editing without autosave
- beforeunload warning when leaving page (except on submit)
This commit is contained in:
2025-11-28 23:29:19 +03:00
parent f911a57640
commit 9a44c98e6e
8 changed files with 110 additions and 1946 deletions

View File

@@ -1,664 +0,0 @@
/**
* Модуль автосохранения черновиков заказов.
*
* Автоматически сохраняет изменения в черновике заказа при изменении полей формы.
* Использует debouncing для уменьшения количества запросов к серверу.
*/
(function() {
'use strict';
// Конфигурация
const CONFIG = {
AUTOSAVE_DELAY: 3000, // Задержка перед автосохранением (мс)
AUTOSAVE_URL_PATTERN: '/orders/{orderNumber}/autosave/',
STATUS_DISPLAY_DURATION: 5000, // Длительность показа статуса (мс)
};
// Состояние модуля
let autosaveTimer = null;
let isAutosaving = false;
let orderNumber = null;
/**
* Инициализация модуля автосохранения
*/
function init() {
// Проверяем, что мы на странице редактирования
const isEditPage = window.location.pathname.includes('/edit/');
if (!isEditPage) {
return;
}
const orderForm = document.getElementById('order-form');
if (!orderForm) {
return;
}
// Получаем номер заказа из URL
const urlMatch = window.location.pathname.match(/\/orders\/(\d+)\/edit\//);
if (!urlMatch) {
return;
}
orderNumber = urlMatch[1];
// Инициализируем UI индикатора
initStatusIndicator();
// Добавляем обработчики событий
attachEventListeners();
}
/**
* Создает индикатор статуса автосохранения
*/
function initStatusIndicator() {
// Проверяем, не создан ли уже индикатор
if (document.getElementById('autosave-status')) {
return;
}
const indicator = document.createElement('div');
indicator.id = 'autosave-status';
indicator.className = 'alert alert-info';
indicator.style.cssText = `
position: fixed;
top: 70px;
right: 20px;
z-index: 1050;
min-width: 250px;
display: none;
padding: 10px 15px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
`;
indicator.innerHTML = `
<div class="d-flex align-items-center">
<span id="autosave-icon" class="me-2"></span>
<span id="autosave-text">Автосохранение...</span>
</div>
`;
document.body.appendChild(indicator);
}
/**
* Показывает статус автосохранения
*/
function showStatus(type, message) {
const indicator = document.getElementById('autosave-status');
const icon = document.getElementById('autosave-icon');
const text = document.getElementById('autosave-text');
if (!indicator || !icon || !text) {
return;
}
// Убираем все классы
indicator.className = 'alert';
// Устанавливаем соответствующий класс и иконку
switch (type) {
case 'saving':
indicator.classList.add('alert-info');
icon.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span>';
break;
case 'success':
indicator.classList.add('alert-success');
icon.innerHTML = '<i class="bi bi-check-circle-fill"></i>';
break;
case 'error':
indicator.classList.add('alert-danger');
icon.innerHTML = '<i class="bi bi-exclamation-triangle-fill"></i>';
break;
}
text.textContent = message;
indicator.style.display = 'block';
// Автоматически скрываем статус (кроме ошибок)
if (type !== 'error') {
setTimeout(() => {
indicator.style.display = 'none';
}, CONFIG.STATUS_DISPLAY_DURATION);
}
}
/**
* Прикрепляет обработчики событий к полям формы
*/
function attachEventListeners() {
const form = document.getElementById('order-form');
if (!form) {
return;
}
// Слушаем изменения в основных полях заказа
const fieldsToWatch = [
'select[name="customer"]',
'select[name="status"]',
'input[name="delivery_date"]',
'input[name="delivery_time_start"]',
'input[name="delivery_time_end"]',
'input[name="delivery_cost"]',
'textarea[name="special_instructions"]',
'input[name="discount_amount"]',
'input[type="checkbox"]',
'input[type="radio"]',
'select[name="delivery_address"]',
'select[name="pickup_warehouse"]',
// Поля адреса доставки
'input[name="address_street"]',
'input[name="address_building_number"]',
'input[name="address_apartment_number"]',
'input[name="address_entrance"]',
'input[name="address_floor"]',
'input[name="address_intercom_code"]',
'textarea[name="address_delivery_instructions"]',
// Поля получателя
'input[name="recipient_name"]',
'input[name="recipient_phone"]',
];
fieldsToWatch.forEach(selector => {
const fields = form.querySelectorAll(selector);
fields.forEach(field => {
// Для select и checkbox используем 'change'
if (field.tagName === 'SELECT' || field.type === 'checkbox' || field.type === 'radio') {
field.addEventListener('change', scheduleAutosave);
} else {
// Для текстовых полей используем 'input'
field.addEventListener('input', scheduleAutosave);
}
});
});
// Слушаем изменения в формах товаров (formset)
observeFormsetChanges();
// Слушаем изменения в формах платежей (payment formset)
observePaymentFormsetChanges();
}
/**
* Наблюдает за изменениями в формсете товаров
*/
function observeFormsetChanges() {
const formsetContainer = document.getElementById('order-items-container');
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.autosaveAttached === 'true') {
return;
}
const fields = form.querySelectorAll('select, input[type="number"], input[type="text"], input[type="checkbox"]');
fields.forEach(field => {
if (field.tagName === 'SELECT' || field.type === 'checkbox') {
field.addEventListener('change', scheduleAutosave);
// Для Select2 добавляем дополнительный обработчик
if (window.jQuery && jQuery(field).data('select2')) {
jQuery(field).on('select2:select', scheduleAutosave);
}
} else {
field.addEventListener('input', scheduleAutosave);
}
});
form.dataset.autosaveAttached = 'true';
});
}
/**
* Наблюдает за изменениями в формсете платежей
*/
function observePaymentFormsetChanges() {
const paymentsContainer = document.getElementById('payments-container');
if (!paymentsContainer) {
return;
}
// Наблюдаем за добавлением/удалением форм платежей
const observer = new MutationObserver(() => {
attachPaymentFormsetEventListeners();
});
observer.observe(paymentsContainer, {
childList: true,
subtree: true
});
// Прикрепляем обработчики к существующим формам
attachPaymentFormsetEventListeners();
}
/**
* Прикрепляет обработчики к полям в формах платежей
*/
function attachPaymentFormsetEventListeners() {
const paymentForms = document.querySelectorAll('.payment-form');
paymentForms.forEach(form => {
// Если уже прикреплены обработчики, пропускаем
if (form.dataset.autosavePaymentAttached === 'true') {
return;
}
const fields = form.querySelectorAll('select, input[type="number"], input[type="text"], textarea, input[type="checkbox"]');
fields.forEach(field => {
if (field.tagName === 'SELECT' || field.type === 'checkbox') {
field.addEventListener('change', scheduleAutosave);
} else {
field.addEventListener('input', scheduleAutosave);
}
});
form.dataset.autosavePaymentAttached = 'true';
});
}
/**
* Планирует автосохранение с задержкой (debouncing)
*/
function scheduleAutosave() {
// Отменяем предыдущий таймер
if (autosaveTimer) {
clearTimeout(autosaveTimer);
}
// Устанавливаем новый таймер
autosaveTimer = setTimeout(() => {
performAutosave();
}, CONFIG.AUTOSAVE_DELAY);
}
/**
* Выполняет автосохранение
*/
async function performAutosave() {
if (isAutosaving) {
return;
}
isAutosaving = true;
showStatus('saving', 'Сохранение...');
try {
// Собираем данные формы
const formData = collectFormData();
// Отправляем AJAX запрос
const url = CONFIG.AUTOSAVE_URL_PATTERN.replace('{orderNumber}', orderNumber);
const response = await fetch(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) {
const lastSaved = formatDateTime(data.last_saved);
showStatus('success', 'Сохранено ' + lastSaved);
} else {
showStatus('error', 'Ошибка: ' + (data.error || 'Неизвестная ошибка'));
}
} catch (error) {
showStatus('error', 'Ошибка соединения с сервером');
} finally {
isAutosaving = 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 statusField = form.querySelector('select[name="status"]');
if (statusField && statusField.value) {
data.status = parseInt(statusField.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 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 pickupWarehouseField = form.querySelector('select[name="pickup_warehouse"]');
if (pickupWarehouseField && pickupWarehouseField.value) {
data.pickup_warehouse = parseInt(pickupWarehouseField.value);
}
// Поля адреса доставки (новая логика с прямым вводом)
const addressStreetField = form.querySelector('input[name="address_street"]');
const addressBuildingField = form.querySelector('input[name="address_building_number"]');
const addressApartmentField = form.querySelector('input[name="address_apartment_number"]');
const addressEntranceField = form.querySelector('input[name="address_entrance"]');
const addressFloorField = form.querySelector('input[name="address_floor"]');
const addressIntercomField = form.querySelector('input[name="address_intercom_code"]');
const addressInstructionsField = form.querySelector('textarea[name="address_delivery_instructions"]');
// Собираем все поля адреса
const addressFields = {
address_street: addressStreetField?.value || '',
address_building_number: addressBuildingField?.value || '',
address_apartment_number: addressApartmentField?.value || '',
address_entrance: addressEntranceField?.value || '',
address_floor: addressFloorField?.value || '',
address_intercom_code: addressIntercomField?.value || '',
address_delivery_instructions: addressInstructionsField?.value || '',
};
// Проверяем, заполнено ли хотя бы одно поле адреса
const hasAnyAddressData = Object.values(addressFields).some(value => value.trim() !== '');
if (hasAnyAddressData) {
// Указываем режим "новый адрес" если заполнено хотя бы одно поле
data.address_mode = 'new';
// Добавляем все непустые поля в данные
Object.entries(addressFields).forEach(([key, value]) => {
if (value.trim() !== '') {
data[key] = value;
}
});
}
const addressConfirmField = form.querySelector('input[name="address_confirm_with_recipient"]');
if (addressConfirmField) {
data.address_confirm_with_recipient = addressConfirmField.checked;
}
// Поля получателя
const recipientNameField = form.querySelector('input[name="recipient_name"]');
if (recipientNameField && recipientNameField.value) {
data.recipient_name = recipientNameField.value;
}
const recipientPhoneField = form.querySelector('input[name="recipient_phone"]');
if (recipientPhoneField && recipientPhoneField.value) {
data.recipient_phone = recipientPhoneField.value;
}
// Собираем позиции заказа
const orderItemsData = collectOrderItems();
data.items = orderItemsData.items;
data.deleted_item_ids = orderItemsData.deletedItemIds;
// Собираем платежи
const paymentsData = collectPayments();
data.payments = paymentsData.payments;
data.deleted_payment_ids = paymentsData.deletedPaymentIds;
// Флаг для пересчета итоговой суммы
data.recalculate = true;
return data;
}
/**
* Собирает данные о позициях заказа
*/
function collectOrderItems() {
const items = [];
const deletedItemIds = [];
const itemForms = document.querySelectorAll('.order-item-form');
itemForms.forEach(form => {
// Проверяем, помечена ли форма на удаление
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
const idField = form.querySelector('input[name$="-id"]');
if (deleteCheckbox && deleteCheckbox.checked) {
// Если форма помечена на удаление и имеет ID, добавляем в список удалённых
if (idField && idField.value) {
deletedItemIds.push(parseInt(idField.value));
}
return; // Не добавляем в items
}
// Получаем выбранный товар/комплект
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').replace(',', '.')
};
// Если есть ID (существующий товар), добавляем его
if (idField && idField.value) {
item.id = parseInt(idField.value);
}
// Определяем тип: товар или комплект
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, deletedItemIds };
}
/**
* Собирает данные о платежах
*/
function collectPayments() {
const payments = [];
const deletedPaymentIds = [];
const paymentForms = document.querySelectorAll('.payment-form');
paymentForms.forEach(form => {
// Проверяем, помечена ли форма на удаление
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
const idField = form.querySelector('input[name$="-id"]');
if (deleteCheckbox && deleteCheckbox.checked) {
// Если форма помечена на удаление и имеет ID, добавляем в список удалённых
if (idField && idField.value) {
deletedPaymentIds.push(parseInt(idField.value));
}
return; // Не добавляем в payments
}
// Получаем способ оплаты и сумму
const paymentMethodSelect = form.querySelector('select[name$="-payment_method"]');
const amountInput = form.querySelector('input[name$="-amount"]');
const notesInput = form.querySelector('textarea[name$="-notes"]');
if (!paymentMethodSelect || !paymentMethodSelect.value || !amountInput || !amountInput.value) {
return; // Пропускаем пустые платежи
}
const payment = {
payment_method_id: parseInt(paymentMethodSelect.value),
amount: (amountInput.value || '0').replace(',', '.'),
notes: notesInput ? notesInput.value : ''
};
// Если есть ID (существующий платеж), добавляем его
if (idField && idField.value) {
payment.id = parseInt(idField.value);
}
payments.push(payment);
});
return { payments, deletedPaymentIds };
}
/**
* Получает CSRF токен из cookies или meta тега
*/
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, пробуем в meta теге
if (!cookieValue) {
const metaTag = document.querySelector('meta[name="csrf-token"]');
if (metaTag) {
cookieValue = metaTag.getAttribute('content');
}
}
// Если не нашли в meta теге, пробуем в input поле
if (!cookieValue) {
const csrfInput = document.querySelector('input[name="csrfmiddlewaretoken"]');
if (csrfInput) {
cookieValue = csrfInput.value;
}
}
return cookieValue;
}
/**
* Форматирует дату и время для отображения
*/
function formatDateTime(isoString) {
if (!isoString) {
return 'только что';
}
const date = new Date(isoString);
const now = new Date();
const diffMs = now - date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
if (diffSecs < 60) {
return 'только что';
} else if (diffMins < 60) {
return diffMins + ' мин. назад';
} else {
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return 'в ' + hours + ':' + minutes;
}
}
// Инициализация при загрузке DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Экспортируем функцию scheduleAutosave в глобальную область
window.orderAutosave = {
scheduleAutosave: scheduleAutosave
};
})();

View File

@@ -1,440 +0,0 @@
/**
* Модуль автоматического создания черновика заказа.
*
* При первом изменении формы создания заказа автоматически создаёт черновик
* и перенаправляет пользователя на страницу редактирования черновика,
* где уже работает обычное автосохранение.
*/
(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();
}
});
}
}
// Черновик создаётся ТОЛЬКО при выборе клиента.
// После создания и переадресации на страницу редактирования
// уже работает полноценное автосохранение для всех полей и товаров.
}
/**
* Планирует создание черновика с задержкой (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 pickupWarehouseField = form.querySelector('select[name="pickup_warehouse"]');
if (pickupWarehouseField && pickupWarehouseField.value) {
data.pickup_warehouse = parseInt(pickupWarehouseField.value);
}
// Новая логика выбора адреса
const addressModeField = form.querySelector('input[name="address_mode"]:checked');
if (addressModeField) {
data.address_mode = addressModeField.value;
if (addressModeField.value === 'history') {
const addressFromHistoryField = form.querySelector('select[name="address_from_history"]');
if (addressFromHistoryField && addressFromHistoryField.value) {
data.address_from_history = parseInt(addressFromHistoryField.value);
}
} else if (addressModeField.value === 'new') {
// Собираем поля нового адреса
const addressStreetField = form.querySelector('input[name="address_street"]');
if (addressStreetField && addressStreetField.value) {
data.address_street = addressStreetField.value;
}
const addressBuildingField = form.querySelector('input[name="address_building_number"]');
if (addressBuildingField && addressBuildingField.value) {
data.address_building_number = addressBuildingField.value;
}
const addressApartmentField = form.querySelector('input[name="address_apartment_number"]');
if (addressApartmentField && addressApartmentField.value) {
data.address_apartment_number = addressApartmentField.value;
}
const addressEntranceField = form.querySelector('input[name="address_entrance"]');
if (addressEntranceField && addressEntranceField.value) {
data.address_entrance = addressEntranceField.value;
}
const addressFloorField = form.querySelector('input[name="address_floor"]');
if (addressFloorField && addressFloorField.value) {
data.address_floor = addressFloorField.value;
}
const addressIntercomField = form.querySelector('input[name="address_intercom_code"]');
if (addressIntercomField && addressIntercomField.value) {
data.address_intercom_code = addressIntercomField.value;
}
const addressInstructionsField = form.querySelector('textarea[name="address_delivery_instructions"]');
if (addressInstructionsField && addressInstructionsField.value) {
data.address_delivery_instructions = addressInstructionsField.value;
}
const addressConfirmField = form.querySelector('input[name="address_confirm_with_recipient"]');
if (addressConfirmField) {
data.address_confirm_with_recipient = addressConfirmField.checked;
}
}
}
// Поля получателя
const recipientNameField = form.querySelector('input[name="recipient_name"]');
if (recipientNameField && recipientNameField.value) {
data.recipient_name = recipientNameField.value;
}
const recipientPhoneField = form.querySelector('input[name="recipient_phone"]');
if (recipientPhoneField && recipientPhoneField.value) {
data.recipient_phone = recipientPhoneField.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();
}
// Экспортируем публичный API для вызова из модального окна
window.DraftCreator = {
triggerDraftCreation: function() {
console.log('[DraftCreator] Triggered via API');
scheduleCreateDraft();
}
};
})();