// POS Terminal JavaScript const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent); let ITEMS = []; // Будем загружать через API let showcaseKits = JSON.parse(document.getElementById('showcaseKitsData').textContent); let currentCategoryId = null; let isShowcaseView = false; const cart = new Map(); // Переменные для пагинации let currentPage = 1; let hasMoreItems = false; let isLoadingItems = false; let currentSearchQuery = ''; // Текущий поисковый запрос let searchDebounceTimer = null; // Переменные для режима редактирования let isEditMode = false; let editingKitId = null; // Временная корзина для модального окна создания/редактирования комплекта const tempCart = new Map(); // ===== СОХРАНЕНИЕ КОРЗИНЫ В REDIS ===== let saveCartTimeout = null; /** * Сохраняет корзину в Redis с debounce 500ms */ function saveCartToRedis() { // Отменяем предыдущий таймер if (saveCartTimeout) { clearTimeout(saveCartTimeout); } // Устанавливаем новый таймер saveCartTimeout = setTimeout(() => { // Конвертируем Map в обычный объект const cartObj = {}; cart.forEach((value, key) => { cartObj[key] = value; }); // Отправляем на сервер fetch('/pos/api/save-cart/', { method: 'POST', headers: { 'X-CSRFToken': getCsrfToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ cart: cartObj }) }) .then(response => response.json()) .then(data => { if (!data.success) { console.error('Ошибка сохранения корзины:', data.error); } }) .catch(error => { console.error('Ошибка при сохранении корзины в Redis:', error); }); }, 500); // Debounce 500ms } // ===== УПРАВЛЕНИЕ КЛИЕНТОМ ===== // Загружаем данные системного клиента const SYSTEM_CUSTOMER = JSON.parse(document.getElementById('systemCustomerData').textContent); // Текущий выбранный клиент (загружается из Redis или системный) let selectedCustomer = JSON.parse(document.getElementById('selectedCustomerData').textContent); function formatMoney(v) { return (Number(v)).toFixed(2); } // ===== ФУНКЦИИ ДЛЯ РАБОТЫ С КЛИЕНТОМ ===== /** * Обновляет отображение выбранного клиента в UI * Обновляет: * - Кнопку "Выбрать клиента" в корзине (показывает имя клиента) * - Кнопку "Выбрать клиента" в модалке продажи (показывает имя клиента) * - Видимость кнопок сброса в обоих местах (показываем только для не-системного клиента) */ function updateCustomerDisplay() { // Обновляем текст кнопки в корзине const btnText = document.getElementById('customerSelectBtnText'); if (btnText) { btnText.textContent = selectedCustomer.name; } // Обновляем текст кнопки в модалке продажи const checkoutBtnText = document.getElementById('checkoutCustomerSelectBtnText'); if (checkoutBtnText) { checkoutBtnText.textContent = selectedCustomer.name; } // Обновляем видимость кнопок сброса (в корзине и в модалке продажи) // Приводим к числу для надёжного сравнения (JSON может вернуть разные типы) const isSystemCustomer = Number(selectedCustomer.id) === Number(SYSTEM_CUSTOMER.id); [document.getElementById('resetCustomerBtn'), document.getElementById('checkoutResetCustomerBtn')].forEach(resetBtn => { if (resetBtn) { resetBtn.style.display = isSystemCustomer ? 'none' : 'block'; } }); } /** * Устанавливает нового клиента и сохраняет в Redis * @param {number} customerId - ID клиента * @param {string} customerName - Имя клиента */ function selectCustomer(customerId, customerName) { selectedCustomer = { id: customerId, name: customerName }; updateCustomerDisplay(); // Сохраняем выбор в Redis через AJAX fetch(`/pos/api/set-customer/${customerId}/`, { method: 'POST', headers: { 'X-CSRFToken': getCsrfToken(), 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (!data.success) { console.error('Ошибка сохранения клиента:', data.error); } }) .catch(error => { console.error('Ошибка при сохранении клиента в Redis:', error); }); } /** * Инициализация Select2 для поиска клиента */ function initCustomerSelect2() { const $searchInput = $('#customerSearchInput'); $searchInput.select2({ theme: 'bootstrap-5', dropdownParent: $('#selectCustomerModal'), placeholder: 'Начните вводить имя, телефон или email (минимум 3 символа)', minimumInputLength: 3, allowClear: true, ajax: { url: '/customers/api/search/', dataType: 'json', delay: 300, data: function(params) { return { q: params.term }; }, processResults: function(data) { return { results: data.results }; }, cache: true }, templateResult: formatCustomerOption, // Форматирование результатов в выпадающем списке templateSelection: formatCustomerSelection // Форматирование выбранного значения }); // Обработка выбора клиента из списка $searchInput.on('select2:select', function(e) { const data = e.params.data; // Проверяем это не опция "Создать нового клиента" if (data.id === 'create_new') { // Открываем модалку создания const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal')); modal.hide(); openCreateCustomerModal(data.text); return; } // Выбираем клиента selectCustomer(parseInt(data.id), data.name); // Закрываем модалку const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal')); modal.hide(); // Очищаем Select2 $searchInput.val(null).trigger('change'); }); } /** * Форматирование опции клиента в выпадающем списке Select2 * Показывает: Имя, телефон, email в одну строку */ function formatCustomerOption(customer) { if (customer.loading) { return customer.text; } // Если это опция "Создать нового клиента" if (customer.id === 'create_new') { return $(' ' + customer.text + ''); } // Формируем текст в одну строку: Имя (жирным) + контакты (мелким) const parts = []; // Имя const name = customer.name || customer.text; parts.push('' + $('
').text(name).html() + ''); // Телефон и Email const contactInfo = []; if (customer.phone) { contactInfo.push($('
').text(customer.phone).html()); } if (customer.email) { contactInfo.push($('
').text(customer.email).html()); } if (contactInfo.length > 0) { parts.push(' (' + contactInfo.join(', ') + ')'); } return $('' + parts.join('') + ''); } /** * Форматирование выбранного клиента в поле Select2 * Показывает только имя */ function formatCustomerSelection(customer) { return customer.name || customer.text; } /** * Открывает модальное окно создания нового клиента * @param {string} prefillName - Предзаполненное имя (из поиска) */ function openCreateCustomerModal(prefillName = '') { const modal = new bootstrap.Modal(document.getElementById('createCustomerModal')); // Очищаем форму document.getElementById('newCustomerName').value = prefillName || ''; document.getElementById('newCustomerPhone').value = ''; document.getElementById('newCustomerEmail').value = ''; document.getElementById('createCustomerError').classList.add('d-none'); modal.show(); } /** * Создаёт нового клиента через API */ async function createNewCustomer() { const name = document.getElementById('newCustomerName').value.trim(); const phone = document.getElementById('newCustomerPhone').value.trim(); const email = document.getElementById('newCustomerEmail').value.trim(); const errorBlock = document.getElementById('createCustomerError'); // Валидация if (!name) { errorBlock.textContent = 'Укажите имя клиента'; errorBlock.classList.remove('d-none'); return; } // Скрываем ошибку errorBlock.classList.add('d-none'); try { const response = await fetch('/customers/api/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() }, body: JSON.stringify({ name: name, phone: phone || null, email: email || null }) }); const data = await response.json(); if (data.success) { // Выбираем созданного клиента selectCustomer(data.id, data.name); // Закрываем модалку const modal = bootstrap.Modal.getInstance(document.getElementById('createCustomerModal')); modal.hide(); // Показываем уведомление alert(`Клиент "${data.name}" успешно создан!`); } else { // Показываем ошибку errorBlock.textContent = data.error || 'Ошибка при создании клиента'; errorBlock.classList.remove('d-none'); } } catch (error) { console.error('Error creating customer:', error); errorBlock.textContent = 'Ошибка сети при создании клиента'; errorBlock.classList.remove('d-none'); } } 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 = async () => { isShowcaseView = true; currentCategoryId = null; await refreshShowcaseKits(); // Загружаем свежие данные renderCategories(); renderProducts(); }; const showcaseBody = document.createElement('div'); showcaseBody.className = 'card-body'; const showcaseName = document.createElement('div'); showcaseName.className = 'category-name'; showcaseName.innerHTML = ' ВИТРИНА'; 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 = async () => { currentCategoryId = null; isShowcaseView = false; currentSearchQuery = ''; // Сбрасываем поиск document.getElementById('searchInput').value = ''; // Очищаем поле поиска renderCategories(); await loadItems(); // Загрузка через API }; 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 = async () => { currentCategoryId = cat.id; isShowcaseView = false; currentSearchQuery = ''; // Сбрасываем поиск document.getElementById('searchInput').value = ''; // Очищаем поле поиска renderCategories(); await loadItems(); // Загрузка через API }; 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 = ''; let filtered; // Если выбран режим витрины - показываем витринные комплекты if (isShowcaseView) { filtered = showcaseKits; // Для витрины — клиентская фильтрация по поиску const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); if (searchTerm) { filtered = filtered.filter(item => { const name = (item.name || '').toLowerCase(); const sku = (item.sku || '').toLowerCase(); return name.includes(searchTerm) || sku.includes(searchTerm); }); } } else { // Обычный режим - ITEMS уже отфильтрованы на сервере (категория + поиск) filtered = ITEMS; } 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.style.position = 'relative'; card.onclick = () => addToCart(item); // Если это витринный комплект - добавляем кнопку редактирования if (item.type === 'showcase_kit') { // ИНДИКАЦИЯ БЛОКИРОВКИ if (item.is_locked) { // Создаем бейдж блокировки const lockBadge = document.createElement('div'); lockBadge.style.position = 'absolute'; lockBadge.style.top = '5px'; lockBadge.style.left = '5px'; lockBadge.style.zIndex = '10'; if (item.locked_by_me) { // Заблокирован мной - зеленый бейдж lockBadge.className = 'badge bg-success'; lockBadge.innerHTML = ' В корзине'; lockBadge.title = 'Добавлен в вашу корзину'; } else { // Заблокирован другим кассиром - красный бейдж + блокируем карточку lockBadge.className = 'badge bg-danger'; lockBadge.innerHTML = ' Занят'; lockBadge.title = `В корзине ${item.locked_by_user}`; // Затемняем карточку и блокируем клики card.style.opacity = '0.5'; card.style.cursor = 'not-allowed'; card.onclick = (e) => { e.stopPropagation(); alert(`Этот букет уже в корзине кассира "${item.locked_by_user}".\nДождитесь освобождения блокировки.`); }; } card.appendChild(lockBadge); } // Кнопка редактирования (только если НЕ заблокирован другим) if (!item.is_locked || item.locked_by_me) { const editBtn = document.createElement('button'); editBtn.className = 'btn btn-sm btn-outline-primary'; editBtn.style.position = 'absolute'; editBtn.style.top = '5px'; editBtn.style.right = '5px'; editBtn.style.zIndex = '10'; editBtn.innerHTML = ''; editBtn.onclick = (e) => { e.stopPropagation(); openEditKitModal(item.id); }; card.appendChild(editBtn); } } 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; img.loading = 'lazy'; // Lazy loading imageDiv.appendChild(img); } else { imageDiv.innerHTML = ''; } // Информация о товаре/комплекте 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 if (item.type === 'product' && item.available_qty !== undefined && item.reserved_qty !== undefined) { // Для обычных товаров показываем остатки: FREE(-RESERVED) // FREE = доступно для продажи (available - reserved) const available = parseFloat(item.available_qty) || 0; const reserved = parseFloat(item.reserved_qty) || 0; const free = available - reserved; // Создаём элементы для стилизации разных размеров const freeSpan = document.createElement('span'); freeSpan.textContent = free; freeSpan.style.fontSize = '1.1em'; freeSpan.style.fontWeight = 'bold'; freeSpan.style.fontStyle = 'normal'; // Отображаем резерв только если он есть if (reserved > 0) { const reservedSpan = document.createElement('span'); reservedSpan.textContent = `(−${reserved})`; reservedSpan.style.fontSize = '0.85em'; reservedSpan.style.marginLeft = '3px'; reservedSpan.style.fontStyle = 'normal'; stock.appendChild(freeSpan); stock.appendChild(reservedSpan); } else { stock.appendChild(freeSpan); } // Цветовая индикация: красный если свободных остатков нет или отрицательные if (free <= 0) { stock.style.color = '#dc3545'; // Красный } else if (free < 5) { stock.style.color = '#ffc107'; // Жёлтый (мало остатков) } else { stock.style.color = '#28a745'; // Зелёный (достаточно) } } else { // Fallback для старых данных или комплектов 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); }); } // Загрузка товаров через API async function loadItems(append = false) { if (isLoadingItems) return; isLoadingItems = true; if (!append) { currentPage = 1; ITEMS = []; } try { const params = new URLSearchParams({ page: currentPage, page_size: 60 }); if (currentCategoryId) { params.append('category_id', currentCategoryId); } // Добавляем поисковый запрос, если есть if (currentSearchQuery) { params.append('query', currentSearchQuery); } const response = await fetch(`/pos/api/items/?${params}`); const data = await response.json(); if (data.success) { if (append) { ITEMS = ITEMS.concat(data.items); } else { ITEMS = data.items; } hasMoreItems = data.has_more; if (data.has_more) { currentPage = data.next_page; } renderProducts(); } } catch (error) { console.error('Ошибка загрузки товаров:', error); } finally { isLoadingItems = false; } } // Infinite scroll function setupInfiniteScroll() { const grid = document.getElementById('productGrid'); const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting && hasMoreItems && !isLoadingItems && !isShowcaseView) { loadItems(true); // Догрузка } }); }, { rootMargin: '200px' } ); // Наблюдаем за концом грида const sentinel = document.createElement('div'); sentinel.id = 'scroll-sentinel'; sentinel.style.height = '1px'; grid.parentElement.appendChild(sentinel); observer.observe(sentinel); } async function addToCart(item) { const cartKey = `${item.type}-${item.id}`; // Уникальный ключ: "product-1" или "kit-1" // СПЕЦИАЛЬНАЯ ЛОГИКА ДЛЯ ВИТРИННЫХ КОМПЛЕКТОВ (Soft Lock) if (item.type === 'showcase_kit') { // Проверяем: не заблокирован ли уже этим пользователем if (cart.has(cartKey)) { alert('Этот букет уже в вашей корзине.\nВитринные комплекты доступны только в количестве 1 шт.'); return; } // Пытаемся создать блокировку через API try { const response = await fetch(`/pos/api/showcase-kits/${item.id}/add-to-cart/`, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/json' } }); const data = await response.json(); if (!response.ok || !data.success) { // Конфликт - комплект занят другим кассиром alert(data.error || 'Не удалось добавить букет в корзину'); return; } // Успешно заблокировали - добавляем в корзину с qty=1 и флагом max_qty cart.set(cartKey, { id: item.id, name: item.name, price: Number(item.price), qty: 1, type: item.type, max_qty: 1, // Флаг: нельзя увеличить количество lock_expires_at: data.lock_expires_at // Время истечения блокировки }); // Обновляем список витрины (чтобы показать блокировку) if (isShowcaseView) { await loadShowcaseKits(); } } catch (error) { console.error('Ошибка при добавлении витринного комплекта:', error); alert('Ошибка сервера. Попробуйте еще раз.'); return; } } else { // ОБЫЧНАЯ ЛОГИКА для товаров и комплектов 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(); saveCartToRedis(); // Сохраняем в Redis // Автоматический фокус на поле количества (только для обычных товаров) if (item.type !== 'showcase_kit') { 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 = '

Корзина пуста

'; document.getElementById('cartTotal').textContent = '0.00'; return; } cart.forEach((item, cartKey) => { const row = document.createElement('div'); row.className = 'cart-item mb-2'; // СПЕЦИАЛЬНАЯ СТИЛИЗАЦИЯ для витринных комплектов const isShowcaseKit = item.type === 'showcase_kit'; if (isShowcaseKit) { row.style.backgroundColor = '#fff3cd'; // Желтый фон row.style.border = '1px solid #ffc107'; row.style.borderRadius = '4px'; row.style.padding = '8px'; } // Левая часть: Название и цена единицы const namePrice = document.createElement('div'); namePrice.className = 'item-name-price'; // Иконка только для комплектов let typeIcon = ''; if (item.type === 'kit' || item.type === 'showcase_kit') { typeIcon = ' '; } namePrice.innerHTML = `
${typeIcon}${item.name}
${formatMoney(item.price)} / шт
`; // Знак умножения const multiplySign = document.createElement('span'); multiplySign.className = 'multiply-sign'; multiplySign.textContent = 'x'; // Контейнер для кнопок количества const qtyControl = document.createElement('div'); qtyControl.className = 'd-flex align-items-center'; qtyControl.style.gap = '2px'; // СПЕЦИАЛЬНАЯ ЛОГИКА для витринных комплектов (только badge, без кнопок) if (isShowcaseKit) { const badge = document.createElement('span'); badge.className = 'badge bg-warning text-dark'; badge.textContent = '1 шт (витрина)'; badge.style.fontSize = '0.85rem'; badge.style.padding = '0.5rem 0.75rem'; qtyControl.appendChild(badge); } else { // ОБЫЧНАЯ ЛОГИКА для товаров и комплектов // Кнопка минус const minusBtn = document.createElement('button'); minusBtn.className = 'btn btn-outline-secondary btn-sm'; minusBtn.innerHTML = ''; minusBtn.onclick = (e) => { e.preventDefault(); const currentQty = cart.get(cartKey).qty; if (currentQty <= 1) { removeFromCart(cartKey); } else { cart.get(cartKey).qty = currentQty - 1; renderCart(); saveCartToRedis(); } }; // Поле ввода количества const qtyInput = document.createElement('input'); qtyInput.type = 'number'; qtyInput.className = 'qty-input form-control form-control-sm'; qtyInput.style.width = '60px'; qtyInput.style.textAlign = 'center'; qtyInput.style.padding = '0.375rem 0.25rem'; 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(); saveCartToRedis(); // Сохраняем в Redis при изменении количества } }; // Кнопка плюс const plusBtn = document.createElement('button'); plusBtn.className = 'btn btn-outline-secondary btn-sm'; plusBtn.innerHTML = ''; plusBtn.onclick = (e) => { e.preventDefault(); cart.get(cartKey).qty += 1; renderCart(); saveCartToRedis(); }; // Собираем контейнер qtyControl.appendChild(minusBtn); qtyControl.appendChild(qtyInput); qtyControl.appendChild(plusBtn); } // Сумма за позицию 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 = ''; deleteBtn.onclick = () => removeFromCart(cartKey); row.appendChild(namePrice); row.appendChild(multiplySign); row.appendChild(qtyControl); row.appendChild(itemTotal); row.appendChild(deleteBtn); list.appendChild(row); total += item.qty * item.price; }); document.getElementById('cartTotal').textContent = formatMoney(total); } async function removeFromCart(cartKey) { const item = cart.get(cartKey); // СПЕЦИАЛЬНАЯ ЛОГИКА для витринных комплектов - снимаем блокировку if (item && item.type === 'showcase_kit') { try { const response = await fetch(`/pos/api/showcase-kits/${item.id}/remove-from-cart/`, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/json' } }); const data = await response.json(); if (!response.ok) { console.error('Ошибка при снятии блокировки:', data.error); // Продолжаем удаление из корзины даже при ошибке } // Обновляем список витрины (чтобы убрать индикацию блокировки) if (isShowcaseView) { await loadShowcaseKits(); } } catch (error) { console.error('Ошибка при снятии блокировки витринного комплекта:', error); // Продолжаем удаление из корзины } } cart.delete(cartKey); renderCart(); saveCartToRedis(); // Сохраняем в Redis } function clearCart() { cart.clear(); renderCart(); saveCartToRedis(); // Сохраняем пустую корзину в Redis } 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; } // Копируем содержимое cart в tempCart (изолированное состояние модалки) tempCart.clear(); cart.forEach((item, key) => { tempCart.set(key, {...item}); // Глубокая копия объекта }); // Генерируем название по умолчанию 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(); // Заполняем список товаров из tempCart renderTempKitItems(); // Открываем модальное окно const modal = new bootstrap.Modal(document.getElementById('createTempKitModal')); modal.show(); } // Открытие модального окна для редактирования комплекта async function openEditKitModal(kitId) { try { // Загружаем данные комплекта const response = await fetch(`/pos/api/product-kits/${kitId}/`); const data = await response.json(); if (!data.success) { alert(`Ошибка: ${data.error}`); return; } const kit = data.kit; // Устанавливаем режим редактирования isEditMode = true; editingKitId = kitId; // Загружаем список витрин await loadShowcases(); // Очищаем tempCart и заполняем составом комплекта tempCart.clear(); kit.items.forEach(item => { const cartKey = `product-${item.product_id}`; tempCart.set(cartKey, { id: item.product_id, name: item.name, price: Number(item.price), qty: Number(item.qty), type: 'product' }); }); renderTempKitItems(); // Отображаем товары в модальном окне // Заполняем поля формы document.getElementById('tempKitName').value = kit.name; document.getElementById('tempKitDescription').value = kit.description; document.getElementById('priceAdjustmentType').value = kit.price_adjustment_type; document.getElementById('priceAdjustmentValue').value = kit.price_adjustment_value; if (kit.sale_price) { document.getElementById('useSalePrice').checked = true; document.getElementById('salePrice').value = kit.sale_price; document.getElementById('salePriceBlock').style.display = 'block'; } else { document.getElementById('useSalePrice').checked = false; document.getElementById('salePrice').value = ''; document.getElementById('salePriceBlock').style.display = 'none'; } // Выбираем витрину if (kit.showcase_id) { document.getElementById('showcaseSelect').value = kit.showcase_id; } // Отображаем фото, если есть if (kit.photo_url) { document.getElementById('photoPreviewImg').src = kit.photo_url; document.getElementById('photoPreview').style.display = 'block'; } else { document.getElementById('photoPreview').style.display = 'none'; } // Обновляем цены updatePriceCalculations(); // Меняем заголовок и кнопку document.getElementById('createTempKitModalLabel').textContent = 'Редактирование витринного букета'; document.getElementById('confirmCreateTempKit').textContent = 'Сохранить изменения'; // Показываем кнопку "Разобрать" в режиме редактирования document.getElementById('disassembleKitBtn').style.display = 'block'; // Открываем модальное окно const modal = new bootstrap.Modal(document.getElementById('createTempKitModal')); modal.show(); } catch (error) { console.error('Error loading kit for edit:', error); alert('Ошибка при загрузке комплекта'); } } // Обновление списка витринных комплектов async function loadShowcaseKits() { try { const response = await fetch('/pos/api/showcase-kits/'); const data = await response.json(); if (data.success) { showcaseKits = data.items; // Перерисовываем грид если мы в режиме витрины if (isShowcaseView) { renderProducts(); } } else { console.error('Failed to refresh showcase kits:', data); } } catch (error) { console.error('Error refreshing showcase kits:', error); } } // Алиас для совместимости const refreshShowcaseKits = loadShowcaseKits; // Загрузка списка витрин async function loadShowcases() { try { const response = await fetch('/pos/api/get-showcases/'); const data = await response.json(); const select = document.getElementById('showcaseSelect'); select.innerHTML = ''; if (data.success && data.showcases.length > 0) { let defaultShowcaseId = null; data.showcases.forEach(showcase => { const option = document.createElement('option'); option.value = showcase.id; option.textContent = `${showcase.name} (${showcase.warehouse_name})`; select.appendChild(option); // Запоминаем витрину по умолчанию if (showcase.is_default) { defaultShowcaseId = showcase.id; } }); // Автовыбор витрины по умолчанию if (defaultShowcaseId) { select.value = defaultShowcaseId; } } else { select.innerHTML = ''; } } catch (error) { console.error('Error loading showcases:', error); alert('Ошибка загрузки витрин'); } } // Отображение товаров из tempCart в модальном окне function renderTempKitItems() { const container = document.getElementById('tempKitItemsList'); container.innerHTML = ''; let estimatedTotal = 0; tempCart.forEach((item, cartKey) => { // Только товары (не комплекты) if (item.type !== 'product') return; const itemDiv = document.createElement('div'); itemDiv.className = 'd-flex justify-content-between align-items-center mb-1 pb-1 border-bottom'; itemDiv.innerHTML = `
${item.name}
${item.qty} шт × ${formatMoney(item.price)} руб.
${formatMoney(item.qty * item.price)} руб.
`; container.appendChild(itemDiv); estimatedTotal += item.qty * item.price; }); // Обновляем все расчеты цен updatePriceCalculations(estimatedTotal); } // Расчет и обновление всех цен function updatePriceCalculations(basePrice = null) { // Если basePrice не передан, пересчитываем из tempCart if (basePrice === null) { basePrice = 0; tempCart.forEach((item, cartKey) => { if (item.type === 'product') { basePrice += item.qty * item.price; } }); } // Базовая цена document.getElementById('tempKitBasePrice').textContent = formatMoney(basePrice) + ' руб.'; // Корректировка const adjustmentType = document.getElementById('priceAdjustmentType').value; const adjustmentValue = parseFloat(document.getElementById('priceAdjustmentValue').value) || 0; let calculatedPrice = basePrice; if (adjustmentType !== 'none' && adjustmentValue > 0) { if (adjustmentType === 'increase_percent') { calculatedPrice = basePrice + (basePrice * adjustmentValue / 100); } else if (adjustmentType === 'increase_amount') { calculatedPrice = basePrice + adjustmentValue; } else if (adjustmentType === 'decrease_percent') { calculatedPrice = Math.max(0, basePrice - (basePrice * adjustmentValue / 100)); } else if (adjustmentType === 'decrease_amount') { calculatedPrice = Math.max(0, basePrice - adjustmentValue); } } document.getElementById('tempKitCalculatedPrice').textContent = formatMoney(calculatedPrice) + ' руб.'; // Финальная цена (с учетом sale_price если задана) const useSalePrice = document.getElementById('useSalePrice').checked; const salePrice = parseFloat(document.getElementById('salePrice').value) || 0; let finalPrice = calculatedPrice; if (useSalePrice && salePrice > 0) { finalPrice = salePrice; } document.getElementById('tempKitFinalPrice').textContent = formatMoney(finalPrice); } // Обработчики для полей цены document.getElementById('priceAdjustmentType').addEventListener('change', function() { const adjustmentBlock = document.getElementById('adjustmentValueBlock'); if (this.value === 'none') { adjustmentBlock.style.display = 'none'; document.getElementById('priceAdjustmentValue').value = '0'; } else { adjustmentBlock.style.display = 'block'; } updatePriceCalculations(); }); document.getElementById('priceAdjustmentValue').addEventListener('input', function() { updatePriceCalculations(); }); document.getElementById('useSalePrice').addEventListener('change', function() { const salePriceBlock = document.getElementById('salePriceBlock'); if (this.checked) { salePriceBlock.style.display = 'block'; } else { salePriceBlock.style.display = 'none'; document.getElementById('salePrice').value = ''; } updatePriceCalculations(); }); document.getElementById('salePrice').addEventListener('input', function() { updatePriceCalculations(); }); // Обработчик загрузки фото document.getElementById('tempKitPhoto').addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { if (!file.type.startsWith('image/')) { alert('Пожалуйста, выберите файл изображения'); this.value = ''; return; } // Превью const reader = new FileReader(); reader.onload = function(event) { document.getElementById('photoPreviewImg').src = event.target.result; document.getElementById('photoPreview').style.display = 'block'; }; reader.readAsDataURL(file); } }); // Удаление фото document.getElementById('removePhoto').addEventListener('click', function() { document.getElementById('tempKitPhoto').value = ''; document.getElementById('photoPreview').style.display = 'none'; document.getElementById('photoPreviewImg').src = ''; }); // Подтверждение создания/редактирования временного комплекта document.getElementById('confirmCreateTempKit').onclick = async () => { const kitName = document.getElementById('tempKitName').value.trim(); const showcaseId = document.getElementById('showcaseSelect').value; const description = document.getElementById('tempKitDescription').value.trim(); const photoFile = document.getElementById('tempKitPhoto').files[0]; // Валидация if (!kitName) { alert('Введите название комплекта'); return; } if (!showcaseId && !isEditMode) { alert('Выберите витрину'); return; } // Собираем товары из tempCart (изолированное состояние модалки) const items = []; tempCart.forEach((item, cartKey) => { if (item.type === 'product') { items.push({ product_id: item.id, quantity: item.qty }); } }); if (items.length === 0) { alert('Нет товаров для создания комплекта'); return; } // Получаем данные о ценах const priceAdjustmentType = document.getElementById('priceAdjustmentType').value; const priceAdjustmentValue = parseFloat(document.getElementById('priceAdjustmentValue').value) || 0; const useSalePrice = document.getElementById('useSalePrice').checked; const salePrice = useSalePrice ? (parseFloat(document.getElementById('salePrice').value) || 0) : 0; // Формируем FormData для отправки с файлом const formData = new FormData(); formData.append('kit_name', kitName); if (showcaseId) { formData.append('showcase_id', showcaseId); } formData.append('description', description); formData.append('items', JSON.stringify(items)); formData.append('price_adjustment_type', priceAdjustmentType); formData.append('price_adjustment_value', priceAdjustmentValue); if (useSalePrice && salePrice > 0) { formData.append('sale_price', salePrice); } // Фото: для редактирования проверяем, удалено ли оно if (photoFile) { formData.append('photo', photoFile); } else if (isEditMode && document.getElementById('photoPreview').style.display === 'none') { // Если фото было удалено formData.append('remove_photo', '1'); } // Отправляем запрос на сервер const confirmBtn = document.getElementById('confirmCreateTempKit'); confirmBtn.disabled = true; const url = isEditMode ? `/pos/api/product-kits/${editingKitId}/update/` : '/pos/api/create-temp-kit/'; const actionText = isEditMode ? 'Сохранение...' : 'Создание...'; confirmBtn.innerHTML = `${actionText}`; try { const response = await fetch(url, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken') // Не указываем Content-Type - браузер сам установит multipart/form-data }, body: formData }); const data = await response.json(); if (data.success) { // Успех! const successMessage = isEditMode ? `✅ ${data.message}\n\nКомплект: ${data.kit_name}\nЦена: ${data.kit_price} руб.` : `✅ ${data.message} Комплект: ${data.kit_name} Цена: ${data.kit_price} руб. Зарезервировано компонентов: ${data.reservations_count}`; alert(successMessage); // Очищаем tempCart (изолированное состояние модалки) tempCart.clear(); // Сбрасываем поля формы document.getElementById('tempKitDescription').value = ''; document.getElementById('tempKitPhoto').value = ''; document.getElementById('photoPreview').style.display = 'none'; document.getElementById('priceAdjustmentType').value = 'none'; document.getElementById('priceAdjustmentValue').value = '0'; document.getElementById('adjustmentValueBlock').style.display = 'none'; document.getElementById('useSalePrice').checked = false; document.getElementById('salePrice').value = ''; document.getElementById('salePriceBlock').style.display = 'none'; // Сбрасываем режим редактирования isEditMode = false; editingKitId = null; // Закрываем модальное окно const modal = bootstrap.Modal.getInstance(document.getElementById('createTempKitModal')); modal.hide(); // Обновляем витринные комплекты и переключаемся на вид витрины isShowcaseView = true; currentCategoryId = null; await refreshShowcaseKits(); renderCategories(); renderProducts(); } else { alert(`Ошибка: ${data.error}`); } } catch (error) { console.error('Error saving kit:', error); alert('Ошибка при сохранении комплекта'); } finally { confirmBtn.disabled = false; const btnText = isEditMode ? ' Сохранить изменения' : ' Создать и зарезервировать'; confirmBtn.innerHTML = btnText; } }; // Обработчик кнопки "Разобрать букет" document.getElementById('disassembleKitBtn').addEventListener('click', async () => { if (!isEditMode || !editingKitId) { alert('Ошибка: режим редактирования не активен'); return; } // Запрос подтверждения const confirmed = confirm( 'Вы уверены?\n\n' + 'Букет будет разобран:\n' + '• Все резервы компонентов будут освобождены\n' + '• Комплект будет помечен как "Снят"\n\n' + 'Это действие нельзя отменить!' ); if (!confirmed) { return; } try { const response = await fetch(`/pos/api/product-kits/${editingKitId}/disassemble/`, { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken') } }); const data = await response.json(); if (data.success) { alert(`✅ ${data.message}\n\nОсвобождено резервов: ${data.released_count}`); // Закрываем модальное окно const modal = bootstrap.Modal.getInstance(document.getElementById('createTempKitModal')); modal.hide(); // Обновляем витринные комплекты isShowcaseView = true; currentCategoryId = null; await refreshShowcaseKits(); renderCategories(); renderProducts(); } else { alert(`❌ Ошибка: ${data.error}`); } } catch (error) { console.error('Error disassembling kit:', error); alert('Произошла ошибка при разборе букета'); } }); // Вспомогательная функция для получения 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('createTempKitModal').addEventListener('hidden.bs.modal', function() { // Очищаем tempCart (изолированное состояние модалки) tempCart.clear(); if (isEditMode) { // Сбрасываем режим редактирования isEditMode = false; editingKitId = null; // Восстанавливаем заголовок и текст кнопки document.getElementById('createTempKitModalLabel').textContent = 'Создать витринный букет из корзины'; document.getElementById('confirmCreateTempKit').innerHTML = ' Создать и зарезервировать'; // Скрываем кнопку "Разобрать" document.getElementById('disassembleKitBtn').style.display = 'none'; } }); // Открытие модалки "Продажа" и рендер сводки корзины document.getElementById('checkoutNow').onclick = () => { if (cart.size === 0) { alert('Корзина пуста. Добавьте товары перед продажей.'); return; } renderCheckoutModal(); const modal = new bootstrap.Modal(document.getElementById('checkoutModal')); modal.show(); }; // Рендер позиций корзины и итога в модалке продажи function renderCheckoutModal() { const container = document.getElementById('checkoutItems'); container.innerHTML = ''; let total = 0; cart.forEach((item) => { const row = document.createElement('div'); row.className = 'd-flex justify-content-between align-items-center mb-2 pb-2 border-bottom'; // Иконка для комплектов let typeIcon = ''; if (item.type === 'kit' || item.type === 'showcase_kit') { typeIcon = ''; } else { typeIcon = ''; } row.innerHTML = `
${typeIcon}${item.name}
${item.qty} шт × ${formatMoney(item.price)} руб.
${formatMoney(item.qty * item.price)} руб.
`; container.appendChild(row); total += item.qty * item.price; }); // Обновляем информацию о клиенте updateCustomerDisplay(); } // ===== CHECKOUT: ПОДТВЕРЖДЕНИЕ ПРОДАЖИ ===== let paymentWidget = null; // При открытии модалки checkout document.getElementById('checkoutModal').addEventListener('show.bs.modal', () => { const customer = selectedCustomer || SYSTEM_CUSTOMER; const walletBalance = customer.wallet_balance || 0; // Показываем баланс кошелька const walletDiv = document.getElementById('checkoutWalletBalance'); if (customer.id !== SYSTEM_CUSTOMER.id) { document.getElementById('checkoutWalletBalanceAmount').textContent = walletBalance.toFixed(2); walletDiv.style.display = 'block'; } else { walletDiv.style.display = 'none'; } // Вычисляем итоговую сумму let totalAmount = 0; cart.forEach((item) => { totalAmount += item.qty * item.price; }); document.getElementById('checkoutFinalPrice').textContent = formatMoney(totalAmount) + ' руб.'; // Инициализируем виджет в single mode initPaymentWidget('single', { order: { total: totalAmount, amount_due: totalAmount, amount_paid: 0 }, customer: { id: customer.id, name: customer.name, wallet_balance: walletBalance } }); }); // Переключение режима оплаты document.getElementById('singlePaymentMode').addEventListener('click', function() { document.getElementById('singlePaymentMode').classList.add('active'); document.getElementById('mixedPaymentMode').classList.remove('active'); reinitPaymentWidget('single'); }); document.getElementById('mixedPaymentMode').addEventListener('click', function() { document.getElementById('mixedPaymentMode').classList.add('active'); document.getElementById('singlePaymentMode').classList.remove('active'); reinitPaymentWidget('mixed'); }); function reinitPaymentWidget(mode) { const customer = selectedCustomer || SYSTEM_CUSTOMER; const totalAmountText = document.getElementById('checkoutFinalPrice').textContent; const totalAmount = parseFloat(totalAmountText.replace(/[^\d.]/g, '')); initPaymentWidget(mode, { order: { total: totalAmount, amount_due: totalAmount, amount_paid: 0 }, customer: { id: customer.id, name: customer.name, wallet_balance: customer.wallet_balance || 0 } }); } async function initPaymentWidget(mode, data) { const paymentMethods = [ { id: 1, code: 'account_balance', name: 'С баланса счёта' }, { id: 2, code: 'cash', name: 'Наличными' }, { id: 3, code: 'card', name: 'Картой' }, { id: 4, code: 'online', name: 'Онлайн' } ]; // Динамически загружаем PaymentWidget если еще не загружен if (!window.PaymentWidget) { try { const module = await import('/static/orders/js/payment_widget.js'); window.PaymentWidget = module.PaymentWidget; } catch (error) { console.error('Ошибка загрузки PaymentWidget:', error); alert('Ошибка загрузки модуля оплаты. Перезагрузите страницу.'); return; } } paymentWidget = new window.PaymentWidget({ containerId: 'paymentWidgetContainer', mode: mode, order: data.order, customer: data.customer, paymentMethods: paymentMethods, onSubmit: (paymentsData) => handleCheckoutSubmit(paymentsData) }); } // Обработчик кнопки "Подтвердить продажу" document.getElementById('confirmCheckoutBtn').onclick = () => { if (paymentWidget) { paymentWidget.submit(); } }; // Отправка заказа на сервер async function handleCheckoutSubmit(paymentsData) { try { // Блокируем кнопку const btn = document.getElementById('confirmCheckoutBtn'); btn.disabled = true; btn.innerHTML = 'Обработка...'; // Собираем данные const customer = selectedCustomer || SYSTEM_CUSTOMER; const orderData = { customer_id: customer.id, warehouse_id: currentWarehouse.id, items: Array.from(cart.values()).map(item => ({ type: item.type, id: item.id, quantity: item.qty, price: item.price })), payments: paymentsData, notes: document.getElementById('orderNote').value.trim() }; // Отправляем на сервер const response = await fetch('/pos/api/checkout/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify(orderData) }); const result = await response.json(); if (result.success) { // Успех alert(`Заказ #${result.order_number} успешно создан!\nСумма: ${result.total_amount.toFixed(2)} руб.`); // Очищаем корзину cart.clear(); updateCartUI(); updateCartCount(); // Закрываем модалку const modal = bootstrap.Modal.getInstance(document.getElementById('checkoutModal')); modal.hide(); // Перезагружаем витринные комплекты loadShowcaseKits(); } else { alert('Ошибка: ' + result.error); } } catch (error) { console.error('Ошибка checkout:', error); alert('Ошибка при проведении продажи: ' + error.message); } finally { // Разблокируем кнопку const btn = document.getElementById('confirmCheckoutBtn'); btn.disabled = false; btn.innerHTML = ' Подтвердить продажу'; } } 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('scheduleLater').onclick = async () => { alert('Функционал будет подключен позже: создание заказа на доставку/самовывоз.'); }; // ===== ОБРАБОТЧИКИ ДЛЯ РАБОТЫ С КЛИЕНТОМ ===== // Кнопка "Выбрать клиента" в корзине document.getElementById('customerSelectBtn').addEventListener('click', () => { const modal = new bootstrap.Modal(document.getElementById('selectCustomerModal')); modal.show(); }); // Кнопка сброса клиента на системного (в корзине) document.getElementById('resetCustomerBtn').addEventListener('click', () => { selectCustomer(SYSTEM_CUSTOMER.id, SYSTEM_CUSTOMER.name); }); // Кнопка "Выбрать клиента" в модалке продажи document.getElementById('checkoutCustomerSelectBtn').addEventListener('click', () => { const modal = new bootstrap.Modal(document.getElementById('selectCustomerModal')); modal.show(); }); // Кнопка сброса клиента на системного (в модалке продажи) document.getElementById('checkoutResetCustomerBtn').addEventListener('click', () => { selectCustomer(SYSTEM_CUSTOMER.id, SYSTEM_CUSTOMER.name); }); // Кнопка "Создать нового клиента" в модалке выбора document.getElementById('createNewCustomerBtn').addEventListener('click', () => { // Закрываем модалку выбора const selectModal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal')); selectModal.hide(); // Открываем модалку создания openCreateCustomerModal(); }); // Кнопка "Выбрать системного клиента" document.getElementById('selectSystemCustomerBtn').addEventListener('click', () => { selectCustomer(SYSTEM_CUSTOMER.id, SYSTEM_CUSTOMER.name); // Закрываем модалку const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal')); modal.hide(); }); // Кнопка подтверждения создания клиента document.getElementById('confirmCreateCustomerBtn').addEventListener('click', () => { createNewCustomer(); }); // Инициализация Select2 при загрузке страницы document.addEventListener('DOMContentLoaded', () => { initCustomerSelect2(); updateCustomerDisplay(); // Обновляем UI с системным клиентом // Восстанавливаем корзину из Redis (если есть сохраненные данные) const savedCartData = JSON.parse(document.getElementById('cartData').textContent); if (savedCartData && Object.keys(savedCartData).length > 0) { // Конвертируем обычный объект обратно в Map Object.entries(savedCartData).forEach(([key, value]) => { cart.set(key, value); }); renderCart(); // Отрисовываем восстановленную корзину } }); // Смена склада const changeWarehouseBtn = document.getElementById('changeWarehouseBtn'); if (changeWarehouseBtn) { changeWarehouseBtn.addEventListener('click', () => { const modal = new bootstrap.Modal(document.getElementById('selectWarehouseModal')); modal.show(); }); } // Обработка выбора склада из списка document.addEventListener('click', async (e) => { const warehouseItem = e.target.closest('.warehouse-item'); if (!warehouseItem) return; const warehouseId = warehouseItem.dataset.warehouseId; const warehouseName = warehouseItem.dataset.warehouseName; // Проверяем, есть ли товары в корзине if (cart.size > 0) { const confirmed = confirm(`При смене склада корзина будет очищена.\n\nПереключиться на склад "${warehouseName}"?`); if (!confirmed) return; } try { // Отправляем запрос на смену склада const response = await fetch(`/pos/api/set-warehouse/${warehouseId}/`, { method: 'POST', headers: { 'X-CSRFToken': getCsrfToken() } }); const data = await response.json(); if (data.success) { // Перезагружаем страницу для обновления данных location.reload(); } else { alert(`Ошибка: ${data.error}`); } } catch (error) { console.error('Ошибка при смене склада:', error); alert('Произошла ошибка при смене склада'); } }); // Вспомогательная функция для получения CSRF токена function getCsrfToken() { const name = 'csrftoken'; 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; } // Обработчик поиска с debounce const searchInput = document.getElementById('searchInput'); const clearSearchBtn = document.getElementById('clearSearchBtn'); searchInput.addEventListener('input', (e) => { const query = e.target.value.trim(); // Показываем/скрываем кнопку очистки if (e.target.value.length > 0) { clearSearchBtn.style.display = 'block'; } else { clearSearchBtn.style.display = 'none'; } // Отменяем предыдущий таймер if (searchDebounceTimer) { clearTimeout(searchDebounceTimer); } // Если поле пустое — очищаем экран if (query === '') { currentSearchQuery = ''; ITEMS = []; // Очистка renderProducts(); // Пустой экран return; } // Минимальная длина поиска — 3 символа if (query.length < 3) { // Не реагируем на ввод менее 3 символов return; } // Для витрины — мгновенная клиентская фильтрация if (isShowcaseView) { renderProducts(); return; } // Для обычных товаров/комплектов — серверный поиск с debounce 300мс searchDebounceTimer = setTimeout(async () => { currentSearchQuery = query; await loadItems(); // Перезагрузка с серверным поиском }, 300); }); // Обработчик кнопки очистки поиска clearSearchBtn.addEventListener('click', () => { searchInput.value = ''; clearSearchBtn.style.display = 'none'; currentSearchQuery = ''; ITEMS = []; renderProducts(); // Пустой экран }); // Инициализация renderCategories(); renderProducts(); // Сначала пустая сетка renderCart(); setupInfiniteScroll(); // Установка infinite scroll // Установить фокус на строку поиска document.getElementById('searchInput').focus();