Feat: Add order items total sum display with real-time calculation
Добавлено отображение общей суммы всех товаров в заказе с автоматическим обновлением в реальном времени при изменении количества, цены, добавлении или удалении товаров. Изменения: - HTML: Добавлен блок отображения итоговой суммы товаров под списком позиций - JavaScript: Реализованы функции calculateOrderItemsTotal() и updateOrderItemsTotal() - Интеграция: Подключены слушатели событий на поля quantity и price - Обновление суммы происходит при: * Изменении количества или цены товара * Добавлении новой позиции * Удалении позиции * Загрузке страницы Сумма автоматически исключает удалённые формы и корректно обрабатывает пустые значения. Форматирование: 2 знака после запятой. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -255,6 +255,20 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Итоговая сумма товаров -->
|
||||||
|
<div id="order-items-total-section" class="border-top pt-3 mt-3 mb-3">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<p class="mb-0 text-muted">Сумма товаров:</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<h5 class="mb-0 text-primary">
|
||||||
|
<span id="order-items-total-value">0.00</span> руб.
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Скрытый шаблон для новых форм -->
|
<!-- Скрытый шаблон для новых форм -->
|
||||||
<template id="empty-form-template">
|
<template id="empty-form-template">
|
||||||
<div class="order-item-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
<div class="order-item-form border rounded p-3 mb-3" data-form-index="__prefix__">
|
||||||
@@ -592,9 +606,6 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Подключение модуля Select2 для поиска товаров/комплектов -->
|
|
||||||
<script src="{% static 'products/js/select2-product-search.js' %}"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Ждем пока jQuery загрузится
|
// Ждем пока jQuery загрузится
|
||||||
function initCustomerSelect2() {
|
function initCustomerSelect2() {
|
||||||
@@ -904,10 +915,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
toggleRecipientFields(); // Инициализация при загрузке
|
toggleRecipientFields(); // Инициализация при загрузке
|
||||||
|
|
||||||
// Инициализация Select2 для поиска товаров/комплектов
|
// Инициализация Select2 для поиска товаров/комплектов
|
||||||
function initOrderItemSelect2(element) {
|
// ВНИМАНИЕ: Эта функция будет вызвана ПОСЛЕ загрузки select2-product-search.js
|
||||||
|
window.initOrderItemSelect2 = function(element) {
|
||||||
const $element = $(element);
|
const $element = $(element);
|
||||||
const formIndex = element.dataset.formIndex;
|
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 поиском
|
// Инициализируем Select2 с AJAX поиском
|
||||||
window.initProductSelect2(
|
window.initProductSelect2(
|
||||||
element,
|
element,
|
||||||
@@ -967,10 +985,40 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
form.querySelector('[name$="-product_kit"]').value = '';
|
form.querySelector('[name$="-product_kit"]').value = '';
|
||||||
form.querySelector('[name$="-price"]').value = '';
|
form.querySelector('[name$="-price"]').value = '';
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===
|
||||||
|
function calculateOrderItemsTotal() {
|
||||||
|
// Собираем все видимые (не удалённые) формы товаров
|
||||||
|
const visibleForms = Array.from(document.querySelectorAll('.order-item-form'))
|
||||||
|
.filter(form => !form.classList.contains('deleted'));
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
// Для каждого товара: количество × цена
|
||||||
|
visibleForms.forEach(form => {
|
||||||
|
const quantityField = form.querySelector('[name$="-quantity"]');
|
||||||
|
const priceField = form.querySelector('[name$="-price"]');
|
||||||
|
|
||||||
|
if (quantityField && priceField) {
|
||||||
|
const quantity = parseFloat(quantityField.value) || 0;
|
||||||
|
const price = parseFloat(priceField.value) || 0;
|
||||||
|
total += quantity * price;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализировать все существующие формы товаров
|
function updateOrderItemsTotal() {
|
||||||
document.querySelectorAll('.select2-order-item').forEach(initOrderItemSelect2);
|
const total = calculateOrderItemsTotal();
|
||||||
|
const totalElement = document.getElementById('order-items-total-value');
|
||||||
|
|
||||||
|
if (totalElement) {
|
||||||
|
// Форматируем до 2 знаков после запятой
|
||||||
|
totalElement.textContent = total.toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Функция для инициализации отслеживания изменения цены
|
// Функция для инициализации отслеживания изменения цены
|
||||||
function initPriceTracking(form) {
|
function initPriceTracking(form) {
|
||||||
@@ -1009,6 +1057,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
originalPriceValue.textContent = originalPrice.toFixed(2);
|
originalPriceValue.textContent = originalPrice.toFixed(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем слушатели для обновления итоговой суммы
|
||||||
|
priceField.addEventListener('input', updateOrderItemsTotal);
|
||||||
|
|
||||||
|
const quantityField = form.querySelector('[name$="-quantity"]');
|
||||||
|
if (quantityField) {
|
||||||
|
quantityField.addEventListener('input', updateOrderItemsTotal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализация отслеживания цены для всех существующих форм
|
// Инициализация отслеживания цены для всех существующих форм
|
||||||
@@ -1042,7 +1098,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Инициализируем Select2 для новой формы
|
// Инициализируем Select2 для новой формы
|
||||||
const select2Element = newForm.querySelector('.select2-order-item');
|
const select2Element = newForm.querySelector('.select2-order-item');
|
||||||
select2Element.dataset.formIndex = formCount;
|
select2Element.dataset.formIndex = formCount;
|
||||||
initOrderItemSelect2(select2Element);
|
if (typeof window.initOrderItemSelect2 === 'function') {
|
||||||
|
window.initOrderItemSelect2(select2Element);
|
||||||
|
} else {
|
||||||
|
console.error('window.initOrderItemSelect2 is not available');
|
||||||
|
}
|
||||||
|
|
||||||
// Инициализируем отслеживание цены
|
// Инициализируем отслеживание цены
|
||||||
initPriceTracking(newForm);
|
initPriceTracking(newForm);
|
||||||
@@ -1055,6 +1115,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
console.log(`Added new form with index ${formCount}`);
|
console.log(`Added new form with index ${formCount}`);
|
||||||
|
|
||||||
|
// Обновляем итоговую сумму после добавления формы
|
||||||
|
updateOrderItemsTotal();
|
||||||
|
|
||||||
return newForm;
|
return newForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1077,10 +1140,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
deleteCheckbox.checked = true;
|
deleteCheckbox.checked = true;
|
||||||
form.classList.add('deleted');
|
form.classList.add('deleted');
|
||||||
console.log('Form marked for deletion');
|
console.log('Form marked for deletion');
|
||||||
|
// Обновляем итоговую сумму после удаления
|
||||||
|
updateOrderItemsTotal();
|
||||||
} else {
|
} else {
|
||||||
// Если форма новая, просто удаляем из DOM
|
// Если форма новая, просто удаляем из DOM
|
||||||
form.remove();
|
form.remove();
|
||||||
console.log('Form removed from DOM');
|
console.log('Form removed from DOM');
|
||||||
|
// Обновляем итоговую сумму после удаления
|
||||||
|
updateOrderItemsTotal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,6 +1167,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
addNewForm();
|
addNewForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Инициализируем итоговую сумму при загрузке страницы
|
||||||
|
updateOrderItemsTotal();
|
||||||
|
|
||||||
// Валидация перед отправкой
|
// Валидация перед отправкой
|
||||||
document.getElementById('order-form').addEventListener('submit', function(e) {
|
document.getElementById('order-form').addEventListener('submit', function(e) {
|
||||||
const visibleForms = Array.from(container.querySelectorAll('.order-item-form'))
|
const visibleForms = Array.from(container.querySelectorAll('.order-item-form'))
|
||||||
@@ -1676,6 +1746,21 @@ if (!document.getElementById('notification-styles')) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Подключение модуля Select2 для поиска товаров/комплектов -->
|
||||||
|
<script src="{% static 'products/js/select2-product-search.js' %}"></script>
|
||||||
|
|
||||||
|
<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');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Скрипты автосохранения и создания черновиков -->
|
<!-- Скрипты автосохранения и создания черновиков -->
|
||||||
{% if order %}
|
{% if order %}
|
||||||
<!-- Автосохранение при редактировании заказа -->
|
<!-- Автосохранение при редактировании заказа -->
|
||||||
|
|||||||
Reference in New Issue
Block a user