Files
octopus/myproject/pos/static/pos/js/terminal.js
Andrey Smakotin 660c0cb97b feat(pos): Add kit icon indicator in cart
- Added Bootstrap icon (bi-box-seam) for kit items in POS cart
- Kits and showcase kits now display with blue icon for visual distinction
- Regular products remain without icons for cleaner look
- Maintains consistency with product list view styling
2025-11-16 21:42:03 +03:00

516 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
// POS Terminal JavaScript
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent);
const ITEMS = JSON.parse(document.getElementById('itemsData').textContent); // Единый массив товаров и комплектов
const SHOWCASE_KITS = JSON.parse(document.getElementById('showcaseKitsData').textContent); // Витринные комплекты
let currentCategoryId = null;
let isShowcaseView = false; // Флаг режима просмотра витринных букетов
const cart = new Map(); // "type-id" -> {id, name, price, qty, type}
function formatMoney(v) {
return (Number(v)).toFixed(2);
}
function renderCategories() {
const grid = document.getElementById('categoryGrid');
grid.innerHTML = '';
// Кнопка "Витрина" - первая в ряду
const showcaseCol = document.createElement('div');
showcaseCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const showcaseCard = document.createElement('div');
showcaseCard.className = 'card category-card showcase-card' + (isShowcaseView ? ' active' : '');
showcaseCard.style.backgroundColor = '#fff3cd';
showcaseCard.style.borderColor = '#ffc107';
showcaseCard.onclick = () => {
isShowcaseView = true;
currentCategoryId = null;
renderCategories();
renderProducts();
};
const showcaseBody = document.createElement('div');
showcaseBody.className = 'card-body';
const showcaseName = document.createElement('div');
showcaseName.className = 'category-name';
showcaseName.innerHTML = '<i class="bi bi-flower1"></i> <strong>ВИТРИНА</strong>';
showcaseBody.appendChild(showcaseName);
showcaseCard.appendChild(showcaseBody);
showcaseCol.appendChild(showcaseCard);
grid.appendChild(showcaseCol);
// Кнопка "Все"
const allCol = document.createElement('div');
allCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const allCard = document.createElement('div');
allCard.className = 'card category-card' + (currentCategoryId === null && !isShowcaseView ? ' active' : '');
allCard.onclick = () => {
currentCategoryId = null;
isShowcaseView = false;
renderCategories();
renderProducts();
};
const allBody = document.createElement('div');
allBody.className = 'card-body';
const allName = document.createElement('div');
allName.className = 'category-name';
allName.textContent = 'Все товары';
allBody.appendChild(allName);
allCard.appendChild(allBody);
allCol.appendChild(allCard);
grid.appendChild(allCol);
// Категории
CATEGORIES.forEach(cat => {
const col = document.createElement('div');
col.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const card = document.createElement('div');
card.className = 'card category-card' + (currentCategoryId === cat.id && !isShowcaseView ? ' active' : '');
card.onclick = () => {
currentCategoryId = cat.id;
isShowcaseView = false;
renderCategories();
renderProducts();
};
const body = document.createElement('div');
body.className = 'card-body';
const name = document.createElement('div');
name.className = 'category-name';
name.textContent = cat.name;
body.appendChild(name);
card.appendChild(body);
col.appendChild(card);
grid.appendChild(col);
});
}
function renderProducts() {
const grid = document.getElementById('productGrid');
grid.innerHTML = '';
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filtered;
// Если выбран режим витрины - показываем витринные комплекты
if (isShowcaseView) {
filtered = SHOWCASE_KITS;
} else {
// Обычный режим - показываем товары и комплекты
filtered = currentCategoryId
? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId))
: ITEMS;
}
if (searchTerm) {
filtered = filtered.filter(item => item.name.toLowerCase().includes(searchTerm));
}
filtered.forEach(item => {
const col = document.createElement('div');
col.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const card = document.createElement('div');
card.className = 'card product-card';
card.onclick = () => addToCart(item);
const body = document.createElement('div');
body.className = 'card-body';
// Изображение товара/комплекта
const imageDiv = document.createElement('div');
imageDiv.className = 'product-image';
if (item.image) {
const img = document.createElement('img');
img.src = item.image;
img.alt = item.name;
imageDiv.appendChild(img);
} else {
imageDiv.innerHTML = '<i class="bi bi-image"></i>';
}
// Информация о товаре/комплекте
const info = document.createElement('div');
info.className = 'product-info';
const name = document.createElement('div');
name.className = 'product-name';
name.textContent = item.name;
const stock = document.createElement('div');
stock.className = 'product-stock';
// Для витринных комплектов показываем название витрины
if (item.type === 'showcase_kit') {
stock.textContent = `🌺 ${item.showcase_name}`;
stock.style.color = '#856404';
stock.style.fontWeight = 'bold';
} else {
stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ';
if (!item.in_stock) {
stock.style.color = '#dc3545';
}
}
const sku = document.createElement('div');
sku.className = 'product-sku';
const skuText = document.createElement('span');
skuText.textContent = item.sku || 'н/д';
const priceSpan = document.createElement('span');
priceSpan.className = 'product-price';
priceSpan.textContent = `${formatMoney(item.price)}`;
sku.appendChild(skuText);
sku.appendChild(priceSpan);
info.appendChild(name);
info.appendChild(stock);
info.appendChild(sku);
body.appendChild(imageDiv);
body.appendChild(info);
card.appendChild(body);
col.appendChild(card);
grid.appendChild(col);
});
}
function addToCart(item) {
const cartKey = `${item.type}-${item.id}`; // Уникальный ключ: "product-1" или "kit-1"
if (!cart.has(cartKey)) {
cart.set(cartKey, { id: item.id, name: item.name, price: Number(item.price), qty: 1, type: item.type });
} else {
cart.get(cartKey).qty += 1;
}
renderCart();
// Автоматический фокус на поле количества
setTimeout(() => {
const qtyInputs = document.querySelectorAll('.qty-input');
const itemIndex = Array.from(cart.keys()).indexOf(cartKey);
if (itemIndex !== -1 && qtyInputs[itemIndex]) {
qtyInputs[itemIndex].focus();
qtyInputs[itemIndex].select(); // Выделяем весь текст
}
}, 50);
}
function renderCart() {
const list = document.getElementById('cartList');
list.innerHTML = '';
let total = 0;
if (cart.size === 0) {
list.innerHTML = '<p class="text-muted text-center py-4 small">Корзина пуста</p>';
document.getElementById('cartTotal').textContent = '0.00';
return;
}
cart.forEach((item, cartKey) => {
const row = document.createElement('div');
row.className = 'cart-item mb-2';
// Левая часть: Название и цена единицы
const namePrice = document.createElement('div');
namePrice.className = 'item-name-price';
// Иконка только для комплектов
let typeIcon = '';
if (item.type === 'kit' || item.type === 'showcase_kit') {
typeIcon = '<i class="bi bi-box-seam text-info" title="Комплект"></i> ';
}
namePrice.innerHTML = `
<div class="fw-semibold small">${typeIcon}${item.name}</div>
<div class="text-muted" style="font-size: 0.75rem;">${formatMoney(item.price)} / шт</div>
`;
// Знак умножения
const multiplySign = document.createElement('span');
multiplySign.className = 'multiply-sign';
multiplySign.textContent = 'x';
// Поле ввода количества
const qtyInput = document.createElement('input');
qtyInput.type = 'number';
qtyInput.className = 'qty-input';
qtyInput.value = item.qty;
qtyInput.min = 1;
qtyInput.onchange = (e) => {
const newQty = parseInt(e.target.value) || 1;
if (newQty <= 0) {
removeFromCart(cartKey);
} else {
cart.get(cartKey).qty = newQty;
renderCart();
}
};
// Сумма за позицию
const itemTotal = document.createElement('div');
itemTotal.className = 'item-total';
itemTotal.textContent = formatMoney(item.price * item.qty);
// Кнопка удаления
const deleteBtn = document.createElement('button');
deleteBtn.className = 'btn btn-sm btn-link text-danger p-0';
deleteBtn.innerHTML = '<i class="bi bi-x"></i>';
deleteBtn.onclick = () => removeFromCart(cartKey);
row.appendChild(namePrice);
row.appendChild(multiplySign);
row.appendChild(qtyInput);
row.appendChild(itemTotal);
row.appendChild(deleteBtn);
list.appendChild(row);
total += item.qty * item.price;
});
document.getElementById('cartTotal').textContent = formatMoney(total);
}
function removeFromCart(cartKey) {
cart.delete(cartKey);
renderCart();
}
function clearCart() {
cart.clear();
renderCart();
}
document.getElementById('clearCart').onclick = clearCart;
// Кнопка "На витрину" - функционал будет добавлен позже
document.getElementById('addToShowcaseBtn').onclick = () => {
openCreateTempKitModal();
};
// Функция открытия модального окна для создания временного комплекта
async function openCreateTempKitModal() {
// Проверяем что корзина не пуста
if (cart.size === 0) {
alert('Корзина пуста. Добавьте товары перед созданием комплекта.');
return;
}
// Проверяем что в корзине только товары (не комплекты)
let hasKits = false;
for (const [cartKey, item] of cart) {
if (item.type === 'kit') {
hasKits = true;
break;
}
}
if (hasKits) {
alert('В корзине есть комплекты. Для витрины добавляйте только отдельные товары.');
return;
}
// Генерируем название по умолчанию
const now = new Date();
const defaultName = `Витрина — ${now.toLocaleDateString('ru-RU')} ${now.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'})}`;
document.getElementById('tempKitName').value = defaultName;
// Загружаем список витрин
await loadShowcases();
// Заполняем список товаров из корзины
renderTempKitItems();
// Открываем модальное окно
const modal = new bootstrap.Modal(document.getElementById('createTempKitModal'));
modal.show();
}
// Загрузка списка витрин
async function loadShowcases() {
try {
const response = await fetch('/pos/api/get-showcases/');
const data = await response.json();
const select = document.getElementById('showcaseSelect');
select.innerHTML = '<option value="">Выберите витрину...</option>';
if (data.success && data.showcases.length > 0) {
data.showcases.forEach(showcase => {
const option = document.createElement('option');
option.value = showcase.id;
option.textContent = `${showcase.name} (${showcase.warehouse_name})`;
select.appendChild(option);
});
} else {
select.innerHTML = '<option value="">Нет доступных витрин</option>';
}
} catch (error) {
console.error('Error loading showcases:', error);
alert('Ошибка загрузки витрин');
}
}
// Отображение товаров из корзины в модальном окне
function renderTempKitItems() {
const container = document.getElementById('tempKitItemsList');
container.innerHTML = '';
let estimatedTotal = 0;
cart.forEach((item, cartKey) => {
// Только товары (не комплекты)
if (item.type !== 'product') return;
const itemDiv = document.createElement('div');
itemDiv.className = 'd-flex justify-content-between align-items-center mb-2 pb-2 border-bottom';
itemDiv.innerHTML = `
<div>
<strong>${item.name}</strong>
<br>
<small class="text-muted">${item.qty} шт × ${formatMoney(item.price)} руб.</small>
</div>
<div class="text-end">
<strong>${formatMoney(item.qty * item.price)} руб.</strong>
</div>
`;
container.appendChild(itemDiv);
estimatedTotal += item.qty * item.price;
});
document.getElementById('tempKitEstimatedPrice').textContent = formatMoney(estimatedTotal);
}
// Подтверждение создания временного комплекта
document.getElementById('confirmCreateTempKit').onclick = async () => {
const kitName = document.getElementById('tempKitName').value.trim();
const showcaseId = document.getElementById('showcaseSelect').value;
// Валидация
if (!kitName) {
alert('Введите название комплекта');
return;
}
if (!showcaseId) {
alert('Выберите витрину');
return;
}
// Собираем товары из корзины
const items = [];
cart.forEach((item, cartKey) => {
if (item.type === 'product') {
items.push({
product_id: item.id,
quantity: item.qty
});
}
});
if (items.length === 0) {
alert('Нет товаров для создания комплекта');
return;
}
// Отправляем запрос на сервер
const confirmBtn = document.getElementById('confirmCreateTempKit');
confirmBtn.disabled = true;
confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Создание...';
try {
const response = await fetch('/pos/api/create-temp-kit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({
kit_name: kitName,
showcase_id: parseInt(showcaseId),
items: items,
price_adjustment_type: 'none',
price_adjustment_value: 0
})
});
const data = await response.json();
if (data.success) {
// Успех!
alert(`${data.message}
Комплект: ${data.kit_name}
Цена: ${data.kit_price} руб.
Зарезервировано компонентов: ${data.reservations_count}`);
// Очищаем корзину
clearCart();
// Закрываем модальное окно
const modal = bootstrap.Modal.getInstance(document.getElementById('createTempKitModal'));
modal.hide();
} else {
alert(`Ошибка: ${data.error}`);
}
} catch (error) {
console.error('Error creating temp kit:', error);
alert('Ошибка при создании комплекта');
} finally {
confirmBtn.disabled = false;
confirmBtn.innerHTML = '<i class="bi bi-check-circle"></i> Создать и зарезервировать';
}
};
// Вспомогательная функция для получения CSRF токена
function getCookie(name) {
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;
}
}
}
return cookieValue;
}
// Заглушки для функционала (будет реализовано позже)
document.getElementById('checkoutNow').onclick = async () => {
alert('Функционал будет подключен позже: создание заказа и списание со склада.');
};
document.getElementById('scheduleLater').onclick = async () => {
alert('Функционал будет подключен позже: создание заказа на доставку/самовывоз.');
};
// Search functionality
document.getElementById('searchInput').addEventListener('input', () => {
renderProducts();
});
// Customer selection
document.getElementById('customerSelectBtn').addEventListener('click', () => {
alert('Функция выбора клиента будет реализована позже');
});
// Инициализация
renderCategories();
renderProducts();
renderCart();
// Установить фокус на строку поиска
document.getElementById('searchInput').focus();