Commit Graph

172 Commits

Author SHA1 Message Date
ffc5f4cfc1 feat(inventory): учитывать коэффициент конверсии при резервировании компонентов комплектов
Добавлены поля original_sales_unit и conversion_factor в KitItemSnapshot для хранения
единиц продажи и коэффициентов конверсии на момент создания снимка. Обновлена логика
резервирования запасов для корректного расчета количества в базовых единицах.

Изменения в шаблоне редактирования комплектов для сохранения выбранных единиц продажи
при обновлении списка опций.

BREAKING CHANGE: Изменена структура данных в KitItemSnapshot, требуется миграция базы данных.
2026-01-21 11:05:00 +03:00
2dc36b3d01 fix(inventory): создавать Sale после применения скидок в POS checkout
Добавлен механизм skip_sale_creation на базе thread-local storage
для управления моментом создания Sale через сигнал.

Проблема: сигнал create_sale_on_order_completion срабатывал при
Order.objects.create(status=completed) до применения скидок.

Решение: пропускать сигнал во время создания заказа, затем явно
создавать Sale после применения всех скидок через order.save().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 01:05:22 +03:00
1e4b7598ae refactor(tests): удалить проверку истории переходов в test_order_status_transitions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 00:38:42 +03:00
1071f3cacc fix(inventory): учитывать скидки при расчёте цены продажи с единицами измерения
- Пересчитывать цену в базовые единицы: price * conversion_factor
- Вычислять скидку как разницу между subtotal и total_amount
- Распределять скидку пропорционально долям позиций
- Использовать refresh_from_db() для актуального total_amount

Пример: 20 ед. (коэфф. 5) по 7₽ со скидкой 10% → Sale: 4 шт. по 31.5₽

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 00:35:43 +03:00
6b327fa7e0 fix(inventory): учитывать скидки на позицию и заказ при расчёте цены продажи
Расширяем логику расчёта цены продажи для учёта как скидок на отдельные позиции,
так и скидок на весь заказ. Скидка на заказ распределяется пропорционально доле
каждой позиции в общей сумме заказа.

Изменения внесены в SaleProcessor и сигнал создания продажи при завершении заказа.
2026-01-20 23:40:27 +03:00
0938878e67 fix(inventory): учитывать скидку при расчёте цены продажи
Внесены изменения в SaleProcessor и сигнал создания продажи для корректного
расчёта цены с учётом скидки на товар. Теперь при наличии discount_amount
производится пересчёт цены за единицу товара с учётом скидки перед
конвертацией в базовые единицы измерения.

Это исправляет ошибку, при которой скидка не учитывалась в итоговой цене продажи.
2026-01-20 23:20:55 +03:00
9cd3796527 feat(woocommerce): реализовать проверку соединения с WooCommerce API
- Добавлена реализация метода test_connection() с обработкой различных HTTP статусов
- Реализованы вспомогательные методы _get_api_url() и _get_auth() для работы с API
- Добавлена интеграция WooCommerceService в get_integration_service()
- Настроены поля формы для WooCommerceIntegration в get_form_fields_meta()

fix(inventory): исправить расчет цены продажи в базовых единицах

