Добавлена функциональность редактирования заказов с обновлением резервов товаров

This commit is contained in:
2025-11-27 21:13:42 +03:00
parent da5d4001b5
commit 82ed5a409e
6 changed files with 296 additions and 113 deletions

View File

@@ -843,6 +843,90 @@
</div>
<script>
// Глобально определяем initOrderItemSelect2, чтобы она была доступна при вызове ниже
window.initOrderItemSelect2 = function(element) {
console.log('[initOrderItemSelect2] Вызвана для элемента:', element);
// Проверяем доступность jQuery
if (typeof $ === 'undefined') {
console.error('[initOrderItemSelect2] jQuery не загружен!');
return;
}
const $element = $(element);
const formIndex = element.dataset.formIndex;
console.log('[initOrderItemSelect2] formIndex:', formIndex);
// Проверяем, что функция initProductSelect2 доступна
if (typeof window.initProductSelect2 !== 'function') {
console.error('[initOrderItemSelect2] window.initProductSelect2 не определена. Убедитесь, что select2-product-search.js загружен.');
return;
}
console.log('[initOrderItemSelect2] Инициализация Select2 через initProductSelect2...');
// Инициализируем Select2 с AJAX поиском
window.initProductSelect2(
element,
'all', // Искать и товары, и комплекты
'{% url "products:api-search-products-variants" %}'
);
// Обработка выбора элемента
$element.on('select2:select', function(e) {
// Проверяем наличие params (может не быть при программном вызове)
if (!e.params || !e.params.data) {
return;
}
const data = e.params.data;
const idParts = data.id.split('_');
const type = idParts[0]; // 'product' или 'kit'
const id = idParts[1];
// Найти скрытые поля product и product_kit
const form = element.closest('.order-item-form');
const productField = form.querySelector('[name$="-product"]');
const kitField = form.querySelector('[name$="-product_kit"]');
const priceField = form.querySelector('[name$="-price"]');
const isCustomPriceField = form.querySelector('[name$="-is_custom_price"]');
const originalPrice = data.actual_price || data.price || '';
// Установить значение в правильное поле
if (type === 'product') {
productField.value = id;
kitField.value = '';
priceField.value = originalPrice;
} else if (type === 'kit') {
kitField.value = id;
productField.value = '';
priceField.value = originalPrice;
}
// Сохраняем оригинальную цену в data-атрибуте
priceField.dataset.originalPrice = originalPrice;
// Сбрасываем флаг кастомной цены
isCustomPriceField.value = 'false';
// Скрываем индикатор
const badge = form.querySelector('.custom-price-badge');
const priceInfo = form.querySelector('.original-price-info');
if (badge) badge.style.display = 'none';
if (priceInfo) priceInfo.style.display = 'none';
});
// Очистка при удалении выбора
$element.on('select2:clear', function() {
const form = element.closest('.order-item-form');
form.querySelector('[name$="-product"]').value = '';
form.querySelector('[name$="-product_kit"]').value = '';
form.querySelector('[name$="-price"]').value = '';
});
console.log('[initOrderItemSelect2] Инициализация завершена успешно');
};
// Ждем пока jQuery загрузится
function initCustomerSelect2() {
if (typeof $ === 'undefined') {
@@ -1158,79 +1242,6 @@ document.addEventListener('DOMContentLoaded', function() {
customerIsRecipientCheckbox.addEventListener('change', toggleRecipientFields);
toggleRecipientFields(); // Инициализация при загрузке
// Инициализация Select2 для поиска товаров/комплектов
// ВНИМАНИЕ: Эта функция будет вызвана ПОСЛЕ загрузки select2-product-search.js
window.initOrderItemSelect2 = function(element) {
const $element = $(element);
const formIndex = element.dataset.formIndex;
// Проверяем, что функция initProductSelect2 доступна
if (typeof window.initProductSelect2 !== 'function') {
console.error('window.initProductSelect2 is not defined. Make sure select2-product-search.js is loaded.');
return;
}
// Инициализируем Select2 с AJAX поиском
window.initProductSelect2(
element,
'all', // Искать и товары, и комплекты
'{% url "products:api-search-products-variants" %}'
);
// Обработка выбора элемента
$element.on('select2:select', function(e) {
// Проверяем наличие params (может не быть при программном вызове)
if (!e.params || !e.params.data) {
return;
}
const data = e.params.data;
const idParts = data.id.split('_');
const type = idParts[0]; // 'product' или 'kit'
const id = idParts[1];
// Найти скрытые поля product и product_kit
const form = element.closest('.order-item-form');
const productField = form.querySelector('[name$="-product"]');
const kitField = form.querySelector('[name$="-product_kit"]');
const priceField = form.querySelector('[name$="-price"]');
const isCustomPriceField = form.querySelector('[name$="-is_custom_price"]');
const originalPrice = data.actual_price || data.price || '';
// Установить значение в правильное поле
if (type === 'product') {
productField.value = id;
kitField.value = '';
priceField.value = originalPrice;
} else if (type === 'kit') {
kitField.value = id;
productField.value = '';
priceField.value = originalPrice;
}
// Сохраняем оригинальную цену в data-атрибуте
priceField.dataset.originalPrice = originalPrice;
// Сбрасываем флаг кастомной цены
isCustomPriceField.value = 'false';
// Скрываем индикатор
const badge = form.querySelector('.custom-price-badge');
const priceInfo = form.querySelector('.original-price-info');
if (badge) badge.style.display = 'none';
if (priceInfo) priceInfo.style.display = 'none';
});
// Очистка при удалении выбора
$element.on('select2:clear', function() {
const form = element.closest('.order-item-form');
form.querySelector('[name$="-product"]').value = '';
form.querySelector('[name$="-product_kit"]').value = '';
form.querySelector('[name$="-price"]').value = '';
});
};
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===
function calculateOrderItemsTotal() {
// Собираем все видимые (не удалённые) формы товаров
@@ -1431,26 +1442,10 @@ document.addEventListener('DOMContentLoaded', function() {
// Инициализируем итоговую сумму при загрузке страницы
updateOrderItemsTotal();
// Валидация перед отправкой
// Валидация перед отправкой (убрана обязательность товаров — можно сохранить пустой заказ)
document.getElementById('order-form').addEventListener('submit', function(e) {
const visibleForms = Array.from(container.querySelectorAll('.order-item-form'))
.filter(f => !f.classList.contains('deleted'));
// Проверяем, что есть хотя бы одна позиция с товаром
let hasItems = false;
visibleForms.forEach(form => {
const productField = form.querySelector('[name$="-product"]');
const kitField = form.querySelector('[name$="-product_kit"]');
if (productField.value || kitField.value) {
hasItems = true;
}
});
if (!hasItems && visibleForms.length > 0) {
e.preventDefault();
alert('Добавьте хотя бы один товар или комплект в заказ');
return false;
}
// Валидация отключена — заказ можно сохранить без товаров
// Товары можно добавить позже
});
// === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
@@ -2147,13 +2142,42 @@ if (!document.getElementById('notification-styles')) {
<script>
// Инициализируем все существующие Select2 для товаров после загрузки модуля
(function() {
if (typeof window.initOrderItemSelect2 === 'function') {
document.querySelectorAll('.select2-order-item').forEach(window.initOrderItemSelect2);
console.log('[Order Items] Select2 initialized for existing items');
} else {
console.error('[Order Items] window.initOrderItemSelect2 is not defined');
(function initExistingOrderItems() {
console.log('[Order Items] Начало инициализации существующих элементов');
console.log('[Order Items] jQuery доступен?', typeof $ !== 'undefined');
console.log('[Order Items] initOrderItemSelect2 доступен?', typeof window.initOrderItemSelect2 === 'function');
console.log('[Order Items] initProductSelect2 доступен?', typeof window.initProductSelect2 === 'function');
// Проверяем все зависимости
if (typeof $ === 'undefined') {
console.log('[Order Items] Ожидание загрузки jQuery...');
setTimeout(initExistingOrderItems, 100);
return;
}
if (typeof window.initOrderItemSelect2 !== 'function') {
console.log('[Order Items] Ожидание инициализации initOrderItemSelect2...');
setTimeout(initExistingOrderItems, 100);
return;
}
if (typeof window.initProductSelect2 !== 'function') {
console.log('[Order Items] Ожидание загрузки initProductSelect2 из select2-product-search.js...');
setTimeout(initExistingOrderItems, 100);
return;
}
// Все зависимости готовы
console.log('[Order Items] Все зависимости готовы, запуск инициализации...');
const items = document.querySelectorAll('.select2-order-item');
console.log('[Order Items] Найдено элементов для инициализации:', items.length);
items.forEach((item, index) => {
console.log(`[Order Items] Инициализация элемента ${index + 1}/${items.length}`);
window.initOrderItemSelect2(item);
});
console.log('[Order Items] Инициализация всех существующих элементов завершена');
})();
</script>

View File

@@ -130,13 +130,23 @@
{% endif %}
</td>
<td>
{% if order.status %}
<span class="badge" style="background-color: {{ order.status.color }}; color: #fff;">
{{ order.status.label|default:order.status.name }}
<div class="js-status-container" data-order-id="{{ order.pk }}">
<span class="badge badge-lg js-status-badge" style="{% if order.status %}background-color: {{ order.status.color }}; color: #fff;{% else %}background-color: #6c757d; color: #fff;{% endif %} cursor: pointer; font-size: 0.9rem; padding: 0.5rem 0.75rem;" title="Кликните для изменения">
{% if order.status %}
{{ order.status.label|default:order.status.name }}
{% else %}
Не установлен
{% endif %}
</span>
{% else %}
<span class="badge bg-secondary">Не установлен</span>
{% endif %}
<select class="form-select form-select-sm js-status-select" style="display: none;">
<option value="" {% if not order.status_id %}selected{% endif %}>Не установлен</option>
{% for s in status_choices %}
<option value="{{ s.pk }}" data-color="{{ s.color }}" data-label="{{ s.label|default:s.name }}" {% if order.status_id == s.pk %}selected{% endif %}>
{{ s.label|default:s.name }}
</option>
{% endfor %}
</select>
</div>
</td>
<td><strong>{{ order.total_amount }} руб.</strong></td>
<td>
@@ -225,4 +235,79 @@
{% block extra_js %}
<script src="{% static 'orders/js/date_filter.js' %}"></script>
<script>
(function() {
const csrfToken = '{{ csrf_token }}';
async function updateStatus(orderId, statusId) {
const body = new URLSearchParams({ status_id: statusId }).toString();
const resp = await fetch(`/orders/api/${orderId}/set-status/`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'X-CSRFToken': csrfToken
},
body
});
return resp.json();
}
document.querySelectorAll('.js-status-container').forEach(function(container) {
const badge = container.querySelector('.js-status-badge');
const select = container.querySelector('.js-status-select');
const orderId = container.dataset.orderId;
// Click on badge: show select
badge.addEventListener('click', function() {
badge.style.display = 'none';
select.style.display = 'inline-block';
select.focus();
// Open dropdown programmatically
if (select.showPicker) {
select.showPicker();
} else {
// Fallback for browsers without showPicker
select.click();
}
});
// Change status
select.addEventListener('change', async function() {
const statusId = select.value || '';
const selectedOption = select.options[select.selectedIndex];
select.disabled = true;
try {
const result = await updateStatus(orderId, statusId);
if (result.success) {
// Update badge
const newColor = selectedOption.dataset.color || '#6c757d';
const newLabel = selectedOption.dataset.label || 'Не установлен';
badge.style.backgroundColor = newColor;
badge.style.color = '#fff';
badge.textContent = newLabel;
// Show badge, hide select
select.style.display = 'none';
badge.style.display = 'inline-block';
} else {
alert(result.error || 'Не удалось обновить статус');
}
} catch (e) {
alert('Ошибка сервера при обновлении статуса');
} finally {
select.disabled = false;
}
});
// Click outside or blur: hide select, show badge
select.addEventListener('blur', function() {
setTimeout(function() {
select.style.display = 'none';
badge.style.display = 'inline-block';
}, 200);
});
});
})();
</script>
{% endblock %}