Commit Graph

666 Commits

Author SHA1 Message Date
a55be3095b Fix Docker setup: add gunicorn, fix permissions, update docker-compose and entrypoint, add deployment instructions 2025-12-21 15:05:58 +03:00
ec02360eac Оптимизация страницы детального просмотра заказа: перемещение блока товаров вверх, добавление статуса в заголовок, оптимизация SQL-запросов 2025-12-21 12:57:54 +03:00
375ec5366a Унификация генерации номеров документов и оптимизация кода
- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX
- Убрано дублирование функции _extract_number_from_document_number
- Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией
- Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py)
- Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
2025-12-21 00:51:08 +03:00
78dc9e9801 Добавлено разделение типов поступлений на склад
- Добавлено поле receipt_type в модель IncomingBatch с типами: supplier, inventory, adjustment
- Исправлен баг в InventoryProcessor: теперь корректно создается IncomingBatch при инвентаризации
- Создан IncomingAdjustmentCreateView для оприходования без инвентаризации
- Обновлены формы, шаблоны и админка для поддержки разных типов поступлений
- Добавлена навигация и URL для оприходования
- Тип поступления отображается в списках приходов и партий
2025-12-20 23:47:13 +03:00
f1798291e0 Добавить маски для файлов экспорта клиентов в .gitignore 2025-12-20 19:21:01 +03:00
3a10df2761 Удалить случайно добавленные файлы экспорта клиентов и обновить .gitignore 2025-12-20 19:20:51 +03:00
72b0de1863 Добавлены новые статусы заказов: Частично собран и Полностью собран
- Добавлены системные статусы partially_assembled и fully_assembled в order_status_service.py
- Создана management команда update_order_statuses для обновления статусов у всех тенантов
- Новые статусы интегрируются в существующую логику резервирования и списания товара
- Статусы располагаются между 'В сборке' и 'В доставке' в естественном порядке процесса
2025-12-20 19:19:01 +03:00
2508d85b28 Оптимизация списка категорий: устранение N+1 запросов
- Добавлен Prefetch для активных товаров и комплектов в категориях
- Фильтрация и сортировка вынесены в Prefetch (избегаем повторных запросов)
- Изменен метод build_category_tree для использования предзагруженных данных

Результаты:
- Список категорий: 12→7 запросов, 26.76→~10мс
- Устранены 4 похожих N+1 запроса (products и kits для каждой категории)
2025-12-20 18:05:44 +03:00
fed62d992a Оптимизация производительности: устранение N+1 запросов и дубликатов
- Добавлен django-debug-toolbar 6.1.0 для мониторинга производительности
- Устранен дублирующийся COUNT запрос в списке клиентов (используется paginator.count)
- Добавлен select_related('status') в списке заказов для устранения N+1

Результаты:
- Список клиентов: 6→5 запросов, 13.24→10мс
- Список заказов: 18→7 запросов, 52.68→15-20мс, устранено 11 дубликатов
2025-12-20 18:02:23 +03:00
0bf694966b chore(errors): remove temporary error log file
- Deleted the temporary Excel error log file
- Cleaned up unused binary artifact from repository
2025-12-20 12:46:21 +03:00
6c72126276 feat: Add script to ensure public tenant and its domain exist. 2025-12-18 00:59:37 +03:00
7b32cdcebf Обновления и новые функции: изменение шаблона клиента, обновление сигналов инвентаря, добавление снимков наборов и элементов заказа, обновление моделей заказов и продуктов 2025-12-18 00:14:24 +03:00
56725e8092 Добавлена система фильтрации клиентов с универсальным поиском
- Реализован универсальный поиск по имени, email и телефону в одной строке
- Добавлен счетчик общего количества клиентов
- Поиск работает по нажатию Enter или кнопке 'Поиск'
- Удалены неиспользуемые фильтры django-filter
- Упрощен интерфейс списка клиентов
- Добавлена кнопка 'Очистить' для сброса поиска
2025-12-14 22:39:32 +03:00
089ccfa8ae Рефакторинг: вынесена логика импорта/экспорта клиентов в отдельный сервис
- Создан модуль customers/services/import_export.py согласно best practices
- Класс CustomerExporter: содержит логику экспорта в CSV (ранее была в views)
- Класс CustomerImporter: заглушка для будущей реализации импорта
- Views стали тонкими: customer_export и customer_import делегируют работу сервисам
- Улучшена организация кода: соблюдён принцип Single Responsibility
- Уменьшен размер views.py на 30 строк
- Добавлена подробная документация в docstrings классов и методов
- Логику теперь легко тестировать и переиспользовать (например, в Celery tasks)