- Исправлен расчет sale_price в SaleProcessor с учетом conversion_factor_snapshot
- Обновлен расчет цены в сигнале create_sale_on_order_completion для корректной работы с sales_unit
2026-01-20 23:05:18 +03:00
2778796118 feat(pos): фиксировать цены товаров в витринных комплектах
- Добавлено поле KitItem.unit_price для хранения зафиксированной цены
- Витринные комплекты больше не обновляются при изменении цен товаров
- Добавлен красный индикатор на карточке если цена неактуальна
- Добавлен warning в модалке редактирования с кнопкой "Актуализировать"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 15:59:44 +03:00
0d882781da fix(orders): исправить удаление позиций заказа в формсете
- Исправлена логика удаления inline-форм для позиций заказа
- Добавлена обработка удаления сохранённых и новых форм
- Добавлено поле id и DELETE в OrderItemForm для корректной работы формсета
- Добавлена проверка на null для created_by на странице отладки
- Расширены права доступа к отладочной странице: теперь доступна owner и manager
- Добавлено логирование для отладки процесса обновления заказа
2026-01-18 17:16:34 +03:00
d182a7b16d feat(inventory): улучшить поиск товаров для инвентаризации
Добавлен параметр skip_stock_filter для отключения фильтрации по остаткам,
опция excludeKits для исключения комплектов из поиска, а также
добавлено явное указание API URL и расширенное логирование для отладки.
2026-01-18 11:12:17 +03:00
ce486f35ca Перенос встроенных стилей из шаблона detail.html в отдельный CSS-файл transformation_detail.css 2026-01-15 15:09:38 +03:00
caeb3f80bd refactor(db): консолидация миграций и рефакторинг кода
Объединены изменения из промежуточных миграций в начальные миграции для упрощения истории базы данных.
Удалены миграции: accounts/0002, discounts/0002, orders/0003-0004, products/0002-0005, user_roles/0002, system_settings/0001-0002, integrations/0001-0002.
Добавлена автоматическая creation пользователя при установке пароля.
Обновлен UI страницы установки пароля с кастомным стилем.
Добавлен conditional rendering для кнопки синхронизации Recommerce.
Исправлены редиректы с 'index' на '/' в accounts views.
Добавлена проверка request.tenant в navbar и authenticate метод в auth backend.
2026-01-14 16:30:28 +03:00
27cb9ba09d fix signals 2026-01-10 23:43:19 +03:00
4ea01b8269 fix(inventory, orders, pos): удалена зависимость от django-simple-history для tenant-моделей
- Добавлен pre_save сигнал для Order вместо django-simple-history
- Переписаны все функции signals.py без использования instance.history
- Заменены .username на .name|default:.email для CustomUser в шаблонах
- Исправлен CSRF-токен в POS для работы с CSRF_USE_SESSIONS=True

Теперь создание заказов работает корректно в мультитенантной архитектуре.
2026-01-10 17:21:00 +03:00
8f3c90c11a fix(inventory): enforce consistent date format in document forms and views
- Add explicit date input format '%Y-%m-%d' to WriteOffDocumentForm and IncomingDocumentForm
- Disable localization for date fields to ensure yyyy-MM-dd format is used
- Set initial date value to current date in WriteOffDocumentCreateView's get_initial method
- Restrict warehouse queryset to active warehouses in forms initialization
- Improve date widget consistency by adding format parameter to DateInput widgets
2026-01-10 11:45:25 +03:00
5f565555e3 fix(products): improve bulk category modal behavior and initialization
- Move default date initialization from form to view initial data
- Add checks for DOM elements existence before event listener attachment
- Handle 'clear' mode in bulk category modal with confirmation and API call
- Improve CSRF token usage and error handling during bulk update requests
- Remove deprecated handleClearAll function and integrate logic into handleApply
- Reset modal state properly including input fields and radio button modes
- Update modal JS file version reference in products list template
2026-01-10 11:41:37 +03:00
2d1f8b78ad Обновления в inventory: модели, миграции и удаление старых миграций 2026-01-08 22:11:23 +03:00
d44ae0b598 Добавлен расчёт и отображение доступного количества комплектов
- Добавлен метод calculate_available_quantity() в модель ProductKit для точного расчёта максимального количества комплектов на основе свободных остатков компонентов
- Обновлён метод check_availability() для использования нового расчёта (обратная совместимость)
- Удалён устаревший сервис kit_availability.py

Исправлено отображение остатков комплектов:
- products_list.html: вместо прочерка показывается количество комплектов
- catalog.html: добавлено отображение доступного количества комплектов с цветовой индикацией
- POS terminal.js: в карточке товара показывается конкретное количество вместо общего 'В наличии'

