feat(pos): добавить редактирование цены товара в корзине

- Добавить модалку редактирования товара в корзине (edit_cart_item_modal.html)
- Создать JS модуль cart-item-editor.js для логики редактирования
- При клике на строку товара открывается модалка с возможностью изменения цены и количества
- Добавить визуальную индикацию изменённой цены (оранжевый цвет и звёздочка)
- Экспортировать корзину в window.cart для доступа из других модулей
- Добавить авто-выделение текста при фокусе в полях ввода

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 22:08:58 +03:00
parent 961cfcb9cd
commit 017fa4b744
5 changed files with 317 additions and 1 deletions

View File

@@ -872,3 +872,35 @@ body {
.mobile-cart-actions .dropdown-item i {
font-size: 1rem;
}
/* ============================================================
ИНТЕРАКТИВНОСТЬ СТРОКИ КОРЗИНЫ (редактирование товара)
============================================================ */
/* Интерактивность строки корзины при наведении */
.cart-item {
transition: background-color 0.15s ease;
}
.cart-item:hover {
background-color: #f8f9fa !important;
border-radius: 4px;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}
/* Исключаем hover для витринных комплектов - они сохраняют свой фон */
.cart-item[style*="background-color"]:hover {
background-color: #ffe6a0 !important; /* чуть светлее желтого */
}
/* Индикатор изменённой цены */
.cart-item.price-overridden .item-name-price .text-muted {
color: #f59e0b !important;
font-weight: 600;
}
.cart-item.price-overridden .item-name-price .text-muted::after {
content: ' *';
color: #f59e0b;
}

View File

@@ -0,0 +1,194 @@
/**
* Модуль редактирования товара в корзине POS-терминала
* Отвечает за открытие модалки и сохранение изменений
*/
(function() {
'use strict';
let editingCartKey = null;
let basePrice = 0;
/**
* Округление цены до 2 знаков
*/
function roundPrice(value) {
if (value === null || value === undefined || isNaN(value)) return '0.00';
return (Number(value)).toFixed(2);
}
/**
* Открытие модалки редактирования
* @param {string} cartKey - ключ товара в корзине
*/
function openModal(cartKey) {
const item = window.cart?.get(cartKey);
if (!item) {
console.error('CartItemEditor: Item not found for key:', cartKey);
return;
}
// Проверяем наличие модалки
const modalEl = document.getElementById('editCartItemModal');
if (!modalEl) {
console.error('CartItemEditor: Modal element not found!');
return;
}
editingCartKey = cartKey;
basePrice = parseFloat(item.price) || 0;
// Заполнение полей
document.getElementById('editModalProductName').textContent = item.name || '—';
// Используем formatMoney из terminal.js
const fmtMoney = typeof formatMoney === 'function' ? formatMoney : (v) => Number(v).toFixed(2);
document.getElementById('editModalBasePrice').textContent = fmtMoney(basePrice) + ' руб.';
document.getElementById('editModalPrice').value = roundPrice(basePrice);
document.getElementById('editModalQuantity').value = item.qty || 1;
// Бейдж единицы измерения
const unitBadge = document.getElementById('editModalUnitBadge');
if (item.unit_name) {
unitBadge.textContent = item.unit_name;
unitBadge.style.display = 'inline-block';
} else {
unitBadge.style.display = 'none';
}
updateTotal();
// Показ модалки
const modal = new bootstrap.Modal(modalEl);
modal.show();
console.log('CartItemEditor: Modal opened for', item.name);
}
/**
* Обновление суммы в модалке
*/
function updateTotal() {
const price = parseFloat(document.getElementById('editModalPrice').value) || 0;
const qty = parseFloat(document.getElementById('editModalQuantity').value) || 0;
const fmtMoney = typeof formatMoney === 'function' ? formatMoney : (v) => Number(v).toFixed(2);
document.getElementById('editModalTotal').textContent = fmtMoney(price * qty) + ' руб.';
// Индикатор изменения цены
const warning = document.getElementById('editModalPriceWarning');
if (Math.abs(price - basePrice) > 0.01) {
warning.style.display = 'block';
} else {
warning.style.display = 'none';
}
}
/**
* Сохранение изменений
*/
function saveChanges() {
if (!editingCartKey) return;
const newPrice = parseFloat(document.getElementById('editModalPrice').value) || 0;
const newQty = parseFloat(document.getElementById('editModalQuantity').value) || 1;
const item = window.cart?.get(editingCartKey);
if (item) {
// Используем roundQuantity из terminal.js
const rndQty = typeof roundQuantity === 'function' ? roundQuantity : (v, d) => Math.round(v * Math.pow(10, d)) / Math.pow(10, d);
item.price = newPrice;
item.qty = rndQty(newQty, 3);
item.price_overridden = Math.abs(newPrice - basePrice) > 0.01;
window.cart.set(editingCartKey, item);
// Перерисовка корзины
if (typeof renderCart === 'function') {
renderCart();
}
// Сохранение на сервере
if (typeof saveCartToServer === 'function') {
saveCartToServer();
}
console.log('CartItemEditor: Changes saved for', item.name);
}
// Закрытие модалки
const modalEl = document.getElementById('editCartItemModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
}
/**
* Сброс состояния модалки
*/
function reset() {
editingCartKey = null;
basePrice = 0;
}
/**
* Инициализация модуля
*/
function init() {
const priceInput = document.getElementById('editModalPrice');
const qtyInput = document.getElementById('editModalQuantity');
const confirmBtn = document.getElementById('confirmEditCartItem');
if (!priceInput || !confirmBtn) {
console.warn('CartItemEditor: Required elements not found, deferring init...');
// Повторная попытка через короткое время
setTimeout(init, 100);
return;
}
console.log('CartItemEditor: Initialized successfully');
// Обновление суммы при изменении полей
priceInput.addEventListener('input', updateTotal);
qtyInput.addEventListener('input', updateTotal);
// Авто-выделение всего текста при фокусе
priceInput.addEventListener('focus', function() {
this.select();
});
qtyInput.addEventListener('focus', function() {
this.select();
});
// Кнопка сохранения
confirmBtn.addEventListener('click', saveChanges);
// Сброс при закрытии модалки
const modalEl = document.getElementById('editCartItemModal');
modalEl.addEventListener('hidden.bs.modal', reset);
// Enter для сохранения
priceInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') saveChanges();
});
qtyInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') saveChanges();
});
}
// Экспорт функций для использования из terminal.js
window.CartItemEditor = {
openModal: openModal,
init: init
};
console.log('CartItemEditor: Module loaded');
// Автоинициализация при загрузке
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

