Добавлена поддержка ProductKit в POS, улучшена прокрутка и фиксация элементов

This commit is contained in:
2025-11-16 17:34:12 +03:00
parent fab4c78966
commit b7eaa5285c
4 changed files with 69 additions and 32 deletions

View File

@@ -20,6 +20,16 @@ body {
max-width: 100%; max-width: 100%;
padding: 1rem; padding: 1rem;
height: 100%; height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Прокручиваемая область товаров */
.products-scrollable {
overflow-y: auto;
overflow-x: hidden;
flex-grow: 1;
} }
.product-card { .product-card {
@@ -63,6 +73,8 @@ body {
justify-content: center; justify-content: center;
color: #adb5bd; color: #adb5bd;
font-size: 3rem; font-size: 3rem;
flex-shrink: 0;
min-height: 150px;
} }
.product-image img { .product-image img {

View File

@@ -1,12 +1,15 @@
// POS Terminal JavaScript // POS Terminal JavaScript
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent); 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('Загружено категорий:', CATEGORIES.length);
console.log('Загружено товаров:', PRODUCTS.length); console.log('Загружено позиций (товары + комплекты):', ITEMS.length);
console.log('Товары:', PRODUCTS); 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; let currentCategoryId = null;
const cart = new Map(); // productId -> {id, name, price, qty} const cart = new Map(); // productId -> {id, name, price, qty}
@@ -72,48 +75,48 @@ function renderProducts() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filtered = currentCategoryId let filtered = currentCategoryId
? PRODUCTS.filter(p => (p.category_ids || []).includes(currentCategoryId)) ? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId))
: PRODUCTS; : ITEMS;
if (searchTerm) { 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'); const col = document.createElement('div');
col.className = 'col-6 col-sm-4 col-md-3 col-lg-2'; col.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card product-card'; card.className = 'card product-card';
card.onclick = () => addToCart(p); card.onclick = () => addToCart(item);
const body = document.createElement('div'); const body = document.createElement('div');
body.className = 'card-body'; body.className = 'card-body';
// Изображение товара // Изображение товара/комплекта
const imageDiv = document.createElement('div'); const imageDiv = document.createElement('div');
imageDiv.className = 'product-image'; imageDiv.className = 'product-image';
if (p.image) { if (item.image) {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = p.image; img.src = item.image;
img.alt = p.name; img.alt = item.name;
imageDiv.appendChild(img); imageDiv.appendChild(img);
} else { } else {
imageDiv.innerHTML = '<i class="bi bi-image"></i>'; imageDiv.innerHTML = '<i class="bi bi-image"></i>';
} }
// Информация о товаре // Информация о товаре/комплекте
const info = document.createElement('div'); const info = document.createElement('div');
info.className = 'product-info'; info.className = 'product-info';
const name = document.createElement('div'); const name = document.createElement('div');
name.className = 'product-name'; name.className = 'product-name';
name.textContent = p.name; name.textContent = item.name;
const stock = document.createElement('div'); const stock = document.createElement('div');
stock.className = 'product-stock'; stock.className = 'product-stock';
stock.textContent = p.in_stock ? 'В наличии' : 'Под заказ'; stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ';
if (!p.in_stock) { if (!item.in_stock) {
stock.style.color = '#dc3545'; stock.style.color = '#dc3545';
} }
@@ -121,11 +124,11 @@ function renderProducts() {
sku.className = 'product-sku'; sku.className = 'product-sku';
const skuText = document.createElement('span'); const skuText = document.createElement('span');
skuText.textContent = p.sku || 'н/д'; skuText.textContent = item.sku || 'н/д';
const priceSpan = document.createElement('span'); const priceSpan = document.createElement('span');
priceSpan.className = 'product-price'; priceSpan.className = 'product-price';
priceSpan.textContent = `${formatMoney(p.price)}`; priceSpan.textContent = `${formatMoney(item.price)}`;
sku.appendChild(skuText); sku.appendChild(skuText);
sku.appendChild(priceSpan); sku.appendChild(priceSpan);
@@ -142,11 +145,11 @@ function renderProducts() {
}); });
} }
function addToCart(p) { function addToCart(item) {
if (!cart.has(p.id)) { if (!cart.has(item.id)) {
cart.set(p.id, { id: p.id, name: p.name, price: Number(p.price), qty: 1 }); cart.set(item.id, { id: item.id, name: item.name, price: Number(item.price), qty: 1, type: item.type });
} else { } else {
cart.get(p.id).qty += 1; cart.get(item.id).qty += 1;
} }
renderCart(); renderCart();
} }