Обновлены представления:
- ProductsListView: аннотирует комплекты атрибутом total_free
- CatalogView: рассчитывает доступное количество для каждого комплекта
- POS get_products(): убран хардкод, используется реальный расчёт по складу
2026-01-06 01:02:28 +03:00
2aba3d2404 Улучшения в тестах переходов статусов заказов
- Исправлены комментарии и форматирование в signals.py
- Улучшена читаемость кода в models.py
- Обновлены шаблоны форм статусов
- Доработаны тесты переходов статусов
2026-01-05 21:30:25 +03:00
9e43f738a4 Добавлена миграция для поля original_order_item_id в Reservation
Поле для отслеживания связи резервов с исходными позициями заказа
2026-01-05 21:29:56 +03:00
541ea5e561 Добавлены тесты параллельных операций с заказами
- Тест 1: Параллельное резервирование одинакового количества
- Тест 2: Резервирование при недостатке товара
- Тест 3: Параллельное завершение заказов (проверка race condition)
- Тест 4: Параллельная отмена заказов
- Тест 5: Параллельный танец статусов (cancelled -> draft -> completed)
- Тест 6: Смешанный сценарий (создание + завершение + отмена)

Все тесты проходят успешно, race condition исправлен
2026-01-05 21:29:45 +03:00
aed9290d7a Исправлен race condition в списании партий товара
- Добавлен параметр lock в get_batches_for_fifo() для блокировки строк
- Используется select_for_update() в write_off_by_fifo() для предотвращения
  параллельной перезаписи quantity при одновременном списании из одной партии
- Защита от потери данных при параллельном завершении заказов
2026-01-05 21:29:29 +03:00
d65a69e2bb Исправлен поиск витринных резервов при создании Sale из отменённого заказа
Проблема:
- При переходе cancelled → completed резервы витринных букетов отвязаны от order_item (order_item=None)
- В create_sale_on_order_completion поиск резервов по order_item не находит их
- Sale не создаются → букет освобождается вместо продажи

Решение:
- inventory/signals.py: в create_sale_on_order_completion добавлена fallback-логика для витринных комплектов
- Если резервы не найдены по order_item=item, проверяем: витринный комплект?
- Для витринных: ищем резервы через product_kit + showcase__isnull=False + status='reserved'
- Найденные резервы привязываем к order_item перед созданием Sale
- Затем создаются Sale и ShowcaseItem корректно переходит в sold

Flow теперь работает полностью:
1. cancelled: ShowcaseItem → available, Reservation order_item=None
2. cancelled → completed:
   - create_sale_on_order_completion находит резервы через product_kit 
   - Привязывает их к order_item 
   - Создаёт Sale для компонентов 
   - Финализирует ShowcaseItem: available → sold 

Гарантирует создание Sale даже если порядок срабатывания сигналов не предсказуем.
2026-01-05 09:45:18 +03:00
0faae69c63 Исправлен порядок обработки ShowcaseItem при переходе cancelled → completed
Проблема:
- При переходе cancelled → completed срабатывали ОБА сигнала:
  1. reserve_stock_on_uncancellation переводил ShowcaseItem: available → reserved
  2. create_sale_on_order_completion искал букеты в available, но они уже в reserved
  3. Букеты оставались в reserved вместо sold

Решение:
- inventory/signals.py: в reserve_stock_on_uncancellation добавлена проверка current_status.is_positive_end
- Если текущий статус положительный финальный (completed) - ShowcaseItem НЕ трогаем
- Оставляем в available для финализации в create_sale_on_order_completion
- Если текущий статус нейтральный (draft/pending) - переводим available → reserved как раньше

Flow теперь работает корректно:
1. cancelled → draft/pending: ShowcaseItem available → reserved 
2. cancelled → completed: ShowcaseItem available → sold  (ИСПРАВЛЕНО!)
   - reserve_stock_on_uncancellation пропускает (видит is_positive_end)
   - create_sale_on_order_completion финализирует: available → sold

Защита от race condition между двумя сигналами.
2026-01-05 09:41:29 +03:00
6095729409 Исправлен поиск ShowcaseItem при переходе cancelled → completed
Проблема:
- При отмене (cancelled) метод return_to_available() сбрасывает sold_order_item = None
- При переходе cancelled → completed поиск ShowcaseItem по sold_order_item__order не находит букеты
- Букеты оставались в статусе 'available' вместо 'sold'