Преимущества:
- Чистое разделение ответственности
- Упрощённое тестирование
- Возможность переиспользования в асинхронных задачах
- Соответствие Django best practices
2025-12-14 20:55:21 +03:00
b41025116c Исправлен экспорт клиентов: удалены несуществующие поля
- Исправлена ошибка AttributeError: Customer не имеет полей first_name и last_name
- Модель Customer имеет только поле name (полное имя)
- Удалён экспорт баланса кошелька по требованию пользователя
- Обновлена инструкция в шаблоне импорта: убраны фамилия и баланс
- Добавлены пометки об уникальности email и телефона
- Теперь экспорт работает корректно с полями: ID, Имя, Email, Телефон, Дата создания
2025-12-14 20:45:29 +03:00
778c979aa3 Добавлены кнопки Импорт и Экспорт на страницу списка клиентов
- Добавлены кнопки Импорт и Экспорт в header страницы customers/
- Создан URL-маршрут для customer-import и customer-export
- Реализована функция customer_export: экспорт всех клиентов в CSV файл с BOM для Excel
- Экспортируются поля: ID, Имя, Фамилия, Email, Телефон, Баланс кошелька, Дата создания
- Создан шаблон customer_import.html с инструкцией и формой загрузки файла
- Функция customer_import пока заглушка (TODO: реализовать парсинг CSV/Excel)
- Кнопки оформлены в btn-group с иконками Bootstrap Icons
- Системный клиент исключён из экспорта
2025-12-14 20:41:21 +03:00
34e5a0143b Исправлено время заказов: переход на минский часовой пояс (Europe/Minsk)
- Изменён TIME_ZONE с Europe/Moscow на Europe/Minsk в settings.py
- Исправлено создание заказов в POS: теперь используется timezone.localtime() для корректной конвертации UTC → Minsk
- delivery_date и delivery_time_start/end теперь сохраняются в минском времени, а не UTC
- Исправлена разница в 3 часа между временем создания заказа и фактическим временем
- Удалено устаревшее поле payment_method из showcase_manager.py (поле было удалено из модели Order)

