Commit Graph

830 Commits

Author SHA1 Message Date
e8d232158c Разрешён NULL для delivery_date в черновиках заказов
- orders/models/delivery.py: delivery_date теперь null=True, blank=True
- Валидация: для черновиков дата необязательна, для обычных заказов - обязательна
- Миграция: 0003_allow_null_delivery_date_for_drafts.py
2026-01-05 01:35: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
666e007931 feat(products): заменить чекбокс наличия на селект статуса склада в подборе товаров
Заменен чекбокс "только в наличии" на выпадающий список с опциями: все товары, в наличии, не в наличии. Обновлена логика фильтрации в API и интерфейсе.
2026-01-04 19:41:28 +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
bcda94f09a Перемещена папка docker в myproject и защита секретов
- Все docker-файлы теперь в myproject/docker/
- Добавлен docker/.env.docker в gitignore для защиты секретов
- Сохранена обратная совместимость с существующими настройками
- Структура проекта стала более организованной
2026-01-04 00:31:02 +03:00
40d1c5eff6 chore(deps): sort requirements.txt alphabetically 2026-01-03 21:32:28 +03:00
95036ed285 Добавлен настраиваемый экспорт клиентов с выбором полей и форматов
Реализован полностью новый функционал экспорта клиентов с возможностью
выбора полей, формата файла (CSV/XLSX) и сохранением предпочтений.

Ключевые изменения:

1. CustomerExporter (import_export.py):
   - Полностью переписан класс с поддержкой динамического выбора полей
   - Добавлена конфигурация AVAILABLE_FIELDS с метаданными полей
   - Реализован метод get_available_fields() для фильтрации по ролям
   - Новый метод export_to_xlsx() с автоподстройкой ширины столбцов
   - Форматирование ContactChannel с переводами строк
   - Поддержка фильтрации queryset

2. CustomerExportForm (forms.py):
   - Динамическое создание checkbox полей на основе роли пользователя
   - Выбор формата файла (CSV/XLSX) через radio buttons
   - Валидация выбора хотя бы одного поля

3. View customer_export (views.py):
   - КРИТИЧНО: Изменён декоратор с @manager_or_owner_required на @owner_required
   - Обработка GET (редирект) и POST запросов
   - Применение фильтров CustomerFilter из списка клиентов
   - Оптимизация с prefetch_related('contact_channels')
   - Сохранение настроек экспорта в session

4. UI изменения:
   - Создан шаблон customer_export_modal.html с модальным окном
   - Обновлён customer_list.html: кнопка экспорта с проверкой роли
   - JavaScript для восстановления сохранённых настроек из session
   - Отображение количества экспортируемых клиентов
   - Бейдж "Только для владельца" на поле баланса кошелька

Безопасность:
- Экспорт доступен ТОЛЬКО владельцу тенанта (OWNER) и superuser
- Поле "Баланс кошелька" скрыто от менеджеров на уровне формы
- Двойная проверка роли при экспорте баланса
- Кнопка экспорта скрыта в UI для всех кроме owner/superuser

Функциональность:
- Выбор полей: ID, имя, email, телефон, заметки, каналы связи, баланс, дата создания
- Форматы: CSV (с BOM для Excel) и XLSX
- Учёт текущих фильтров и поиска из списка клиентов
- Сохранение предпочтений между экспортами в session
- Исключение системного клиента из экспорта

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 21:12:08 +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
e6fb30aa02 Удалены тестовые CSV файлы для импорта и валидации клиентов 2026-01-03 17:25:51 +03:00
6c3b970395 Исправлен Select2 поиск товаров при создании группы вариантов
Проблема: при создании новой группы вариантов (VariantGroup) поиск
товаров через Select2 не работал. При редактировании существующих
групп всё работало корректно.

Причина: отсутствовали проверки инициализации Select2, обработка
ошибок AJAX запросов и валидация параметров.

Изменения:

1. select2-product-init.html - улучшена функция initProductSelect2:
   - Добавлена валидация входных параметров (element, apiUrl)
   - Добавлена проверка загрузки jQuery и Select2
   - Улучшена проверка повторной инициализации
   - Добавлен try-catch для обработки ошибок
   - Функция возвращает boolean (успех/неудача)
   - Добавлено логирование для отладки