Решение:
- inventory/signals.py: в сигнале create_sale_on_order_completion изменена логика поиска
- Разделён поиск на два этапа:
  1. Поиск по sold_order_item для букетов в 'reserved' (обычный flow)
  2. Поиск по product_kit для букетов в 'available' (переход из cancelled)
- Для букетов в 'available': ищем через product_kit + status='available' + sold_order_item__isnull=True
- Вызываем mark_sold(order_item) для каждого найденного букета
- Букет корректно переходит available → sold и привязывается к OrderItem

Flow теперь работает:
1. draft → completed: ShowcaseItem reserved → sold 
2. cancelled → completed: ShowcaseItem available → sold  (ИСПРАВЛЕНО!)

Защита от двойной продажи работает корректно.
2026-01-05 09:30:00 +03:00
6c497bbde3 Исправлен баг: ShowcaseItem теперь корректно переходит available → sold при cancelled → completed
Проблема:
- При переходе заказа cancelled → completed витринный букет оставался в статусе 'available'
- Логика финализации искала только ShowcaseItem в статусе 'reserved'
- НО при отмене (cancelled) ShowcaseItem переходит в 'available', а не остаётся в 'reserved'
- Итог: букет не финализировался, оставался свободным вместо проданного

Решение:
- inventory/signals.py: в сигнале create_sale_on_order_completion обновлена логика финализации
- Теперь ищем ShowcaseItem в статусах ['reserved', 'available']
- Для статуса 'reserved': вызываем mark_sold_from_reserved() (обычный flow)
- Для статуса 'available': вызываем mark_sold() (переход из отмены cancelled → completed)
- Оба метода корректно переводят букет в 'sold' и устанавливают sold_at

Flow переходов:
1. Обычный: draft → completed: ShowcaseItem reserved → sold 
2. Из отмены: cancelled → completed: ShowcaseItem available → sold  (ИСПРАВЛЕНО)
2026-01-05 09:23:50 +03:00
366ead7404 Исправлен баг: ShowcaseItem теперь возвращается в reserved при переходе из отрицательного статуса в нейтральный
Проблема:
- При отмене заказа (cancelled) ShowcaseItem переходил в 'available'
- При возврате из cancelled в нейтральный статус (draft/pending) резервы привязывались обратно
- НО ShowcaseItem оставался в 'available', что позволяло добавить букет в другой заказ
- Итог: один физический букет в двух заказах

Решение:
- inventory/signals.py: в сигнале reserve_stock_on_uncancellation добавлена логика
- При переходе cancelled → нейтральный:
  * Находим ShowcaseItem витринных комплектов в статусе 'available'
  * Вызываем return_to_reserved(order_item) для каждого экземпляра
  * ShowcaseItem: available → reserved (привязан к OrderItem)
- Теперь букет корректно возвращается в резерв и недоступен на витрине

Lifecycle при откате отмены:
1. cancelled: ShowcaseItem = available, резервы отвязаны
2. cancelled → draft/pending: ShowcaseItem = reserved, резервы привязаны
3. Букет остаётся за заказом, защищён от двойной продажи
2026-01-05 09:09:41 +03:00
a1f5557036 Исключены зарезервированные букеты из отображения в POS
- inventory/views/showcase.py: фильтр .exclude(status='reserved')
  * Витринные букеты со статусом 'reserved' не отображаются в POS
  * Защита от конфликтов: один букет - один заказ
- pos/views.py: фильтр .exclude(showcase_items__status='reserved')
  * Showcase комплекты без доступных букетов скрыты в POS
  * Фильтрация на уровне queryset для производительности
- Консистентная видимость витрины для всех кассиров
2026-01-05 01:39:14 +03:00
7cab70e8b0 Расширена debug страница для отслеживания статусов ShowcaseItem
- inventory/templates/inventory/debug_page.html: добавлена секция ShowcaseItem
  * Таблица с полями: ID, Название, Статус, OrderItem, Locked By
  * Цветовые индикаторы статусов (available/in_cart/reserved/sold)
  * Ссылки на связанные OrderItem