View File

@@ -10,9 +10,9 @@
<!-- Main POS Container --> <!-- Main POS Container -->
<div class="pos-main-container"> <div class="pos-main-container">
<div class="pos-container"> <div class="pos-container">
<div class="row g-3"> <div class="row g-3" style="height: 100%;">
<!-- Products Grid (Left side - 8/12) --> <!-- Products Grid (Left side - 8/12) -->
<div class="col-md-8"> <div class="col-md-8" style="display: flex; flex-direction: column; height: 100%;">
<!-- Search Box --> <!-- Search Box -->
<div class="mb-3"> <div class="mb-3">
<input type="text" class="form-control" id="searchInput" placeholder="Поиск по товарам..."> <input type="text" class="form-control" id="searchInput" placeholder="Поиск по товарам...">
@@ -23,9 +23,11 @@
<div class="row g-3" id="categoryGrid"></div> <div class="row g-3" id="categoryGrid"></div>
</div> </div>
<!-- Products Grid (Блок товаров) --> <!-- Products Grid (Блок товаров) - Прокручиваемая область -->
<div class="products-scrollable">
<div class="row g-3" id="productGrid"></div> <div class="row g-3" id="productGrid"></div>
</div> </div>
</div>
<!-- Right Panel (4/12) - Fixed --> <!-- Right Panel (4/12) - Fixed -->
<div class="col-md-4"> <div class="col-md-4">
@@ -106,7 +108,7 @@
{% block extra_js %} {% block extra_js %}
<!-- Hidden data containers for JavaScript --> <!-- Hidden data containers for JavaScript -->
<script id="categoriesData" type="application/json">{{ categories_json|safe }}</script> <script id="categoriesData" type="application/json">{{ categories_json|safe }}</script>
<script id="productsData" type="application/json">{{ products_json|safe }}</script> <script id="itemsData" type="application/json">{{ items_json|safe }}</script>
<script src="{% static 'pos/js/terminal.js' %}"></script> <script src="{% static 'pos/js/terminal.js' %}"></script>
{% endblock %} {% endblock %}

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from products.models import Product, ProductCategory from products.models import Product, ProductCategory, ProductKit
import json import json
@@ -9,13 +9,17 @@ import json
def pos_terminal(request): def pos_terminal(request):
""" """
Tablet-friendly POS screen prototype. 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) categories_qs = ProductCategory.objects.filter(is_active=True)
# Показываем все товары, не только in_stock # Показываем все товары, не только in_stock
products_qs = Product.objects.all().prefetch_related('categories', 'photos') 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] categories = [{'id': c.id, 'name': c.name} for c in categories_qs]
# Сериализация товаров
products = [{ products = [{
'id': p.id, 'id': p.id,
'name': p.name, 'name': p.name,
@@ -23,12 +27,28 @@ def pos_terminal(request):
'category_ids': [c.id for c in p.categories.all()], 'category_ids': [c.id for c in p.categories.all()],
'in_stock': p.in_stock, 'in_stock': p.in_stock,
'sku': p.sku or '', '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] } 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 = { context = {
'categories_json': json.dumps(categories), 'categories_json': json.dumps(categories),
'products_json': json.dumps(products), 'items_json': json.dumps(all_items), # Единый массив товаров и комплектов
'title': 'POS Terminal', 'title': 'POS Terminal',
} }
return render(request, 'pos/terminal.html', context) return render(request, 'pos/terminal.html', context)