Commit Graph

33 Commits

Author SHA1 Message Date
a8ba5ce780 Улучшения инвентаризации: автоматическое проведение документов, оптимизация запросов и улучшения UI
- Автоматическое проведение документов списания и оприходования после завершения инвентаризации
- Оптимизация SQL-запросов: устранение N+1, bulk-операции для Stock, агрегация для StockBatch и Reservation
- Изменение формулы расчета разницы: (quantity_fact + quantity_reserved) - quantity_available
- Переименование поля 'По факту' в 'Подсчитано (факт, свободные)'
- Добавлены столбцы 'В резервах' и 'Всего на складе' в таблицу инвентаризации
- Перемещение столбца 'В системе (свободно)' после 'В резервах' с визуальным выделением
- Центральное выравнивание значений в столбцах таблицы
- Автоматическое выделение текста при фокусе на поле ввода количества
- Исправление форматирования разницы (убраны лишние нули)
- Изменение статуса 'Не обработана' на 'Не проведено'
- Добавление номера документа для инвентаризаций (INV-XXXXXX)
- Отображение всех типов списаний в debug-странице (WriteOff, WriteOffDocument, WriteOffDocumentItem)
- Улучшение отображения документов в детальном просмотре инвентаризации с возможностью перехода к ним
2025-12-21 23:59:02 +03:00
812ecb53e6 Fix media file storage path and permissions
- Fix MEDIA_ROOT path to match Docker volume mount (/app/myproject/media)
- Update docker-compose.yml volume mounts to match MEDIA_ROOT
- Add setup_directories() function in entrypoint.sh to create media directories with proper permissions
- Add logging to TenantAwareFileSystemStorage for debugging
- Fix is_returned flag logic improvements (from previous work)
2025-12-21 16:54:44 +03:00
7b32cdcebf Обновления и новые функции: изменение шаблона клиента, обновление сигналов инвентаря, добавление снимков наборов и элементов заказа, обновление моделей заказов и продуктов 2025-12-18 00:14:24 +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
cd5b8c3ef2 Добавлена поддержка документов списания в админке и сигналах
- Зарегистрированы модели WriteOffDocument и WriteOffDocumentItem в админке
- Настроен inline для позиций документа в админке
- Добавлены цветовые индикаторы статусов документа
- Настроены фильтры, поиск и сортировка для удобной работы
- Добавлен сигнал release_reservation_on_writeoff_item_delete
- Автоматическое освобождение резервов при удалении позиций через админку
- Защита от утечки резервов при прямом удалении через ORM
2025-12-10 23:38:48 +03:00
a244d82e49 Исправлено: сохранение флага is_returned при отмене ранее проданного заказа 2025-12-08 18:40:05 +03:00
5f1c982bf7 Исправлено: снятие блокировок корзины при отмене заказа с витринным комплектом 2025-12-08 18:22:37 +03:00
5b03a95b5a Исправлена логика освобождения резервов для витринных комплектов во всех сигналах 2025-12-08 18:15:41 +03:00
5d24b1cd6e Витринные комплекты остаются на витрине при отмене заказа
Проблема: при отмене заказа с витринным временным комплектом резервы освобождались
(status='released'), и букет исчезал с витрины. Это неправильное поведение для временных
комплектов на витрине - они должны оставаться доступными для продажи.

Решение:
- В сигнале rollback_sale_on_status_change добавлено разделение резервов на:
  * Обычные резервы - работают как раньше (released при отмене, reserved при возврате)
  * Витринные временные комплекты (is_temporary=True, showcase!=null) - ВСЕГДА возвращаются
    в статус reserved, независимо от типа отката заказа

- Для витринных комплектов сохраняются привязки к showcase и product_kit
- Букеты остаются видимыми на витрине и доступны для повторной продажи

Бизнес-логика:
- При ВОЗВРАТЕ (completed → draft/in_delivery): букет возвращается на витрину
- При ОТМЕНЕ (completed → cancelled): букет ТАКЖЕ возвращается на витрину
- Букет можно убрать только вручную через функцию разбора комплекта
2025-12-08 17:58:40 +03:00
3ef2a19537 Исправлена логика продажи витринных комплектов через POS
- Добавлена специальная обработка витринных комплектов в сигнале 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 для заказа с витринным комплектом
2025-12-08 17:56:47 +03:00
1168659df8 Fixed: Re-reserve stock when transitioning from cancelled to other statuses 2025-12-01 12:22:50 +03:00
9f062f527d Fixed critical bug: release reservations on draft->cancelled transition 2025-12-01 11:57:06 +03:00
4597ddbd87 🔥 КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Дублирование резервов при изменении количества
Проблема:
- При изменении количества в 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 (полная документация бага и решения)

