From b7eaa5285cf81b2f4df9889ab2f24e637baa3ec8 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 16 Nov 2025 17:34:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20ProductKit=20=D0=B2=20POS,=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=BA=D1=80=D1=83?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B8=20=D1=84=D0=B8=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- myproject/pos/static/pos/css/terminal.css | 12 ++++++ myproject/pos/static/pos/js/terminal.js | 49 ++++++++++++----------- myproject/pos/templates/pos/terminal.html | 12 +++--- myproject/pos/views.py | 28 +++++++++++-- 4 files changed, 69 insertions(+), 32 deletions(-) diff --git a/myproject/pos/static/pos/css/terminal.css b/myproject/pos/static/pos/css/terminal.css index 28a7a70..6c69e67 100644 --- a/myproject/pos/static/pos/css/terminal.css +++ b/myproject/pos/static/pos/css/terminal.css @@ -20,6 +20,16 @@ body { max-width: 100%; padding: 1rem; height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} + +/* Прокручиваемая область товаров */ +.products-scrollable { + overflow-y: auto; + overflow-x: hidden; + flex-grow: 1; } .product-card { @@ -63,6 +73,8 @@ body { justify-content: center; color: #adb5bd; font-size: 3rem; + flex-shrink: 0; + min-height: 150px; } .product-image img { diff --git a/myproject/pos/static/pos/js/terminal.js b/myproject/pos/static/pos/js/terminal.js index dc878ee..c85733c 100644 --- a/myproject/pos/static/pos/js/terminal.js +++ b/myproject/pos/static/pos/js/terminal.js @@ -1,12 +1,15 @@ // POS Terminal JavaScript const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent); -const PRODUCTS = JSON.parse(document.getElementById('productsData').textContent); +const ITEMS = JSON.parse(document.getElementById('itemsData').textContent); // Единый массив товаров и комплектов -// Отладка: проверить количество загруженных товаров +// Отладка: проверить количество загруженных позиций console.log('Загружено категорий:', CATEGORIES.length); -console.log('Загружено товаров:', PRODUCTS.length); -console.log('Товары:', PRODUCTS); +console.log('Загружено позиций (товары + комплекты):', ITEMS.length); +const productsCount = ITEMS.filter(i => i.type === 'product').length; +const kitsCount = ITEMS.filter(i => i.type === 'kit').length; +console.log(` - Товаров: ${productsCount}, Комплектов: ${kitsCount}`); +console.log('Позиции:', ITEMS); let currentCategoryId = null; const cart = new Map(); // productId -> {id, name, price, qty} @@ -72,48 +75,48 @@ function renderProducts() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); let filtered = currentCategoryId - ? PRODUCTS.filter(p => (p.category_ids || []).includes(currentCategoryId)) - : PRODUCTS; + ? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId)) + : ITEMS; if (searchTerm) { - filtered = filtered.filter(p => p.name.toLowerCase().includes(searchTerm)); + filtered = filtered.filter(item => item.name.toLowerCase().includes(searchTerm)); } - filtered.forEach(p => { + 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(p); + card.onclick = () => addToCart(item); const body = document.createElement('div'); body.className = 'card-body'; - // Изображение товара + // Изображение товара/комплекта const imageDiv = document.createElement('div'); imageDiv.className = 'product-image'; - if (p.image) { + if (item.image) { const img = document.createElement('img'); - img.src = p.image; - img.alt = p.name; + img.src = item.image; + img.alt = item.name; 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 = p.name; + name.textContent = item.name; const stock = document.createElement('div'); stock.className = 'product-stock'; - stock.textContent = p.in_stock ? 'В наличии' : 'Под заказ'; - if (!p.in_stock) { + stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ'; + if (!item.in_stock) { stock.style.color = '#dc3545'; } @@ -121,11 +124,11 @@ function renderProducts() { sku.className = 'product-sku'; const skuText = document.createElement('span'); - skuText.textContent = p.sku || 'н/д'; + skuText.textContent = item.sku || 'н/д'; const priceSpan = document.createElement('span'); priceSpan.className = 'product-price'; - priceSpan.textContent = `${formatMoney(p.price)}`; + priceSpan.textContent = `${formatMoney(item.price)}`; sku.appendChild(skuText); sku.appendChild(priceSpan); @@ -142,11 +145,11 @@ function renderProducts() { }); } -function addToCart(p) { - if (!cart.has(p.id)) { - cart.set(p.id, { id: p.id, name: p.name, price: Number(p.price), qty: 1 }); +function addToCart(item) { + if (!cart.has(item.id)) { + cart.set(item.id, { id: item.id, name: item.name, price: Number(item.price), qty: 1, type: item.type }); } else { - cart.get(p.id).qty += 1; + cart.get(item.id).qty += 1; } renderCart(); } diff --git a/myproject/pos/templates/pos/terminal.html b/myproject/pos/templates/pos/terminal.html index cc3c204..0312200 100644 --- a/myproject/pos/templates/pos/terminal.html +++ b/myproject/pos/templates/pos/terminal.html @@ -10,9 +10,9 @@
-
+
-
+
@@ -23,8 +23,10 @@
- -
+ +
+
+
@@ -106,7 +108,7 @@ {% block extra_js %} - + {% endblock %} diff --git a/myproject/pos/views.py b/myproject/pos/views.py index 8349d2d..6b10cea 100644 --- a/myproject/pos/views.py +++ b/myproject/pos/views.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.shortcuts import render from django.contrib.auth.decorators import login_required -from products.models import Product, ProductCategory +from products.models import Product, ProductCategory, ProductKit import json @@ -9,13 +9,17 @@ import json def pos_terminal(request): """ Tablet-friendly POS screen prototype. - Shows categories and in-stock products for quick tap-to-add. + Shows categories and all items (products + kits) for quick tap-to-add. """ categories_qs = ProductCategory.objects.filter(is_active=True) # Показываем все товары, не только in_stock products_qs = Product.objects.all().prefetch_related('categories', 'photos') + # Показываем все комплекты (кроме временных) + kits_qs = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos') categories = [{'id': c.id, 'name': c.name} for c in categories_qs] + + # Сериализация товаров products = [{ 'id': p.id, 'name': p.name, @@ -23,12 +27,28 @@ def pos_terminal(request): 'category_ids': [c.id for c in p.categories.all()], 'in_stock': p.in_stock, 'sku': p.sku or '', - 'image': p.photos.first().get_thumbnail_url() if p.photos.exists() else None + 'image': p.photos.first().get_thumbnail_url() if p.photos.exists() else None, + 'type': 'product' } for p in products_qs] + + # Сериализация комплектов + kits = [{ + 'id': k.id, + 'name': k.name, + 'price': str(k.actual_price), + 'category_ids': [c.id for c in k.categories.all()], + 'in_stock': False, # Комплекты всегда "Под заказ" (пока не интегрируем проверку наличия) + 'sku': k.sku or '', + 'image': k.photos.first().get_thumbnail_url() if k.photos.exists() else None, + 'type': 'kit' + } for k in kits_qs] + + # Объединяем все позиции + all_items = products + kits context = { 'categories_json': json.dumps(categories), - 'products_json': json.dumps(products), + 'items_json': json.dumps(all_items), # Единый массив товаров и комплектов 'title': 'POS Terminal', } return render(request, 'pos/terminal.html', context)