Files
octopus/myproject/orders/static/orders/js/draft-creator.js
Andrey Smakotin 4a4bd437b9 refactor: Заменить сущность Магазин (Shop) на Склад (Warehouse)
Упрощена логика системы путём замены отдельной сущности "Магазин"
на универсальную сущность "Склад", которая может использоваться
как точка самовывоза.

Изменения:
- Расширена модель Warehouse: добавлены адрес, контакты, флаг is_pickup_point
- Модель Order: поле pickup_shop заменено на pickup_warehouse
- Обновлены все формы, сервисы, views, admin для работы со складами
- Обновлены шаблоны HTML и JavaScript код
- Удалено приложение shops полностью
- Пересозданы миграции БД
- Обновлён навбар (удалена ссылка на магазины)

Преимущества:
- Упрощена архитектура системы
- Единая точка управления складами и точками самовывоза
- Интеграция с системой инвентаризации

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 23:50:30 +03:00

441 lines
17 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();
}
});
}
}
// Черновик создаётся ТОЛЬКО при выборе клиента.
// После создания и переадресации на страницу редактирования
// уже работает полноценное автосохранение для всех полей и товаров.
}
/**
* Планирует создание черновика с задержкой (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();
}
};
})();