Исправлено двойное списание товаров при смене статуса заказа
Проблема: - При изменении статуса заказа на 'Выполнен' товар списывался дважды - Заказ на 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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user