Проблема:
- При смене статуса заказа на 'Выполнен' товар списывался со склада
- Резервы обновлялись на статус '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>
## Changes
### 1. Fixed missing signal handler for Incoming edit (inventory/signals.py)
- Added new signal handler `update_stock_batch_on_incoming_edit()` that:
- Triggers when Incoming is edited (created=False)
- Synchronizes StockBatch with new quantity and cost_price values
- Automatically triggers cost price recalculation for the product
- Updates Stock (inventory balance) for the warehouse
- Includes proper logging and error handling
### 2. Created IncomingModelForm for editing individual incoming items (inventory/forms.py)
- New ModelForm: `IncomingModelForm` that:
- Inherits from forms.ModelForm (accepts 'instance' parameter required by UpdateView)
- Allows editing: product, quantity, cost_price, notes
- Includes validation for positive quantity and non-negative cost_price
- Filters only active products
### 3. Updated IncomingUpdateView (inventory/views/incoming.py)
- Changed form_class from IncomingForm to IncomingModelForm
- Updated imports to include IncomingModelForm
- Removed obsolete comments from form_valid method
## Architecture
When editing an Incoming item:
1. User submits form with new quantity/cost_price
2. form.save() triggers post_save signal (created=False)
3. update_stock_batch_on_incoming_edit() synchronizes StockBatch
4. StockBatch.save() triggers update_product_cost_on_batch_change()
5. Product.cost_price is recalculated with weighted average
## Problem Solved
Previously, editing an Incoming item would NOT:
- Update the related StockBatch
- Recalculate product cost_price
- Update warehouse inventory balance
- Maintain data consistency between Incoming and StockBatch
Now all these operations happen automatically through the signal chain.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Add management command to recalculate quantity_reserved for all Stock records
- Add signals to automatically update Stock when Reservation changes
- Implement post_save signal for Reservation creation/updates
- Implement post_delete signal for Reservation deletion
- Both signals call Stock.refresh_from_batches() to recalculate quantities
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Исправлены 4 проблемы:
1. Расчёт цены первого товара - улучшена валидация в getProductPrice и calculateFinalPrice
2. Отображение actual_price в Select2 вместо обычной цены
3. Количество по умолчанию = 1 для новых форм компонентов
4. Auto-select текста при клике на поле количества для удобства редактирования
Изменённые файлы:
- products/forms.py: добавлен __init__ в KitItemForm для quantity.initial = 1
- products/templates/includes/select2-product-init.html: обновлена formatSelectResult
- products/templates/productkit_create.html: добавлен focus handler для auto-select
- products/templates/productkit_edit.html: добавлен focus handler для auto-select
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: товары отображались как "нет в наличии" несмотря на наличие остатков на складе.
Причина: сигналы на обновление Product.in_stock срабатывают только при изменении Stock через Django ORM.
Если Stock была создана напрямую (импорт, миграция и т.д.), сигналы не срабатывали.
Решение:
1. Исправлена логика сигналов (inventory/signals.py):
- Добавлен импорт post_delete для правильной обработки удаления Stock
- Изменён pre_delete на post_delete для более надёжной проверки остатков
- Сигналы теперь правильно срабатывают при любом изменении Stock
2. Добавлена миграция (products/migrations/0004_fix_product_in_stock.py):
- Пересчитывает in_stock для всех существующих товаров на основе Stock.quantity_available
- Товар считается в наличии если есть хотя бы один Stock с quantity_available > 0
- Обратима и безопасна (может быть отменена)
3. Добавлена команда управления (products/management/commands/update_product_in_stock.py):
- Позволяет вручную пересчитать in_stock если потребуется
- Поддерживает параметр --verbose для подробного логирования
- Может быть запущена по расписанию или вручную
После этого исправления:
- Все товары с остатками на складе автоматически обновляют статус in_stock
- Сигналы срабатывают при любом изменении Stock (создание, обновление, удаление)
- Отображение наличия товаров в UI будет корректным
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Добавлена система управления наличием товаров на трёх уровнях:
1. Product.in_stock (поле БД)
- Булево значение: есть/нет в наличии
- Автоматически обновляется при изменении Stock
- Используется для быстрого поиска и фильтрации товаров
2. Сигналы для синхронизации (inventory/signals.py)
- При изменении Stock → обновляется Product.in_stock
- Логика: товар в наличии если есть Stock с quantity_available > 0
3. ProductVariantGroup.in_stock (свойство)
- Вариант в наличии если хотя бы один из товаров в наличии
- Динамически рассчитывается по Product.in_stock товаров в группе
4. ProductVariantGroup.price (свойство)
- Цена по приоритету: берём цену товара с приоритетом 1, если он в наличии
- Если никто не в наличии: берём максимальную цену из всех товаров
- Возвращает Decimal или None если группа пуста
Файлы:
- myproject/products/models.py: добавлено поле in_stock и свойства в ProductVariantGroup
- myproject/inventory/signals.py: добавлены сигналы для синхронизации
- myproject/products/migrations/0003_add_product_in_stock.py: миграция для поля in_stock
- VARIANT_STOCK_IMPLEMENTATION.md: полная документация архитектуры
- QUICK_REFERENCE.md: быстрая справка по использованию
Особенности:
✓ Система простая и элегантная (без костылей)
✓ Обратная совместимость не требуется
✓ Высокая производительность (индексирование, минимум JOIN'ов)
✓ Актуальные данные (сигналы гарантируют синхронизацию)
✓ Легко расширяемая (свойства можно менять без миграций)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Основные изменения:
- Создана модель IncomingBatch для группировки товаров по документам
- Каждое поступление (Incoming) связано с одной батчем поступления
- Автоматическое создание StockBatch для каждого товара в приходе
- Реализована система нумерации партий (IN-XXXX-XXXX) с поиском максимума в БД
- Обновлены все представления (views) для работы с новой архитектурой
- Добавлены детальные страницы просмотра партий поступлений
- Обновлены шаблоны для отображения информации о партиях и их товарах
- Исправлена логика сигналов для создания StockBatch при приходе товара
- Обновлены формы для работы с новой структурой IncomingBatch
Архитектура FIFO:
- IncomingBatch: одна партия поступления (номер IN-XXXX-XXXX)
- Incoming: товар в партии поступления
- StockBatch: одна партия товара на складе (создается для каждого товара)
Это позволяет системе правильно применять FIFO при продаже товаров.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>