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:
@@ -872,3 +872,35 @@ body {
|
|||||||
.mobile-cart-actions .dropdown-item i {
|
.mobile-cart-actions .dropdown-item i {
|
||||||
font-size: 1rem;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
194
myproject/pos/static/pos/js/cart-item-editor.js
Normal file
194
myproject/pos/static/pos/js/cart-item-editor.js
Normal 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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -19,6 +19,8 @@ let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textCo
|
|||||||
let currentCategoryId = null;
|
let currentCategoryId = null;
|
||||||
let isShowcaseView = false;
|
let isShowcaseView = false;
|
||||||
const cart = new Map();
|
const cart = new Map();
|
||||||
|
// Экспорт корзины для использования в других модулях
|
||||||
|
window.cart = cart;
|
||||||
|
|
||||||
// Переменные для пагинации
|
// Переменные для пагинации
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
@@ -1270,6 +1272,13 @@ function renderCart() {
|
|||||||
cart.forEach((item, cartKey) => {
|
cart.forEach((item, cartKey) => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'cart-item mb-2';
|
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';
|
const isShowcaseKit = item.type === 'showcase_kit';
|
||||||
@@ -1417,6 +1426,20 @@ function renderCart() {
|
|||||||
row.appendChild(itemTotal);
|
row.appendChild(itemTotal);
|
||||||
row.appendChild(deleteBtn);
|
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);
|
list.appendChild(row);
|
||||||
|
|
||||||
total += item.qty * item.price;
|
total += item.qty * item.price;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -713,6 +713,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Модалка редактирования товара в корзине -->
|
||||||
|
{% include 'pos/components/edit_cart_item_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
@@ -732,4 +735,5 @@
|
|||||||
|
|
||||||
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
|
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
|
||||||
<script src="{% static 'pos/js/terminal.js' %}"></script>
|
<script src="{% static 'pos/js/terminal.js' %}"></script>
|
||||||
|
<script src="{% static 'pos/js/cart-item-editor.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user