- inventory/views/debug_views.py: добавлены данные ShowcaseItem в контекст
  * showcase_items queryset с select_related для оптимизации
  * Статистика по статусам ShowcaseItem
- Инструмент для тестирования lifecycle витринных букетов
2026-01-05 01:38:59 +03:00
dd37931f5e Реализована логика резервирования витринных букетов через сигналы
- inventory/signals.py: обработчик изменения статуса Order
  * При смене статуса на 'завершён' (is_positive_end=True): reserved → sold
  * При смене на 'отменён' (is_negative_end=True): reserved → available
- inventory/services/showcase_manager.py: метод reserve_for_order()
  * Переводит ShowcaseItem: in_cart → reserved
  * Создаёт жёсткую связь с OrderItem
  * Автоматическое управление статусами через сигналы
- Транзакционная безопасность через @transaction.atomic
2026-01-05 01:38:14 +03:00
24a64edc82 Добавлен статус 'reserved' для витринных букетов ShowcaseItem
- inventory/models.py: добавлен статус 'reserved' в STATUS_CHOICES
- Миграция: 0004_add_reserved_status_to_showcaseitem.py
- Статус reserved используется для витринных букетов в отложенных заказах
- Жизненный цикл: available → in_cart → reserved → sold
2026-01-05 01:37:59 +03:00
ef0f935aa9 Debug logging for showcase return 2026-01-04 23:18:26 +03:00
8041ceb04a Исправлены баги витринных комплектов: резервы и валидация восстановления заказов 2026-01-04 22:53:53 +03:00
595cf6a018 Исправлены баги с дублированием резервов и Sale для витринных комплектов
Проблемы:
1. При продаже витринного комплекта через POS создавались дубликаты резервов
   - reserve_stock_on_item_create создавал новые резервы для витринного комплекта
   - Хотя резервы уже существовали от ShowcaseManager.reserve_kit_to_showcase

2. При переходе заказа в Completed создавались дубликаты Sale
   - ShowcaseManager.sell_showcase_items создавал Sale
   - Затем сигнал create_sale_on_order_completion создавал Sale повторно

3. При отмене заказа (Completed → Cancelled) терялась связь ShowcaseItem с резервами
   - ShowcaseItem возвращался на витрину, но резервы теряли поле showcase_item
   - При повторном переходе в Completed резервы дублировались

Исправления:

1. inventory/signals.py - reserve_stock_on_item_create (строки 165-180):
   - Добавлена проверка витринного комплекта (is_temporary && showcase)
   - Для витринных комплектов сигнал пропускает создание новых резервов
   - Привязка существующих резервов происходит в update_reservation_on_item_change

2. inventory/signals.py - create_sale_on_order_completion (строки 346-365):
   - Добавлена проверка уже обработанных резервов (status='converted_to_sale')
   - Сигнал пропускает витринные резервы, уже обработанные ShowcaseManager
   - Логирует информацию о пропущенных резервах

3. inventory/signals.py - rollback_sale_on_status_change (строки 746-774):
   - При возврате ShowcaseItem на витрину восстанавливается связь с резервами
   - Обновляется поле showcase_item в резервах через Reservation.objects.update()
   - Логируется количество восстановленных связей

4. inventory/services/showcase_manager.py - sell_showcase_items (строки 201-206):
   - Добавлена проверка статуса резерва перед созданием Sale
   - Если резерв уже в 'converted_to_sale', он пропускается
   - Защита от двойного списания одного резерва

Результат:
 Резервы создаются только один раз при размещении на витрине
 Sale создаются только один раз при продаже
 ShowcaseItem корректно возвращается на витрину со связью с резервами
 Остатки на складе корректные (60 → 55 после продажи, 60 после отмены)
 Нет дублирования при многократных переходах Completed ↔ Cancelled
