FIX: Добавлен баланс кошелька клиента в модальное окно продажи POS
Проблема: - Баланс кошелька клиента не отображался в модальном окне при нажатии "ПРОДАТЬ" - Данные о балансе не передавались из backend в frontend Исправления: 1. pos/views.py: - Добавлен wallet_balance в selected_customer при загрузке из Redis - Добавлен wallet_balance в system_customer - Добавлен wallet_balance в API set_customer (Redis + response) - Используется json.dumps() для корректной сериализации данных клиента 2. customers/views.py: - Добавлен wallet_balance в API поиска клиентов (api_search_customers) - Добавлен wallet_balance в API создания клиента (api_create_customer) 3. pos/static/pos/js/terminal.js: - Обновлена функция selectCustomer() для получения walletBalance - Обновлены все вызовы selectCustomer() для передачи баланса - selectedCustomer теперь содержит wallet_balance 4. pos/templates/pos/terminal.html: - Используются готовые JSON-строки из backend (system_customer_json, selected_customer_json) - Исправлена проблема с локализацией чисел в JSON Результат: Баланс кошелька клиента теперь корректно отображается в модальном окне продажи 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,7 @@ function formatMoney(v) {
|
||||
* - Кнопку "Выбрать клиента" в корзине (показывает имя клиента)
|
||||
* - Кнопку "Выбрать клиента" в модалке продажи (показывает имя клиента)
|
||||
* - Видимость кнопок сброса в обоих местах (показываем только для не-системного клиента)
|
||||
* - Ссылку на анкету клиента (показываем только для не-системного клиента)
|
||||
*/
|
||||
function updateCustomerDisplay() {
|
||||
// Обновляем текст кнопки в корзине
|
||||
@@ -110,17 +111,30 @@ function updateCustomerDisplay() {
|
||||
resetBtn.style.display = isSystemCustomer ? 'none' : 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Обновляем ссылку на анкету клиента
|
||||
const profileLink = document.getElementById('customerProfileLink');
|
||||
if (profileLink) {
|
||||
if (isSystemCustomer) {
|
||||
profileLink.style.display = 'none';
|
||||
} else {
|
||||
profileLink.href = `/customers/${selectedCustomer.id}/`;
|
||||
profileLink.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает нового клиента и сохраняет в Redis
|
||||
* @param {number} customerId - ID клиента
|
||||
* @param {string} customerName - Имя клиента
|
||||
* @param {number} walletBalance - Баланс кошелька клиента (опционально)
|
||||
*/
|
||||
function selectCustomer(customerId, customerName) {
|
||||
function selectCustomer(customerId, customerName, walletBalance = 0) {
|
||||
selectedCustomer = {
|
||||
id: customerId,
|
||||
name: customerName
|
||||
name: customerName,
|
||||
wallet_balance: walletBalance
|
||||
};
|
||||
updateCustomerDisplay();
|
||||
|
||||
@@ -136,6 +150,9 @@ function selectCustomer(customerId, customerName) {
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
console.error('Ошибка сохранения клиента:', data.error);
|
||||
} else {
|
||||
// Обновляем баланс из ответа сервера
|
||||
selectedCustomer.wallet_balance = data.wallet_balance || 0;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -188,8 +205,8 @@ function initCustomerSelect2() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Выбираем клиента
|
||||
selectCustomer(parseInt(data.id), data.name);
|
||||
// Выбираем клиента с балансом
|
||||
selectCustomer(parseInt(data.id), data.name, data.wallet_balance || 0);
|
||||
|
||||
// Закрываем модалку
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('selectCustomerModal'));
|
||||
@@ -297,8 +314,8 @@ async function createNewCustomer() {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Выбираем созданного клиента
|
||||
selectCustomer(data.id, data.name);
|
||||
// Выбираем созданного клиента с балансом
|
||||
selectCustomer(data.id, data.name, data.wallet_balance || 0);
|
||||
|
||||
// Закрываем модалку
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('createCustomerModal'));
|
||||
@@ -516,33 +533,46 @@ function renderProducts() {
|
||||
stock.style.color = '#856404';
|
||||
stock.style.fontWeight = 'bold';
|
||||
} else if (item.type === 'product' && item.available_qty !== undefined && item.reserved_qty !== undefined) {
|
||||
// Для обычных товаров показываем остатки: FREE(-RESERVED)
|
||||
// FREE = доступно для продажи (available - reserved)
|
||||
// Для обычных товаров показываем остатки: FREE(-RESERVED-IN_CART)
|
||||
// FREE = доступно для продажи (available - reserved - в корзине)
|
||||
const available = parseFloat(item.available_qty) || 0;
|
||||
const reserved = parseFloat(item.reserved_qty) || 0;
|
||||
const free = available - reserved;
|
||||
|
||||
|
||||
// Вычитаем количество из корзины для визуализации
|
||||
const cartKey = `product-${item.id}`;
|
||||
const inCart = cart.has(cartKey) ? cart.get(cartKey).qty : 0;
|
||||
|
||||
const free = available - reserved - inCart;
|
||||
|
||||
// Создаём элементы для стилизации разных размеров
|
||||
const freeSpan = document.createElement('span');
|
||||
freeSpan.textContent = free;
|
||||
freeSpan.style.fontSize = '1.1em';
|
||||
freeSpan.style.fontWeight = 'bold';
|
||||
freeSpan.style.fontStyle = 'normal';
|
||||
|
||||
// Отображаем резерв только если он есть
|
||||
|
||||
// Отображаем резерв и корзину если они есть
|
||||
const suffixParts = [];
|
||||
if (reserved > 0) {
|
||||
const reservedSpan = document.createElement('span');
|
||||
reservedSpan.textContent = `(−${reserved})`;
|
||||
reservedSpan.style.fontSize = '0.85em';
|
||||
reservedSpan.style.marginLeft = '3px';
|
||||
reservedSpan.style.fontStyle = 'normal';
|
||||
|
||||
suffixParts.push(`−${reserved}`);
|
||||
}
|
||||
if (inCart > 0) {
|
||||
suffixParts.push(`−${inCart}🛒`);
|
||||
}
|
||||
|
||||
if (suffixParts.length > 0) {
|
||||
const suffixSpan = document.createElement('span');
|
||||
suffixSpan.textContent = `(${suffixParts.join(' ')})`;
|
||||
suffixSpan.style.fontSize = '0.85em';
|
||||
suffixSpan.style.marginLeft = '3px';
|
||||
suffixSpan.style.fontStyle = 'normal';
|
||||
|
||||
stock.appendChild(freeSpan);
|
||||
stock.appendChild(reservedSpan);
|
||||
stock.appendChild(suffixSpan);
|
||||
} else {
|
||||
stock.appendChild(freeSpan);
|
||||
}
|
||||
|
||||
|
||||
// Цветовая индикация: красный если свободных остатков нет или отрицательные
|
||||
if (free <= 0) {
|
||||
stock.style.color = '#dc3545'; // Красный
|
||||
@@ -619,13 +649,13 @@ async function loadItems(append = false) {
|
||||
} else {
|
||||
ITEMS = data.items;
|
||||
}
|
||||
|
||||
|
||||
hasMoreItems = data.has_more;
|
||||
|
||||
|
||||
if (data.has_more) {
|
||||
currentPage = data.next_page;
|
||||
}
|
||||
|
||||
|
||||
renderProducts();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -721,6 +751,11 @@ async function addToCart(item) {
|
||||
renderCart();
|
||||
saveCartToRedis(); // Сохраняем в Redis
|
||||
|
||||
// Перерисовываем товары для обновления визуального остатка
|
||||
if (!isShowcaseView && item.type === 'product') {
|
||||
renderProducts();
|
||||
}
|
||||
|
||||
// Автоматический фокус на поле количества (только для обычных товаров)
|
||||
if (item.type !== 'showcase_kit') {
|
||||
setTimeout(() => {
|
||||
@@ -735,17 +770,36 @@ async function addToCart(item) {
|
||||
}
|
||||
}
|
||||
|
||||
// Вспомогательная функция для обновления количества товара в корзине
|
||||
async function updateCartItemQty(cartKey, newQty) {
|
||||
const item = cart.get(cartKey);
|
||||
if (!item) return;
|
||||
|
||||
if (newQty <= 0) {
|
||||
await removeFromCart(cartKey);
|
||||
} else {
|
||||
item.qty = newQty;
|
||||
renderCart();
|
||||
saveCartToRedis();
|
||||
|
||||
// Перерисовываем товары для обновления визуального остатка
|
||||
if (!isShowcaseView && item.type === 'product') {
|
||||
renderProducts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const list = document.getElementById('cartList');
|
||||
list.innerHTML = '';
|
||||
let total = 0;
|
||||
|
||||
|
||||
if (cart.size === 0) {
|
||||
list.innerHTML = '<p class="text-muted text-center py-4 small">Корзина пуста</p>';
|
||||
document.getElementById('cartTotal').textContent = '0.00';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
cart.forEach((item, cartKey) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'cart-item mb-2';
|
||||
@@ -799,16 +853,10 @@ function renderCart() {
|
||||
const minusBtn = document.createElement('button');
|
||||
minusBtn.className = 'btn btn-outline-secondary btn-sm';
|
||||
minusBtn.innerHTML = '<i class="bi bi-dash-circle" style="font-size: 1.2em;"></i>';
|
||||
minusBtn.onclick = (e) => {
|
||||
minusBtn.onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
const currentQty = cart.get(cartKey).qty;
|
||||
if (currentQty <= 1) {
|
||||
removeFromCart(cartKey);
|
||||
} else {
|
||||
cart.get(cartKey).qty = currentQty - 1;
|
||||
renderCart();
|
||||
saveCartToRedis();
|
||||
}
|
||||
await updateCartItemQty(cartKey, currentQty - 1);
|
||||
};
|
||||
|
||||
// Поле ввода количества
|
||||
@@ -820,26 +868,19 @@ function renderCart() {
|
||||
qtyInput.style.padding = '0.375rem 0.25rem';
|
||||
qtyInput.value = item.qty;
|
||||
qtyInput.min = 1;
|
||||
qtyInput.onchange = (e) => {
|
||||
qtyInput.onchange = async (e) => {
|
||||
const newQty = parseInt(e.target.value) || 1;
|
||||
if (newQty <= 0) {
|
||||
removeFromCart(cartKey);
|
||||
} else {
|
||||
cart.get(cartKey).qty = newQty;
|
||||
renderCart();
|
||||
saveCartToRedis(); // Сохраняем в Redis при изменении количества
|
||||
}
|
||||
await updateCartItemQty(cartKey, newQty);
|
||||
};
|
||||
|
||||
// Кнопка плюс
|
||||
const plusBtn = document.createElement('button');
|
||||
plusBtn.className = 'btn btn-outline-secondary btn-sm';
|
||||
plusBtn.innerHTML = '<i class="bi bi-plus-circle" style="font-size: 1.2em;"></i>';
|
||||
plusBtn.onclick = (e) => {
|
||||
plusBtn.onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
cart.get(cartKey).qty += 1;
|
||||
renderCart();
|
||||
saveCartToRedis();
|
||||
const currentQty = cart.get(cartKey).qty;
|
||||
await updateCartItemQty(cartKey, currentQty + 1);
|
||||
};
|
||||
|
||||
// Собираем контейнер
|
||||
@@ -908,6 +949,11 @@ async function removeFromCart(cartKey) {
|
||||
cart.delete(cartKey);
|
||||
renderCart();
|
||||
saveCartToRedis(); // Сохраняем в Redis
|
||||
|
||||
// Перерисовываем товары для обновления визуального остатка
|
||||
if (!isShowcaseView && item && item.type === 'product') {
|
||||
renderProducts();
|
||||
}
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
|
||||
Reference in New Issue
Block a user