POS: улучшения работы с витринными букетами
- Упрощено добавление в корзину: 1 клик = 1 шт (без prompt) - API показывает все букеты (available + in_cart), не только доступные - Карточка показывает available/total и сколько в корзине - Корзина показывает реальное количество витринных букетов - Кнопка "Очистить" сбрасывает блокировки и обновляет отображение - API release-all-my-locks для сброса зависших блокировок - Автоочистка истёкших блокировок при загрузке витрины 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -570,10 +570,18 @@ function renderProducts() {
|
||||
const stock = document.createElement('div');
|
||||
stock.className = 'product-stock';
|
||||
|
||||
// Для витринных комплектов показываем название витрины И доступное количество
|
||||
// Для витринных комплектов показываем название витрины И количество (доступно/всего)
|
||||
if (item.type === 'showcase_kit') {
|
||||
const availableCount = item.available_count || 1;
|
||||
stock.innerHTML = `🌺 ${item.showcase_name} <span class="badge bg-success ms-1">${availableCount} шт</span>`;
|
||||
const availableCount = item.available_count || 0;
|
||||
const totalCount = item.total_count || availableCount;
|
||||
const inCart = totalCount - availableCount;
|
||||
|
||||
// Показываем: доступно / всего (и сколько в корзине)
|
||||
let badgeClass = availableCount > 0 ? 'bg-success' : 'bg-secondary';
|
||||
let badgeText = totalCount > 1 ? `${availableCount}/${totalCount}` : `${availableCount}`;
|
||||
let cartInfo = inCart > 0 ? ` <span class="badge bg-warning text-dark">🛒${inCart}</span>` : '';
|
||||
|
||||
stock.innerHTML = `🌺 ${item.showcase_name} <span class="badge ${badgeClass} ms-1">${badgeText}</span>${cartInfo}`;
|
||||
stock.style.color = '#856404';
|
||||
stock.style.fontWeight = 'bold';
|
||||
} else if (item.type === 'product' && item.available_qty !== undefined && item.reserved_qty !== undefined) {
|
||||
@@ -736,35 +744,10 @@ function setupInfiniteScroll() {
|
||||
async function addToCart(item) {
|
||||
const cartKey = `${item.type}-${item.id}`; // Уникальный ключ: "product-1" или "kit-1"
|
||||
|
||||
// СПЕЦИАЛЬНАЯ ЛОГИКА ДЛЯ ВИТРИННЫХ КОМПЛЕКТОВ (Soft Lock + количество)
|
||||
// СПЕЦИАЛЬНАЯ ЛОГИКА ДЛЯ ВИТРИННЫХ КОМПЛЕКТОВ (Soft Lock)
|
||||
if (item.type === 'showcase_kit') {
|
||||
// Определяем сколько доступно и сколько добавить
|
||||
const availableCount = item.available_count || 1;
|
||||
const currentInCart = cart.has(cartKey) ? cart.get(cartKey).qty : 0;
|
||||
const remainingAvailable = availableCount - currentInCart;
|
||||
|
||||
if (remainingAvailable <= 0) {
|
||||
alert(`Все ${availableCount} экз. этого букета уже в корзине.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Если доступно > 1, спрашиваем количество
|
||||
let quantityToAdd = 1;
|
||||
if (remainingAvailable > 1 && !cart.has(cartKey)) {
|
||||
const input = prompt(
|
||||
`Доступно ${availableCount} экз. букета "${item.name}".\n` +
|
||||
`Сколько добавить в корзину? (1-${remainingAvailable})`,
|
||||
'1'
|
||||
);
|
||||
if (input === null) return; // Отмена
|
||||
quantityToAdd = parseInt(input, 10);
|
||||
if (isNaN(quantityToAdd) || quantityToAdd < 1 || quantityToAdd > remainingAvailable) {
|
||||
alert(`Введите число от 1 до ${remainingAvailable}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Пытаемся создать блокировку через API
|
||||
// Пытаемся заблокировать 1 экземпляр через API
|
||||
// API сам проверит доступность и вернёт ошибку если нет свободных
|
||||
try {
|
||||
const response = await fetch(`/pos/api/showcase-kits/${item.id}/add-to-cart/`, {
|
||||
method: 'POST',
|
||||
@@ -772,14 +755,18 @@ async function addToCart(item) {
|
||||
'X-CSRFToken': getCookie('csrftoken'),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ quantity: quantityToAdd })
|
||||
body: JSON.stringify({ quantity: 1 })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
// Конфликт - комплект занят другим кассиром
|
||||
// Нет доступных экземпляров или другая ошибка
|
||||
alert(data.error || 'Не удалось добавить букет в корзину');
|
||||
// Обновляем витрину чтобы показать актуальное состояние
|
||||
if (isShowcaseView) {
|
||||
await loadShowcaseKits();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -791,7 +778,6 @@ async function addToCart(item) {
|
||||
const existing = cart.get(cartKey);
|
||||
existing.qty += lockedItemIds.length;
|
||||
existing.showcase_item_ids = [...(existing.showcase_item_ids || []), ...lockedItemIds];
|
||||
existing.max_qty = availableCount;
|
||||
} else {
|
||||
// Создаём новую запись
|
||||
cart.set(cartKey, {
|
||||
@@ -800,13 +786,12 @@ async function addToCart(item) {
|
||||
price: Number(item.price),
|
||||
qty: lockedItemIds.length,
|
||||
type: item.type,
|
||||
max_qty: availableCount, // Максимум = сколько доступно
|
||||
showcase_item_ids: lockedItemIds, // ID заблокированных экземпляров
|
||||
showcase_item_ids: lockedItemIds,
|
||||
lock_expires_at: data.lock_expires_at
|
||||
});
|
||||
}
|
||||
|
||||
// Обновляем список витрины (чтобы показать блокировку)
|
||||
// Обновляем список витрины (чтобы показать актуальные available_count)
|
||||
if (isShowcaseView) {
|
||||
await loadShowcaseKits();
|
||||
}
|
||||
@@ -919,7 +904,7 @@ function renderCart() {
|
||||
if (isShowcaseKit) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'badge bg-warning text-dark';
|
||||
badge.textContent = '1 шт (витрина)';
|
||||
badge.textContent = `${item.qty} шт (витрина)`;
|
||||
badge.style.fontSize = '0.85rem';
|
||||
badge.style.padding = '0.5rem 0.75rem';
|
||||
qtyControl.appendChild(badge);
|
||||
@@ -1040,10 +1025,28 @@ async function removeFromCart(cartKey) {
|
||||
}
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
async function clearCart() {
|
||||
// Сбрасываем все свои блокировки витринных букетов
|
||||
try {
|
||||
await fetch('/pos/api/showcase-kits/release-all-my-locks/', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Ошибка сброса блокировок:', e);
|
||||
}
|
||||
|
||||
// Очищаем корзину
|
||||
cart.clear();
|
||||
renderCart();
|
||||
saveCartToRedis(); // Сохраняем пустую корзину в Redis
|
||||
|
||||
// Обновляем отображение товаров/витрины чтобы показать актуальные остатки
|
||||
if (isShowcaseView) {
|
||||
await loadShowcaseKits();
|
||||
} else {
|
||||
renderProducts(); // Перерисовать карточки товаров с актуальными остатками
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('clearCart').onclick = clearCart;
|
||||
|
||||
Reference in New Issue
Block a user