2026-01-04 22:04:51 +03:00
b7db4cd162 conventional-commit
feat(inventory): introduce stock deficit notifications and base quantity tracking

- Added `quantity_base` field to reservation model for precise inventory calculations
- Implemented non-blocking stock deficit warnings during kit creation process
- Enhanced API responses with warning details for frontend display
- Updated terminal interface to show formatted stock shortage alerts

BREAKING CHANGE: API response structure now includes `warnings` array instead of previous stock warning format
2026-01-04 16:18:57 +03:00
a03f3df086 feat(inventory): add support for selling in negative stock
Implement functionality to allow sales even when stock is insufficient, tracking pending quantities and resolving them when new stock arrives via incoming documents. This includes new fields in Sale model (is_pending_cost, pending_quantity), updates to batch manager for negative write-offs, and signal handlers for automatic processing.

- Add is_pending_cost and pending_quantity fields to Sale model
- Modify write_off_by_fifo to support allow_negative flag and return pending quantity
- Update incoming document service to allocate pending sales to new batches
- Enhance sale processor and signals to handle pending sales
- Remove outdated tests.py file
- Add migration for new Sale fields
2026-01-04 12:27:10 +03:00
123f330a26 chore(migrations): update migration generation timestamps to latest time
- Updated generated timestamps in initial migrations of accounts, customers,
  inventory, orders, products, tenants, and user_roles apps
- Reflect new generation time from 08:35 to 23:23 on 2026-01-03
- No changes to migration logic or schema detected
- Ensures migration files align with most recent generation time for consistency
2026-01-04 02:29:49 +03:00
0f09702094 Добавлена защита от удаления дефолтных Склада и Витрины
Проблема: при создании тенанта автоматически создаются дефолтные
Склад и Витрина. Если пользователь удалит их, система может сломаться:
POS, создание заказов и резервирование перестанут работать.

Решение: реализована строгая валидация + мягкое удаление для витрин.

Изменения в inventory/views/warehouse.py:
- Добавлена валидация перед деактивацией склада:
  * Блокировка деактивации последнего активного склада
  * Проверка ненулевых остатков товаров
  * Проверка активных резервов
  * Предупреждение при деактивации дефолтного склада

Изменения в inventory/views/showcase.py:
- ShowcaseListView: по умолчанию показывает только активные витрины
- ShowcaseDeleteView: изменена логика с жесткого на мягкое удаление
- Добавлена валидация перед деактивацией витрины:
  * Блокировка деактивации последней активной витрины склада
  * Проверка активных резервов
  * Проверка физических экземпляров комплектов (ShowcaseItem)
  * Предупреждение при деактивации дефолтной витрины

Изменения в inventory/forms_showcase.py:
- Проверка уникальности названия витрины учитывает только активные

Изменения в inventory/admin.py:
- ShowcaseAdmin: добавлены методы delete_model() и delete_queryset()
  для блокировки удаления последней витрины через админку
- WarehouseAdmin: добавлены методы delete_model() и delete_queryset()
  для блокировки удаления последнего склада через админку

Преимущества:
 Система не сломается - всегда есть хотя бы один активный склад/витрина
 Данные в безопасности - мягкое удаление для обеих сущностей
 Понятные сообщения об ошибках для пользователя
 Защита работает как в UI, так и в Django Admin

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 19:52:01 +03:00
208c6b55de Консолидация миграций и добавление unit_service
- Обновлены начальные миграции для всех приложений
- Удалены устаревшие миграции для единиц измерения и SKU
- Добавлен новый сервис unit_service.py для управления единицами
- Обновлены команды инициализации данных тенанта
2026-01-03 12:09:31 +03:00
973e20bf60 Исправлен поиск товаров при приёмке: добавлен параметр skip_stock_filter
Проблема: при приёмке товаров отображались только товары с ненулевым
остатком на складе, товары с нулевым остатком не находились.

Решение: добавлен параметр skip_stock_filter в компонент поиска товаров,
который отключает фильтрацию по остаткам. Для приёмки этот параметр
включён по умолчанию.