View File

@@ -19,6 +19,8 @@ let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textCo
let currentCategoryId = null;
let isShowcaseView = false;
const cart = new Map();
// Экспорт корзины для использования в других модулях
window.cart = cart;
// Переменные для пагинации
let currentPage = 1;
@@ -1270,6 +1272,13 @@ function renderCart() {
cart.forEach((item, cartKey) => {
const row = document.createElement('div');
row.className = 'cart-item mb-2';
row.style.cursor = 'pointer';
row.title = 'Нажмите для редактирования';
// Индикатор изменённой цены
if (item.price_overridden) {
row.classList.add('price-overridden');
}
// СПЕЦИАЛЬНАЯ СТИЛИЗАЦИЯ для витринных комплектов
const isShowcaseKit = item.type === 'showcase_kit';
@@ -1417,6 +1426,20 @@ function renderCart() {
row.appendChild(itemTotal);
row.appendChild(deleteBtn);
// Обработчик клика для редактирования товара
row.addEventListener('click', function(e) {
// Игнорируем клики на кнопки управления количеством и удаления
if (e.target.closest('button') || e.target.closest('input')) {
return;
}
console.log('Cart row clicked, cartKey:', cartKey, 'CartItemEditor:', typeof window.CartItemEditor);
if (window.CartItemEditor) {
window.CartItemEditor.openModal(cartKey);
} else {
console.error('CartItemEditor not available!');
}
});
list.appendChild(row);
total += item.qty * item.price;

View File

@@ -0,0 +1,63 @@
{% load static %}
<div class="modal fade" id="editCartItemModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-pencil-square"></i> Редактирование товара
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Название товара -->
<div class="mb-3">
<label class="form-label text-muted small">Товар</label>
<div id="editModalProductName" class="fw-semibold"></div>
</div>
<!-- Базовая цена (оригинальная) -->
<div class="mb-3">
<label class="form-label text-muted small">Базовая цена</label>
<div class="d-flex align-items-center gap-2">
<span id="editModalBasePrice" class="text-muted">0.00 руб.</span>
<span id="editModalUnitBadge" class="badge bg-secondary" style="display: none;"></span>
</div>
</div>
<!-- Новая цена -->
<div class="mb-3">
<label for="editModalPrice" class="form-label fw-semibold">Цена за единицу</label>
<div class="input-group">
<input type="number" class="form-control" id="editModalPrice"
min="0" step="0.01" placeholder="0.00">
<span class="input-group-text">руб.</span>
</div>
<div id="editModalPriceWarning" class="text-warning small mt-1" style="display: none;">
<i class="bi bi-exclamation-triangle"></i> Цена изменена
</div>
</div>
<!-- Количество -->
<div class="mb-3">
<label for="editModalQuantity" class="form-label fw-semibold">Количество</label>
<input type="number" class="form-control" id="editModalQuantity"
min="0.001" step="0.001" value="1">
</div>
<!-- Итого -->
<div class="alert alert-info mb-0">
<div class="d-flex justify-content-between align-items-center">
<strong>Сумма:</strong>
<span class="fs-5" id="editModalTotal">0.00 руб.</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="confirmEditCartItem">
<i class="bi bi-check-lg"></i> Сохранить
</button>
</div>
</div>
</div>
</div>

View File

@@ -713,6 +713,9 @@
</div>
</div>
</div>
<!-- Модалка редактирования товара в корзине -->
{% include 'pos/components/edit_cart_item_modal.html' %}
{% endblock %}
{% block extra_js %}
@@ -732,4 +735,5 @@
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
<script src="{% static 'pos/js/terminal.js' %}"></script>
<script src="{% static 'pos/js/cart-item-editor.js' %}"></script>
{% endblock %}