Добавлена поддержка ProductKit в POS, улучшена прокрутка и фиксация элементов
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +23,10 @@
|
|||||||
<div class="row g-3" id="categoryGrid"></div>
|
<div class="row g-3" id="categoryGrid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Products Grid (Блок товаров) -->
|
<!-- Products Grid (Блок товаров) - Прокручиваемая область -->
|
||||||
<div class="row g-3" id="productGrid"></div>
|
<div class="products-scrollable">
|
||||||
|
<div class="row g-3" id="productGrid"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Panel (4/12) - Fixed -->
|
<!-- Right Panel (4/12) - Fixed -->
|
||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user