fix(pos): исправлены проблемы с ценами витринных комплектов

- Исправлена логика установки useSalePrice при загрузке данных комплекта
- Исправлено сохранение sale_price при снятии чекбокса 'Установить свою цену'
- Исправлено сохранение измененных цен товаров в составе комплекта (unit_price)
- Добавлен блок предупреждения о неактуальных ценах с функцией пересчета
- Улучшена логика агрегации товаров при сохранении комплекта
This commit is contained in:
2026-01-27 23:57:18 +03:00
parent 0839b0a507
commit 17d85e96d6
4 changed files with 199 additions and 41 deletions

View File

@@ -519,14 +519,74 @@ export class ProductManager {
_renderShowcaseKitBadges(card, item, cart) {
// Кнопка редактирования (только если не заблокирован другим)
if (!item.is_locked || item.locked_by_me) {
// Индикатор неактуальной цены (показываем первым, если есть)
if (item.price_outdated) {
const outdatedBadge = document.createElement('button');
outdatedBadge.className = 'btn btn-sm p-0';
outdatedBadge.style.position = 'absolute';
outdatedBadge.style.top = '8px';
outdatedBadge.style.right = '45px';
outdatedBadge.style.zIndex = '10';
outdatedBadge.style.width = '28px';
outdatedBadge.style.height = '28px';
outdatedBadge.style.borderRadius = '50%';
outdatedBadge.style.display = 'flex';
outdatedBadge.style.alignItems = 'center';
outdatedBadge.style.justifyContent = 'center';
outdatedBadge.style.backgroundColor = '#ff6b6b';
outdatedBadge.style.border = '2px solid #fff';
outdatedBadge.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
outdatedBadge.style.cursor = 'pointer';
outdatedBadge.style.transition = 'all 0.2s ease';
outdatedBadge.title = 'Цена неактуальна - требуется обновление';
outdatedBadge.innerHTML = '<i class="bi bi-exclamation-triangle-fill text-white" style="font-size: 14px;"></i>';
outdatedBadge.onmouseenter = function() {
this.style.transform = 'scale(1.1)';
this.style.boxShadow = '0 3px 6px rgba(0,0,0,0.3)';
};
outdatedBadge.onmouseleave = function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
};
outdatedBadge.onclick = (e) => {
e.stopPropagation();
if (window.showcaseManager) {
window.showcaseManager.openEditModal(item.id);
}
};
card.appendChild(outdatedBadge);
}
// Кнопка редактирования (карандаш)
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-sm btn-outline-primary';
editBtn.className = 'btn btn-sm p-0';
editBtn.style.position = 'absolute';
editBtn.style.top = '5px';
editBtn.style.right = '5px';
editBtn.style.top = '8px';
editBtn.style.right = '8px';
editBtn.style.zIndex = '10';
editBtn.innerHTML = '<i class="bi bi-pencil"></i>';
editBtn.style.width = '32px';
editBtn.style.height = '32px';
editBtn.style.borderRadius = '6px';
editBtn.style.display = 'flex';
editBtn.style.alignItems = 'center';
editBtn.style.justifyContent = 'center';
editBtn.style.backgroundColor = '#4dabf7';
editBtn.style.border = '2px solid #fff';
editBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
editBtn.style.cursor = 'pointer';
editBtn.style.transition = 'all 0.2s ease';
editBtn.title = 'Редактировать комплект';
editBtn.innerHTML = '<i class="bi bi-pencil-fill text-white" style="font-size: 14px;"></i>';
editBtn.onmouseenter = function() {
this.style.backgroundColor = '#339af0';
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 3px 6px rgba(0,0,0,0.3)';
};
editBtn.onmouseleave = function() {
this.style.backgroundColor = '#4dabf7';
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
};
editBtn.onclick = (e) => {
e.stopPropagation();
if (window.showcaseManager) {
@@ -534,28 +594,6 @@ export class ProductManager {
}
};
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);
}
}
// Индикация блокировки

View File

