Commit Graph

554 Commits

Author SHA1 Message Date
52baf4295f fix: add missing imports and routes for password setup flow
- Added get_user_model import in accounts/views.py
- Fixed User variable scope in password_setup_confirm view
- Added accounts URLs to urls_public.py for password setup on main domain
- Password setup link now accessible from public schema

Technical details:
- get_user_model() needed to be imported from django.contrib.auth
- User model reference moved outside try block to fix UnboundLocalError
- accounts.urls included in public URLconf for /accounts/setup-password/ route

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:27:23 +03:00
4404bebba7 fix: add missing RoleService import and improve admin buttons layout
- Fixed UnboundLocalError by importing RoleService before use
- Changed action buttons to vertical stack layout to prevent overlap
- Buttons now use flexbox column layout with proper spacing

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:20:14 +03:00
4da6d6922e fix: trigger tenant activation when status changed to approved
Added save_model override to TenantRegistrationAdmin to catch status
changes from PENDING to APPROVED and trigger the full activation flow.

Previously, changing status via admin form only saved the status field
without creating the tenant, sending emails, etc.

Now both methods work:
- Click "Activate" button (via GET parameter)
- Change status dropdown and save (via save_model)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:15:43 +03:00
fb4bcf37ec feat: implement password setup link for tenant registration
When admin approves tenant registration:
- Owner account created in tenant schema (in addition to admin@localhost)
- Owner assigned 'owner' role with full permissions
- Password setup email sent with secure 7-day token link
- Owner sets password via link and auto-logs into their shop

Key changes:
- Added password_setup_token fields to TenantRegistration model
- Created tenants/services.py with formatted email service
- Modified _approve_registration to create owner account
- Added password_setup_confirm view with token validation
- Created password setup template and URL route
- Added admin action to resend password setup emails

Security:
- Token expires after 7 days
- Password not transmitted in email (secure setup link)
- Owner account inactive until password set
- Admin@localhost preserved for system administrator access

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 22:08:18 +03:00
14cc73722f feat: add user roles management UI with owner access control
- Added role management views (list, create, edit, delete)
- Created user_roles URL routing
- Added role management templates with Bootstrap styling
- Updated navbar with Roles link for owners and superusers
- Enhanced decorators and mixins with superuser bypass
- Added assign_owner_role.py utility script

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 21:24:27 +03:00
f4e7ad0aac feat: implement user roles system with tenant isolation
Добавлена система ролей пользователей для управления доступом в multi-tenant приложении.

Новые роли:
- Владелец (Owner): полный доступ, управление пользователями
- Менеджер (Manager): управление заказами, клиентами, товарами, складом
- Флорист (Florist): работа с заказами и складскими операциями
- Курьер (Courier): роль создана, права будут определены позже

Архитектура:
- Роли автоматически изолируются по тенантам через django-tenants (TENANT_APPS)
- Не требуется FK на Client/Tenant - изоляция через PostgreSQL schemas
- Роли автоматически создаются при создании нового тенанта

Компоненты:
- user_roles/models.py: модели Role и UserRole
- user_roles/services.py: RoleService для управления ролями
- user_roles/decorators.py: @role_required, @owner_required
- user_roles/mixins.py: RoleBasedAdminMixin, OwnerOnlyAdminMixin
- user_roles/admin.py: админка для управления ролями
- user_roles/management/commands/init_roles.py: команда для инициализации