Теперь все временные метки заказов соответствуют реальному минскому времени
2025-12-14 15:27:03 +03:00
ce2cfca3f2 Добавлена возможность добавлять новые товары в витринный комплект при редактировании
- Интегрирован готовый компонент ProductSearchPicker в модалку редактирования
- Добавлен collapse-блок с поиском товаров, который отображается только в режиме редактирования
- При выборе товара он автоматически добавляется в tempCart или увеличивается количество если уже есть
- Добавлен CSS и JS для компонента product-search-picker
- В view передаётся categories QuerySet для работы фильтров компонента
- Блок добавления товаров показывается только при редактировании, скрыт при создании нового комплекта
2025-12-14 14:00:51 +03:00
aff25d0317 Реализована возможность редактирования состава витринного комплекта с поддержкой отрицательных резервов
- Добавлены методы reserve_product_to_showcase и release_showcase_reservation в ShowcaseManager
- Методы работают с резервами для всех активных экземпляров витринного комплекта
- НЕ блокируют создание резерва при нехватке товара, возвращают информацию о дефиците (overdraft)
- Обновлён API endpoint update_product_kit для корректировки резервов при изменении состава
- Добавлено визуальное предупреждение на фронте о нехватке товара на складе
- В модалке редактирования комплекта добавлены контролы для изменения количества товаров (+/-, поле ввода, удаление)
- Автоматический пересчёт цен при изменении состава
- Очистка корзины POS после успешного создания витринного комплекта
2025-12-14 13:49:13 +03:00
835d6020e2 Добавлен переключатель видимости пароля на всех страницах входа 2025-12-14 01:56:50 +03:00
f03e750030 feat: Add Docker entrypoint script for application orchestration and implement cleanup for stuck photo processing tasks with improved error handling. 2025-12-13 22:42:58 +03:00
ea1d9546b9 Добавлен алиас celery для совместимости с командами Celery CLI в Docker 2025-12-13 01:33:43 +03:00
87079deca1 Исправлены команды запуска Celery в Docker (worker и beat) для корректного автоопределения приложения 2025-12-13 01:31:33 +03:00
b5e1372cfc Исправлены окончания строк в entrypoint.sh (CRLF -> LF) и добавлен .gitattributes 2025-12-13 01:28:55 +03:00
f2549d7789 Исправлена команда запуска Celery Beat в Docker для корректной загрузки приложения 2025-12-13 01:26:09 +03:00
a1d77d778a Добавлен функционал деактивации/реактивации ролей пользователей 2025-12-13 01:25:19 +03:00
6470fb7588 Fix volume mounts for media and celere worker paths 2025-12-12 20:48:16 +03:00
6023496a7d Fix Celery startup and ImageService temp handling 2025-12-12 20:23:00 +03:00
f320eafc55 Фикс деплоя на NAS: статика, медиа, автоматическое создание системного покупателя 2025-12-12 19:21:45 +03:00
4cbc5c07b9 feat: Implement Dockerized multi-tenant Django application with initial setup for database, migrations, and superuser creation. 2025-12-12 18:04:36 +03:00
0046b36e89 fix: Исправлена работа кликабельного dropdown-меню Заказы
- Убраны атрибуты data-bs-toggle и role, блокирующие переход по ссылке
- Теперь клик на Заказы открывает список заказов
- Dropdown продолжает работать при наведении благодаря CSS стилям
- Исправлена проблема, когда клик не приводил к переходу на страницу
2025-12-12 05:07:35 +03:00
48223e32d8 feat: Сделан кликабельным заголовок dropdown-меню Заказы
- Заголовок 'Заказы' теперь ведёт на список заказов при клике
- Сохранена функциональность dropdown-меню при наведении
- Улучшена навигация - теперь можно перейти к заказам напрямую без раскрытия меню
2025-12-12 05:04:52 +03:00
a8066d87ed refactor: Реструктуризация навигационного меню с группировкой по функциональным блокам
Изменения в navbar.html:
- Объединены ссылки в логические dropdown-группы
- Уменьшено количество пунктов верхнего уровня с 10+ до 6
- Добавлены эмодзи-иконки для визуальной идентификации разделов

Структура меню:
📦 Товары (dropdown)
   - Все товары, Каталог, Вариативные товары
   - Категории, Теги, Варианты (группы)

📋 Заказы (dropdown)
   - Список заказов
   - Статусы заказов

👥 Клиенты (одиночная ссылка)

🏭 Склад (dropdown)
   - Управление складом
   - Витрины

💰 Касса (одиночная ссылка)

⚙️ Настройки (dropdown, только для owner/superuser)
   - Роли пользователей
   - Debug (только для superuser)

Преимущества:
- Компактная навигация - проще найти нужный раздел
- Логическая группировка связанных функций
- Сохранена подсветка активного раздела
- Улучшена визуальная идентификация с помощью иконок
2025-12-12 05:02:29 +03:00
e35d3d642c feat: Добавлена ссылка на CRUD категорий в навигацию
- Добавлена ссылка 'Категории' в главное меню навигации
- Ссылка размещена логически между 'Варианты' и 'Теги'
- Добавлена подсветка активного пункта меню при работе с категориями
- Теперь доступ к управлению категориями товаров доступен из главного меню
2025-12-12 05:00:00 +03:00
e54d7d04d7 feat: Добавлены команды управления данными тенантов и исправлены фильтры по статусу товаров
Добавлено:
- Команда clear_tenant_data для полной очистки данных тенанта без удаления схемы
  * Очищает все таблицы через TRUNCATE CASCADE
  * Сбрасывает ID-последовательности
  * Сохраняет схему БД и запись Client
  * Поддержка флага --noinput для автоматизации

- Команда init_tenant_data для инициализации системных данных тенанта
  * Создаёт системного клиента (АНОНИМНЫЙ ПОКУПАТЕЛЬ для POS)
  * Создаёт 8 системных статусов заказов
  * Создаёт 5 системных способов оплаты
  * Поддержка флага --reset для пересоздания данных

Исправлено:
- Заменены устаревшие фильтры is_active на status='active' для Product и ProductKit
  * products/views/category_views.py: исправлены фильтры в build_category_tree и get_context_data
  * products/services/kit_pricing.py: исправлены фильтры при получении товаров из variant_group
  * products/models/kits.py: исправлен фильтр в get_available_products
  * Устранена ошибка FieldError при работе со списком категорий