2. variantgroup_form.html - улучшены все функции работы с формой:

   initSelect2ForRow:
   - Добавлена проверка существования row и select элемента
   - Удаление старых обработчиков перед инициализацией
   - Проверка результата инициализации Select2

   updateRowData:
   - Добавлен timeout (5 сек) для fetch запросов
   - Добавлена проверка статуса HTTP ответа
   - Улучшена обработка ошибок с fallback данными
   - Добавлено логирование ошибок

   DOMContentLoaded инициализация:
   - Добавлена валидация контейнера, totalFormsInput и apiUrl
   - Задержка перед инициализацией существующих строк (100ms)
   - Проверка успешности инициализации перед updateRowData

   Добавление нового товара:
   - Задержка (50ms) перед инициализацией Select2
   - Повторная попытка при неудаче (через 500ms)
   - Улучшена надежность работы с динамическими элементами

Результат: Select2 поиск работает корректно как при создании новых
групп, так и при редактировании существующих. Добавлена надежная
обработка ошибок и логирование для отладки.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 17:11:47 +03:00
e6fd44ef6b Добавлен фильтр 'Есть канал связи' в список клиентов
- Новый фильтр has_contact_channel показывает клиентов с записями в ContactChannel
- Проверяет наличие альтернативных контактов (Instagram, Telegram и т.д.)
- Добавлен чекбокс в форму фильтров с flex-wrap для переноса
- Обновлено условие показа кнопки Очистить
- Фильтр автоматически сохраняется в пагинации через url_replace
2026-01-03 15:38:22 +03:00
36cca23b60 Рефакторинг пагинации через custom template tag url_replace
- Создан элегантный тег для автоматического сохранения GET-параметров
- Код пагинации сократился в 10 раз
- Переиспользуется в любых шаблонах проекта
- 100 процентов Django-way без хаков
2026-01-03 15:19:57 +03:00
f1f44a93b2 Исправлена передача GET-параметров в пагинации
- Убран нерабочий хак с params.pop
- Все ссылки пагинации теперь явно передают параметры:
  * q (поисковый запрос)
  * has_notes (фильтр заметок)
  * no_phone (фильтр отсутствия телефона)
  * no_email (фильтр отсутствия email)
- Пагинация теперь работает корректно с сохранением всех фильтров
2026-01-03 15:15:45 +03:00
b27fb1236a Добавлена нумерация страниц в пагинацию
- Вместо текста 'X / Y' теперь кликабельные цифры страниц
- Показывается до 10 страниц (±5 от текущей)
- Текущая страница выделена (active)
- Умная логика: если на странице 15, показывает 10-20
- Все параметры фильтров сохраняются при клике на номер страницы
2026-01-03 15:13:41 +03:00
ce67062ac3 Исправлено сохранение фильтров при пагинации
- Пагинация теперь сохраняет ВСЕ GET-параметры (query, has_notes, no_phone, no_email)
- Использован request.GET.copy() и params.urlencode для передачи всех параметров
- Фильтры больше не сбрасываются при переходе между страницами
2026-01-03 15:10:05 +03:00
5ded404346 Добавлены фильтры для списка клиентов через django-filter
- Создан CustomerFilter с тремя фильтрами:
  * Есть заметки (has_notes)
  * Нет телефона (no_phone)
  * Нет email (no_email)

- Обновлен views.py для использования фильтров
- Добавлены чекбоксы фильтров в шаблон списка клиентов
- Фильтры работают совместно с поиском
- Кнопка Очистить отображается при активных фильтрах или поиске
2026-01-03 14:50:24 +03:00
63a965ae5c Добавлен столбец Заметки в список клиентов
- Новый столбец Notes в таблице клиентов
- Текст обрезается до 10 слов (truncatewords)
- Максимальная ширина 300px с ellipsis
- При наведении показывается полный текст через title
- Если заметок нет, отображается тире (—)
2026-01-03 14:46:23 +03:00
a2ce8d648f Исправлена логика прогресс-бара импорта: форма отправляется до блокировки UI
- Форма начинает отправку сразу при submit
- Прогресс-бар и защита включаются через 10ms (после начала отправки)
- Предупреждение появляется только при попытке закрыть страницу во время импорта
- Импорт корректно выполняется на сервере
2026-01-03 14:35:47 +03:00
b201c71311 Улучшение импорта клиентов: предобработка данных, умное слияние, прогресс-бар
- Добавлена предобработка email перед валидацией:
  * Исправление типичных опечаток (mail ru -> mail.ru, .ry -> .ru)
  * Удаление пробелов и двойных @@
  * Умное добавление @ для популярных доменов
  * Исправление доменов без точки (gmail -> gmail.com)