@@ -85,6 +85,9 @@ export class ShowcaseManager {
document.getElementById('priceAdjustmentValue')?.addEventListener('input', () => this.updatePriceCalculation());
document.getElementById('useSalePrice')?.addEventListener('change', () => this.updatePriceCalculation());
document.getElementById('salePrice')?.addEventListener('input', () => this.updatePriceCalculation());
// Кнопка пересчета цен
document.getElementById('recalculatePricesBtn')?.addEventListener('click', () => this.recalculatePrices());
}
/**
@@ -157,26 +160,44 @@ export class ShowcaseManager {
// Заполняем временную корзину
this.tempCart.clear();
let hasOutdatedPrices = false;
if (kit.items) {
kit.items.forEach(item => {
const key = `product-${item.product_id}-${item.sales_unit_id || 'base'}`;
const actualPrice = item.actual_catalog_price ? parseFloat(item.actual_catalog_price) : null;
const currentPrice = parseFloat(item.price) || 0;
const isOutdated = item.price_outdated || (actualPrice !== null && Math.abs(currentPrice - actualPrice) > 0.01);
if (isOutdated) {
hasOutdatedPrices = true;
}
this.tempCart.set(key, {
id: item.product_id,
name: item.name || item.product_name, // Сервер отдаёт name
price: parseFloat(item.price) || 0,
price: currentPrice,
qty: parseFloat(item.qty || item.quantity) || 1, // Сервер отдаёт qty
type: 'product',
sales_unit_id: item.sales_unit_id,
unit_name: item.unit_name
unit_name: item.unit_name,
actual_catalog_price: actualPrice, // Сохраняем актуальную цену для пересчета
price_outdated: isOutdated
});
});
}
// Показываем блок предупреждения о неактуальных ценах
this.updatePriceOutdatedWarning(hasOutdatedPrices);
// Заполняем цены
document.getElementById('priceAdjustmentType').value = kit.price_adjustment_type || 'none';
document.getElementById('priceAdjustmentValue').value = kit.price_adjustment_value || 0;
document.getElementById('useSalePrice').checked = kit.use_sale_price || false;
document.getElementById('salePrice').value = kit.sale_price || '';
// Если sale_price установлен, автоматически включаем useSalePrice
const salePriceValue = kit.sale_price || '';
const hasSalePrice = salePriceValue && parseFloat(salePriceValue) > 0;
document.getElementById('useSalePrice').checked = hasSalePrice;
document.getElementById('salePrice').value = salePriceValue;
this.renderTempKitItems();
this.updatePriceCalculation();
@@ -363,6 +384,15 @@ export class ShowcaseManager {
// Обновляем базовую цену
document.getElementById('tempKitBasePrice').textContent = formatMoney(totalBasePrice) + ' руб.';
// Проверяем наличие неактуальных цен и обновляем предупреждение
let hasOutdatedPrices = false;
this.tempCart.forEach((item) => {
if (item.price_outdated || (item.actual_catalog_price !== null && item.actual_catalog_price !== undefined && Math.abs(item.price - item.actual_catalog_price) > 0.01)) {
hasOutdatedPrices = true;
}
});
this.updatePriceOutdatedWarning(hasOutdatedPrices);
}
/**
@@ -416,6 +446,50 @@ export class ShowcaseManager {
document.getElementById('tempKitFinalPrice').textContent = formatMoney(finalPrice);
}
/**
* Обновляет отображение предупреждения о неактуальных ценах
* @param {boolean} hasOutdated - Есть ли неактуальные цены
*/
updatePriceOutdatedWarning(hasOutdated) {
const warningBlock = document.getElementById('priceOutdatedWarning');
if (warningBlock) {
warningBlock.style.display = hasOutdated ? 'block' : 'none';
}
}
/**
* Пересчитывает цены товаров на актуальные из каталога
*/
recalculatePrices() {
let updated = false;
this.tempCart.forEach((item, key) => {
if (item.actual_catalog_price !== null && item.actual_catalog_price !== undefined) {
const oldPrice = item.price;
const newPrice = item.actual_catalog_price;
if (Math.abs(oldPrice - newPrice) > 0.01) {
item.price = newPrice;
item.price_outdated = false;
updated = true;
}
}
});
if (updated) {
// Перерисовываем список товаров и пересчитываем цены
this.renderTempKitItems();
this.updatePriceCalculation();
// Скрываем предупреждение
this.updatePriceOutdatedWarning(false);
showToast('success', 'Цены обновлены на актуальные из каталога');
} else {
showToast('info', 'Все цены уже актуальны');
}
}
/**
* Обрабатывает загрузку фото
*/
@@ -462,6 +536,8 @@ export class ShowcaseManager {
document.getElementById('useSalePrice').checked = false;
document.getElementById('salePrice').value = '';
this.removePhoto();
// Скрываем предупреждение о неактуальных ценах
this.updatePriceOutdatedWarning(false);
}
/**
@@ -537,8 +613,9 @@ export class ShowcaseManager {
formData.append('showcase_created_at', data.createdAt || '');
formData.append('price_adjustment_type', data.adjustmentType);
formData.append('price_adjustment_value', data.adjustmentValue);
formData.append('use_sale_price', data.useSalePrice);
formData.append('sale_price', data.salePrice || 0);
formData.append('use_sale_price', data.useSalePrice ? '1' : '0');
// Если useSalePrice выключен, отправляем пустую строку для явной очистки sale_price на сервере
formData.append('sale_price', data.useSalePrice ? (data.salePrice || '') : '');
if (!this.isEditMode) {
formData.append('quantity', data.quantity);
@@ -550,7 +627,7 @@ export class ShowcaseManager {
items.push({
product_id: item.id,
quantity: item.qty,
price: item.price,
unit_price: item.price, // Используем unit_price для сохранения измененной цены товара
sales_unit_id: item.sales_unit_id || null
});
});