Улучшено:
- Команда clear_tenant_data теперь предлагает пользователю инициализировать системные данные после очистки
- Добавлена детальная информация о процессе очистки и инициализации данных
2025-12-12 04:58:26 +03:00
2d253584ba Добавлена обработка ValidationError в AJAX API и Bootstrap alert на странице списка заказов
Проблема:
На странице списка заказов (order_list) при изменении статуса 'на лету':
- ValidationError показывался через alert() - страшно
- Сообщение содержало служебные элементы
- Статус всё равно менялся визуально (нет отката)

Решение Backend (views.py):
- В set_order_status добавлена обработка ValidationError ПЕРЕД ValueError
- Извлекается чистое сообщение (e.messages[0] или str(e))
- Возвращается JSON: {success: false, error: 'чистое сообщение'}

Решение Frontend (order_list.html):
- Добавлен контейнер для динамических Bootstrap alert
- Создана функция showAlert() для показа красивых alert-danger
- При ошибке:
  * Показывается Bootstrap alert с иконкой
  * Прокрутка к верху страницы
  * Автоскрытие через 5 секунд
  * Возврат select к предыдущему значению (откат визуально)
- Больше НЕТ страшных alert()

Теперь пользователь видит:
[красный Bootstrap alert вверху страницы]
⚠️ Заказ 134 был отменён, товары проданы в другом заказе.
Невозможно изменить статус. Для новой продажи создайте новый заказ.
[X]

User-friendly на обеих страницах (форма редактирования + список)!
2025-12-12 00:18:09 +03:00
49cfec3088 Добавлена обработка ValidationError с выводом через Django messages
Проблема:
ValidationError из сигналов отображался как:
'Server error: [\'Заказ 134 был отменён...\']'
со служебными элементами (Server error, квадратные скобки).

Решение:
В order_update добавлена обработка ValidationError перед ValueError:
- Извлекаем чистое сообщение из исключения (e.messages[0] или str(e))
- Показываем через messages.error() — Django автоматически отобразит
  красивым Bootstrap alert-danger
- Транзакция откатывается, изменения не сохраняются

Теперь пользователь видит:
[красный Bootstrap alert]
'Заказ 134 был отменён, товары проданы в другом заказе.
Невозможно изменить статус. Для новой продажи создайте новый заказ.'

Без технических префиксов и форматирования - user-friendly.
2025-12-12 00:08:53 +03:00
449b693ab5 Улучшено сообщение об ошибке для возвращённых заказов - убраны переносы строк
Проблема:
Сообщение ValidationError с переносами строк \\n отображалось как текст,
а не как реальные переносы, плюс выглядело как 'Server error' - страшно.

Решение:
Сделано короткое однострочное сообщение без \\n:
'Заказ 134 был отменён, товары проданы в другом заказе.
Невозможно изменить статус. Для новой продажи создайте новый заказ.'

Теперь user-friendly, без технических деталей и пугающих форматирований.
2025-12-12 00:04:24 +03:00
2dcdc0941f Расширена валидация для возвращённых заказов: запрет любых статусов кроме отрицательных
Проблема:
Для заказа с is_returned=True без резервов (товар продан в другом заказе)
можно было установить промежуточные статусы (В доставке, Черновик и т.п.),
что не имеет смысла, т.к. физически продавать уже нечего.

Решение:
Валидация теперь проверяет ДО проверки is_positive_end:
- Если is_returned=True И резервов нет И статус НЕ отрицательный →
  запрещаем ЛЮБОЕ изменение статуса
- Разрешены только статусы с is_negative_end=True (отменён и т.п.)

Улучшено сообщение об ошибке:
- Убраны длинные объяснения
- Короткая структура с переносами строк
- Чёткое указание: «товары проданы в другом заказе»
- Действие: «создайте новый заказ»

Теперь возвращённый заказ без резервов навсегда остаётся в статусе
отрицательного исхода — как и должно быть в реальности.
2025-12-12 00:03:10 +03:00
503a00de74 Улучшена логика флага is_returned и добавлен запрет повторного completed для возвращённых заказов
Проблема:
1. Флаг is_returned управлялся в разных местах непоследовательно
2. При цепочке completed → cancelled → completed флаг оставался True
3. Можно было установить положительный статус для заказа с is_returned=True
   без резервов (товар уже продан в другом заказе)