- Улучшена нормализация телефонов:
  * Умное добавление кода страны (+375, +7, +380)
  * Конверсия старого формата 8XXXXXXXXXX -> +7XXXXXXXXXX
  * Проверка длины номера (10-15 символов)
  * Поддержка локальных белорусских номеров (9 цифр)

- Реализована идемпотентность импорта:
  * Notes не раздуваются при повторных импортах (метод _append_unique_note)
  * ContactChannel не дублируется для одного клиента
  * Проверка существования альтернативных контактов по customer+type+value

- Добавлен прогресс-бар и защита от закрытия:
  * Визуальный прогресс-бар с анимацией и динамическим текстом
  * Блокировка формы во время импорта
  * Предупреждение браузера при попытке закрыть страницу

- Создана команда clear_anatol_customers для тестирования

- Добавлен тестовый файл test_customer_preprocess.csv с примерами исправляемых ошибок
2026-01-03 14:30:18 +03:00
cca9a908c9 Uluchshena paginaciya na stranice klientov: kompaktniy format s knopkami pervaya/poslednyaya/predydushchaya/sleduyushchaya 2026-01-03 13:34:09 +03:00
3248fadffa Dobavlen funkcional importa i eksporta klientov s validaciey i umnym sliyaniem kontaktov 2026-01-03 13:33:34 +03:00
208c6b55de Консолидация миграций и добавление unit_service
- Обновлены начальные миграции для всех приложений
- Удалены устаревшие миграции для единиц измерения и SKU
- Добавлен новый сервис unit_service.py для управления единицами
- Обновлены команды инициализации данных тенанта
2026-01-03 12:09:31 +03:00
030d5ad198 Добавлено отображение единиц продажи на странице товара
На странице детализации товара теперь отображается таблица с единицами
продажи: название, единица измерения, коэффициент, цена, мин. количество
и шаг. Единица по умолчанию выделена зелёным.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 00:17:14 +03:00
d28a845664 Исправлено форматирование остатков: показ дробных значений
Изменён floatformat с :0 на :-3 для корректного отображения
дробных остатков товаров (до 3 знаков после запятой).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 00:16:55 +03:00
b4f42f97b0 Исправлена валидация SKU: разрешено сохранение существующего артикула
При редактировании товара проверка зарезервированных префиксов теперь
пропускается, если артикул не изменился. Это позволяет редактировать
товары с автоматически сгенерированными артикулами.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 00:16:33 +03:00
7ccdbbdfb5 Упрощение генерации SKU: удалён автоматический суффикс варианта
Удалена функция parse_variant_suffix и логика автоматического добавления
суффикса варианта к артикулу товара. SKU теперь всегда имеет формат
PROD-XXXXXX без дополнительных суффиксов.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 23:50:26 +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
5ba38f39f5 Упрощение base.py: удаление неиспользуемого кода
- Удалён импорт неиспользуемых менеджеров (ActiveManager, SoftDeleteManager, SoftDeleteQuerySet)
- Удалён неиспользуемый active_objects manager
- Заменены хаки __import__ на нормальные импорты (slugify, unidecode)
- Перенесён IntegrityError в импорты модуля
- Добавлен TODO для унификации системы soft delete

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 23:18:44 +03:00
d87e6a4e65 Финальная очистка кода order_form.html 2026-01-02 19:48:02 +03:00
00224ba5e6 Удалены избыточные логи из order_form.html
- Убраны детальные console.log при загрузке товаров из черновика

- Оставлены только критичные логи ошибок

