feat(pos): Display showcase kits in POS interface

- Added get_showcase_kits_for_pos() function to retrieve showcase kits with active reservations
- Modified POS terminal to show showcase kits when 'Витрина' button is clicked
- Showcase kits displayed as product cards with showcase name badge (🌺 icon)
- Added isShowcaseView flag to toggle between regular and showcase view modes
- Implemented distinct styling for active showcase button:
  * Bright orange background (#ff6600)
  * Black text for contrast
  * Thicker border (3px)
  * Enhanced shadow and scale effect (1.05)
- Showcase kits can be added to cart for sale from POS interface
This commit is contained in:
2025-11-16 21:36:55 +03:00
parent 156f646252
commit 852bb92cfb
4 changed files with 114 additions and 34 deletions

View File

@@ -216,6 +216,17 @@ body {
color: white; 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 { .category-card .card-body {
padding: 0.5rem; padding: 0.5rem;
display: flex; display: flex;

View File

@@ -2,8 +2,10 @@
const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent); const CATEGORIES = JSON.parse(document.getElementById('categoriesData').textContent);
const ITEMS = JSON.parse(document.getElementById('itemsData').textContent); // Единый массив товаров и комплектов const ITEMS = JSON.parse(document.getElementById('itemsData').textContent); // Единый массив товаров и комплектов
const SHOWCASE_KITS = JSON.parse(document.getElementById('showcaseKitsData').textContent); // Витринные комплекты
let currentCategoryId = null; let currentCategoryId = null;
let isShowcaseView = false; // Флаг режима просмотра витринных букетов
const cart = new Map(); // "type-id" -> {id, name, price, qty, type} const cart = new Map(); // "type-id" -> {id, name, price, qty, type}
function formatMoney(v) { function formatMoney(v) {
@@ -18,33 +20,14 @@ function renderCategories() {
const showcaseCol = document.createElement('div'); const showcaseCol = document.createElement('div');
showcaseCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2'; showcaseCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const showcaseCard = document.createElement('div'); 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.backgroundColor = '#fff3cd';
showcaseCard.style.borderColor = '#ffc107'; showcaseCard.style.borderColor = '#ffc107';
showcaseCard.onclick = async () => { showcaseCard.onclick = () => {
try { isShowcaseView = true;
const response = await fetch('/pos/api/showcase-items/'); currentCategoryId = null;
const data = await response.json(); renderCategories();
renderProducts();
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('Ошибка загрузки витринных букетов');
}
}; };
const showcaseBody = document.createElement('div'); const showcaseBody = document.createElement('div');
showcaseBody.className = 'card-body'; showcaseBody.className = 'card-body';
@@ -60,9 +43,10 @@ function renderCategories() {
const allCol = document.createElement('div'); const allCol = document.createElement('div');
allCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2'; allCol.className = 'col-6 col-sm-4 col-md-3 col-lg-2';
const allCard = document.createElement('div'); const allCard = document.createElement('div');
allCard.className = 'card category-card' + (currentCategoryId === null ? ' active' : ''); allCard.className = 'card category-card' + (currentCategoryId === null && !isShowcaseView ? ' active' : '');
allCard.onclick = () => { allCard.onclick = () => {
currentCategoryId = null; currentCategoryId = null;
isShowcaseView = false;
renderCategories(); renderCategories();
renderProducts(); renderProducts();
}; };
@@ -82,9 +66,10 @@ function renderCategories() {
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 category-card' + (currentCategoryId === cat.id ? ' active' : ''); card.className = 'card category-card' + (currentCategoryId === cat.id && !isShowcaseView ? ' active' : '');
card.onclick = () => { card.onclick = () => {
currentCategoryId = cat.id; currentCategoryId = cat.id;
isShowcaseView = false;
renderCategories(); renderCategories();
renderProducts(); renderProducts();
}; };
@@ -108,9 +93,17 @@ function renderProducts() {
grid.innerHTML = ''; grid.innerHTML = '';
const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const searchTerm = document.getElementById('searchInput').value.toLowerCase();
let filtered = currentCategoryId let filtered;
// Если выбран режим витрины - показываем витринные комплекты
if (isShowcaseView) {
filtered = SHOWCASE_KITS;
} else {
// Обычный режим - показываем товары и комплекты
filtered = currentCategoryId
? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId)) ? ITEMS.filter(item => (item.category_ids || []).includes(currentCategoryId))
: ITEMS; : ITEMS;
}
if (searchTerm) { if (searchTerm) {
filtered = filtered.filter(item => item.name.toLowerCase().includes(searchTerm)); filtered = filtered.filter(item => item.name.toLowerCase().includes(searchTerm));
@@ -149,10 +142,18 @@ function renderProducts() {
const stock = document.createElement('div'); const stock = document.createElement('div');
stock.className = 'product-stock'; stock.className = 'product-stock';
// Для витринных комплектов показываем название витрины
if (item.type === 'showcase_kit') {
stock.textContent = `🌺 ${item.showcase_name}`;
stock.style.color = '#856404';
stock.style.fontWeight = 'bold';
} else {
stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ'; stock.textContent = item.in_stock ? 'В наличии' : 'Под заказ';
if (!item.in_stock) { if (!item.in_stock) {
stock.style.color = '#dc3545'; stock.style.color = '#dc3545';
} }
}
const sku = document.createElement('div'); const sku = document.createElement('div');
sku.className = 'product-sku'; sku.className = 'product-sku';

View File

@@ -159,6 +159,7 @@
<!-- 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="itemsData" type="application/json">{{ items_json|safe }}</script> <script id="itemsData" type="application/json">{{ items_json|safe }}</script>
<script id="showcaseKitsData" type="application/json">{{ showcase_kits_json|safe }}</script>
<script src="{% static 'pos/js/terminal.js' %}"></script> <script src="{% static 'pos/js/terminal.js' %}"></script>
{% endblock %} {% endblock %}

View File

@@ -13,6 +13,69 @@ from inventory.models import Showcase, Reservation, Warehouse
from inventory.services import ShowcaseManager 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 @login_required
def pos_terminal(request): def pos_terminal(request):
""" """
@@ -51,12 +114,16 @@ def pos_terminal(request):
'type': 'kit' 'type': 'kit'
} for k in kits_qs] } for k in kits_qs]
# Получаем витринные комплекты (временные комплекты с резервами на витринах)
showcase_kits_data = get_showcase_kits_for_pos()
# Объединяем все позиции # Объединяем все позиции
all_items = products + kits all_items = products + kits
context = { context = {
'categories_json': json.dumps(categories), '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', 'title': 'POS Terminal',
} }
return render(request, 'pos/terminal.html', context) return render(request, 'pos/terminal.html', context)