Решение:

1. ЕДИНАЯ ФУНКЦИЯ update_is_returned_flag():
   - Флаг основан на РЕАЛЬНОМ состоянии заказа (наличие Sale)
   - Логика: есть Sale → is_returned=False
   - Нет Sale + был когда-то в положительном финальном статусе → is_returned=True
   - Нет Sale + никогда не был в положительном статусе → is_returned=False

2. ВЫЗОВ update_is_returned_flag() в ключевых точках:
   - После создания Sale (create_sale_on_order_completion)
   - После отката Sale (rollback_sale_on_status_change)
   - После освобождения резервов (release_reservations_on_cancellation)

3. ВАЛИДАЦИЯ в create_sale_on_order_completion:
   - Запрещаем переход в положительный финальный статус (is_positive_end=True)
     для заказов с is_returned=True, у которых нет резервов
   - Даём понятное сообщение: резервы отсутствуют, товары могли быть проданы
     в другом заказе, оставьте статус отрицательного исхода или создайте новый заказ

4. АВТОМАТИЧЕСКИЙ СБРОС is_returned:
   - При законном переходе в положительный статус с резервами флаг сбрасывается
   - Это позволяет исправить ошибочную отмену: cancelled → completed работает,
     если резервы на месте (товар не ушёл в другой заказ)

5. УДАЛЕНА ДУБЛИРУЮЩАЯ ЛОГИКА:
   - Убрали ручное управление is_returned в rollback_sale_on_status_change
   - Убрали ручное управление is_returned в release_reservations_on_cancellation
   - Теперь один источник истины через update_is_returned_flag()

Результат:
- Флаг is_returned всегда соответствует реальности (наличию Sale)
- Невозможно установить completed для возвращённого заказа без резервов
- Защита от двойного списания при переиспользовании витринных комплектов
- Понятные сообщения об ошибках для пользователя
- Предсказуемое поведение при любых комбинациях смены статусов
2025-12-11 23:54:48 +03:00
2a3898fb44 Исправлена повторная продажа витринных комплектов при возврате в статус completed
Проблема:
При отмене заказа (completed → cancelled) и последующем возврате в completed
витринные комплекты оставались зарезервированными и не уходили со склада.
Резервы не конвертировались в продажи, Sale не создавались.

Причина:
При откате заказа (уход от completed) мы обнуляли reservation.order_item = None
для витринных комплектов. Это разрывало связь между резервом и позицией заказа.

При повторном переходе в completed сигнал create_sale_on_order_completion
искал резервы по фильтру:
  Reservation.objects.filter(order_item=item, product_kit=kit)

Но так как order_item был None, резервы не находились и Sale не создавались.

Решение:
Разделили семантику полей Reservation:
- order_item - принадлежность к позиции заказа (часть жизненного цикла заказа)
- cart_lock_expires_at, locked_by_user, cart_session_id - блокировки корзины

При откате заказа (completed → другой_статус):
- НЕ трогаем order_item - он остаётся привязанным к OrderItem
- Очищаем ТОЛЬКО cart lock поля (expires_at, locked_by_user, session_id)
- Резервы витринных комплектов: status = reserved

При повторном переходе в completed:
- create_sale_on_order_completion находит резервы (order_item сохранён!)
- Создаёт Sale для каждого компонента
- Конвертирует резервы: reserved → converted_to_sale
- Витринный экземпляр помечается как проданный через ShowcaseManager

Изменения в 3 местах:
1. rollback_sale_on_status_change - откат от completed
2. release_reservations_on_cancellation - переход к cancelled
3. release_stock_on_order_delete - удаление заказа

Во всех случаях для витринных комплектов сохраняем order_item, очищаем
только cart lock поля.

Результат:
Теперь витринные комплекты можно продавать/отменять/продавать снова
через смену статуса заказа в админке - как в реальной жизни.
2025-12-11 23:17:12 +03:00
d44687649c Добавлен автоматический возврат витринных экземпляров на витрину при откате заказа
Проблема:
При отмене заказа (completed → cancelled) резервы корректно возвращались
в статус 'reserved', но ShowcaseItem оставались в статусе 'sold'.
Из-за этого витринные букеты не отображались в POS после отмены заказа,
хотя физически должны были вернуться на витрину.