- Код стал чище и компактнее
2026-01-02 19:11:21 +03:00
676cfad401 Исправлено отображение единиц продажи при открытии отложенного заказа
- Добавлена функция loadAndDisplaySalesUnitsFromHidden для загрузки UI единиц продажи из черновика

- При загрузке товара из черновика теперь автоматически отображается и устанавливается сохранённая единица продажи

- Теперь при открытии отложенного заказа с 0.3 кг корректно отображается килограмм, а не базовая единица
2026-01-02 18:41:16 +03:00
2995710a3e Исправлена передача единиц продажи при создании отложенного заказа из POS
- В terminal.js добавлена передача sales_unit_id в данные черновика заказа

- В order_form.html добавлено заполнение поля sales_unit при предзаполнении из черновика

- Теперь при создании отложенного заказа с товаром в единицах продажи сохраняется корректная единица измерения
2026-01-02 18:33:51 +03:00
9bd06cf5c6 Изменено поле quantity в OrderItem для поддержки дробных количеств
- Поле quantity изменено с PositiveIntegerField на DecimalField(max_digits=10, decimal_places=3)

- Это необходимо для корректной работы с единицами продажи (например, 2.5 банча)

- Создана миграция 0004_change_orderitem_quantity_to_decimal

- Теперь POS корректно обрабатывает товары с дробными количествами в единицах продажи
2026-01-02 18:01:49 +03:00
f0327b264c Добавлена поддержка единиц продажи в POS checkout
- При создании OrderItem теперь передаётся sales_unit из данных корзины

- Это позволяет корректно рассчитывать total_amount для товаров с единицами продажи

- Исправлена ошибка когда сумма заказа была 0 при использовании единиц продажи
2026-01-02 17:51:01 +03:00
eab4f8a4ae Смягчена валидация времени доставки: разрешены равные времена начала и окончания
- Изменена проверка с >= на > в Delivery.clean()

- Равные времена разрешены для POS-продаж (самовывоз в точное время)

- Обновлены сообщения об ошибках валидации
2026-01-02 17:47:30 +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
1ead77b2d8 Добавлено округление количества для корректного отображения в POS
Проблема:

- JavaScript float arithmetic даёт погрешность при вычислениях

- На карточке товара показывалось -0.050000000000044

- Происходило при: available - reserved - inCart

Решение:

- Добавлена функция roundQuantity(value, decimals=3)

- Округляет результат вычислений до 3 знаков после запятой

- Применяется ТОЛЬКО для отображения, не для расчётов

- Используется для: free, reserved, inCart в карточках товаров

Результат:

- Отображение: -0.05 вместо -0.050000000000044

- Данные с бэка остаются точными (строка)

- Погрешность устранена только визуально

Примечание:

- Округление в JS НЕИЗБЕЖНО для отображения

- Это НЕ маскировка - это правильное форматирование

- Бэкенд уже отдаёт точные данные как строки
2026-01-02 15:38:41 +03:00
4d121e95af Исправлено: передача free_qty как строки для сохранения точности
Проблема:

- free_qty передавался как float(decimal) в JSON API

- При конвертации Decimal→float терялась точность

- JavaScript показывал -0.050000000000044 вместо -0.05

Решение:

- free_qty теперь передаётся как строка: str(free_qty)

- Добавлено отдельное поле free_qty_sort (float) для сортировки

- После сортировки free_qty_sort удаляется из результата

- JavaScript parseFloat() корректно парсит строку без потери точности

Результат:

- Отображение остатков точное: -0.05 вместо -0.050000000000044

- Нет округления на фронте - видны реальные данные

- Сортировка по остаткам работает корректно
2026-01-02 15:30:00 +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
baa9780ce1 Исправлено: quantity обнуляется при смене единицы продажи
- Проблема: при смене единицы продажи с базовой на другую поле quantity визуально показывало 1, но при отправке формы значение терялось

- Решение: при смене единицы проверяем quantity и устанавливаем минимальное значение если оно пустое/нулевое

- Изменения:

  - sales-units.js: добавлена проверка и установка min_quantity при смене единицы
2026-01-02 14:01:42 +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