- Добавлен пункт 'Документы списания' в выпадающее меню 'Операции' в base_inventory_minimal.html
- Добавлена карточка 'Документы списания' на главную страницу склада (home.html)
- Новая функциональность теперь доступна из двух мест для удобства пользователей
- Старый функционал одиночных списаний остается доступным
- Зарегистрированы модели WriteOffDocument и WriteOffDocumentItem в админке
- Настроен inline для позиций документа в админке
- Добавлены цветовые индикаторы статусов документа
- Настроены фильтры, поиск и сортировка для удобной работы
- Добавлен сигнал release_reservation_on_writeoff_item_delete
- Автоматическое освобождение резервов при удалении позиций через админку
- Защита от утечки резервов при прямом удалении через ORM
- В WriteOffCreateView добавлена передача категорий и тегов в контекст
- Шаблон writeoff_form.html обновлен с использованием product_search_picker
- Автоматическая фильтрация партий по выбранному товару
- Отображение информации о выбранном товаре с фото
- Улучшенный UX при выборе товара для списания
- Подключены CSS и JS компонента поиска товаров
- list.html - список документов с фильтрацией по статусу и складу
- form.html - форма создания документа
- detail.html - детальный просмотр документа с возможностью добавления/редактирования позиций
- Интерактивное управление позициями через AJAX (добавление, редактирование, удаление)
- Отображение статистики документа (количество позиций, общее количество, себестоимость)
- Кнопки проведения и отмены документа с подтверждением
- Адаптивный дизайн с использованием Bootstrap 5
- WriteOffDocumentListView - список документов с пагинацией
- WriteOffDocumentCreateView - создание нового документа
- WriteOffDocumentDetailView - детальный просмотр документа
- WriteOffDocumentAddItemView - добавление позиции (AJAX)
- WriteOffDocumentUpdateItemView - обновление позиции (AJAX)
- WriteOffDocumentRemoveItemView - удаление позиции (AJAX)
- WriteOffDocumentConfirmView - проведение документа
- WriteOffDocumentCancelView - отмена документа
- Добавлены URL-маршруты для всех операций с документами списания
- Поддержка AJAX запросов для динамической работы с позициями
- WriteOffDocumentForm - создание/редактирование документа списания
- WriteOffDocumentItemForm - добавление/редактирование позиций документа
- Автоматическая установка текущей даты и склада по умолчанию
- Фильтрация товаров по наличию на выбранном складе
- Валидация количества с проверкой доступных остатков
- Учет текущего резерва при редактировании позиций
- Создан WriteOffDocumentService с методами работы с документами списания
- create_document() - создание документа с автогенерацией номера (WO-XXXXXX)
- add_item() - добавление позиции с автоматическим созданием резерва
- update_item() - обновление позиции с пересчетом резерва
- remove_item() - удаление позиции с освобождением резерва
- confirm_document() - проведение документа (создание WriteOff записей по FIFO)
- cancel_document() - отмена с освобождением всех резервов
- Добавлена валидация доступного количества товара при создании/обновлении позиций
- Добавлена функция generate_writeoff_document_number() для генерации номеров документов
- Создана модель WriteOffDocument для коллективного списания с поддержкой статусов (черновик/проведен/отменен)
- Добавлена модель WriteOffDocumentItem для позиций документа
- Расширена модель Reservation связью с WriteOffDocumentItem для резервирования товара в черновике
- Добавлен тип счетчика 'writeoff' в DocumentCounter для автонумерации
- Реализована логика резервирования товара в черновике документа (уменьшает quantity_free)
- При проведении документа создаются WriteOff записи по методу FIFO
Новая архитектура:
- ShowcaseItem модель - физический экземпляр букета на витрине
- OneToOneField(sold_order_item) - БД-уровневая защита от двойной продажи
- Поддержка создания нескольких экземпляров одного букета
- Возможность продавать N из M доступных (например 2 из 5)
Изменения:
- inventory/models.py: добавлена модель ShowcaseItem с методами lock/unlock/mark_sold
- inventory/services/showcase_manager.py: переработан для работы с ShowcaseItem
- pos/views.py: API поддерживает quantity и showcase_item_ids
- pos/templates/pos/terminal.html: поле "Сколько букетов создать"
- pos/static/pos/js/terminal.js: выбор количества, передача showcase_item_ids
Миграции:
- 0007: создание модели ShowcaseItem
- 0008: data migration существующих букетов
- 0009: очистка ShowcaseItem для уже проданных букетов
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Проблема: При переводе заказа в статус 'completed' возникала ошибка
"Не удалось создать Sale для заказа", т.к. резервы этого же заказа
блокировали списание товара.
Причина: write_off_by_fifo() считал все резервы со статусом 'reserved'
как занятые, включая резервы текущего заказа, которые ещё не были
переведены в 'converted_to_sale'.
Решение:
- Добавлен параметр exclude_order в write_off_by_fifo() для исключения
резервов конкретного заказа из расчёта занятого товара
- SaleProcessor.create_sale() теперь передаёт order в write_off_by_fifo()
- Добавлены транзакции в views для атомарности операций с заказами:
при ошибке в сигналах статус заказа откатывается вместе со всеми
связанными изменениями
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Проблема: при отмене заказа с витринным временным комплектом резервы освобождались
(status='released'), и букет исчезал с витрины. Это неправильное поведение для временных
комплектов на витрине - они должны оставаться доступными для продажи.
Решение:
- В сигнале rollback_sale_on_status_change добавлено разделение резервов на:
* Обычные резервы - работают как раньше (released при отмене, reserved при возврате)
* Витринные временные комплекты (is_temporary=True, showcase!=null) - ВСЕГДА возвращаются
в статус reserved, независимо от типа отката заказа
- Для витринных комплектов сохраняются привязки к showcase и product_kit
- Букеты остаются видимыми на витрине и доступны для повторной продажи
Бизнес-логика:
- При ВОЗВРАТЕ (completed → draft/in_delivery): букет возвращается на витрину
- При ОТМЕНЕ (completed → cancelled): букет ТАКЖЕ возвращается на витрину
- Букет можно убрать только вручную через функцию разбора комплекта
- Добавлена специальная обработка витринных комплектов в сигнале update_reservation_on_item_change:
* При создании OrderItem с витринным комплектом привязываются существующие витринные резервы компонентов
* Не создаются новые резервы на уровне комплекта
- Исправлена логика создания Sale для комплектов в сигнале create_sale_on_order_completion:
* Для комплектов (витринных и обычных) создаются Sale для каждого компонента через резервы
* Используется FIFO-списание для компонентов
* Предотвращена ошибка передачи ProductKit в поле Reservation.product
Fixes: Cannot assign ProductKit to Reservation.product field
Fixes: Не удалось создать Sale для заказа с витринным комплектом
- Исправлен метод write_off_by_fifo() для учета зарезервированных партий
- Добавлено автоматическое проставление даты и времени при создании заказов в POS
- Исправлена ошибка фильтрации Product (is_active -> status='active') в transfers
- Предотвращает списание из зарезервированных партий, устраняя отрицательные остатки
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Удалены из git:
- Скрипты активации и диагностики тенантов
- Тестовые файлы (test_*.py, test_*.txt)
- SQL скрипты для отладки
- Backup файлы (*.backup, *.old)
- Дубликат .gitignore из myproject/
Файлы остались на диске, но теперь игнорируются git.
В репозитории остались только:
- myproject/ (основной код проекта)
- requirements.txt
- .gitignore
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Удалены все файлы .md (30 файлов)
- Добавлена маска *.md в .gitignore для защиты от будущих коммитов
- Причина: .md файлы содержали примеры паролей и внутреннюю документацию
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added all real order statuses: new, confirmed, in_assembly, in_delivery, return
- Test #2: expanded to test cancellation from 4 different statuses (new, confirmed, in_assembly, in_delivery)
- Test #3: updated to use new → cancelled → in_assembly flow
- Test #4: updated to use in_delivery status instead of packing
- All 5 tests passing successfully
Проблема:
- Поле OrderStatus.color имеет max_length=7 (для hex типа #FF5733)
- В тестах использовались названия: 'secondary', 'warning', 'success' (9 символов)
- PostgreSQL выдавал ошибку: value too long for type character varying(7)
Решение:
- Заменены названия цветов на hex коды:
* 'secondary' → '#9E9E9E' (серый)
* 'warning' → '#FF9800' (оранжевый)
* 'success' → '#4CAF50' (зелёный)
* 'danger' → '#F44336' (красный)
* 'info' → '#17A2B8' (голубой)
Tests cover:
1. Multiple status transitions (draft->completed->cancelled->completed)
2. Cancellation from draft (reservation release)
3. Un-cancellation to pending (reservation restore)
4. Creating order with intermediate status
5. Rollback from completed to draft
Each test verifies:
- Stock state (available, reserved, free)
- Reservation status transitions
- Sale creation/deletion without duplicates
- StockBatch quantity changes
Files:
- inventory/tests/test_order_status_transitions.py (570 lines)
- inventory/tests/README.md (138 lines)
- inventory/tests/__init__.py
- run_status_tests.bat (launch script)
Проблема:
- Поиск товаров возвращал пустые результаты
- API /products/api/search-products-variants/ возвращает ID в формате 'product_123'
- Форма incoming ожидает числовой ID (123)
- Select2 не мог сохранить значение из-за несовпадения формата
Решение:
- Добавлена функция processResults в AJAX настройках Select2
- Извлекаем числовой ID из строки 'product_123' -> '123'
- Обрабатываем как группированные результаты, так и плоские
- Сохраняем остальные поля (text, sku, price, actual_price)
Логика обработки:
1. Проверяем наличие children (группа)
2. Если группа - обрабатываем каждый item в children
3. Если плоский список - обрабатываем напрямую
4. Используем .replace('product_', '') для извлечения ID
Теперь Select2 корректно:
- Показывает список товаров
- Сохраняет выбранное значение
- Отправляет числовой ID в форму
Проблема:
- На странице /inventory/incoming/create/ не работал поиск товаров
- Использовался обычный <select> с предзагруженным списком всех товаров
- При большом количестве товаров список был неудобным
- Невозможно было искать товары по названию в реальном времени
Решение:
- Заменён обычный <select> на Select2 с AJAX автокомплитом
- Подключен API endpoint /products/api/search-products-variants/
- Поиск товаров работает в реальном времени (с задержкой 250ms)
- Минимальная длина поиска: 0 символов (можно открыть весь список)
- Поддержка русского языка
- Theme: bootstrap-5 для визуального соответствия
Изменения:
- Удалён предзагруженный список товаров из контекста шаблона
- Добавлена инициализация Select2 для каждой новой строки товара
- При удалении строки вызывается select2('destroy') для очистки
- Исправлена логика восстановления товаров при ошибке валидации
- Используется Option API для программной установки значений
Технические детали:
- jQuery и Select2 уже подключены в base.html
- Не дублируем подключение библиотек
- Используем событие 'change' для Select2 вместо 'input'
- Кэширование AJAX запросов включено
Теперь поиск товаров работает корректно! 🎉
Проблема:
- При изменении количества в OrderItem для заказа в статусе 'completed'
- Создавался ДУБЛИКАТ резерва (старый converted_to_sale + новый reserved)
- Это приводило к двойному списанию товара со склада (-10 лишних единиц)
- Фильтр status='reserved' пропускал существующие резервы в других статусах
Сценарий бага:
1. Заказ выполнен: 20 шт → резерв 20 шт (converted_to_sale)
2. Увеличить на 10 шт (до 30) → создаётся НОВЫЙ резерв 30 шт (reserved)
3. Итого: 20 + 30 = 50 шт зарезервировано вместо 30!
4. При переводе обратно в 'completed' → двойное списание (50 вместо 30)
Решение:
- Убран фильтр status='reserved' из update_reservation_on_item_change
- Теперь резерв ищется по order_item независимо от статуса
- Обновляется ТОЛЬКО quantity, статус НЕ меняется
- Добавлен @transaction.atomic для атомарности операции
- Добавлено логирование всех операций с резервами
- Используется save(update_fields=['quantity']) для оптимизации
Безопасность решения:
- Резервы разных заказов НЕ конфликтуют (разные order_item)
- Один товар в разных заказах = разные OrderItem = разные Reservation
- Каждый OrderItem имеет уникальный резерв
- Дубликаты больше НЕ создаются
Изменённые файлы:
- inventory/signals.py (функция update_reservation_on_item_change)
- FIX_RESERVATION_DUPLICATE_BUG.md (полная документация бага и решения)
Покрытие всех сценариев:
✅ Создание заказа с товарами
✅ Добавление товара при редактировании
✅ Изменение количества (черновик)
✅ Изменение количества (выполнен) - ИСПРАВЛЕНО
✅ Повторное сохранение заказа
КРИТИЧНО: Это исправление влияет на учёт товара и требует тестирования!
Проблема:
- Фильтр по заказу не работал
- order_number в форме передается как строка (ORD-103 или 103)
- В модели Order поле order_number имеет тип PositiveIntegerField
- Не было преобразования строки в число
Исправление:
- Добавлен парсинг номера заказа:
* Если формат 'ORD-103' -> извлекается число 103
* Если просто '103' -> преобразуется в число 103
- Добавлена обработка ошибок (try-except)
- Добавлена фильтрация таблицы заказов (показывать только выбранный)
Теперь фильтр работает с любым форматом ввода:
- ORD-103 ✓
- ord-103 ✓
- 103 ✓
Проблема:
- В шаблоне использовалось несуществующее поле total_price
- Фактическое поле в модели Order называется total_amount
Исправление:
- Заменено order.total_price на order.total_amount
- Теперь итоговая сумма заказа отображается корректно
Проблема:
- В шаблоне использовалось несуществующее поле cost_per_unit
- Фактическое поле в модели StockBatch называется cost_price
Исправление:
- Заменено batch.cost_per_unit на batch.cost_price
- Теперь закупочная цена партии отображается корректно
Проблема:
- В шаблоне использовалось несуществующее поле cost_per_unit
- Фактическое поле в модели называется cost_price
Исправление:
- Заменено alloc.cost_per_unit на alloc.cost_price
- Теперь себестоимость за единицу отображается корректно
Реализация:
- Создан view debug_inventory_page (только для суперюзеров)
- URL: /inventory/debug/
- Компактный дизайн с минимальными отступами (10-11px)
- Ссылка 🔧 Debug в navbar (видна только суперюзерам)
Функционал:
1. Показывает полную картину инвентаризации на одной странице:
- Заказы (Order) - номер, статус, покупатель, is_returned
- Остатки (Stock) - доступно, зарезервировано, свободно
- Партии (StockBatch) - количество, активность, дата поступления
- Резервы (Reservation) - статус, заказ, все даты
- Продажи (Sale) - количество, цена продажи, заказ
- Списания (SaleBatchAllocation) - откуда списано, сколько
2. Фильтры:
- По товару (dropdown)
- По номеру заказа (текстовое поле)
- По складу (dropdown)
3. UI:
- Цветовая индикация статусов резервов
- Бейджи для ключевых данных
- Компактные таблицы Bootstrap
- Неактивные партии выделены красным
Исправления:
- Reservation.created_at → reserved_at (у модели нет created_at)
- Sale.created_at → date (дата операции хранится в поле date)
- Product.is_active → archived_at__isnull=True (используется soft delete)
- Удалена колонка себестоимости из Sale (это поле не хранится в модели)
Файлы:
- inventory/views/debug_views.py - новый view
- inventory/templates/inventory/debug_page.html - шаблон
- inventory/urls.py - добавлен роут
- templates/navbar.html - добавлена ссылка
Юзкейс:
Суперюзер принимает товар → оформляет заказ → меняет статусы →
переходит на /inventory/debug/ → видит полную картину изменений
Проблема:
- У модели Reservation нет поля created_at
- Есть поле reserved_at для даты создания резерва
Исправление:
- В view изменена сортировка order_by('-reserved_at')
- В шаблоне изменено отображение даты res.reserved_at
Реализация:
- Создан view debug_inventory_page (только для суперюзеров)
- URL: /inventory/debug/
- Компактный дизайн с минимальными отступами и маленьким шрифтом (10-11px)
Функционал:
1. Показывает полную картину инвентаризации на одной странице:
- Заказы (Order) - номер, статус, покупатель, is_returned
- Остатки (Stock) - доступно, зарезервировано, свободно
- Партии (StockBatch) - количество, активность, дата поступления
- Резервы (Reservation) - статус (reserved/converted_to_sale/released), заказ, даты
- Продажи (Sale) - количество, цены, заказ
- Списания (SaleBatchAllocation) - откуда списано, сколько
2. Фильтры:
- По товару (dropdown с названием и SKU)
- По номеру заказа (текстовое поле)
- По складу (dropdown)
- Кнопка 'Применить' и 'Сбросить'
3. UI:
- Цветовая индикация статусов резервов
- Бейджи для ключевых данных
- Компактные таблицы Bootstrap
- Неактивные партии выделены красным
- Ограничение в 100 записей на таблицу для производительности
4. Навигация:
- Ссылка 🔧 Debug в navbar (видна только суперюзерам)
- Красный цвет для видимости
Юзкейс:
Суперюзер принимает товар на склад → оформляет заказ → меняет статусы →
переходит на /inventory/debug/ → видит полную картину всех изменений
Файлы:
- inventory/views/debug_views.py - новый view
- inventory/templates/inventory/debug_page.html - шаблон
- inventory/urls.py - добавлен роут
- templates/navbar.html - добавлена ссылка для суперюзеров
Проблема:
- При смене статуса заказа ОТМЕНЁН → ВЫПОЛНЕН
- Sale создавался и товар списывался корректно ✓
- НО резервы оставались в статусе 'released' вместо 'converted_to_sale'
- Это приводило к некорректной истории и возможным проблемам при откате
Причина:
- Сигнал искал только резервы в статусе 'reserved'
- После отмены резервы были в статусе 'released'
- При повторном выполнении они не обновлялись
Решение:
- Изменён фильтр резервов: берём ВСЕ кроме 'converted_to_sale'
- Теперь обрабатываются резервы в любом статусе (reserved, released, и др.)
- Элегантное решение без хардкода конкретных статусов
Дополнительно:
- Добавлен @transaction.atomic к сигналам обновления Stock
- Защита от race conditions при одновременном изменении резервов
- Минимальные издержки, максимальная надёжность
Результат:
- Корректная работа при ЛЮБЫХ переходах статусов:
* reserved → converted_to_sale ✓
* released → converted_to_sale ✓
* повторный вызов → пропуск ✓
- Целостность данных гарантирована транзакциями
- Элегантный код без костылей
Проблема:
- При откате заказа из статуса 'completed' в 'возврат' или другой статус
- Резервы правильно обновлялись на 'reserved' или 'released'
- НО Stock.quantity_reserved не обновлялся
- В результате товар показывался как полностью свободный, хотя был резерв
Причина:
- В сигнале rollback_sale_on_status_change использовался .update()
- Это не вызывало сигнал update_stock_on_reservation_change
- Stock не пересчитывался автоматически
Решение:
- Заменен .update() на .save(update_fields=[...]) в сигнале отката
- Теперь при изменении резервов автоматически срабатывает сигнал
- Stock корректно обновляется в обоих направлениях:
* completed → резервы converted_to_sale → Stock обновляется
* откат → резервы reserved/released → Stock обновляется
- Убран костыль с ручным вызовом refresh_from_batches()
Результат:
- Элегантное единообразное решение для всех сценариев
- Stock автоматически синхронизируется с резервами
- Работает корректно при любых изменениях статуса заказа
Проблема:
- При смене статуса заказа на 'Выполнен' товар списывался со склада
- Резервы обновлялись на статус 'converted_to_sale'
- НО Stock.quantity_reserved не обновлялся автоматически
- В результате резервы продолжали 'держать' товар, хотя он уже продан
Решение:
1. Изменен сигнал create_sale_on_order_completion:
- Используется .save(update_fields=[...]) вместо .update()
- Это вызывает сигнал update_stock_on_reservation_change
- Убран костыль с ручным вызовом refresh_from_batches()
2. Оптимизирован сигнал update_stock_on_reservation_change:
- Stock обновляется ТОЛЬКО при изменении status или quantity
- При изменении других полей (даты и т.д.) Stock НЕ пересчитывается
- Предотвращены лишние пересчёты и улучшена производительность
3. Добавлены диагностические инструменты:
- check_stock_103.py - для диагностики проблем с Stock
- fix_stock_after_sale.py - команда для исправления старых заказов
- diagnose_reservation_issue.py - универсальная диагностика
Результат:
- Элегантное решение без дублирования логики
- Stock автоматически обновляется при изменении резервов
- Работает везде, не только в заказах
- Оптимизировано для производительности
Проблема #1: Резервы не создавались при создании заказа
- Order сохранялся БЕЗ items → сигнал reserve_stock_on_order_create
не мог создать резервы (items.all() был пустой)
- OrderItem создавались ПОСЛЕ, но сигнал уже отработал
Решение #1: Создание резервов через сигнал OrderItem
- Доработан сигнал update_reservation_on_item_change
- Убрано раннее возвращение для created=True
- Теперь резервы создаются при добавлении OrderItem (любым способом)
- Работает для всех сценариев:
* Создание заказа с товарами
* Добавление товаров при редактировании
* Изменение количества
Проблема #2: Риск расхождений при удалении заказа
- Сигнал pre_delete освобождал резервы ДО удаления
- Если удаление падало с ошибкой → резервы освобождены, Order не удалён
- Возникало расхождение данных
Решение #2: transaction.on_commit для освобождения резервов
- Добавлен @transaction.atomic к сигналу release_stock_on_order_delete
- Резервы получаются ДО удаления через list()
- Освобождение происходит через transaction.on_commit()
- Резервы освобождаются ТОЛЬКО если удаление успешно
- Гарантия целостности данных
Изменённые файлы:
- myproject/inventory/signals.py - оба сигнала исправлены
- RESERVATION_FIX.md - полная документация
Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 10 шт создавал Sale на 10 шт, но со склада уходило 20 шт
Найдено ДВЕ причины:
1. Повторное обновление резервов через .save() (inventory/signals.py)
- Резервы обновлялись через res.save() каждый раз при сохранении заказа
- Это вызывало сигнал update_stock_on_reservation_change
- При повторном сохранении заказа происходило двойное срабатывание
Решение:
- Проверка дубликатов ПЕРЕД обновлением резервов
- Замена .save() на .update() для массового обновления без вызова сигналов
- Ручное обновление Stock после .update()
2. Двойное FIFO-списание (inventory/services/sale_processor.py)
- Sale создавалась с processed=False
- Сигнал process_sale_fifo срабатывал и списывал товар (1-й раз)
- Затем SaleProcessor.create_sale() тоже списывал товар (2-й раз)
Решение:
- Sale создаётся сразу с processed=True
- Сигнал не срабатывает, списание только в сервисе
Дополнительно:
- Ограничен выбор статусов при создании заказа только промежуточными
- Статус 'Черновик' установлен по умолчанию
- Убран пустой выбор '-------' из поля статуса
Изменённые файлы:
- myproject/orders/forms.py - настройки статусов для формы заказа
- myproject/inventory/signals.py - исправление сигнала create_sale_on_order_completion
- myproject/inventory/services/sale_processor.py - исправление create_sale
- myproject/test_order_status_default.py - обновлён тест
- DOUBLE_SALE_FIX.md - документация по исправлению
Проблема: Резервы не обновлялись в статус 'converted_to_sale' если Sale уже существовали,
что приводило к отображению завершенных заказов на странице активных резервов.
Решение: Переместил обновление резервов ПЕРЕД проверкой существования Sale.
Теперь резервы всегда обновляются при переходе заказа в статус 'completed',
независимо от того, были ли уже созданы записи Sale.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: При создании заказа instance.status может быть None,
что вызывало AttributeError при попытке доступа к .code
Решение: Добавлена проверка 'not instance.status' перед
обращением к instance.status.code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: Сигнал post_save срабатывает несколько раз,
создавая дубликаты Sale для одного заказа.
Решения:
1. Добавлена проверка Sale.objects.filter(order=instance).exists()
перед созданием продаж (inventory/signals.py:74-75)
2. Создана management команда fix_duplicate_sales для исправления
существующих дубликатов
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: Order.status - это объект OrderStatus, а не строка.
Сравнение instance.status != 'completed' всегда возвращало True.
Решение: Сравниваем instance.status.code != 'completed'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Изменения:
- Сигнал create_sale_on_order_shipment переименован в create_sale_on_order_completion
- Списание товаров (создание Sale) теперь происходит при статусе 'completed' вместо 'in_delivery'
- Исправлен выбор склада: используется Order.pickup_warehouse, если задан
- Та же логика применена к резервированию товаров
Обоснование для цветочного бизнеса:
- Букет может вернуться в магазин (клиента нет дома, перенос доставки)
- Товар физически находится в магазине до момента доставки
- Резерв показывает что товар занят - этого достаточно для промежуточных статусов
- Простота: списываем только когда ТОЧНО продали
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add total_available, total_reserved, total_free annotations to product queries
- Display free stock (green/red) with reserved count in product list
- Show detailed stock info in product detail page (moved to top)
- Make reservation count clickable to view filtered reservations
- Add product filter support to ReservationListView
- Add product link in reservation list for easy navigation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 1. Add cart lock validation to sell_from_showcase()
- Prevent selling showcase kits locked in another cashier's cart
- Check cart_lock_expires_at before allowing direct sales
- Return clear error message with lock holder's name and time remaining
- File: inventory/services/showcase_manager.py
## 2. Improve error handling in POS create_temp_kit_to_showcase()
- Add detailed logging for all error types (JSON, validation, generic)
- Provide user-friendly error messages instead of generic 500
- Log full context (kit name, showcase ID, items, user) for debugging
- Categorize errors: stock issues, integrity, locks, not found
- File: pos/views.py
## 3. Fix critical bug in create_temporary_kit()
- Replace non-existent is_active field with status='active'
- Affects 3 locations: kit creation, product lookup, kit duplication
- This was causing 500 errors when creating temporary kits from order edit
- File: products/services/kit_service.py
## 4. Improve error handling in create_temporary_kit_api()
- Add comprehensive logging for order creation endpoint
- Provide specific error messages for common failure scenarios
- Help diagnose issues when creating kits from order editing UI
- File: products/views/api_views.py
These changes complete the Soft Lock system and fix the 500 error issue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>