diff --git a/myproject/pos/static/pos/css/terminal.css b/myproject/pos/static/pos/css/terminal.css index 1267790..caf8056 100644 --- a/myproject/pos/static/pos/css/terminal.css +++ b/myproject/pos/static/pos/css/terminal.css @@ -216,6 +216,17 @@ body { color: white; } +/* Специальный стиль для активной кнопки витрины */ +.showcase-card.active { + background: #ff6600; + border-color: #ff6600; + border-width: 3px; + color: #000000; + font-weight: bold; + box-shadow: 0 6px 16px rgba(255, 102, 0, 0.6); + transform: scale(1.05); +} + .category-card .card-body { padding: 0.5rem; display: flex; diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index cd38774..1eccf4c 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -2,8 +2,10 @@ 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) { @@ -18,33 +20,14 @@ function renderCategories() { 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'; + showcaseCard.className = 'card category-card showcase-card' + (isShowcaseView ? ' active' : ''); showcaseCard.style.backgroundColor = '#fff3cd'; showcaseCard.style.borderColor = '#ffc107'; - showcaseCard.onclick = async () => { - try { - const response = await fetch('/pos/api/showcase-items/'); - const data = await response.json(); - - if (data.success && data.showcases.length > 0) { - let message = '🌺 ВИТРИННЫЕ БУКЕТЫ\n\n'; - - data.showcases.forEach(showcase => { - message += `● ${showcase.name} (Склад: ${showcase.warehouse})\n`; - showcase.items.forEach(item => { - message += ` - ${item.product_name}: ${item.quantity} шт\n`; - }); - message += '\n'; - }); - - alert(message); - } else { - alert('Витрины пусты'); - } - } catch (error) { - console.error('Error fetching showcase items:', error); - alert('Ошибка загрузки витринных букетов'); - } + showcaseCard.onclick = () => { + isShowcaseView = true; + currentCategoryId = null; + renderCategories(); + renderProducts(); }; const showcaseBody = document.createElement('div'); showcaseBody.className = 'card-body'; @@ -60,9 +43,10 @@ function renderCategories() { 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 ? ' active' : ''); + allCard.className = 'card category-card' + (currentCategoryId === null && !isShowcaseView ? ' active' : ''); allCard.onclick = () => { currentCategoryId = null; + isShowcaseView = false; renderCategories(); renderProducts(); }; @@ -82,9 +66,10 @@ function renderCategories() { 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 ? ' active' : ''); + card.className = 'card category-card' + (currentCategoryId === cat.id && !isShowcaseView ? ' active' : ''); card.onclick = () => { currentCategoryId = cat.id; + isShowcaseView = false; renderCategories(); renderProducts(); }; @@ -108,9 +93,17 @@ function renderProducts() { grid.innerHTML = ''; const searchTerm = document.getElementById('searchInput').value.toLowerCase(); - let filtered = currentCategoryId - ? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId)) - : ITEMS; + 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)); @@ -149,9 +142,17 @@ function renderProducts() { const stock = document.createElement('div'); stock.className = 'product-stock'; - stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ'; - if (!item.in_stock) { - stock.style.color = '#dc3545'; + + // Для витринных комплектов показываем название витрины + 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'); diff --git a/myproject/pos/templates/pos/terminal.html b/myproject/pos/templates/pos/terminal.html index 5a853fd..778f517 100644 --- a/myproject/pos/templates/pos/terminal.html +++ b/myproject/pos/templates/pos/terminal.html @@ -159,6 +159,7 @@ + {% endblock %} diff --git a/myproject/pos/views.py b/myproject/pos/views.py index 973d91b..b86fd9f 100644 --- a/myproject/pos/views.py +++ b/myproject/pos/views.py @@ -13,6 +13,69 @@ from inventory.models import Showcase, Reservation, Warehouse from inventory.services import ShowcaseManager +def get_showcase_kits_for_pos(): + """ + Получает витринные комплекты для отображения в POS. + Возвращает список временных комплектов, которые зарезервированы на витринах. + """ + # Получаем все уникальные комплекты, у которых есть резервы на витринах + showcase_reservations = Reservation.objects.filter( + showcase__isnull=False, + showcase__is_active=True, + status='reserved' + ).select_related('showcase', 'product').values( + 'showcase_id', 'showcase__name' + ).distinct() + + # Собираем все kit_items, связанные с этими резервами + showcase_kits = [] + + # Получаем все временные комплекты с резервами на витринах + reserved_products = Reservation.objects.filter( + showcase__isnull=False, + showcase__is_active=True, + status='reserved' + ).values_list('product_id', flat=True).distinct() + + # Находим комплекты, в которых есть эти товары + kits_with_showcase_items = ProductKit.objects.filter( + is_temporary=True, + status='active', + kit_items__product_id__in=reserved_products + ).prefetch_related('photos', 'kit_items__product').distinct() + + for kit in kits_with_showcase_items: + # Проверяем что все компоненты этого комплекта зарезервированы на одной витрине + kit_product_ids = set(kit.kit_items.values_list('product_id', flat=True)) + + # Находим резервы для этих товаров + kit_reservations = Reservation.objects.filter( + product_id__in=kit_product_ids, + showcase__isnull=False, + showcase__is_active=True, + status='reserved' + ).select_related('showcase') + + if kit_reservations.exists(): + # Берём первую витрину (обычно комплект резервируется на одной) + showcase = kit_reservations.first().showcase + + showcase_kits.append({ + 'id': kit.id, + 'name': kit.name, + 'price': str(kit.actual_price), + 'category_ids': [], # Временные комплекты обычно без категорий + 'in_stock': True, # На витрине = в наличии + 'sku': kit.sku or '', + 'image': kit.photos.first().get_thumbnail_url() if kit.photos.exists() else None, + 'type': 'showcase_kit', + 'showcase_name': showcase.name, + 'showcase_id': showcase.id + }) + + return showcase_kits + + @login_required def pos_terminal(request): """ @@ -51,12 +114,16 @@ def pos_terminal(request): 'type': 'kit' } for k in kits_qs] + # Получаем витринные комплекты (временные комплекты с резервами на витринах) + showcase_kits_data = get_showcase_kits_for_pos() + # Объединяем все позиции all_items = products + kits context = { 'categories_json': json.dumps(categories), - 'items_json': json.dumps(all_items), # Единый массив товаров и комплектов + 'items_json': json.dumps(all_items), + 'showcase_kits_json': json.dumps(showcase_kits_data), 'title': 'POS Terminal', } return render(request, 'pos/terminal.html', context)