Изменения:
- api_views.py: добавлен параметр skip_stock_filter в _apply_product_filters
- product_search_picker.html: добавлен data-атрибут skip_stock_filter
- product-search-picker.js: передача параметра в API
- incoming_document_detail.html: включён skip_stock_filter=True

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 23:49:01 +03:00
275bc1b78d Исправлена ошибка создания заказов в POS после рефакторинга модели доставки
- Обновлён pos/views.py: метод pos_checkout теперь создаёт Order и связанную модель Delivery

- Обновлён showcase_manager.py: метод sell_showcase_item_to_customer использует новую архитектуру

- Удалён устаревший скрипт create_demo_orders.py

- Исправлена ошибка 'property is_delivery of Order object has no setter'
2026-01-02 17:46:32 +03:00
f55f358e8f Исправлено: Sale теперь использует quantity_base вместо quantity
КРИТИЧНО: При создании Sale использовалось неправильное поле!

Проблема:

- При проведении заказа Sale создавался с reservation.quantity

- Это количество в ЕДИНИЦАХ ПРОДАЖИ, а не в базовых!

- Пример: 1 ветка списывала 1 банч вместо 0.05 банча

Решение:

- Строка 410: sale_quantity = reservation.quantity_base (для товаров)

- Строка 368: quantity=reservation.quantity_base (для комплектов)

- Fallback на .quantity для обратной совместимости

Теперь:

- Sale.quantity всегда в базовых единицах

- FIFO списание корректно

- StockBatch уменьшается на правильное количество
2026-01-02 15:06:03 +03:00
4ee7c0d23b Улучшено: оптимизация сигнала обновления Stock
- Добавлено 'quantity_base' в список полей, влияющих на Stock

- Теперь Stock пересчитывается при изменении quantity_base

- Обновлена документация сигнала update_stock_on_reservation_change
2026-01-02 14:47:16 +03:00
d2b49cca56 Исправлено: агрегация резервов теперь использует quantity_base
КРИТИЧНО: Все агрегации Reservation.quantity заменены на quantity_base

Проблемы и решения:

🔴 КРИТИЧНО - BatchManager.write_off_by_fifo():

  - Проблема: суммировал quantity вместо quantity_base

  - Влияние: FIFO расчет свободного товара был некорректен

  - Решение: aggregate(Sum('quantity_base')) в строках 118, 125

🟡 СРЕДНЯЯ ВАЖНОСТЬ - ShowcaseManager:

  - reserve_showcase_item(): обновление quantity и quantity_base (строка 403)

  - release_showcase_reservation(): обновление обоих полей (строка 481)

  - Теперь витринные резервы полностью консистентны

🟡 СРЕДНЯЯ ВАЖНОСТЬ - TransformationService:

  - confirm(): проверка доступности через quantity_base (строка 254)

  - Корректная валидация при трансформации товаров

🟢 НИЗКАЯ ВАЖНОСТЬ - WriteOffDocumentService:

  - update_item(): синхронизация quantity и quantity_base (строка 175)

  - Полнота данных в резервах документов списания

🟢 НИЗКАЯ ВАЖНОСТЬ - Сигналы (signals.py):

  - update_order_item_reservation(): обновление обоих полей для товаров

  - Для обычных товаров: quantity_base = quantity_in_base_units (строка 1081)

  - Для комплектов: quantity_base = quantity (компоненты в базовых) (строка 1107)

  - Добавлено обновление sales_unit при изменении OrderItem

Архитектура:

- Принцип: quantity_base ВСЕГДА содержит количество в базовых единицах

- Все агрегации резервов используют quantity_base для корректных расчетов

- quantity сохраняется для совместимости и отображения

- sales_unit хранит ссылку на единицу продажи для аудита
2026-01-02 14:46:02 +03:00
f34cfaeca0 Исправлено: отображение резервов и расчет quantity_reserved
- Проблема: debug_page показывал quantity вместо quantity_base для резервов

- Проблема: Stock.refresh_from_batches() суммировал quantity вместо quantity_base

