feat: добавлено редактирование витринных комплектов и изолированное состояние tempCart

- Добавлены API endpoints для получения и обновления витринных комплектов
  - GET /pos/api/product-kits/<id>/ - получение деталей комплекта
  - POST /pos/api/product-kits/<id>/update/ - обновление комплекта
- Реализовано редактирование комплектов из POS интерфейса
  - Кнопка редактирования (карандаш) на карточках витринных букетов
  - Модальное окно предзаполняется данными комплекта
  - Поддержка изменения состава, цен, описания и фото
  - Умное управление резервами при изменении состава
- Введено изолированное состояние tempCart для модального окна
  - Основная корзина (cart) больше не затрагивается при редактировании
  - tempCart используется для создания и редактирования комплектов
  - Автоочистка tempCart при закрытии модального окна
- Устранён побочный эффект загрузки состава комплекта в основную корзину
This commit is contained in:
2025-11-16 23:41:27 +03:00
parent 9dff9cc200
commit cefd6c98a2
3 changed files with 364 additions and 19 deletions

View File

@@ -8,6 +8,13 @@ let currentCategoryId = null;
let isShowcaseView = false; // Флаг режима просмотра витринных букетов
const cart = new Map(); // "type-id" -> {id, name, price, qty, type}
// Переменные для режима редактирования
let isEditMode = false;
let editingKitId = null;
// Временная корзина для модального окна создания/редактирования комплекта
const tempCart = new Map(); // Изолированное состояние для модалки
function formatMoney(v) {
return (Number(v)).toFixed(2);
}
@@ -116,8 +123,25 @@ function renderProducts() {
const card = document.createElement('div');
card.className = 'card product-card';
card.style.position = 'relative';
card.onclick = () => addToCart(item);
// Если это витринный комплект - добавляем кнопку редактирования
if (item.type === 'showcase_kit') {
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.onclick = (e) => {
e.stopPropagation();
openEditKitModal(item.id);
};
card.appendChild(editBtn);
}
const body = document.createElement('div');
body.className = 'card-body';
@@ -319,6 +343,12 @@ async function openCreateTempKitModal() {
return;
}
// Копируем содержимое cart в tempCart (изолированное состояние модалки)
tempCart.clear();
cart.forEach((item, key) => {
tempCart.set(key, {...item}); // Глубокая копия объекта
});
// Генерируем название по умолчанию
const now = new Date();
const defaultName = `Витрина — ${now.toLocaleDateString('ru-RU')} ${now.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'})}`;
@@ -327,7 +357,7 @@ async function openCreateTempKitModal() {
// Загружаем список витрин
await loadShowcases();
// Заполняем список товаров из корзины
// Заполняем список товаров из tempCart
renderTempKitItems();
// Открываем модальное окно
@@ -335,6 +365,87 @@ async function openCreateTempKitModal() {
modal.show();
}
// Открытие модального окна для редактирования комплекта
async function openEditKitModal(kitId) {
try {
// Загружаем данные комплекта
const response = await fetch(`/pos/api/product-kits/${kitId}/`);
const data = await response.json();
if (!data.success) {
alert(`Ошибка: ${data.error}`);
return;
}
const kit = data.kit;
// Устанавливаем режим редактирования
isEditMode = true;
editingKitId = kitId;
// Загружаем список витрин
await loadShowcases();
// Очищаем tempCart и заполняем составом комплекта
tempCart.clear();
kit.items.forEach(item => {
const cartKey = `product-${item.product_id}`;
tempCart.set(cartKey, {
id: item.product_id,
name: item.name,
price: Number(item.price),
qty: Number(item.qty),
type: 'product'
});
});
renderTempKitItems(); // Отображаем товары в модальном окне
// Заполняем поля формы
document.getElementById('tempKitName').value = kit.name;
document.getElementById('tempKitDescription').value = kit.description;
document.getElementById('priceAdjustmentType').value = kit.price_adjustment_type;
document.getElementById('priceAdjustmentValue').value = kit.price_adjustment_value;
if (kit.sale_price) {
document.getElementById('useSalePrice').checked = true;
document.getElementById('salePrice').value = kit.sale_price;
document.getElementById('salePriceBlock').style.display = 'block';
} else {
document.getElementById('useSalePrice').checked = false;
document.getElementById('salePrice').value = '';
document.getElementById('salePriceBlock').style.display = 'none';
}
// Выбираем витрину
if (kit.showcase_id) {
document.getElementById('showcaseSelect').value = kit.showcase_id;
}
// Отображаем фото, если есть
if (kit.photo_url) {
document.getElementById('photoPreviewImg').src = kit.photo_url;
document.getElementById('photoPreview').style.display = 'block';
} else {
document.getElementById('photoPreview').style.display = 'none';
}
// Обновляем цены
updatePriceCalculations();
// Меняем заголовок и кнопку
document.getElementById('createTempKitModalLabel').textContent = 'Редактирование витринного букета';
document.getElementById('confirmCreateTempKit').textContent = 'Сохранить изменения';
// Открываем модальное окно
const modal = new bootstrap.Modal(document.getElementById('createTempKitModal'));
modal.show();
} catch (error) {
console.error('Error loading kit for edit:', error);
alert('Ошибка при загрузке комплекта');
}
}
// Обновление списка витринных комплектов
async function refreshShowcaseKits() {
try {
@@ -388,14 +499,14 @@ async function loadShowcases() {
}
}
// Отображение товаров из корзины в модальном окне
// Отображение товаров из tempCart в модальном окне
function renderTempKitItems() {
const container = document.getElementById('tempKitItemsList');
container.innerHTML = '';
let estimatedTotal = 0;
cart.forEach((item, cartKey) => {
tempCart.forEach((item, cartKey) => {
// Только товары (не комплекты)
if (item.type !== 'product') return;
@@ -422,10 +533,10 @@ function renderTempKitItems() {
// Расчет и обновление всех цен
function updatePriceCalculations(basePrice = null) {
// Если basePrice не передан, пересчитываем из корзины
// Если basePrice не передан, пересчитываем из tempCart
if (basePrice === null) {
basePrice = 0;
cart.forEach((item, cartKey) => {
tempCart.forEach((item, cartKey) => {
if (item.type === 'product') {
basePrice += item.qty * item.price;
}
@@ -524,7 +635,7 @@ document.getElementById('removePhoto').addEventListener('click', function() {
document.getElementById('photoPreviewImg').src = '';
});
// Подтверждение создания временного комплекта
// Подтверждение создания/редактирования временного комплекта
document.getElementById('confirmCreateTempKit').onclick = async () => {
const kitName = document.getElementById('tempKitName').value.trim();
const showcaseId = document.getElementById('showcaseSelect').value;
@@ -537,14 +648,14 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
return;
}
if (!showcaseId) {
if (!showcaseId && !isEditMode) {
alert('Выберите витрину');
return;
}
// Собираем товары из корзины
// Собираем товары из tempCart (изолированное состояние модалки)
const items = [];
cart.forEach((item, cartKey) => {
tempCart.forEach((item, cartKey) => {
if (item.type === 'product') {
items.push({
product_id: item.id,
@@ -567,7 +678,9 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
// Формируем FormData для отправки с файлом
const formData = new FormData();
formData.append('kit_name', kitName);
formData.append('showcase_id', showcaseId);
if (showcaseId) {
formData.append('showcase_id', showcaseId);
}
formData.append('description', description);
formData.append('items', JSON.stringify(items));
formData.append('price_adjustment_type', priceAdjustmentType);
@@ -575,17 +688,28 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
if (useSalePrice && salePrice > 0) {
formData.append('sale_price', salePrice);
}
// Фото: для редактирования проверяем, удалено ли оно
if (photoFile) {
formData.append('photo', photoFile);
} else if (isEditMode && document.getElementById('photoPreview').style.display === 'none') {
// Если фото было удалено
formData.append('remove_photo', '1');
}
// Отправляем запрос на сервер
const confirmBtn = document.getElementById('confirmCreateTempKit');
confirmBtn.disabled = true;
confirmBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Создание...';
const url = isEditMode
? `/pos/api/product-kits/${editingKitId}/update/`
: '/pos/api/create-temp-kit/';
const actionText = isEditMode ? 'Сохранение...' : 'Создание...';
confirmBtn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${actionText}`;
try {
const response = await fetch('/pos/api/create-temp-kit/', {
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken')
@@ -598,14 +722,18 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
if (data.success) {
// Успех!
alert(`${data.message}
const successMessage = isEditMode
? `${data.message}\n\nКомплект: ${data.kit_name}\nЦена: ${data.kit_price} руб.`
: `${data.message}
Комплект: ${data.kit_name}
Цена: ${data.kit_price} руб.
Зарезервировано компонентов: ${data.reservations_count}`);
Зарезервировано компонентов: ${data.reservations_count}`;
// Очищаем корзину
clearCart();
alert(successMessage);
// Очищаем tempCart (изолированное состояние модалки)
tempCart.clear();
// Сбрасываем поля формы
document.getElementById('tempKitDescription').value = '';
@@ -618,6 +746,10 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
document.getElementById('salePrice').value = '';
document.getElementById('salePriceBlock').style.display = 'none';
// Сбрасываем режим редактирования
isEditMode = false;
editingKitId = null;
// Закрываем модальное окно
const modal = bootstrap.Modal.getInstance(document.getElementById('createTempKitModal'));
modal.hide();
@@ -632,11 +764,14 @@ document.getElementById('confirmCreateTempKit').onclick = async () => {
alert(`Ошибка: ${data.error}`);
}
} catch (error) {
console.error('Error creating temp kit:', error);
alert('Ошибка при создании комплекта');
console.error('Error saving kit:', error);
alert('Ошибка при сохранении комплекта');
} finally {
confirmBtn.disabled = false;
confirmBtn.innerHTML = '<i class="bi bi-check-circle"></i> Создать и зарезервировать';
const btnText = isEditMode
? '<i class="bi bi-check-circle"></i> Сохранить изменения'
: '<i class="bi bi-check-circle"></i> Создать и зарезервировать';
confirmBtn.innerHTML = btnText;
}
};
@@ -656,6 +791,22 @@ function getCookie(name) {
return cookieValue;
}
// Сброс режима редактирования при закрытии модального окна
document.getElementById('createTempKitModal').addEventListener('hidden.bs.modal', function() {
// Очищаем tempCart (изолированное состояние модалки)
tempCart.clear();
if (isEditMode) {
// Сбрасываем режим редактирования
isEditMode = false;
editingKitId = null;
// Восстанавливаем заголовок и текст кнопки
document.getElementById('createTempKitModalLabel').textContent = 'Создать витринный букет из корзины';
document.getElementById('confirmCreateTempKit').innerHTML = '<i class="bi bi-check-circle"></i> Создать и зарезервировать';
}
});
// Заглушки для функционала (будет реализовано позже)
document.getElementById('checkoutNow').onclick = async () => {
alert('Функционал будет подключен позже: создание заказа и списание со склада.');