Исправлено двойное списание товаров при смене статуса заказа

Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 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 - документация по исправлению
This commit is contained in:
2025-12-01 00:56:26 +03:00
parent 4e66f03957
commit e0437cdb5a
8 changed files with 1284 additions and 70 deletions

View File

@@ -1,21 +1,57 @@
# Удаление Тенантов в Django-Tenants
## ⚠️ КРИТИЧЕСКИ ВАЖНО
**В этом проекте `auto_drop_schema = False`, поэтому ВСЕ команды удаления (включая `cleanup_tenant` и `delete_tenant`) НЕ удаляют схему из PostgreSQL автоматически!**
После удаления тенанта через любую команду **ОБЯЗАТЕЛЬНО** нужно вручную удалить схему из базы данных, иначе:
- ✅ Запись `Client` удалится
- ❌ Схема со всеми таблицами и данными останется в PostgreSQL
- ❌ При повторной регистрации с тем же именем новый тенант увидит старые данные!
### Как удалить схему вручную:
**Вариант 1 — через Django shell:**
```python
from django.db import connection
with connection.cursor() as cursor:
cursor.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;')
```
**Вариант 2 — одной командой из консоли:**
```bash
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;')"
```
**Вариант 3 — напрямую в PostgreSQL (psql):**
```sql
DROP SCHEMA IF EXISTS имя_схемы CASCADE;
```
---
## Быстрая справка
### Рекомендуемый способ (с улучшенной командой):
```bash
# Базовое удаление (Client + БД, заявка остается в истории)
# Базовое удаление (Client + файлы, заявка остается в истории)
python manage.py cleanup_tenant --schema=papa --noinput
# Полная очистка (Client + БД + заявка + файлы)
# Полная очистка (Client + заявка + файлы)
python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files
# ЗАТЕМ ВРУЧНУЮ удалить схему из PostgreSQL:
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
### Альтернативный способ (встроенная команда django-tenants):
⚠️ **ВНИМАНИЕ:** В этом проекте `auto_drop_schema = False`, поэтому команда `delete_tenant` **НЕ удаляет схему из PostgreSQL**. Удаляется только запись `Client`, но все таблицы и данные в схеме остаются в базе. Для полного удаления используйте `cleanup_tenant` (см. выше) или удалите схему вручную.
```bash
# Удалить конкретного тенанта
# Удалить конкретного тенанта (только запись Client, схема БД остаётся!)
python manage.py delete_tenant --schema=papa --noinput
# Удалить все файлы тенанта (после удаления из БД)
@@ -28,16 +64,18 @@ Remove-Item -Path 'media/tenants' -Recurse -Force
### ⭐ Способ 0: Новая улучшенная команда cleanup_tenant (РЕКОМЕНДУЕТСЯ)
**Эта команда решает проблему с TenantRegistration и удаляет все в одной операции**
**Эта команда решает проблему с TenantRegistration и управляет связанными данными**
#### Что это за команда?
Это новая management команда, которая автоматически:
- Удаляет Client и схему БД (как delete_tenant)
- Удаляет запись Client из таблицы тенантов
- Обрабатывает TenantRegistration (может оставить в истории или удалить)
- Опционально удаляет физические файлы
- Показывает красивый прогресс с подтверждением
⚠️ **ВАЖНО:** Команда **НЕ удаляет схему PostgreSQL** (т.к. `auto_drop_schema = False`). После выполнения команды нужно вручную удалить схему через SQL (см. раздел выше).
#### Параметры:
```bash
@@ -52,20 +90,29 @@ Remove-Item -Path 'media/tenants' -Recurse -Force
**1⃣ Базовое удаление (рекомендуется):**
```bash
python manage.py cleanup_tenant --schema=papa --noinput
# ЗАТЕМ вручную:
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
Удаляет: Client + БД, заявка остается в истории с tenant=NULL
Удаляет: Client (заявка остается в истории с tenant=NULL)
Требует: Ручное удаление схемы PostgreSQL
**2⃣ Полная очистка:**
```bash
python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files
# ЗАТЕМ вручную:
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
Удаляет: Client + БД + заявка + файлы (максимальная очистка)
Удаляет: Client + заявка + файлы
Требует: Ручное удаление схемы PostgreSQL
**3Только заявка и БД:**
**3Удаление с заявкой:**
```bash
python manage.py cleanup_tenant --schema=papa --noinput --purge-registration
# ЗАТЕМ вручную:
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
Удаляет: Client + БД + заявка (файлы остаются)
Удаляет: Client + заявка (файлы остаются)
Требует: Ручное удаление схемы PostgreSQL
#### Пример вывода:
@@ -82,8 +129,8 @@ python manage.py cleanup_tenant --schema=papa --noinput --purge-registration
• Владелец: admin@example.com
💾 База данных:
Схема БД "papa" будет полностью удалена
Все таблицы и данные будут удалены
Запись Client "papa" будет удалена
⚠️ Схема PostgreSQL останется и требует ручного удаления!
📝 TenantRegistration:
• Заявка от Papa Owner (papa@example.com)
@@ -96,9 +143,9 @@ python manage.py cleanup_tenant --schema=papa --noinput --purge-registration
1⃣ Обновляю TenantRegistration (сохраняю историю)...
✓ TenantRegistration обновлена (tenant=NULL)
2⃣ Удаляю Client и схему БД...
2⃣ Удаляю Client...
✓ Client "Papa Shop" удален
Схема БД "papa" удалена
⚠️ Схема БД "papa" НЕ удалена - требует ручного удаления!
======================================================================
✓ Тенант успешно удален!
@@ -125,11 +172,15 @@ Deleting 'papa'
Deleted 'papa'
```
⚠️ **ВНИМАНИЕ:** В этом проекте `auto_drop_schema = False`, поэтому команда `delete_tenant` **НЕ удаляет схему из PostgreSQL**!
Эта команда удаляет:
-Схему БД тенанта
- Все таблицы и данные в schema
-Запись Client из таблицы тенантов
- НЕ удаляет схему PostgreSQL (остаются все таблицы и данные!)
-НЕ удаляет файлы в `/media/tenants/{tenant_id}/`
После выполнения нужно вручную удалить схему (см. раздел "КРИТИЧЕСКИ ВАЖНО" выше).
---
### Способ 2: Интерактивное удаление (с выбором)
@@ -177,9 +228,13 @@ rm -rf ./media/tenants
## Что удаляется?
### БД (удаляется автоматически):
### БД (требует РУЧНОГО удаления):
⚠️ **Схема PostgreSQL НЕ удаляется автоматически** из-за `auto_drop_schema = False`!
Что остаётся в базе после `cleanup_tenant` или `delete_tenant`:
```
PostgreSQL/MySQL schema: papa
PostgreSQL schema: papa ← ОСТАЁТСЯ В БАЗЕ!
├── products_product
├── products_productphoto
├── products_productkit
@@ -191,6 +246,11 @@ PostgreSQL/MySQL schema: papa
└── ... (все другие таблицы в schema)
```
Для полного удаления выполните:
```bash
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
### Файлы (нужно удалить вручную):
```
media/tenants/papa/
@@ -209,24 +269,17 @@ media/tenants/papa/
## Полный цикл удаления тенанта
### 1⃣ Удалить из БД:
### 1⃣ Удалить Client:
```bash
python manage.py delete_tenant --schema=papa --noinput
python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files
```
### 2Проверить оставшиеся файлы:
```powershell
# На Windows
Get-ChildItem -Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants' -Recurse | Measure-Object | Select-Object -ExpandProperty Count
### 2Удалить схему PostgreSQL:
```bash
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
```
### 3Удалить файлы:
```powershell
# На Windows (PowerShell)
Remove-Item -Path 'media/tenants' -Recurse -Force
```
### 4⃣ Проверить удаление:
### 3Проверить удаление схемы (опционально):
```powershell
Test-Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants'
# Должно вернуть: False
@@ -390,9 +443,10 @@ python manage.py cleanup_tenant --schema=papa --noinput --purge-registration
```
Удаляет:
- ✅ Client + схему БД
- ✅ Client
- ✅ TenantRegistration (полная очистка)
- ❌ Теряется история регистраций
- ⚠️ Требует ручного удаления схемы PostgreSQL
**Если клиент захочет зарегистрироваться снова:**
1. Просто заполняет форму регистрации
@@ -440,12 +494,90 @@ for tenant in Client.objects.all():
---
---
## Ручной контроль удаления схем (PostgreSQL + django-tenants 3.7.0)
### Что важно знать про django-tenants
В `django-tenants==3.7.0` удаление тенанта работает так:
- Тенант удаляется через обычный ORM:
```python
tenant.delete()
```
- На модели тенанта (`Client`) есть флаг:
```python
auto_drop_schema = False # по умолчанию
```
- Если `auto_drop_schema = False`:
- При `tenant.delete()` удаляется только запись в таблице клиентов.
- Схема в PostgreSQL (`schema_name`) физически остаётся со всеми таблицами и данными.
- Если `auto_drop_schema = True`:
- При `tenant.delete()` будет выполнен `DROP SCHEMA <schema_name> CASCADE`.
- Это удобно, но ОЧЕНЬ опасно: любое удаление тенанта через ORM (например, через админку) без дополнительных проверок сразу дропает схему.
### Выбранная стратегия: полный ручной контроль
Для этого проекта принято решение:
- В модели `Client` оставить:
```python
auto_drop_schema = False
```
- НЕ полагаться на автоматический `auto_drop_schema=True`.
- Всегда явно контролировать момент, когда схема в PostgreSQL удаляется.
Это даёт:
- ✅ Защиту от случайного дропа схемы через админку или произвольный `.delete()`.
- ✅ Прозрачный и предсказуемый процесс: схема дропается только явной SQL-командой.
- ✅ Возможность временно сохранить схему для отладки/анализа.
- ❌ Требуется дополнительный шаг — ручное удаление схемы после каждого удаления тенанта.
### Рекомендуемый workflow удаления
```bash
# Шаг 1: Удалить Client и связанные данные
python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files
# Шаг 2: Явно удалить схему PostgreSQL
python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')"
# Или через psql напрямую:
# DROP SCHEMA IF EXISTS papa CASCADE;
```
### Практические выводы
- При удалении тенанта в этом проекте НЕЛЬЗЯ полагаться только на:
```bash
python manage.py delete_tenant --schema=...
```
- Рекомендуется ВСЕГДА использовать двухшаговый процесс:
1. `cleanup_tenant` для удаления Client, TenantRegistration и файлов
2. Явное `DROP SCHEMA ... CASCADE` для удаления схемы PostgreSQL
- Если потребуется временно оставить схему (например, для отладки), достаточно:
- Выполнить только шаг 1 (cleanup_tenant)
- Отложить шаг 2 (DROP SCHEMA) на потом
---
**Дата создания:** 2025-11-23
**Дата обновления:** 2025-11-23
**Версия:** 2.0
**Дата обновления:** 2025-12-01
**Версия:** 2.1
**Статус:** Production Ready ✅
### Что нового в версии 2.0:
### Что нового в версии 2.1:
- 🔴 **КРИТИЧЕСКОЕ:** Добавлено предупреждение о необходимости ручного удаления схем PostgreSQL
- 📖 Добавлен раздел "Ручной контроль удаления схем" с объяснением стратегии `auto_drop_schema = False`
- 📖 Обновлены все примеры команд с указанием необходимости ручного удаления схемы
- 📖 Исправлены описания того, что именно удаляют команды `cleanup_tenant` и `delete_tenant`
- ✨ Добавлены три способа удаления схемы вручную (Django shell, консоль, psql)
### Что было в версии 2.0:
- ✨ Добавлена новая улучшенная команда `cleanup_tenant`
- ✨ Команда автоматически обрабатывает TenantRegistration