- Решение:

  - debug_page.html: добавлены колонки для единиц продажи и базовых единиц

  - debug_page.html: теперь показывается quantity (ед.прод.) и quantity_base (базовые)

  - models.py: Stock.refresh_from_batches() теперь суммирует quantity_base

- Теперь quantity_reserved и quantity_free отображаются корректно в базовых единицах
2026-01-02 14:40:53 +03:00
25f2ba6b82 Исправлено: резервирование теперь работает с единицами продажи
- Проблема: сигнал на Order срабатывал ДО вычисления quantity_in_base_units в OrderItem.save()

- Решение: переместили резервирование на сигнал post_save для OrderItem

- Теперь quantity_in_base_units гарантированно вычислено перед резервированием

- Изменения:

  - signals.py: reserve_stock_on_order_create → reserve_stock_on_item_create

  - Сигнал теперь на OrderItem вместо Order

  - Резервы создаются для каждой позиции отдельно после её сохранения
2026-01-02 14:36:13 +03:00
c5e1ea06f9 Исправлено: резервирование и списание с учетом единиц продажи
- Проблема: при заказе 1 ветки резервировался 1 банч вместо 1/15

- Решение: используем quantity_in_base_units из OrderItem

- Изменения:

  - signals.py: reserve_stock_on_order_create использует quantity_in_base_units

  - signals.py: _create_or_update_reservation сохраняет sales_unit

  - signals.py: create_sale_on_order_completion берет quantity из резерва

  - sale_processor.py: уточнена документация параметра quantity
2026-01-02 13:45:22 +03:00
0d801680d7 Исправлено: ProductSearchPicker динамически создает <option> для HTML select
- Проблема: при выборе товара через ProductSearchPicker, select оставался пустым
- Решение: динамическое создание <option> элемента с выбранным товаром
- Затронутые файлы:
  - incoming_document_detail.html: функции selectProduct() и clearSelectedProduct()
  - writeoff_document/detail.html: аналогичные исправления
- Теперь компонент корректно работает во всех документах системы
2026-01-02 13:40:18 +03:00
e831c4fb6e feat(products): реализована система единиц продажи на фронтенде
Добавлена полноценная интеграция единиц измерения (UoM) для продажи
товаров в разных единицах с автоматическим пересчётом цен и остатков.

## Основные изменения:

### Backend
- Расширен API поиска товаров (api_views.py): добавлена сериализация sales_units
- Создан новый endpoint get_product_sales_units_api для загрузки единиц с остатками
- Добавлено поле sales_unit в OrderItemForm и SaleForm с валидацией
- Созданы CRUD views для управления единицами продажи (uom_views.py)
- Обновлена ProductForm: использует base_unit вместо устаревшего unit

### Frontend
- Создан модуль sales-units.js с функциями для работы с единицами
- Интегрирован в select2-product-search.js: автозагрузка единиц при выборе товара
- Добавлены контейнеры для единиц в order_form.html и sale_form.html
- Реализовано автоматическое обновление цены при смене единицы продажи
- При выборе базовой единицы цена возвращается к базовой цене товара

### UI
- Добавлены страницы управления единицами продажи в навбар
- Созданы шаблоны: sales_unit_list.html, sales_unit_form.html, sales_unit_delete.html
- Добавлены фильтры по товару, единице, активности и дефолтности

## Исправленные ошибки:
- Порядок инициализации: обработчики устанавливаются ДО триггера события change
- Цена корректно обновляется при выборе единицы продажи
- При выборе "Базовая единица" возвращается базовая цена товара

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-02 12:35:01 +03:00
5b68f14bb4 feat(products): add support for product sales units
Add new models UnitOfMeasure and ProductSalesUnit to enable selling products in different units (e.g., bunches, kg). Update Product model with base_unit field and methods for unit conversions and availability. Extend Sale, Reservation, and OrderItem models with sales_unit fields and snapshots. Modify SaleProcessor to handle quantity conversions. Include admin interfaces for managing units. Add corresponding database migrations.
2026-01-02 02:09:44 +03:00