feat(pos): улучшена работа с витринными комплектами
- добавлена кнопка редактирования комплектов с индикатором устаревшей цены - реализовано редактирование цен товаров в комплекте через inline-поля ввода - добавлена автовыбор витрины по умолчанию при создании комплекта - улучшена генерация названия комплекта по умолчанию - исправлены поля API для совместимости с сервером (kit_name, qty, name) - добавлен глобальный доступ к showcaseManager из terminal.js
This commit is contained in:
@@ -517,6 +517,47 @@ export class ProductManager {
|
||||
* @private
|
||||
*/
|
||||
_renderShowcaseKitBadges(card, item, cart) {
|
||||
// Кнопка редактирования (только если не заблокирован другим)
|
||||
if (!item.is_locked || item.locked_by_me) {
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'btn btn-sm btn-outline-primary';
|
||||
editBtn.style.position = 'absolute';
|
||||
editBtn.style.top = '5px';
|
||||
editBtn.style.right = '5px';
|
||||
editBtn.style.zIndex = '10';
|
||||
editBtn.innerHTML = '<i class="bi bi-pencil"></i>';
|
||||
editBtn.title = 'Редактировать комплект';
|
||||
editBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
if (window.showcaseManager) {
|
||||
window.showcaseManager.openEditModal(item.id);
|
||||
}
|
||||
};
|
||||
card.appendChild(editBtn);
|
||||
|
||||
// Индикатор неактуальной цены (красный кружок)
|
||||
if (item.price_outdated) {
|
||||
const outdatedBadge = document.createElement('div');
|
||||
outdatedBadge.className = 'badge bg-danger';
|
||||
outdatedBadge.style.position = 'absolute';
|
||||
outdatedBadge.style.top = '5px';
|
||||
outdatedBadge.style.right = '45px';
|
||||
outdatedBadge.style.zIndex = '10';
|
||||
outdatedBadge.style.width = '18px';
|
||||
outdatedBadge.style.height = '18px';
|
||||
outdatedBadge.style.padding = '0';
|
||||
outdatedBadge.style.borderRadius = '50%';
|
||||
outdatedBadge.style.display = 'flex';
|
||||
outdatedBadge.style.alignItems = 'center';
|
||||
outdatedBadge.style.justifyContent = 'center';
|
||||
outdatedBadge.style.fontSize = '10px';
|
||||
outdatedBadge.style.minWidth = '18px';
|
||||
outdatedBadge.title = 'Цена неактуальна';
|
||||
outdatedBadge.innerHTML = '!';
|
||||
card.appendChild(outdatedBadge);
|
||||
}
|
||||
}
|
||||
|
||||
// Индикация блокировки
|
||||
if (item.is_locked) {
|
||||
const lockBadge = document.createElement('div');
|
||||
|
||||
@@ -162,9 +162,9 @@ export class ShowcaseManager {
|
||||
const key = `product-${item.product_id}-${item.sales_unit_id || 'base'}`;
|
||||
this.tempCart.set(key, {
|
||||
id: item.product_id,
|
||||
name: item.product_name,
|
||||
price: item.price,
|
||||
qty: item.quantity,
|
||||
name: item.name || item.product_name, // Сервер отдаёт name
|
||||
price: parseFloat(item.price) || 0,
|
||||
qty: parseFloat(item.qty || item.quantity) || 1, // Сервер отдаёт qty
|
||||
type: 'product',
|
||||
sales_unit_id: item.sales_unit_id,
|
||||
unit_name: item.unit_name
|
||||
@@ -254,11 +254,26 @@ export class ShowcaseManager {
|
||||
if (!select) return;
|
||||
|
||||
let html = '<option value="">Выберите витрину...</option>';
|
||||
let defaultShowcaseId = null;
|
||||
|
||||
this.showcases.forEach(showcase => {
|
||||
html += `<option value="${showcase.id}">${escapeHtml(showcase.name)}</option>`;
|
||||
const displayName = showcase.warehouse_name
|
||||
? `${showcase.name} (${showcase.warehouse_name})`
|
||||
: showcase.name;
|
||||
html += `<option value="${showcase.id}">${escapeHtml(displayName)}</option>`;
|
||||
|
||||
// Запоминаем витрину по умолчанию
|
||||
if (showcase.is_default) {
|
||||
defaultShowcaseId = showcase.id;
|
||||
}
|
||||
});
|
||||
|
||||
select.innerHTML = html;
|
||||
|
||||
// Автовыбор витрины по умолчанию (только в режиме создания)
|
||||
if (!this.isEditMode && defaultShowcaseId) {
|
||||
select.value = defaultShowcaseId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,33 +283,83 @@ export class ShowcaseManager {
|
||||
const container = document.getElementById('tempKitItemsList');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
if (this.tempCart.size === 0) {
|
||||
container.innerHTML = '<p class="text-muted text-center mb-0">Нет товаров</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
let totalBasePrice = 0;
|
||||
|
||||
this.tempCart.forEach((item, key) => {
|
||||
const itemTotal = item.price * item.qty;
|
||||
totalBasePrice += itemTotal;
|
||||
|
||||
html += `
|
||||
<div class="d-flex justify-content-between align-items-center py-1 border-bottom">
|
||||
<div class="flex-grow-1">
|
||||
<div class="small">${escapeHtml(item.name)}</div>
|
||||
<div class="text-muted" style="font-size: 0.75rem;">
|
||||
${formatMoney(item.price)} × ${roundQuantity(item.qty)}
|
||||
${item.unit_name ? ' ' + item.unit_name : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fw-semibold small">${formatMoney(itemTotal)}</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'd-flex justify-content-between align-items-center py-1 border-bottom';
|
||||
|
||||
container.innerHTML = html;
|
||||
// Левая часть: название и цена
|
||||
const leftDiv = document.createElement('div');
|
||||
leftDiv.className = 'flex-grow-1';
|
||||
|
||||
// Название товара
|
||||
const nameDiv = document.createElement('div');
|
||||
nameDiv.className = 'small';
|
||||
nameDiv.textContent = item.name;
|
||||
leftDiv.appendChild(nameDiv);
|
||||
|
||||
// Контейнер цены с полем ввода
|
||||
const priceContainer = document.createElement('div');
|
||||
priceContainer.className = 'text-muted d-flex align-items-center';
|
||||
priceContainer.style.fontSize = '0.75rem';
|
||||
priceContainer.style.gap = '4px';
|
||||
|
||||
// Поле ввода цены (всегда видимое, без стрелочек)
|
||||
const priceInput = document.createElement('input');
|
||||
priceInput.type = 'text';
|
||||
priceInput.inputMode = 'decimal';
|
||||
priceInput.className = 'form-control form-control-sm';
|
||||
priceInput.style.width = '70px';
|
||||
priceInput.style.padding = '2px 4px';
|
||||
priceInput.style.textAlign = 'right';
|
||||
priceInput.value = formatMoney(item.price);
|
||||
|
||||
// Сохранение цены при изменении
|
||||
const savePrice = () => {
|
||||
const newPrice = parseFloat(priceInput.value.replace(',', '.')) || 0;
|
||||
if (item.price !== newPrice) {
|
||||
item.price = newPrice;
|
||||
this.renderTempKitItems();
|
||||
this.updatePriceCalculation();
|
||||
}
|
||||
};
|
||||
|
||||
priceInput.onblur = savePrice;
|
||||
priceInput.onkeydown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
priceInput.blur();
|
||||
}
|
||||
};
|
||||
priceInput.onfocus = () => priceInput.select();
|
||||
|
||||
priceContainer.appendChild(priceInput);
|
||||
priceContainer.appendChild(document.createTextNode(`× ${roundQuantity(item.qty)}`));
|
||||
if (item.unit_name) {
|
||||
priceContainer.appendChild(document.createTextNode(` ${item.unit_name}`));
|
||||
}
|
||||
leftDiv.appendChild(priceContainer);
|
||||
|
||||
// Правая часть: итоговая сумма
|
||||
const rightDiv = document.createElement('div');
|
||||
rightDiv.className = 'fw-semibold small';
|
||||
rightDiv.textContent = formatMoney(itemTotal);
|
||||
|
||||
itemDiv.appendChild(leftDiv);
|
||||
itemDiv.appendChild(rightDiv);
|
||||
container.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
// Обновляем базовую цену
|
||||
document.getElementById('tempKitBasePrice').textContent = formatMoney(totalBasePrice) + ' руб.';
|
||||
@@ -384,8 +449,11 @@ export class ShowcaseManager {
|
||||
* Сбрасывает форму
|
||||
*/
|
||||
resetForm() {
|
||||
document.getElementById('tempKitName').value = '';
|
||||
document.getElementById('showcaseSelect').value = '';
|
||||
// Генерируем название по умолчанию
|
||||
const randomSuffix = Math.floor(Math.random() * 900) + 100;
|
||||
const defaultName = `Витринный букет ${randomSuffix}`;
|
||||
document.getElementById('tempKitName').value = defaultName;
|
||||
// Не сбрасываем showcaseSelect - витрина по умолчанию выбирается в renderShowcaseSelect()
|
||||
document.getElementById('showcaseKitQuantity').value = '1';
|
||||
document.getElementById('tempKitDescription').value = '';
|
||||
document.getElementById('showcaseCreatedAt').value = '';
|
||||
@@ -463,7 +531,7 @@ export class ShowcaseManager {
|
||||
|
||||
// Формируем данные для отправки
|
||||
const formData = new FormData();
|
||||
formData.append('name', data.name);
|
||||
formData.append('kit_name', data.name); // Сервер ожидает kit_name
|
||||
formData.append('showcase_id', data.showcaseId);
|
||||
formData.append('description', data.description || '');
|
||||
formData.append('showcase_created_at', data.createdAt || '');
|
||||
|
||||
@@ -157,6 +157,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Глобальные ссылки для обратной совместимости с cart-item-editor.js
|
||||
window.cart = cart;
|
||||
window.renderCart = renderCart;
|
||||
window.showcaseManager = showcaseManager;
|
||||
});
|
||||
|
||||
// ===== РЕНДЕРИНГ КАТЕГОРИЙ =====
|
||||
|
||||
Reference in New Issue
Block a user