- 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
516 lines
17 KiB
JavaScript
516 lines
17 KiB
JavaScript
// 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();
|