Покрытие всех сценариев:
 Создание заказа с товарами
 Добавление товара при редактировании
 Изменение количества (черновик)
 Изменение количества (выполнен) - ИСПРАВЛЕНО
 Повторное сохранение заказа

КРИТИЧНО: Это исправление влияет на учёт товара и требует тестирования!
2025-12-01 10:22:28 +03:00
7b1922c186 Исправлена обработка резервов при переходе ОТМЕНЁН → ВЫПОЛНЕН
Проблема:
- При смене статуса заказа ОТМЕНЁН → ВЫПОЛНЕН
- 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 ✓
  * повторный вызов → пропуск ✓
- Целостность данных гарантирована транзакциями
- Элегантный код без костылей
2025-12-01 02:57:44 +03:00
a5a983b198 Исправлена проблема с резервами при откате из статуса 'Выполнен'
Проблема:
- При откате заказа из статуса '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 автоматически синхронизируется с резервами
- Работает корректно при любых изменениях статуса заказа
2025-12-01 02:40:40 +03:00
e4cb175db2 Исправлена критическая проблема с резервами при смене статуса заказа
Проблема:
- При смене статуса заказа на 'Выполнен' товар списывался со склада
- Резервы обновлялись на статус '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 автоматически обновляется при изменении резервов
- Работает везде, не только в заказах
- Оптимизировано для производительности
2025-12-01 02:34:54 +03:00
293e8640ef Исправлено создание резервов при сохранении заказов
Проблема #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 - полная документация
2025-12-01 01:10:58 +03:00
e0437cdb5a Исправлено двойное списание товаров при смене статуса заказа
Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 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 - документация по исправлению
2025-12-01 00:56:26 +03:00
4e66f03957 Исправлено обновление резервов при завершении заказа
Проблема: Резервы не обновлялись в статус 'converted_to_sale' если Sale уже существовали,
что приводило к отображению завершенных заказов на странице активных резервов.

Решение: Переместил обновление резервов ПЕРЕД проверкой существования Sale.
Теперь резервы всегда обновляются при переходе заказа в статус 'completed',
независимо от того, были ли уже созданы записи Sale.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 22:36:39 +03:00
6a2e180b29 Исправлена ошибка NoneType при создании заказа
Проблема: При создании заказа 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>
2025-11-30 22:16:48 +03:00
920dbf4273 Добавлена защита от повторного списания + команда исправления дубликатов
Проблема: Сигнал 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>
2025-11-30 22:15:33 +03:00
24292b2e47 Исправлено сравнение статуса заказа в сигнале списания
Проблема: 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>
2025-11-30 22:05:46 +03:00
d502a37583 Перенесено списание товаров со склада на статус 'completed'
Изменения:
- Сигнал 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>
2025-11-30 21:51:28 +03:00
65a316649b feat: Add signal handler for synchronizing Incoming edits with StockBatch
## 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>
2025-11-15 00:22:58 +03:00
d2f15b5126 Add stock reservation auto-update system
- 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>
2025-11-08 11:07:35 +03:00
6c8af5ab2c fix: Улучшения системы ценообразования комплектов
Исправлены 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>
2025-11-02 19:04:03 +03:00
83412f3447 fix: Исправить логику обновления Product.in_stock из Stock остатков
Проблема: товары отображались как "нет в наличии" несмотря на наличие остатков на складе.

Причина: сигналы на обновление 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>
2025-10-29 23:42:20 +03:00
2341cf57c1 feat: Реализовать систему наличия товаров и цены вариантов
Добавлена система управления наличием товаров на трёх уровнях:

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>
2025-10-29 23:22:01 +03:00
6735be9b08 feat: Реализовать систему поступления товаров с партиями (IncomingBatch)
Основные изменения:
- Создана модель 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>
2025-10-29 03:26:06 +03:00