Изменения:
- accounts/models.py: добавлены helper методы (is_owner, has_role, etc)
- settings.py: добавлен user_roles в TENANT_APPS
- tenants/admin.py: автосоздание ролей при создании тенанта

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 18:06:47 +03:00
eef2cb820f refactor: remove unused cleanup_draft_orders management command
The cleanup_draft_orders command was no longer needed in the project.
Also updated test output file with latest test results.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 16:38:00 +03:00
93e4c9b600 test: update order status transition tests with real system statuses
- Added all real order statuses: new, confirmed, in_assembly, in_delivery, return
- Test #2: expanded to test cancellation from 4 different statuses (new, confirmed, in_assembly, in_delivery)
- Test #3: updated to use new → cancelled → in_assembly flow
- Test #4: updated to use in_delivery status instead of packing
- All 5 tests passing successfully
2025-12-01 13:46:17 +03:00
f4bb9d9e1e Исправлено: использовать hex коды вместо названий цветов в тестах
Проблема:
- Поле OrderStatus.color имеет max_length=7 (для hex типа #FF5733)
- В тестах использовались названия: 'secondary', 'warning', 'success' (9 символов)
- PostgreSQL выдавал ошибку: value too long for type character varying(7)

Решение:
- Заменены названия цветов на hex коды:
  * 'secondary' → '#9E9E9E' (серый)
  * 'warning' → '#FF9800' (оранжевый)
  * 'success' → '#4CAF50' (зелёный)
  * 'danger' → '#F44336' (красный)
  * 'info' → '#17A2B8' (голубой)
2025-12-01 13:03:31 +03:00
d023d1ab25 Added 5 critical tests for order status transitions
Tests cover:
1. Multiple status transitions (draft->completed->cancelled->completed)
2. Cancellation from draft (reservation release)
3. Un-cancellation to pending (reservation restore)
4. Creating order with intermediate status
5. Rollback from completed to draft

Each test verifies:
- Stock state (available, reserved, free)
- Reservation status transitions
- Sale creation/deletion without duplicates
- StockBatch quantity changes

Files:
- inventory/tests/test_order_status_transitions.py (570 lines)
- inventory/tests/README.md (138 lines)
- inventory/tests/__init__.py
- run_status_tests.bat (launch script)
2025-12-01 12:42:37 +03:00
1168659df8 Fixed: Re-reserve stock when transitioning from cancelled to other statuses 2025-12-01 12:22:50 +03:00
702d42e943 Removed 'Not set' status option from order list dropdown 2025-12-01 12:19:16 +03:00
9f062f527d Fixed critical bug: release reservations on draft->cancelled transition 2025-12-01 11:57:06 +03:00
cdaf43afbd Improved incoming form validation: require cost price, better error highlighting 2025-12-01 11:41:44 +03:00
11c76ece53 Optimized catalog view: filter only active products/kits in prefetch 2025-12-01 11:20:39 +03:00
f7b62b45f3 Исправлен метод delete(): теперь делает статус СНЯТ (discontinued), а не АРХИВНЫЙ (archived) 2025-12-01 11:09:18 +03:00
4cb5a605f8 Simplified product/kit deletion message - available for recovery for some time 2025-12-01 11:07:59 +03:00
c670406ae0 Исправлено извлечение ID товаров из API в форме поступления
Проблема:
- Поиск товаров возвращал пустые результаты
- API /products/api/search-products-variants/ возвращает ID в формате 'product_123'
- Форма incoming ожидает числовой ID (123)
- Select2 не мог сохранить значение из-за несовпадения формата

Решение:
- Добавлена функция processResults в AJAX настройках Select2
- Извлекаем числовой ID из строки 'product_123' -> '123'
- Обрабатываем как группированные результаты, так и плоские
- Сохраняем остальные поля (text, sku, price, actual_price)

Логика обработки:
1. Проверяем наличие children (группа)
2. Если группа - обрабатываем каждый item в children
3. Если плоский список - обрабатываем напрямую
4. Используем .replace('product_', '') для извлечения ID

Теперь Select2 корректно:
- Показывает список товаров
- Сохраняет выбранное значение
- Отправляет числовой ID в форму
2025-12-01 10:31:25 +03:00
dc39f56b9a Исправлен поиск товаров в форме массового поступления
Проблема:
- На странице /inventory/incoming/create/ не работал поиск товаров
- Использовался обычный <select> с предзагруженным списком всех товаров
- При большом количестве товаров список был неудобным
- Невозможно было искать товары по названию в реальном времени

Решение:
- Заменён обычный <select> на Select2 с AJAX автокомплитом
- Подключен API endpoint /products/api/search-products-variants/
- Поиск товаров работает в реальном времени (с задержкой 250ms)
- Минимальная длина поиска: 0 символов (можно открыть весь список)
- Поддержка русского языка
- Theme: bootstrap-5 для визуального соответствия

Изменения:
- Удалён предзагруженный список товаров из контекста шаблона
- Добавлена инициализация Select2 для каждой новой строки товара
- При удалении строки вызывается select2('destroy') для очистки
- Исправлена логика восстановления товаров при ошибке валидации
- Используется Option API для программной установки значений

Технические детали:
- jQuery и Select2 уже подключены в base.html
- Не дублируем подключение библиотек
- Используем событие 'change' для Select2 вместо 'input'
- Кэширование AJAX запросов включено

Теперь поиск товаров работает корректно! 🎉
2025-12-01 10:29:57 +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
5300b83565 Исправлен фильтр по номеру заказа на отладочной странице
Проблема:
- Фильтр по заказу не работал
- order_number в форме передается как строка (ORD-103 или 103)
- В модели Order поле order_number имеет тип PositiveIntegerField
- Не было преобразования строки в число

Исправление:
- Добавлен парсинг номера заказа:
  * Если формат 'ORD-103' -> извлекается число 103
  * Если просто '103' -> преобразуется в число 103
- Добавлена обработка ошибок (try-except)
- Добавлена фильтрация таблицы заказов (показывать только выбранный)

Теперь фильтр работает с любым форматом ввода:
- ORD-103 ✓
- ord-103 ✓
- 103 ✓
2025-12-01 10:10:49 +03:00
d712da1816 Добавлены столбцы статуса оплаты и суммы оплаты в таблицу заказов
Добавлено:
- Столбец 'Статус оплаты' с цветовой индикацией:
  * Зеленый бейдж - Оплачен полностью
  * Желтый бейдж - Частично оплачен
  * Красный бейдж - Не оплачен
- Столбец 'Оплачено' показывает: amount_paid / total_amount
  * Например: 500.00 / 1000.00 (оплачено 500 из 1000)

Использованные поля:
- payment_status (paid/partial/unpaid)
- amount_paid (сумма внесенная клиентом)
- total_amount (итоговая сумма заказа)
2025-12-01 10:08:47 +03:00
a8dc3897c5 Исправлено отображение суммы заказа в таблице Order
Проблема:
- В шаблоне использовалось несуществующее поле total_price
- Фактическое поле в модели Order называется total_amount

Исправление:
- Заменено order.total_price на order.total_amount
- Теперь итоговая сумма заказа отображается корректно
2025-12-01 10:07:58 +03:00
992db6bd69 Исправлено отображение себестоимости в таблице StockBatch
Проблема:
- В шаблоне использовалось несуществующее поле cost_per_unit
- Фактическое поле в модели StockBatch называется cost_price

Исправление:
- Заменено batch.cost_per_unit на batch.cost_price
- Теперь закупочная цена партии отображается корректно
2025-12-01 10:06:51 +03:00
5c61789b71 Исправлено отображение себестоимости в таблице SaleBatchAllocation
Проблема:
- В шаблоне использовалось несуществующее поле cost_per_unit
- Фактическое поле в модели называется cost_price

Исправление:
- Заменено alloc.cost_per_unit на alloc.cost_price
- Теперь себестоимость за единицу отображается корректно
2025-12-01 10:05:32 +03:00
337335ec58 Добавлена отладочная страница для суперюзеров + исправлены ошибки полей
Реализация:
- Создан view debug_inventory_page (только для суперюзеров)
- URL: /inventory/debug/
- Компактный дизайн с минимальными отступами (10-11px)
- Ссылка 🔧 Debug в navbar (видна только суперюзерам)

Функционал:
1. Показывает полную картину инвентаризации на одной странице:
   - Заказы (Order) - номер, статус, покупатель, is_returned
   - Остатки (Stock) - доступно, зарезервировано, свободно
   - Партии (StockBatch) - количество, активность, дата поступления
   - Резервы (Reservation) - статус, заказ, все даты
   - Продажи (Sale) - количество, цена продажи, заказ
   - Списания (SaleBatchAllocation) - откуда списано, сколько

2. Фильтры:
   - По товару (dropdown)
   - По номеру заказа (текстовое поле)
   - По складу (dropdown)

3. UI:
   - Цветовая индикация статусов резервов
   - Бейджи для ключевых данных
   - Компактные таблицы Bootstrap
   - Неактивные партии выделены красным

Исправления:
- Reservation.created_at → reserved_at (у модели нет created_at)
- Sale.created_at → date (дата операции хранится в поле date)
- Product.is_active → archived_at__isnull=True (используется soft delete)
- Удалена колонка себестоимости из Sale (это поле не хранится в модели)

Файлы:
- inventory/views/debug_views.py - новый view
- inventory/templates/inventory/debug_page.html - шаблон
- inventory/urls.py - добавлен роут
- templates/navbar.html - добавлена ссылка

Юзкейс:
Суперюзер принимает товар → оформляет заказ → меняет статусы →
переходит на /inventory/debug/ → видит полную картину изменений
2025-12-01 10:04:00 +03:00
8e036ba5e1 Исправлено поле created_at на reserved_at для Reservation
Проблема:
- У модели Reservation нет поля created_at
- Есть поле reserved_at для даты создания резерва

Исправление:
- В view изменена сортировка order_by('-reserved_at')
- В шаблоне изменено отображение даты res.reserved_at
2025-12-01 09:59:45 +03:00
6bb15db5a0 Добавлена отладочная страница для суперюзеров (Inventory Debug)
Реализация:
- Создан view debug_inventory_page (только для суперюзеров)
- URL: /inventory/debug/
- Компактный дизайн с минимальными отступами и маленьким шрифтом (10-11px)

Функционал:
1. Показывает полную картину инвентаризации на одной странице:
   - Заказы (Order) - номер, статус, покупатель, is_returned
   - Остатки (Stock) - доступно, зарезервировано, свободно
   - Партии (StockBatch) - количество, активность, дата поступления
   - Резервы (Reservation) - статус (reserved/converted_to_sale/released), заказ, даты
   - Продажи (Sale) - количество, цены, заказ
   - Списания (SaleBatchAllocation) - откуда списано, сколько

2. Фильтры:
   - По товару (dropdown с названием и SKU)
   - По номеру заказа (текстовое поле)
   - По складу (dropdown)
   - Кнопка 'Применить' и 'Сбросить'

3. UI:
   - Цветовая индикация статусов резервов
   - Бейджи для ключевых данных
   - Компактные таблицы Bootstrap
   - Неактивные партии выделены красным
   - Ограничение в 100 записей на таблицу для производительности

4. Навигация:
   - Ссылка 🔧 Debug в navbar (видна только суперюзерам)
   - Красный цвет для видимости

Юзкейс:
Суперюзер принимает товар на склад → оформляет заказ → меняет статусы →
переходит на /inventory/debug/ → видит полную картину всех изменений

Файлы:
- inventory/views/debug_views.py - новый view
- inventory/templates/inventory/debug_page.html - шаблон
- inventory/urls.py - добавлен роут
- templates/navbar.html - добавлена ссылка для суперюзеров
2025-12-01 09:57:06 +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
490e5d5401 Добавлены интеграционные тесты создания тенантов
Создан файл tenants/tests/test_tenant_creation.py с 7 E2E тестами:

1. test_new_tenant_gets_all_5_payment_methods (КРИТИЧЕСКИЙ)
   - Проверяет что новый тенант получает все 5 способов оплаты
   - Включая account_balance (основной баг который исправили)
   - Проверяет правильность порядка, флагов, названий

2. test_new_tenant_gets_order_statuses
   - Проверяет создание системных статусов заказов
   - Минимум 3 статуса (draft, completed и другие)

3. test_new_tenant_gets_system_customer
   - Проверяет создание системного клиента
   - Для анонимных продаж

4. test_new_tenant_gets_superuser
   - Проверяет создание суперпользователя
   - С email из настроек TENANT_ADMIN_EMAIL

5. test_new_tenant_gets_domain
   - Проверяет создание домена
   - Формат: {schema_name}.localhost

6. test_registration_status_changes_to_approved
   - Проверяет изменение статуса заявки
   - PENDING → APPROVED

7. test_complete_tenant_onboarding (КОМПЛЕКСНЫЙ E2E)
   - Проверяет весь процесс онбординга
   - Все предыдущие проверки в одном тесте
   - Красивый вывод результата в консоль

Особенности:
- TransactionTestCase для работы с реальными схемами
- Создание администратора в setUp для каждого теста
- Автоматическая очистка схем в tearDown
- Вызов реального метода _approve_registration из админки
- Полное тестирование процесса как в продакшене

Результат: 2 главных теста прошли успешно ✓
test_new_tenant_gets_all_5_payment_methods: OK (10s)
test_complete_tenant_onboarding: OK (9.5s)
2025-12-01 01:41:01 +03:00
8a64b569bd Добавлены тесты для способов оплаты
Создан файл orders/tests/test_payment_methods.py с комплексными тестами:

1. PaymentMethodCreationTest (6 тестов)
   - Проверка создания всех 5 способов оплаты через команду
   - Проверка системных флагов и активности
   - Проверка правильности порядка сортировки
   - Проверка идемпотентности команды
   - Критический тест наличия account_balance

2. PaymentMethodMultiTenantTest (2 теста)
   - Проверка изоляции данных между тенантами
   - Проверка кастомных способов оплаты в разных тенантах

3. PaymentMethodTransactionTest (7 тестов)
   - Проверка связи PaymentMethod.transactions
   - Проверка создания транзакций
   - Проверка изоляции транзакций по способам оплаты
   - Проверка защиты от удаления (PROTECT)
   - Критический тест использования account_balance
   - Исправление бага obj.payments → obj.transactions

4. PaymentMethodOrderingTest (2 теста)
   - Проверка сортировки по полю order
   - Проверка что account_balance первый (order=0)

Особенности тестирования:
- Использование TenantTestCase для изоляции тенантов
- Использование TransactionTestCase для мультитенантных тестов
- Ручное создание/удаление схем для безопасности
- Проверка изоляции данных между схемами

Результат: 15 тестов, все прошли успешно ✓
2025-12-01 01:30:23 +03:00
7188b11f65 Единый источник истины для способов оплаты
Проблема #1: Дублирование кода способов оплаты
- В tenants/admin.py был полный список способов оплаты (45 строк)
- В orders/management/commands/create_payment_methods.py был другой список
- При создании тенанта отсутствовал способ 'account_balance'
- Нарушение DRY принципа

Решение: Single Source of Truth
- Единственный источник истины: команда create_payment_methods
- В tenants/admin.py заменено дублирование на call_command()
- Удалено 45 строк дублирующего кода
- Теперь все тенанты получают одинаковый полный список

Проблема #2: AttributeError в админке PaymentMethod
- obj.payments.count() вызывал ошибку
- В модели Transaction связь называется 'transactions', а не 'payments'

Решение: Исправлено в orders/admin.py
- obj.payments → obj.transactions (2 места)
- Админка PaymentMethod теперь работает корректно

Для тенанта buba:
- Создан скрипт add_payment_methods_to_buba.py
- Добавлен недостающий способ оплаты 'С баланса счёта'

Изменённые файлы:
- myproject/tenants/admin.py - вызов команды вместо дублирования
- myproject/orders/admin.py - исправлено на transactions
- add_payment_methods_to_buba.py - скрипт для существующих тенантов
2025-12-01 01:22:40 +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
e723c26e6c Удаление фото по расписанию из папки temp 2025-11-30 21:20:45 +03:00
e5b37dcd81 Удалена дублирующая форма загрузки фото в форме создания товара 2025-11-30 19:01:12 +03:00
f483b04488 Исправлен импорт pillow-heif: убрана несуществующая функция register_avif_opener (AVIF регистрируется автоматически через register_heif_opener) 2025-11-30 18:41:49 +03:00
213fedcad5 Исправлена регистрация HEIF/AVIF декодеров в Celery worker для поддержки HEIC фото 2025-11-30 18:37:30 +03:00
1cea3661e0 Добавлена поддержка HEIC/HEIF фото с iPhone: подключен pillow-heif, расширен валидатор форматов, увеличен лимит размера до 20MB 2025-11-30 18:23:44 +03:00
03048b6345 Очистка временных файлов документации 2025-11-30 14:51:24 +03:00
4343b2eb5b Улучшен UI формы оплаты: красивый бейдж НЕ ОПЛАЧЕНО с пульсирующей анимацией, убран устаревший код смешанной оплаты, исправлены критичные ошибки JavaScript 2025-11-29 22:47:56 +03:00
9c6092262c Добавлена автоподстановка суммы для оплаты с кошелька и подсказка при нулевом балансе 2025-11-29 22:29:28 +03:00
4d197790fc Упрощена форма оплаты заказа: единая форма платежа/возврата с переключателем режимов 2025-11-29 22:23:42 +03:00