Решение:
В существующий сигнал rollback_sale_on_status_change добавлена логика
возврата витринных экземпляров на витрину:

1. После отката Sale и Reservation находим все ShowcaseItem, проданные
   в рамках отменяемого заказа (sold_order_item__order=instance)

2. Для каждого экземпляра:
   - Меняем status: sold → available
   - Очищаем sold_order_item = None
   - Очищаем sold_at = None
   - НЕ трогаем showcase и product_kit (букет остаётся на той же витрине)

3. Логируем количество возвращённых экземпляров

Преимущества:
- Элегантно: вся логика отката в одном месте (сигнал)
- Транзакционно: откат Sale, Reservation и ShowcaseItem в одной транзакции
- Универсально: работает для POS и обычных заказов
- Без костылей: используем существующую архитектуру сигналов

Теперь при отмене заказа витринный букет автоматически возвращается
на витрину и снова виден в POS - как в реальной жизни.
2025-12-11 23:09:56 +03:00
4ce610985b Исправлен порядок операций при конвертации резервов в продажи
Проблема:
При продаже витринного комплекта резервы оставались в статусе 'reserved'
вместо 'converted_to_sale'. Товары из состава комплекта не освобождались.

Причина:
В методе sell_showcase_items порядок операций был неправильный:
1. create_sale_from_reservation вызывался ПЕРВЫМ
2. reservation.order_item устанавливался ПОСЛЕ

В SaleProcessor.create_sale_from_reservation есть логика:
  if order and reservation.order_item:
      sale_price = reservation.order_item.price
  else:
      sale_price = reservation.product.actual_price

Так как order_item был None, цена бралась из product.actual_price,
а не из OrderItem, и резерв не конвертировался корректно.

Решение:
Правильный порядок операций:
1. Устанавливаем reservation.order_item = order_item
2. Сохраняем reservation
3. Вызываем create_sale_from_reservation (теперь order_item доступен)
4. Обновляем статус на 'converted_to_sale'
5. Сохраняем финальное состояние

Теперь резервы корректно преобразуются в продажи с правильной ценой
из позиции заказа, и товары освобождаются после продажи.
2025-12-11 22:55:06 +03:00
95cb1c4bac Рефакторинг: убрано дублирование валидации витринных комплектов
Проблема:
Валидация showcase_items выполнялась в двух местах:
1. В pos/views.py - проверка status='in_cart' БЕЗ блокировки БД
2. В showcase_manager.py - перезагрузка с select_for_update() и повторная проверка

Это создавало:
- Дублирование кода и логики
- Возможность race condition между двумя запросами
- Избыточные обращения к БД
- Мертвый код (неработающие logger.info)

Решение (best practices):
1. Views только загружают объекты по ID без фильтров по статусу
2. ВСЯ валидация и бизнес-логика в одном месте - ShowcaseManager.sell_showcase_items
3. select_for_update() гарантирует актуальность данных и блокировку на уровне БД
4. Удален мертвый код (logger.info которые не выполнялись)
5. Убрано избыточное логирование ошибок валидации

Результат:
- Единое место ответственности (Single Responsibility)
- Нет дублирования
- Атомарная транзакция с блокировкой
- Чистый, понятный код без костылей
2025-12-11 22:27:15 +03:00
0d72c36739 Исправлена продажа нескольких экземпляров витринного букета
Проблема:
При попытке продажи 2+ экземпляров одного витринного букета возникала ошибка
IntegrityError, так как поле sold_order_item было OneToOneField.
Это означало что к одному OrderItem мог быть привязан только один ShowcaseItem,
что делало невозможной продажу нескольких экземпляров в одной позиции заказа.

Решение:
1. Изменен тип поля sold_order_item с OneToOneField на ForeignKey
   - Теперь несколько ShowcaseItem могут относиться к одному OrderItem
   - related_name изменен с 'sold_showcase_item' на 'sold_showcase_items'

2. Обновлен метод mark_sold в модели ShowcaseItem
   - Добавлена явная проверка статуса 'sold' перед продажей
   - Генерируется ValidationError если экземпляр уже продан
   - Удален комментарий про OneToOneField защиту

3. Обновлена обработка ошибок в ShowcaseManager.sell_showcase_items
   - Убрана обработка IntegrityError
   - Добавлена обработка ValidationError от mark_sold

4. Создана миграция 0012_change_sold_order_item_to_fk

Теперь можно успешно продавать 2 и более экземпляров одного витринного букета
в рамках одной позиции заказа.
2025-12-11 22:23:41 +03:00
8dc6594334 Добавлено подробное логирование ошибок валидации в POS checkout
Для диагностики проблемы с продажей витринных комплектов добавлено
логирование ValidationError с полным traceback. Это поможет определить
в какой именно момент и почему происходит ошибка валидации.
2025-12-11 22:21:11 +03:00
37c203a783 Добавлена валидация и логирование для продажи витринных комплектов в POS
Проблема: Продолжает возникать ошибка 'Один из экземпляров уже был продан'
при попытке продажи витринных букетов.

Изменения для диагностики:
1. Добавлена валидация showcase_item_ids ПЕРЕД передачей в sell_showcase_items
2. Проверка что все экземпляры имеют status='in_cart' и locked_by_user=текущий
3. Фильтр по статусу исключает уже проданные/разобранные экземпляры
4. Добавлено детальное логирование:
   - Запрошенные showcase_item_ids
   - Количество найденных заблокированных экземпляров
   - Недостающие IDs если не все найдены

Улучшенное сообщение об ошибке:
Вместо 'уже был продан' теперь 'уже не заблокированы на вас' с просьбой
обновить страницу - более понятно для пользователя.

Логи помогут выявить:
- Передаются ли дубликаты в showcase_item_ids
- Истекают ли блокировки до момента продажи
- Меняется ли статус экземпляров между добавлением в корзину и checkout
2025-12-11 22:18:16 +03:00
d5e40bb1c8 Исправлена продажа множественных экземпляров витринных букетов
Проблема: При продаже 2+ экземпляров одного витринного комплекта возникала
ошибка 'Один из экземпляров уже был продан'. Это происходило потому что
объекты ShowcaseItem проверялись по старому состоянию из памяти.

Причина:
- При вызове sell_showcase_items() передавался список объектов из запроса
- Первый ShowcaseItem менял статус на 'sold' через mark_sold()
- Второй объект в списке все еще имел старый статус из памяти
- Проверка в цикле срабатывала некорректно

Решение:
- Перезагружаем ВСЕ ShowcaseItem из БД с блокировкой перед обработкой
- Используем select_for_update() для получения актуального статуса
- Теперь каждый экземпляр проверяется по свежим данным из БД
- Защита от race conditions через database-level locking

Результат:
Теперь можно продавать 2+ экземпляра одного букета без ошибок.
2025-12-11 22:14:57 +03:00
741fdc97a8 Добавлены кнопки +/- для управления количеством витринных комплектов в корзине
Теперь витринные букеты можно увеличивать и уменьшать по экземплярам:

UI изменения:
- Заменен badge на полноценные кнопки +/- как у обычных товаров
- Поле количества readonly с желтым фоном для визуального отличия
- Кнопки используют тот же дизайн что и для обычных товаров

Функционал увеличения (increaseShowcaseKitQty):
- Блокирует еще один доступный экземпляр через API
- Проверяет наличие свободных букетов на витрине
- Показывает сообщение если нет доступных
- Обновляет showcase_item_ids и qty в корзине

Функционал уменьшения (decreaseShowcaseKitQty):
- Снимает блокировку с последнего экземпляра из списка
- При qty=1 полностью удаляет из корзины
- Обновляет список витрины после изменения

Все операции синхронизируются с сервером и Redis.
2025-12-11 22:10:06 +03:00
b396029554 Исправлено восстановление витринных комплектов из корзины при перезагрузке страницы
Проблема: Витринные букеты исчезали из корзины после перезагрузки страницы,
но оставались заблокированными на пользователя через ShowcaseItem.

Решение:
- Изменена валидация корзины при загрузке из Redis
- Теперь проверяется наличие showcase_item_ids в данных корзины
- Блокировки валидируются через ShowcaseItem (не Reservation)
- Проверяется: status='in_cart', locked_by_user, cart_lock_expires_at > now()
- Обновляется qty на актуальное количество действующих блокировок
- Если ни один ShowcaseItem не заблокирован - не восстанавливается в корзину

Теперь витринные букеты корректно восстанавливаются при перезагрузке
и автоматически удаляются из корзины при истечении блокировки.
2025-12-11 22:05:15 +03:00