Очистка репозитория: удалены тестовые и служебные файлы
Удалены из git: - Скрипты активации и диагностики тенантов - Тестовые файлы (test_*.py, test_*.txt) - SQL скрипты для отладки - Backup файлы (*.backup, *.old) - Дубликат .gitignore из myproject/ Файлы остались на диске, но теперь игнорируются git. В репозитории остались только: - myproject/ (основной код проекта) - requirements.txt - .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для добавления способов оплаты в тенант buba
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Добавляем путь к проекту
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject'))
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
from django.core.management import call_command
|
||||
|
||||
print("=" * 70)
|
||||
print("Добавление способов оплаты в тенант 'buba'")
|
||||
print("=" * 70)
|
||||
|
||||
# Переключаемся на схему buba и создаём способы оплаты
|
||||
with schema_context('buba'):
|
||||
print("\n1. Создание способов оплаты...")
|
||||
call_command('create_payment_methods')
|
||||
|
||||
# Проверяем что создалось
|
||||
from orders.models import PaymentMethod
|
||||
methods = PaymentMethod.objects.all().order_by('order')
|
||||
|
||||
print(f"\n2. Проверка созданных способов оплаты:")
|
||||
print(f" Всего: {methods.count()}")
|
||||
for method in methods:
|
||||
status = "✓" if method.is_active else "✗"
|
||||
print(f" {status} [{method.order}] {method.name} ({method.code})")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✓ Готово!")
|
||||
print("=" * 70)
|
||||
@@ -1,48 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Добавляем путь к проекту
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject'))
|
||||
|
||||
# Настраиваем Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django_tenants.utils import schema_context
|
||||
from user_roles.services import RoleService
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# Схема тенанта
|
||||
schema_name = 'buba'
|
||||
|
||||
# Email пользователя
|
||||
user_email = 'admin@localhost'
|
||||
|
||||
print(f"Назначение роли Owner для пользователя {user_email} в тенанте {schema_name}...")
|
||||
|
||||
# Переключаемся на схему тенанта
|
||||
with schema_context(schema_name):
|
||||
try:
|
||||
# Находим пользователя
|
||||
user = User.objects.get(email=user_email)
|
||||
print(f"Пользователь найден: {user.email} (ID: {user.id})")
|
||||
|
||||
# Назначаем роль Owner
|
||||
user_role = RoleService.assign_role_to_user(user, 'owner')
|
||||
print(f"✓ Роль '{user_role.role.name}' успешно назначена!")
|
||||
print(f" - Email: {user_role.user.email}")
|
||||
print(f" - Имя: {user_role.user.name}")
|
||||
print(f" - Роль: {user_role.role.name} ({user_role.role.code})")
|
||||
print(f" - Активен: {user_role.is_active}")
|
||||
|
||||
except User.DoesNotExist:
|
||||
print(f"✗ Ошибка: Пользователь с email '{user_email}' не найден")
|
||||
except Exception as e:
|
||||
print(f"✗ Ошибка при назначении роли: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\nГотово!")
|
||||
167
check_order.py
167
check_order.py
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для проверки заказа и его складских операций
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Настройка Django
|
||||
sys.path.insert(0, '/c/Users/team_/Desktop/test_qwen/myproject')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from orders.models import Order
|
||||
from inventory.models import Reservation, Sale, Stock, StockBatch, Warehouse
|
||||
|
||||
# Устанавливаем схему тенанта
|
||||
connection.set_schema('buba')
|
||||
|
||||
# Получаем заказ
|
||||
try:
|
||||
order = Order.objects.get(order_number='101')
|
||||
|
||||
print("=" * 60)
|
||||
print(f"ЗАКАЗ: {order.order_number}")
|
||||
print("=" * 60)
|
||||
print(f"ID: {order.id}")
|
||||
print(f"Статус: {order.status}")
|
||||
print(f"Склад самовывоза: {order.pickup_warehouse}")
|
||||
print(f"Создан: {order.created_at}")
|
||||
print(f"Обновлен: {order.updated_at}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ТОВАРЫ В ЗАКАЗЕ (OrderItem)")
|
||||
print("=" * 60)
|
||||
items = order.items.all()
|
||||
if items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
print(f" ID: {item.id}")
|
||||
print(f" Товар: {product.name if product else 'НЕТ ТОВАРА!'}")
|
||||
print(f" Количество: {item.quantity}")
|
||||
print(f" Цена: {item.price}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ ТОВАРОВ В ЗАКАЗЕ!")
|
||||
|
||||
print("=" * 60)
|
||||
print("РЕЗЕРВЫ (Reservation)")
|
||||
print("=" * 60)
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
if reservations:
|
||||
for res in reservations:
|
||||
print(f" ID: {res.id}")
|
||||
print(f" Товар: {res.product.name}")
|
||||
print(f" Склад: {res.warehouse.name}")
|
||||
print(f" Количество: {res.quantity}")
|
||||
print(f" Статус: {res.status}")
|
||||
print(f" Создан: {res.reserved_at}")
|
||||
if res.converted_at:
|
||||
print(f" Конвертирован: {res.converted_at}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ РЕЗЕРВОВ!")
|
||||
|
||||
print("=" * 60)
|
||||
print("ПРОДАЖИ (Sale)")
|
||||
print("=" * 60)
|
||||
sales = Sale.objects.filter(order=order)
|
||||
if sales:
|
||||
for sale in sales:
|
||||
print(f" ID: {sale.id}")
|
||||
print(f" Товар: {sale.product.name}")
|
||||
print(f" Склад: {sale.warehouse.name}")
|
||||
print(f" Количество: {sale.quantity}")
|
||||
print(f" Цена продажи: {sale.sale_price}")
|
||||
print(f" Обработано: {sale.processed}")
|
||||
print(f" Дата: {sale.date}")
|
||||
print()
|
||||
else:
|
||||
print(" ⚠️ НЕТ ПРОДАЖ (Sale не созданы!)")
|
||||
|
||||
print("=" * 60)
|
||||
print("СКЛАДЫ")
|
||||
print("=" * 60)
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
if warehouse:
|
||||
print(f" Используется склад: {warehouse.name} (ID: {warehouse.id})")
|
||||
print(f" Активен: {warehouse.is_active}")
|
||||
else:
|
||||
print(" ⚠️ НЕТ ДОСТУПНЫХ СКЛАДОВ!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ОСТАТКИ НА СКЛАДЕ (StockBatch)")
|
||||
print("=" * 60)
|
||||
if warehouse and items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
print(f"\nТовар: {product.name}")
|
||||
batches = StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
)
|
||||
if batches:
|
||||
total = sum(b.quantity for b in batches)
|
||||
print(f" Всего доступно: {total}")
|
||||
for batch in batches:
|
||||
print(f" Партия ID {batch.id}: {batch.quantity} шт (себестоимость: {batch.cost_price})")
|
||||
else:
|
||||
print(f" ⚠️ НЕТ ПАРТИЙ НА СКЛАДЕ!")
|
||||
|
||||
# Проверяем Stock
|
||||
try:
|
||||
stock = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
print(f" Stock.quantity_available: {stock.quantity_available}")
|
||||
print(f" Stock.quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" Stock.quantity_free: {stock.quantity_free}")
|
||||
except Stock.DoesNotExist:
|
||||
print(f" ⚠️ Stock запись не существует!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ДИАГНОСТИКА")
|
||||
print("=" * 60)
|
||||
|
||||
# Проверка условий для срабатывания сигнала
|
||||
if order.status != 'completed':
|
||||
print(" ⚠️ СТАТУС НЕ 'completed'! Сигнал НЕ СРАБОТАЕТ")
|
||||
print(f" Текущий статус: {order.status}")
|
||||
else:
|
||||
print(" ✅ Статус 'completed' - условие выполнено")
|
||||
|
||||
if not warehouse:
|
||||
print(" ⚠️ НЕТ СКЛАДА! Сигнал выйдет на строке 76-77")
|
||||
else:
|
||||
print(f" ✅ Склад есть: {warehouse.name}")
|
||||
|
||||
if not items:
|
||||
print(" ⚠️ НЕТ ТОВАРОВ! Сигнал ничего не сделает")
|
||||
else:
|
||||
print(f" ✅ Товаров в заказе: {items.count()}")
|
||||
|
||||
# Проверка наличия товара на складе
|
||||
if warehouse and items:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
batches = StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
)
|
||||
total = sum(b.quantity for b in batches)
|
||||
if total < item.quantity:
|
||||
print(f" ⚠️ НЕДОСТАТОЧНО ТОВАРА '{product.name}'!")
|
||||
print(f" Нужно: {item.quantity}, доступно: {total}")
|
||||
else:
|
||||
print(f" ✅ Товара '{product.name}' достаточно: {total} >= {item.quantity}")
|
||||
|
||||
except Order.DoesNotExist:
|
||||
print(f"⚠️ ЗАКАЗ С НОМЕРОМ '101' НЕ НАЙДЕН В ТЕНАНТЕ 'buba'!")
|
||||
except Exception as e:
|
||||
print(f"❌ ОШИБКА: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script to cleanup old photo files with collision suffixes.
|
||||
Deletes files like: original_b374WLW.jpg, large_lmCnBYn.webp etc.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Determine media directory
|
||||
media_dir = Path(__file__).parent / 'myproject' / 'media'
|
||||
|
||||
if not media_dir.exists():
|
||||
print(f"ERROR: media directory not found: {media_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Cleaning old photo files in: {media_dir}")
|
||||
print("=" * 60)
|
||||
|
||||
deleted_count = 0
|
||||
errors = []
|
||||
|
||||
# Walk through all files in media
|
||||
for root, dirs, files in os.walk(str(media_dir)):
|
||||
for filename in files:
|
||||
# Look for files with suffix (pattern: name_XXXXX.extension)
|
||||
# where XXXXX is a random suffix added by Django on collision
|
||||
parts = filename.rsplit('.', 1) # Split name and extension
|
||||
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
|
||||
name, ext = parts
|
||||
|
||||
# Check if there's a suffix (8 chars after last underscore)
|
||||
# Django adds suffixes like: _b374WLW, _lmCnBYn etc.
|
||||
# Also match patterns like testovyi_17613999927705342_original
|
||||
if '_' in name:
|
||||
# Get the last part after underscore
|
||||
parts_by_underscore = name.split('_')
|
||||
last_part = parts_by_underscore[-1]
|
||||
|
||||
# Check for collision suffix (8 alphanumeric chars)
|
||||
# or timestamp-like suffix (14+ digits)
|
||||
is_collision_suffix = (len(last_part) == 8 and last_part.isalnum())
|
||||
is_timestamp_suffix = (len(last_part) >= 14 and last_part.isdigit())
|
||||
|
||||
if is_collision_suffix or is_timestamp_suffix:
|
||||
file_path = os.path.join(root, filename)
|
||||
rel_path = os.path.relpath(file_path, str(media_dir))
|
||||
|
||||
try:
|
||||
os.remove(file_path)
|
||||
deleted_count += 1
|
||||
print(f"[OK] Deleted: {rel_path}")
|
||||
except Exception as e:
|
||||
errors.append(f"[FAIL] Error deleting {rel_path}: {str(e)}")
|
||||
print(f"[FAIL] Error deleting {rel_path}: {str(e)}")
|
||||
|
||||
print("=" * 60)
|
||||
print(f"\nResults:")
|
||||
print(f" [OK] Successfully deleted: {deleted_count} files")
|
||||
|
||||
if errors:
|
||||
print(f" [FAIL] Deletion errors: {len(errors)}")
|
||||
for error in errors:
|
||||
print(f" {error}")
|
||||
else:
|
||||
print(f" [OK] No errors")
|
||||
|
||||
print("\n[DONE] Cleanup completed!")
|
||||
79
myproject/.gitignore
vendored
79
myproject/.gitignore
vendored
@@ -1,79 +0,0 @@
|
||||
# Django
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
__pycache__/
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Environment variables (contains secrets!)
|
||||
.env
|
||||
|
||||
# Virtual environment
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Static and media files
|
||||
/staticfiles/
|
||||
/media/
|
||||
|
||||
# Python
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Migrations (раскомментируйте если не хотите коммитить миграции)
|
||||
# */migrations/*.py
|
||||
# !*/migrations/__init__.py
|
||||
|
||||
# Celery Beat schedule database (автоматически создаётся при запуске celery beat)
|
||||
celerybeat-schedule
|
||||
celerybeat-schedule-shm
|
||||
celerybeat-schedule-wal
|
||||
|
||||
# Documentation files in root (сгенерированные документы)
|
||||
/CELERY_SETUP_GUIDE.md
|
||||
/FINAL_REPORT.md
|
||||
/IMPLEMENTATION_SUMMARY.md
|
||||
/MIGRATION_GUIDE.md
|
||||
/QUICK_START.md
|
||||
/README_CELERY.md
|
||||
/start_celery.bat
|
||||
/start_celery.sh
|
||||
@@ -1,97 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для активации заявки mixflowers
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from tenants.models import TenantRegistration, Client, Domain, Subscription
|
||||
|
||||
# Ищем заявку
|
||||
registration = TenantRegistration.objects.get(schema_name='mixflowers')
|
||||
print(f'Найдена заявка: {registration.shop_name} ({registration.schema_name})')
|
||||
print(f'Статус: {registration.get_status_display()}')
|
||||
print(f'Email: {registration.owner_email}')
|
||||
print('')
|
||||
|
||||
with transaction.atomic():
|
||||
# Создаем тенант
|
||||
print(f'Создание тенанта: {registration.schema_name}')
|
||||
client = Client.objects.create(
|
||||
schema_name=registration.schema_name,
|
||||
name=registration.shop_name,
|
||||
owner_email=registration.owner_email,
|
||||
owner_name=registration.owner_name,
|
||||
phone=registration.phone,
|
||||
is_active=True
|
||||
)
|
||||
print(f'[OK] Тенант создан (ID: {client.id})')
|
||||
|
||||
# Создаем домен
|
||||
domain_name = f"{registration.schema_name}.localhost"
|
||||
print(f'Создание домена: {domain_name}')
|
||||
domain = Domain.objects.create(
|
||||
domain=domain_name,
|
||||
tenant=client,
|
||||
is_primary=True
|
||||
)
|
||||
print(f'[OK] Домен создан (ID: {domain.id})')
|
||||
|
||||
# Создаем триальную подписку
|
||||
print('Создание триальной подписки на 90 дней')
|
||||
subscription = Subscription.create_trial(client)
|
||||
print(f'[OK] Подписка создана (ID: {subscription.id})')
|
||||
print(f' Истекает: {subscription.expires_at.strftime("%Y-%m-%d")} ({subscription.days_left()} дней)')
|
||||
|
||||
# Создаем суперпользователя для тенанта
|
||||
print('Создание суперпользователя для тенанта')
|
||||
from django.db import connection
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
|
||||
# Переключаемся на схему тенанта
|
||||
connection.set_tenant(client)
|
||||
|
||||
User = get_user_model()
|
||||
if not User.objects.filter(email=settings.TENANT_ADMIN_EMAIL).exists():
|
||||
superuser = User.objects.create_superuser(
|
||||
email=settings.TENANT_ADMIN_EMAIL,
|
||||
name=settings.TENANT_ADMIN_NAME,
|
||||
password=settings.TENANT_ADMIN_PASSWORD
|
||||
)
|
||||
print(f'[OK] Суперпользователь создан (ID: {superuser.id})')
|
||||
print(f' Email: {superuser.email}')
|
||||
print(f' Password: {settings.TENANT_ADMIN_PASSWORD}')
|
||||
else:
|
||||
print(f'[SKIP] Пользователь с email {settings.TENANT_ADMIN_EMAIL} уже существует')
|
||||
|
||||
# Возвращаемся в public схему
|
||||
public_tenant = Client.objects.get(schema_name='public')
|
||||
connection.set_tenant(public_tenant)
|
||||
|
||||
# Обновляем заявку
|
||||
registration.status = TenantRegistration.STATUS_APPROVED
|
||||
registration.processed_at = timezone.now()
|
||||
registration.processed_by = None
|
||||
registration.tenant = client
|
||||
registration.save()
|
||||
print('[OK] Заявка обновлена')
|
||||
|
||||
print('')
|
||||
print('=' * 60)
|
||||
print('АКТИВАЦИЯ ЗАВЕРШЕНА УСПЕШНО!')
|
||||
print('=' * 60)
|
||||
print(f'Магазин: {client.name}')
|
||||
print(f'Schema: {client.schema_name}')
|
||||
print(f'Домен: http://{domain_name}:8000/')
|
||||
print(f'Подписка до: {subscription.expires_at.strftime("%Y-%m-%d")} ({subscription.days_left()} дней)')
|
||||
print('')
|
||||
print('Доступ к админке:')
|
||||
print(f' URL: http://{domain_name}:8000/admin/')
|
||||
print(f' Email: {settings.TENANT_ADMIN_EMAIL}')
|
||||
print(f' Password: {settings.TENANT_ADMIN_PASSWORD}')
|
||||
@@ -1,176 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Универсальный скрипт для активации заявки на создание тенанта.
|
||||
|
||||
Использование:
|
||||
python activate_tenant.py <schema_name>
|
||||
|
||||
Примеры:
|
||||
python activate_tenant.py grach
|
||||
python activate_tenant.py myshop
|
||||
|
||||
Скрипт выполняет:
|
||||
1. Находит заявку по schema_name
|
||||
2. Создает тенант (Client)
|
||||
3. Создает домен ({schema_name}.localhost)
|
||||
4. Создает триальную подписку (90 дней)
|
||||
5. Создает суперпользователя (credentials из .env)
|
||||
6. Обновляет статус заявки на "Одобрено"
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import transaction, connection
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
from tenants.models import TenantRegistration, Client, Domain, Subscription
|
||||
|
||||
def print_usage():
|
||||
"""Вывод справки по использованию"""
|
||||
print("Использование: python activate_tenant.py <schema_name>")
|
||||
print("")
|
||||
print("Примеры:")
|
||||
print(" python activate_tenant.py grach")
|
||||
print(" python activate_tenant.py myshop")
|
||||
print("")
|
||||
print("Доступные заявки (со статусом 'pending'):")
|
||||
pending_regs = TenantRegistration.objects.filter(status=TenantRegistration.STATUS_PENDING)
|
||||
if pending_regs.exists():
|
||||
for reg in pending_regs:
|
||||
print(f" - {reg.schema_name}: {reg.shop_name} ({reg.owner_email})")
|
||||
else:
|
||||
print(" Нет заявок, ожидающих активации")
|
||||
|
||||
def activate_tenant(schema_name):
|
||||
"""Активация тенанта по schema_name"""
|
||||
|
||||
# Ищем заявку
|
||||
try:
|
||||
registration = TenantRegistration.objects.get(schema_name=schema_name)
|
||||
except TenantRegistration.DoesNotExist:
|
||||
print(f"Ошибка: Заявка с schema_name '{schema_name}' не найдена")
|
||||
print("")
|
||||
print_usage()
|
||||
return False
|
||||
|
||||
print(f'Найдена заявка: {registration.shop_name} ({registration.schema_name})')
|
||||
print(f'Статус: {registration.get_status_display()}')
|
||||
print(f'Email: {registration.owner_email}')
|
||||
print('')
|
||||
|
||||
# Проверяем статус
|
||||
if registration.status == TenantRegistration.STATUS_APPROVED:
|
||||
print(f'Внимание: Эта заявка уже была активирована!')
|
||||
if registration.tenant:
|
||||
print(f'Тенант: {registration.tenant.name} (ID: {registration.tenant.id})')
|
||||
print(f'Домен: http://{registration.schema_name}.localhost:8000/')
|
||||
return False
|
||||
|
||||
# Проверяем, не существует ли уже тенант
|
||||
if Client.objects.filter(schema_name=schema_name).exists():
|
||||
print(f'Ошибка: Тенант с schema_name "{schema_name}" уже существует!')
|
||||
return False
|
||||
|
||||
print('Начинаю активацию...')
|
||||
print('')
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# Создаем тенант
|
||||
print(f'1. Создание тенанта: {registration.schema_name}')
|
||||
client = Client.objects.create(
|
||||
schema_name=registration.schema_name,
|
||||
name=registration.shop_name,
|
||||
owner_email=registration.owner_email,
|
||||
owner_name=registration.owner_name,
|
||||
phone=registration.phone,
|
||||
is_active=True
|
||||
)
|
||||
print(f' [OK] Тенант создан (ID: {client.id})')
|
||||
|
||||
# Создаем домен
|
||||
domain_name = f"{registration.schema_name}.localhost"
|
||||
print(f'2. Создание домена: {domain_name}')
|
||||
domain = Domain.objects.create(
|
||||
domain=domain_name,
|
||||
tenant=client,
|
||||
is_primary=True
|
||||
)
|
||||
print(f' [OK] Домен создан (ID: {domain.id})')
|
||||
|
||||
# Создаем триальную подписку
|
||||
print('3. Создание триальной подписки на 90 дней')
|
||||
subscription = Subscription.create_trial(client)
|
||||
print(f' [OK] Подписка создана (ID: {subscription.id})')
|
||||
print(f' Истекает: {subscription.expires_at.strftime("%Y-%m-%d")} ({subscription.days_left()} дней)')
|
||||
|
||||
# Создаем суперпользователя для тенанта
|
||||
print('4. Создание суперпользователя для тенанта')
|
||||
|
||||
# Переключаемся на схему тенанта
|
||||
connection.set_tenant(client)
|
||||
|
||||
User = get_user_model()
|
||||
if not User.objects.filter(email=settings.TENANT_ADMIN_EMAIL).exists():
|
||||
superuser = User.objects.create_superuser(
|
||||
email=settings.TENANT_ADMIN_EMAIL,
|
||||
name=settings.TENANT_ADMIN_NAME,
|
||||
password=settings.TENANT_ADMIN_PASSWORD
|
||||
)
|
||||
print(f' [OK] Суперпользователь создан (ID: {superuser.id})')
|
||||
else:
|
||||
print(f' [SKIP] Пользователь с email {settings.TENANT_ADMIN_EMAIL} уже существует')
|
||||
|
||||
# Возвращаемся в public схему
|
||||
public_tenant = Client.objects.get(schema_name='public')
|
||||
connection.set_tenant(public_tenant)
|
||||
|
||||
# Обновляем заявку
|
||||
print('5. Обновление статуса заявки')
|
||||
registration.status = TenantRegistration.STATUS_APPROVED
|
||||
registration.processed_at = timezone.now()
|
||||
registration.processed_by = None # Активировано через скрипт
|
||||
registration.tenant = client
|
||||
registration.save()
|
||||
print(' [OK] Заявка помечена как "Одобрено"')
|
||||
|
||||
print('')
|
||||
print('=' * 70)
|
||||
print('АКТИВАЦИЯ ЗАВЕРШЕНА УСПЕШНО!')
|
||||
print('=' * 70)
|
||||
print(f'Магазин: {client.name}')
|
||||
print(f'Schema: {client.schema_name}')
|
||||
print(f'Домен: http://{domain_name}:8000/')
|
||||
print(f'Подписка до: {subscription.expires_at.strftime("%Y-%m-%d")} ({subscription.days_left()} дней)')
|
||||
print('')
|
||||
print('Доступ к админке тенанта:')
|
||||
print(f' URL: http://{domain_name}:8000/admin/')
|
||||
print(f' Email: {settings.TENANT_ADMIN_EMAIL}')
|
||||
print(f' Password: {settings.TENANT_ADMIN_PASSWORD}')
|
||||
print('=' * 70)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print('')
|
||||
print(f'Ошибка при активации: {str(e)}')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Ошибка: Не указан schema_name")
|
||||
print("")
|
||||
print_usage()
|
||||
sys.exit(1)
|
||||
|
||||
schema_name = sys.argv[1]
|
||||
success = activate_tenant(schema_name)
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,74 +0,0 @@
|
||||
from django.db import connection
|
||||
from orders.models import Order
|
||||
from inventory.models import Reservation, Sale, Stock, StockBatch, Warehouse
|
||||
|
||||
# Устанавливаем схему тенанта
|
||||
connection.set_schema('buba')
|
||||
|
||||
# Получаем заказ
|
||||
try:
|
||||
order = Order.objects.get(order_number='101')
|
||||
|
||||
print("=" * 60)
|
||||
print(f"ЗАКАЗ: {order.order_number}")
|
||||
print("=" * 60)
|
||||
print(f"ID: {order.id}")
|
||||
print(f"Статус: '{order.status}'")
|
||||
print(f"Склад самовывоза: {order.pickup_warehouse}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ТОВАРЫ В ЗАКАЗЕ")
|
||||
print("=" * 60)
|
||||
items = order.items.all()
|
||||
print(f"Количество товаров: {items.count()}")
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
print(f" - {product.name if product else 'НЕТ!'}: {item.quantity} шт, цена: {item.price}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("РЕЗЕРВЫ")
|
||||
print("=" * 60)
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
print(f"Количество резервов: {reservations.count()}")
|
||||
for res in reservations:
|
||||
print(f" - {res.product.name}: {res.quantity} шт, статус: '{res.status}'")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ПРОДАЖИ (Sale)")
|
||||
print("=" * 60)
|
||||
sales = Sale.objects.filter(order=order)
|
||||
print(f"Количество продаж: {sales.count()}")
|
||||
if sales:
|
||||
for sale in sales:
|
||||
print(f" - {sale.product.name}: {sale.quantity} шт, обработано: {sale.processed}")
|
||||
else:
|
||||
print(" ⚠️ ПРОДАЖ НЕТ!")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ДИАГНОСТИКА")
|
||||
print("=" * 60)
|
||||
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
|
||||
print(f"Статус заказа: '{order.status}' (тип: {type(order.status).__name__})")
|
||||
print(f"Условие: order.status != 'completed' = {order.status != 'completed'}")
|
||||
print(f"Склад: {warehouse.name if warehouse else 'НЕ НАЙДЕН!'}")
|
||||
|
||||
# Проверяем наличие товара
|
||||
if warehouse and items:
|
||||
print("\nОстатки на складе:")
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
batches = StockBatch.objects.filter(product=product, warehouse=warehouse, is_active=True)
|
||||
total = sum(b.quantity for b in batches)
|
||||
print(f" - {product.name}: {total} доступно (нужно {item.quantity})")
|
||||
if total < item.quantity:
|
||||
print(f" ⚠️ НЕДОСТАТОЧНО! (не хватает {item.quantity - total})")
|
||||
|
||||
except Order.DoesNotExist:
|
||||
print("⚠️ ЗАКАЗ 101 НЕ НАЙДЕН В ТЕНАНТЕ 'buba'!")
|
||||
except Exception as e:
|
||||
print(f"❌ ОШИБКА: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -1,53 +0,0 @@
|
||||
"""
|
||||
Проверка созданных заказов и резервов
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SET search_path TO grach")
|
||||
|
||||
# Считаем заказы
|
||||
cursor.execute("SELECT COUNT(*) FROM grach.orders_order")
|
||||
orders_count = cursor.fetchone()[0]
|
||||
print(f"Заказов: {orders_count}")
|
||||
|
||||
# Считаем позиции заказов
|
||||
cursor.execute("SELECT COUNT(*) FROM grach.orders_orderitem")
|
||||
items_count = cursor.fetchone()[0]
|
||||
print(f"Позиций в заказах: {items_count}")
|
||||
|
||||
# Считаем резервы
|
||||
cursor.execute("SELECT COUNT(*) FROM grach.inventory_reservation")
|
||||
reservations_count = cursor.fetchone()[0]
|
||||
print(f"Резервов: {reservations_count}")
|
||||
|
||||
# Детали по заказам без резервов
|
||||
print("\nПервые 10 позиций без резервов:")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
o.order_number,
|
||||
oi.id as item_id,
|
||||
p.name as product_name,
|
||||
oi.quantity,
|
||||
COUNT(r.id) as reservations_count
|
||||
FROM grach.orders_order o
|
||||
JOIN grach.orders_orderitem oi ON oi.order_id = o.id
|
||||
LEFT JOIN grach.products_product p ON p.id = oi.product_id
|
||||
LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id
|
||||
GROUP BY o.order_number, oi.id, p.name, oi.quantity
|
||||
HAVING COUNT(r.id) = 0
|
||||
ORDER BY o.order_number
|
||||
LIMIT 10
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
if rows:
|
||||
for row in rows:
|
||||
print(f" Заказ {row[0]}: ItemID={row[1]}, Товар=\"{row[2]}\", Кол-во={row[3]}, Резервов={row[4]}")
|
||||
else:
|
||||
print(" Все позиции имеют резервы!")
|
||||
@@ -1,34 +0,0 @@
|
||||
"""
|
||||
Проверка Stock с quantity_reserved
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SET search_path TO grach")
|
||||
|
||||
# Проверяем Stock с резервами
|
||||
print("Stock с резервами:\n")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
s.id,
|
||||
p.name as product_name,
|
||||
s.quantity_available,
|
||||
s.quantity_reserved,
|
||||
(s.quantity_available - s.quantity_reserved) as free_quantity
|
||||
FROM grach.inventory_stock s
|
||||
JOIN grach.products_product p ON p.id = s.product_id
|
||||
ORDER BY s.quantity_reserved DESC
|
||||
""")
|
||||
|
||||
print(f"{'ID':<5} {'Товар':<30} {'Всего':<10} {'Резерв':<10} {'Свободно':<10}")
|
||||
print("=" * 75)
|
||||
|
||||
for row in cursor.fetchall():
|
||||
stock_id, product_name, qty_available, qty_reserved, free_qty = row
|
||||
print(f"{stock_id:<5} {product_name:<30} {qty_available:<10} {qty_reserved:<10} {free_qty:<10}")
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Диагностика Stock для заказа 103 в схеме buba
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
from orders.models import Order
|
||||
from inventory.models import Reservation, Stock, Sale
|
||||
from django.db.models import Sum
|
||||
|
||||
# Работаем в схеме buba
|
||||
with schema_context('buba'):
|
||||
# Получаем заказ 103
|
||||
order = Order.objects.get(order_number=103)
|
||||
|
||||
print("="*80)
|
||||
print(f"ДИАГНОСТИКА ЗАКАЗА #{order.order_number} (схема: buba)")
|
||||
print("="*80)
|
||||
|
||||
# Проверяем резервы
|
||||
print("\n📝 РЕЗЕРВЫ:")
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
for res in reservations:
|
||||
print(f" - {res.product.name}:")
|
||||
print(f" Количество: {res.quantity}")
|
||||
print(f" Статус: {res.status}")
|
||||
print(f" Склад: {res.warehouse.name}")
|
||||
print(f" Product ID: {res.product_id}")
|
||||
print(f" Warehouse ID: {res.warehouse_id}")
|
||||
|
||||
# Проверяем Sale
|
||||
print("\n💰 ПРОДАЖИ (Sale):")
|
||||
sales = Sale.objects.filter(order=order)
|
||||
for sale in sales:
|
||||
print(f" - {sale.product.name}: {sale.quantity} шт.")
|
||||
|
||||
# Проверяем Stock
|
||||
print("\n📊 STOCK:")
|
||||
for res in reservations:
|
||||
stock = Stock.objects.get(
|
||||
product_id=res.product_id,
|
||||
warehouse_id=res.warehouse_id
|
||||
)
|
||||
print(f" - {stock.product.name} на {stock.warehouse.name}:")
|
||||
print(f" quantity_available: {stock.quantity_available}")
|
||||
print(f" quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" quantity_free: {stock.quantity_free}")
|
||||
|
||||
# Проверяем: сколько РЕАЛЬНО резервов со статусом 'reserved'
|
||||
print("\n🔍 ПЕРЕСЧЁТ РЕЗЕРВОВ ВРУЧНУЮ:")
|
||||
for res in reservations.values('product_id', 'warehouse_id').distinct():
|
||||
product_id = res['product_id']
|
||||
warehouse_id = res['warehouse_id']
|
||||
|
||||
# Считаем резервы со статусом 'reserved'
|
||||
reserved_count = Reservation.objects.filter(
|
||||
product_id=product_id,
|
||||
warehouse_id=warehouse_id,
|
||||
status='reserved'
|
||||
).aggregate(Sum('quantity'))['quantity__sum'] or 0
|
||||
|
||||
# Считаем резервы со статусом 'converted_to_sale'
|
||||
converted_count = Reservation.objects.filter(
|
||||
product_id=product_id,
|
||||
warehouse_id=warehouse_id,
|
||||
status='converted_to_sale'
|
||||
).aggregate(Sum('quantity'))['quantity__sum'] or 0
|
||||
|
||||
print(f" Product ID {product_id}, Warehouse ID {warehouse_id}:")
|
||||
print(f" Резервов 'reserved': {reserved_count}")
|
||||
print(f" Резервов 'converted_to_sale': {converted_count}")
|
||||
|
||||
# Что должно быть в Stock
|
||||
stock = Stock.objects.get(product_id=product_id, warehouse_id=warehouse_id)
|
||||
print(f" Stock.quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" ❌ ПРОБЛЕМА!" if stock.quantity_reserved != reserved_count else " ✅ OK")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("ВЫВОД:")
|
||||
print("="*80)
|
||||
if stock.quantity_reserved > 0 and converted_count > 0:
|
||||
print("❌ Stock НЕ обновился после конвертации резервов!")
|
||||
print(" quantity_reserved показывает старое значение")
|
||||
print("\n🔧 Попробуем обновить вручную...")
|
||||
stock.refresh_from_batches()
|
||||
print(f" После refresh_from_batches():")
|
||||
print(f" quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" ✅ ИСПРАВЛЕНО!" if stock.quantity_reserved == 0 else " ❌ НЕ ПОМОГЛО!")
|
||||
else:
|
||||
print("✅ Всё в порядке!")
|
||||
@@ -1,192 +0,0 @@
|
||||
"""
|
||||
Скрипт для очистки дубликатов резервов в базе данных (для tenant: buba).
|
||||
|
||||
Проблема: У некоторых позиций заказов (OrderItem) существует несколько резервов
|
||||
в статусе 'reserved', что вызывает ошибку MultipleObjectsReturned.
|
||||
|
||||
Решение: Оставляем только первый резерв, остальные удаляем.
|
||||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import transaction, connection
|
||||
from django.db.models import Count
|
||||
from inventory.models import Reservation
|
||||
from orders.models import OrderItem
|
||||
|
||||
|
||||
# Устанавливаем tenant-схему
|
||||
def set_tenant_schema(schema_name='buba'):
|
||||
"""Переключаемся на нужную tenant-схему"""
|
||||
connection.set_schema(schema_name)
|
||||
print(f"✓ Переключились на схему: {schema_name}")
|
||||
|
||||
|
||||
def find_duplicate_reservations():
|
||||
"""Находит OrderItem с несколькими резервами в статусе 'reserved'"""
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("Поиск дубликатов резервов...")
|
||||
print("="*80)
|
||||
|
||||
# Группируем резервы по order_item и считаем количество
|
||||
duplicates = Reservation.objects.filter(
|
||||
status='reserved'
|
||||
).values('order_item').annotate(
|
||||
count=Count('id')
|
||||
).filter(count__gt=1).order_by('-count')
|
||||
|
||||
print(f"\nНайдено OrderItem с дубликатами: {duplicates.count()}")
|
||||
|
||||
if duplicates.count() == 0:
|
||||
print("✅ Дубликатов не обнаружено!")
|
||||
return []
|
||||
|
||||
# Выводим детали
|
||||
problem_items = []
|
||||
|
||||
for dup in duplicates:
|
||||
order_item_id = dup['order_item']
|
||||
count = dup['count']
|
||||
|
||||
try:
|
||||
order_item = OrderItem.objects.get(id=order_item_id)
|
||||
|
||||
product_name = (
|
||||
order_item.product.sku if order_item.product
|
||||
else order_item.product_kit.name if order_item.product_kit
|
||||
else "Unknown"
|
||||
)
|
||||
|
||||
print(f"\n OrderItem #{order_item_id}:")
|
||||
print(f" Заказ: #{order_item.order.order_number}")
|
||||
print(f" Товар: {product_name}")
|
||||
print(f" Количество резервов: {count}")
|
||||
|
||||
# Показываем все резервы
|
||||
reservations = Reservation.objects.filter(
|
||||
order_item=order_item,
|
||||
status='reserved'
|
||||
).order_by('reserved_at') # Сортируем по дате создания
|
||||
|
||||
for idx, res in enumerate(reservations, 1):
|
||||
marker = "✓ ОСТАВИТЬ" if idx == 1 else "✗ УДАЛИТЬ"
|
||||
print(f" {marker} - Резерв #{res.id}: qty={res.quantity}, создан {res.reserved_at}")
|
||||
|
||||
problem_items.append({
|
||||
'order_item': order_item,
|
||||
'count': count,
|
||||
'reservations': list(reservations)
|
||||
})
|
||||
|
||||
except OrderItem.DoesNotExist:
|
||||
print(f"\n ⚠ OrderItem #{order_item_id} не существует (удален)")
|
||||
|
||||
return problem_items
|
||||
|
||||
|
||||
def clean_duplicate_reservations(problem_items, dry_run=True):
|
||||
"""
|
||||
Очищает дубликаты резервов.
|
||||
|
||||
Args:
|
||||
problem_items: Список OrderItem с дубликатами
|
||||
dry_run: Если True, только показывает что будет сделано, но не выполняет
|
||||
"""
|
||||
|
||||
print("\n" + "="*80)
|
||||
if dry_run:
|
||||
print("РЕЖИМ ПРОВЕРКИ (dry_run=True) - изменения НЕ будут сохранены")
|
||||
else:
|
||||
print("⚠ РЕЖИМ ОЧИСТКИ (dry_run=False) - изменения БУДУТ сохранены!")
|
||||
print("="*80)
|
||||
|
||||
if not problem_items:
|
||||
print("\nНечего очищать!")
|
||||
return
|
||||
|
||||
total_deleted = 0
|
||||
|
||||
for item_data in problem_items:
|
||||
order_item = item_data['order_item']
|
||||
reservations = item_data['reservations']
|
||||
|
||||
# Оставляем первый (самый старый) резерв
|
||||
first_reservation = reservations[0]
|
||||
duplicates = reservations[1:]
|
||||
|
||||
print(f"\nOrderItem #{order_item.id} (Заказ #{order_item.order.order_number}):")
|
||||
print(f" Оставляем: Резерв #{first_reservation.id}")
|
||||
print(f" Удаляем: {len(duplicates)} дубликатов")
|
||||
|
||||
if not dry_run:
|
||||
with transaction.atomic():
|
||||
for dup in duplicates:
|
||||
print(f" ✗ Удаляем Резерв #{dup.id}")
|
||||
dup.delete()
|
||||
total_deleted += 1
|
||||
else:
|
||||
for dup in duplicates:
|
||||
print(f" [DRY RUN] Будет удален Резерв #{dup.id}")
|
||||
total_deleted += len(duplicates)
|
||||
|
||||
print("\n" + "="*80)
|
||||
if dry_run:
|
||||
print(f"[DRY RUN] Будет удалено резервов: {total_deleted}")
|
||||
else:
|
||||
print(f"✅ УДАЛЕНО резервов: {total_deleted}")
|
||||
print("="*80)
|
||||
|
||||
|
||||
def main():
|
||||
"""Главная функция"""
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("ОЧИСТКА ДУБЛИКАТОВ РЕЗЕРВОВ")
|
||||
print("="*80)
|
||||
|
||||
# Переключаемся на tenant buba
|
||||
set_tenant_schema('buba')
|
||||
|
||||
# Шаг 1: Находим дубликаты
|
||||
problem_items = find_duplicate_reservations()
|
||||
|
||||
if not problem_items:
|
||||
return
|
||||
|
||||
# Шаг 2: Сначала делаем dry run
|
||||
print("\n" + "="*80)
|
||||
print("ШАГ 1: ПРОВЕРКА (без изменений)")
|
||||
print("="*80)
|
||||
clean_duplicate_reservations(problem_items, dry_run=True)
|
||||
|
||||
# Шаг 3: Спрашиваем подтверждение
|
||||
print("\n" + "="*80)
|
||||
response = input("\n⚠ Выполнить очистку? (yes/no): ")
|
||||
|
||||
if response.lower() in ['yes', 'y', 'да', 'д']:
|
||||
print("\n" + "="*80)
|
||||
print("ШАГ 2: ОЧИСТКА (с изменениями)")
|
||||
print("="*80)
|
||||
clean_duplicate_reservations(problem_items, dry_run=False)
|
||||
|
||||
# Проверяем что дубликатов больше нет
|
||||
print("\n" + "="*80)
|
||||
print("ПРОВЕРКА ПОСЛЕ ОЧИСТКИ")
|
||||
print("="*80)
|
||||
remaining = find_duplicate_reservations()
|
||||
|
||||
if not remaining:
|
||||
print("\n✅ ВСЕ ДУБЛИКАТЫ УСПЕШНО УДАЛЕНЫ!")
|
||||
else:
|
||||
print(f"\n⚠ Еще остались дубликаты: {len(remaining)}")
|
||||
else:
|
||||
print("\n❌ Очистка отменена")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,186 +0,0 @@
|
||||
-- Создание демо-заказов для схемы grach
|
||||
SET search_path TO grach;
|
||||
|
||||
-- Создаем 25 заказов с разными датами (от -15 до +15 дней от сегодня)
|
||||
DO $$
|
||||
DECLARE
|
||||
customer_ids INT[];
|
||||
product_ids INT[];
|
||||
address_ids INT[];
|
||||
shop_ids INT[];
|
||||
i INT;
|
||||
random_customer_id INT;
|
||||
random_product_id INT;
|
||||
random_address_id INT;
|
||||
random_shop_id INT;
|
||||
is_delivery_flag BOOLEAN;
|
||||
delivery_date_val DATE;
|
||||
status_val VARCHAR(20);
|
||||
payment_status_val VARCHAR(20);
|
||||
payment_method_val VARCHAR(20);
|
||||
order_id INT;
|
||||
items_total DECIMAL(10,2);
|
||||
delivery_cost_val DECIMAL(10,2);
|
||||
total_amount_val DECIMAL(10,2);
|
||||
BEGIN
|
||||
-- Получаем существующие ID
|
||||
SELECT ARRAY_AGG(id) INTO customer_ids FROM grach.customers_customer;
|
||||
SELECT ARRAY_AGG(id) INTO product_ids FROM grach.products_product;
|
||||
SELECT ARRAY_AGG(id) INTO address_ids FROM grach.customers_address;
|
||||
SELECT ARRAY_AGG(id) INTO shop_ids FROM grach.shops_shop;
|
||||
|
||||
-- Проверяем наличие данных
|
||||
IF customer_ids IS NULL OR array_length(customer_ids, 1) = 0 THEN
|
||||
RAISE EXCEPTION 'Нет клиентов в базе!';
|
||||
END IF;
|
||||
|
||||
IF product_ids IS NULL OR array_length(product_ids, 1) = 0 THEN
|
||||
RAISE EXCEPTION 'Нет товаров в базе!';
|
||||
END IF;
|
||||
|
||||
-- Создаем 25 заказов
|
||||
FOR i IN 1..25 LOOP
|
||||
-- Случайные значения
|
||||
random_customer_id := customer_ids[1 + floor(random() * array_length(customer_ids, 1))::int];
|
||||
is_delivery_flag := (random() > 0.5);
|
||||
delivery_date_val := CURRENT_DATE + (floor(random() * 31) - 15)::int;
|
||||
|
||||
-- Случайный статус
|
||||
CASE floor(random() * 6)::int
|
||||
WHEN 0 THEN status_val := 'new';
|
||||
WHEN 1 THEN status_val := 'confirmed';
|
||||
WHEN 2 THEN status_val := 'in_assembly';
|
||||
WHEN 3 THEN status_val := 'in_delivery';
|
||||
WHEN 4 THEN status_val := 'delivered';
|
||||
ELSE status_val := 'cancelled';
|
||||
END CASE;
|
||||
|
||||
-- Случайный статус оплаты
|
||||
CASE floor(random() * 3)::int
|
||||
WHEN 0 THEN payment_status_val := 'unpaid';
|
||||
WHEN 1 THEN payment_status_val := 'partial';
|
||||
ELSE payment_status_val := 'paid';
|
||||
END CASE;
|
||||
|
||||
-- Случайный способ оплаты
|
||||
CASE floor(random() * 4)::int
|
||||
WHEN 0 THEN payment_method_val := 'cash_to_courier';
|
||||
WHEN 1 THEN payment_method_val := 'card_to_courier';
|
||||
WHEN 2 THEN payment_method_val := 'online';
|
||||
ELSE payment_method_val := 'bank_transfer';
|
||||
END CASE;
|
||||
|
||||
-- Стоимость доставки
|
||||
IF is_delivery_flag THEN
|
||||
delivery_cost_val := 200 + floor(random() * 300)::int;
|
||||
ELSE
|
||||
delivery_cost_val := 0;
|
||||
END IF;
|
||||
|
||||
-- Создаем заказ
|
||||
INSERT INTO grach.orders_order (
|
||||
customer_id,
|
||||
order_number,
|
||||
is_delivery,
|
||||
delivery_address_id,
|
||||
pickup_shop_id,
|
||||
delivery_date,
|
||||
delivery_time_start,
|
||||
delivery_time_end,
|
||||
delivery_cost,
|
||||
status,
|
||||
payment_method,
|
||||
is_paid,
|
||||
total_amount,
|
||||
discount_amount,
|
||||
amount_paid,
|
||||
payment_status,
|
||||
customer_is_recipient,
|
||||
recipient_name,
|
||||
recipient_phone,
|
||||
is_anonymous,
|
||||
special_instructions,
|
||||
created_at,
|
||||
updated_at,
|
||||
modified_by_id
|
||||
) VALUES (
|
||||
random_customer_id,
|
||||
'ORD-' || to_char(CURRENT_DATE, 'YYYYMMDD') || '-' || substring(md5(random()::text) from 1 for 4),
|
||||
is_delivery_flag,
|
||||
CASE WHEN is_delivery_flag AND address_ids IS NOT NULL THEN address_ids[1 + floor(random() * array_length(address_ids, 1))::int] ELSE NULL END,
|
||||
CASE WHEN NOT is_delivery_flag AND shop_ids IS NOT NULL THEN shop_ids[1 + floor(random() * array_length(shop_ids, 1))::int] ELSE NULL END,
|
||||
delivery_date_val,
|
||||
CASE WHEN random() > 0.3 THEN ((9 + floor(random() * 10)::int)::text || ':00:00')::time ELSE NULL END,
|
||||
CASE WHEN random() > 0.3 THEN ((11 + floor(random() * 8)::int)::text || ':00:00')::time ELSE NULL END,
|
||||
delivery_cost_val,
|
||||
status_val,
|
||||
payment_method_val,
|
||||
(payment_status_val = 'paid'),
|
||||
1000, -- Временное значение, пересчитаем позже
|
||||
CASE WHEN random() > 0.8 THEN (100 + floor(random() * 400)::int) ELSE 0 END,
|
||||
0, -- Временное значение
|
||||
payment_status_val,
|
||||
(random() > 0.7),
|
||||
CASE WHEN random() > 0.7 THEN 'Получатель ' || i ELSE NULL END,
|
||||
CASE WHEN random() > 0.7 THEN '+79' || lpad(floor(random() * 1000000000)::text, 9, '0') ELSE NULL END,
|
||||
(random() > 0.8),
|
||||
CASE WHEN random() > 0.5 THEN
|
||||
CASE floor(random() * 5)::int
|
||||
WHEN 0 THEN 'Позвонить за час до доставки'
|
||||
WHEN 1 THEN 'Доставить точно в указанное время'
|
||||
WHEN 2 THEN 'Не звонить в дверь'
|
||||
WHEN 3 THEN 'Упаковать покрасивее'
|
||||
ELSE 'Приложить открытку'
|
||||
END
|
||||
ELSE NULL END,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL
|
||||
) RETURNING id INTO order_id;
|
||||
|
||||
-- Добавляем 1-3 товара в заказ
|
||||
items_total := 0;
|
||||
FOR j IN 1..(1 + floor(random() * 3)::int) LOOP
|
||||
random_product_id := product_ids[1 + floor(random() * array_length(product_ids, 1))::int];
|
||||
|
||||
-- Получаем цену товара и добавляем позицию
|
||||
INSERT INTO grach.orders_orderitem (
|
||||
order_id,
|
||||
product_id,
|
||||
product_kit_id,
|
||||
quantity,
|
||||
price,
|
||||
is_custom_price,
|
||||
created_at
|
||||
)
|
||||
SELECT
|
||||
order_id,
|
||||
random_product_id,
|
||||
NULL,
|
||||
1 + floor(random() * 3)::int,
|
||||
price,
|
||||
FALSE,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM grach.products_product
|
||||
WHERE id = random_product_id
|
||||
RETURNING (quantity * price) INTO STRICT total_amount_val;
|
||||
|
||||
items_total := items_total + total_amount_val;
|
||||
END LOOP;
|
||||
|
||||
-- Обновляем итоговую сумму заказа
|
||||
UPDATE grach.orders_order
|
||||
SET
|
||||
total_amount = items_total + delivery_cost - discount_amount,
|
||||
amount_paid = CASE
|
||||
WHEN payment_status = 'paid' THEN items_total + delivery_cost - discount_amount
|
||||
WHEN payment_status = 'partial' THEN (items_total + delivery_cost - discount_amount) * (0.2 + random() * 0.6)
|
||||
ELSE 0
|
||||
END
|
||||
WHERE id = order_id;
|
||||
|
||||
RAISE NOTICE 'Создан заказ % на дату %', order_id, delivery_date_val;
|
||||
END LOOP;
|
||||
|
||||
RAISE NOTICE 'Успешно создано 25 заказов!';
|
||||
END $$;
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для создания способа оплаты 'account_balance' для тенанта buba
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.core.management import call_command
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
# Создаём способ оплаты для тенанта buba
|
||||
with schema_context('buba'):
|
||||
call_command('create_payment_methods')
|
||||
print("\n✓ Способ оплаты успешно создан для тенанта 'buba'")
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Скрипт для диагностики проблемы с резервами при смене статуса на 'completed'.
|
||||
Проверяет:
|
||||
1. Изменяется ли статус резервов на 'converted_to_sale'
|
||||
2. Создаются ли Sale
|
||||
3. Списывается ли товар со склада
|
||||
4. Освобождаются ли резервы
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from orders.models import Order, OrderStatus
|
||||
from inventory.models import Reservation, Sale, Stock
|
||||
|
||||
|
||||
def diagnose_order(order_number):
|
||||
"""Диагностика конкретного заказа"""
|
||||
print(f"\n{'='*80}")
|
||||
print(f"ДИАГНОСТИКА ЗАКАЗА #{order_number}")
|
||||
print(f"{'='*80}\n")
|
||||
|
||||
# Проверяем тенант
|
||||
print(f"📌 Текущая схема БД: {connection.schema_name}")
|
||||
|
||||
try:
|
||||
order = Order.objects.get(order_number=order_number)
|
||||
except Order.DoesNotExist:
|
||||
print(f"❌ ОШИБКА: Заказ #{order_number} не найден!")
|
||||
return
|
||||
|
||||
print(f"✓ Заказ найден")
|
||||
print(f" - Клиент: {order.customer.name}")
|
||||
print(f" - Статус: {order.status.name if order.status else 'Нет статуса'} (code: {order.status.code if order.status else 'None'})")
|
||||
print(f" - Дата создания: {order.created_at}")
|
||||
print(f" - Дата обновления: {order.updated_at}")
|
||||
|
||||
# Проверяем позиции заказа
|
||||
print(f"\n📦 ПОЗИЦИИ ЗАКАЗА:")
|
||||
items = order.items.all()
|
||||
if not items.exists():
|
||||
print(" ⚠ Нет позиций в заказе")
|
||||
else:
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
print(f" - {product.name}: {item.quantity} шт. по {item.price} руб.")
|
||||
|
||||
# Проверяем резервы
|
||||
print(f"\n📝 РЕЗЕРВЫ:")
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
if not reservations.exists():
|
||||
print(" ⚠ НЕТ РЕЗЕРВОВ для этого заказа!")
|
||||
else:
|
||||
for res in reservations:
|
||||
print(f" - {res.product.name}:")
|
||||
print(f" • Количество: {res.quantity}")
|
||||
print(f" • Статус: {res.get_status_display()} ({res.status})")
|
||||
print(f" • Склад: {res.warehouse.name}")
|
||||
print(f" • Зарезервировано: {res.reserved_at}")
|
||||
if res.converted_at:
|
||||
print(f" • Конвертировано: {res.converted_at}")
|
||||
if res.released_at:
|
||||
print(f" • Освобождено: {res.released_at}")
|
||||
|
||||
# Проверяем Sale
|
||||
print(f"\n💰 ПРОДАЖИ (Sale):")
|
||||
sales = Sale.objects.filter(order=order)
|
||||
if not sales.exists():
|
||||
print(" ⚠ НЕТ ПРОДАЖ для этого заказа!")
|
||||
if order.status and order.status.code == 'completed':
|
||||
print(" ❌ ПРОБЛЕМА: Заказ в статусе 'completed', но Sale не созданы!")
|
||||
else:
|
||||
for sale in sales:
|
||||
print(f" - {sale.product.name}:")
|
||||
print(f" • Количество: {sale.quantity}")
|
||||
print(f" • Цена продажи: {sale.sale_price}")
|
||||
print(f" • Склад: {sale.warehouse.name}")
|
||||
print(f" • Дата: {sale.created_at}")
|
||||
|
||||
# Проверяем распределение по партиям
|
||||
from inventory.models import SaleBatchAllocation
|
||||
allocations = SaleBatchAllocation.objects.filter(sale=sale)
|
||||
if allocations.exists():
|
||||
print(f" • Распределение по партиям:")
|
||||
for alloc in allocations:
|
||||
print(f" - Партия #{alloc.batch.id}: {alloc.quantity} шт.")
|
||||
|
||||
# Проверяем Stock
|
||||
print(f"\n📊 ОСТАТКИ НА СКЛАДЕ (Stock):")
|
||||
for item in items:
|
||||
product = item.product or item.product_kit
|
||||
warehouse = order.pickup_warehouse or order.items.first().order.pickup_warehouse
|
||||
|
||||
if warehouse:
|
||||
try:
|
||||
stock = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
print(f" - {product.name} на складе {warehouse.name}:")
|
||||
print(f" • Всего: {stock.quantity}")
|
||||
print(f" • Зарезервировано: {stock.reserved_quantity}")
|
||||
print(f" • Доступно: {stock.available_quantity}")
|
||||
except Stock.DoesNotExist:
|
||||
print(f" - {product.name}: ❌ Stock не найден")
|
||||
else:
|
||||
print(f" - {product.name}: ⚠ Склад не определён")
|
||||
|
||||
# Проверяем историю изменений
|
||||
print(f"\n📜 ИСТОРИЯ ИЗМЕНЕНИЙ СТАТУСА:")
|
||||
history = order.history.all()[:5] # Последние 5 записей
|
||||
for idx, record in enumerate(history, 1):
|
||||
status_name = record.status.name if record.status else "Нет статуса"
|
||||
print(f" {idx}. {status_name} - {record.history_date}")
|
||||
|
||||
# Выводим выводы
|
||||
print(f"\n{'='*80}")
|
||||
print("ДИАГНОСТИКА:")
|
||||
print(f"{'='*80}")
|
||||
|
||||
issues = []
|
||||
|
||||
# Проверка 1: Резервы существуют?
|
||||
if not reservations.exists():
|
||||
issues.append("❌ КРИТИЧНО: Нет резервов для заказа")
|
||||
|
||||
# Проверка 2: Статус 'completed' + нет Sale
|
||||
if order.status and order.status.code == 'completed':
|
||||
if not sales.exists():
|
||||
issues.append("❌ КРИТИЧНО: Заказ 'completed', но Sale не созданы")
|
||||
|
||||
# Проверка 3: Статус резервов
|
||||
reserved_count = reservations.filter(status='reserved').count()
|
||||
converted_count = reservations.filter(status='converted_to_sale').count()
|
||||
|
||||
if reserved_count > 0:
|
||||
issues.append(f"❌ ПРОБЛЕМА: {reserved_count} резервов ещё в статусе 'reserved' при completed заказе")
|
||||
|
||||
if converted_count == 0 and reservations.exists():
|
||||
issues.append("❌ ПРОБЛЕМА: Ни один резерв не конвертирован в продажу")
|
||||
|
||||
if issues:
|
||||
print("\n🔴 НАЙДЕНЫ ПРОБЛЕМЫ:")
|
||||
for issue in issues:
|
||||
print(f" {issue}")
|
||||
else:
|
||||
print("\n✅ Проблем не обнаружено")
|
||||
|
||||
print(f"\n{'='*80}\n")
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "="*80)
|
||||
print("ДИАГНОСТИКА ПРОБЛЕМЫ С РЕЗЕРВАМИ")
|
||||
print("="*80)
|
||||
|
||||
# Спрашиваем номер заказа
|
||||
order_number_input = input("\nВведите номер заказа для диагностики: ").strip()
|
||||
|
||||
try:
|
||||
order_number = int(order_number_input)
|
||||
except ValueError:
|
||||
print("❌ Ошибка: Введите корректный номер заказа (число)")
|
||||
return
|
||||
|
||||
diagnose_order(order_number)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,60 +0,0 @@
|
||||
"""
|
||||
Скрипт для исправления дубликатов продаж в заказе 119
|
||||
"""
|
||||
from django.db import connection, transaction
|
||||
from orders.models import Order
|
||||
from inventory.models import Sale, SaleBatchAllocation, Stock, StockBatch
|
||||
|
||||
connection.set_schema('buba')
|
||||
|
||||
order = Order.objects.get(order_number='119')
|
||||
sales = Sale.objects.filter(order=order).order_by('date')
|
||||
|
||||
print(f"Заказ {order.order_number}: найдено {sales.count()} продаж")
|
||||
|
||||
if sales.count() <= 1:
|
||||
print("Дубликатов нет, всё в порядке")
|
||||
else:
|
||||
# Оставляем первую продажу, остальные удаляем
|
||||
first_sale = sales.first()
|
||||
duplicate_sales = sales.exclude(id=first_sale.id)
|
||||
|
||||
print(f"\nОставляем продажу ID {first_sale.id}")
|
||||
print(f"Удаляем {duplicate_sales.count()} дубликатов:")
|
||||
|
||||
with transaction.atomic():
|
||||
for sale in duplicate_sales:
|
||||
print(f" - Продажа ID {sale.id}: {sale.product.name} x {sale.quantity}")
|
||||
|
||||
# Получаем SaleBatchAllocation для восстановления товара
|
||||
allocations = SaleBatchAllocation.objects.filter(sale=sale)
|
||||
|
||||
# Восстанавливаем товар в партиях
|
||||
for alloc in allocations:
|
||||
batch = alloc.batch
|
||||
print(f" Восстанавливаем партию ID {batch.id}: +{alloc.quantity}")
|
||||
batch.quantity += alloc.quantity
|
||||
batch.is_active = True
|
||||
batch.save()
|
||||
|
||||
# Удаляем продажу (каскадно удалятся и SaleBatchAllocation)
|
||||
sale.delete()
|
||||
|
||||
# Обновляем Stock
|
||||
for item in order.items.all():
|
||||
product = item.product or item.product_kit
|
||||
if product:
|
||||
warehouse = order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
|
||||
if warehouse:
|
||||
stock, _ = Stock.objects.get_or_create(product=product, warehouse=warehouse)
|
||||
stock.refresh_from_batches()
|
||||
print(f"\nStock обновлен для {product.name}:")
|
||||
print(f" quantity_available: {stock.quantity_available}")
|
||||
print(f" quantity_reserved: {stock.quantity_reserved}")
|
||||
print(f" quantity_free: {stock.quantity_free}")
|
||||
|
||||
print("\n✅ Дубликаты удалены, товар восстановлен на складе")
|
||||
|
||||
# Проверяем результат
|
||||
sales_after = Sale.objects.filter(order=order)
|
||||
print(f"\nПосле исправления: {sales_after.count()} продаж")
|
||||
@@ -1,59 +0,0 @@
|
||||
-- Создание резервов для всех позиций заказов без резервов
|
||||
SET search_path TO grach;
|
||||
|
||||
-- Проверяем наличие активного склада
|
||||
DO $$
|
||||
DECLARE
|
||||
warehouse_id_val INT;
|
||||
created_count INT := 0;
|
||||
BEGIN
|
||||
-- Получаем ID активного склада (если есть)
|
||||
SELECT id INTO warehouse_id_val
|
||||
FROM grach.inventory_warehouse
|
||||
WHERE is_active = true
|
||||
LIMIT 1;
|
||||
|
||||
IF warehouse_id_val IS NULL THEN
|
||||
RAISE NOTICE 'WARNING: Нет активного склада, резервы будут созданы без склада';
|
||||
ELSE
|
||||
RAISE NOTICE 'Используем склад ID: %', warehouse_id_val;
|
||||
END IF;
|
||||
|
||||
-- Создаем резервы для всех позиций без резервов
|
||||
INSERT INTO grach.inventory_reservation (
|
||||
order_item_id,
|
||||
product_id,
|
||||
warehouse_id,
|
||||
quantity,
|
||||
status,
|
||||
reserved_at,
|
||||
released_at,
|
||||
converted_at
|
||||
)
|
||||
SELECT
|
||||
oi.id,
|
||||
COALESCE(oi.product_id, oi.product_kit_id),
|
||||
warehouse_id_val,
|
||||
oi.quantity,
|
||||
'reserved',
|
||||
CURRENT_TIMESTAMP,
|
||||
NULL,
|
||||
NULL
|
||||
FROM grach.orders_orderitem oi
|
||||
LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id
|
||||
WHERE r.id IS NULL -- Только позиции без резервов
|
||||
AND (oi.product_id IS NOT NULL OR oi.product_kit_id IS NOT NULL) -- Есть товар
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
GET DIAGNOSTICS created_count = ROW_COUNT;
|
||||
|
||||
RAISE NOTICE 'Создано резервов: %', created_count;
|
||||
END $$;
|
||||
|
||||
-- Проверяем результат
|
||||
SELECT
|
||||
COUNT(*) as total_items,
|
||||
COUNT(r.id) as items_with_reservations,
|
||||
COUNT(*) - COUNT(r.id) as items_without_reservations
|
||||
FROM grach.orders_orderitem oi
|
||||
LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id;
|
||||
@@ -1,10 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
@login_required
|
||||
def inventory_home(request):
|
||||
"""
|
||||
Главная страница Склада для управления инвентаризацией
|
||||
"""
|
||||
return render(request, 'inventory/home.html')
|
||||
@@ -1,33 +0,0 @@
|
||||
"""
|
||||
URL configuration for myproject project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('_nested_admin/', include('nested_admin.urls')), # Для nested admin
|
||||
path('admin/', admin.site.urls),
|
||||
path('', views.index, name='index'), # Main page
|
||||
path('accounts/', include('accounts.urls')),
|
||||
path('products/', include('products.urls')),
|
||||
]
|
||||
|
||||
# Serve media files during development
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
@@ -1,12 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def index(request):
|
||||
# Главная страница - отображается для всех пользователей
|
||||
# Если пользователь авторизован, можно показать персонализированное содержимое
|
||||
if request.user.is_authenticated:
|
||||
# Здесь можно отобразить персонализированное содержимое для авторизованных пользователей
|
||||
return render(request, 'dashboard.html') # или другую страницу
|
||||
else:
|
||||
# Для неавторизованных пользователей показываем приветственную страницу
|
||||
return render(request, 'home.html')
|
||||
@@ -1,39 +0,0 @@
|
||||
"""
|
||||
Скрипт для пересчета quantity_reserved для всех Stock записей
|
||||
Нужен после добавления сигналов для Reservation
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from inventory.models import Stock
|
||||
|
||||
# Устанавливаем схему для работы с tenant
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SET search_path TO grach')
|
||||
|
||||
print('[НАЧАЛО] Обновление quantity_reserved для всех Stock записей...')
|
||||
|
||||
# Получаем все Stock записи
|
||||
stocks = Stock.objects.all()
|
||||
total_count = stocks.count()
|
||||
|
||||
print(f'[INFO] Найдено Stock записей: {total_count}')
|
||||
|
||||
updated_count = 0
|
||||
for stock in stocks:
|
||||
try:
|
||||
# Вызываем refresh_from_batches() который пересчитывает quantity_reserved
|
||||
stock.refresh_from_batches()
|
||||
updated_count += 1
|
||||
|
||||
if stock.quantity_reserved > 0:
|
||||
print(f' [OK] Stock #{stock.id}: {stock.product.name} - зарезервировано: {stock.quantity_reserved}')
|
||||
except Exception as e:
|
||||
print(f' [ОШИБКА] Stock #{stock.id}: {str(e)}')
|
||||
|
||||
print(f'\n[ЗАВЕРШЕНО] Обновлено записей: {updated_count} из {total_count}')
|
||||
print('[OK] Все резервы пересчитаны!')
|
||||
@@ -1,24 +0,0 @@
|
||||
"""
|
||||
Скрипт для создания демо-заказов напрямую через SQL
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
|
||||
# Читаем SQL скрипт
|
||||
with open('create_demo_orders.sql', 'r', encoding='utf-8') as f:
|
||||
sql = f.read()
|
||||
|
||||
# Выполняем SQL
|
||||
with connection.cursor() as cursor:
|
||||
try:
|
||||
cursor.execute(sql)
|
||||
print("[OK] SQL script executed successfully!")
|
||||
print("[OK] 25 demo orders created!")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {e}")
|
||||
raise
|
||||
@@ -1,70 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для переключения в пространство конкретного тенанта.
|
||||
|
||||
Использование:
|
||||
python switch_to_tenant.py grach
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.db import connection
|
||||
from tenants.models import Client
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Использование: python switch_to_tenant.py <schema_name>")
|
||||
print("\nДоступные тенанты:")
|
||||
for client in Client.objects.all():
|
||||
print(f" - {client.schema_name}: {client.name}")
|
||||
sys.exit(1)
|
||||
|
||||
schema_name = sys.argv[1]
|
||||
|
||||
try:
|
||||
# Находим тенанта
|
||||
client = Client.objects.get(schema_name=schema_name)
|
||||
print(f"Найден тенант: {client.name} ({client.schema_name})")
|
||||
|
||||
# Переключаемся на схему тенанта
|
||||
connection.set_tenant(client)
|
||||
print(f"Переключено на схему: {connection.tenant.schema_name}")
|
||||
print("")
|
||||
|
||||
# Теперь можем работать с данными тенанта
|
||||
print("=" * 60)
|
||||
print("Теперь вы работаете в контексте тенанта!")
|
||||
print("=" * 60)
|
||||
print("")
|
||||
|
||||
# Примеры работы с данными тенанта
|
||||
from products.models import Product
|
||||
from customers.models import Customer
|
||||
from orders.models import Order
|
||||
|
||||
products_count = Product.objects.count()
|
||||
customers_count = Customer.objects.count()
|
||||
orders_count = Order.objects.count()
|
||||
|
||||
print(f"Товары: {products_count}")
|
||||
print(f"Клиенты: {customers_count}")
|
||||
print(f"Заказы: {orders_count}")
|
||||
print("")
|
||||
|
||||
# Интерактивный режим
|
||||
print("Запуск интерактивной оболочки...")
|
||||
print("Вы можете использовать: Product, Customer, Order и другие модели")
|
||||
print("")
|
||||
|
||||
import code
|
||||
code.interact(local=locals())
|
||||
|
||||
except Client.DoesNotExist:
|
||||
print(f"Ошибка: Тенант с schema_name '{schema_name}' не найден")
|
||||
print("\nДоступные тенанты:")
|
||||
for client in Client.objects.all():
|
||||
print(f" - {client.schema_name}: {client.name}")
|
||||
sys.exit(1)
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test card-based interface for ConfigurableKitProduct attributes
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from products.models.kits import (
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitProductAttribute,
|
||||
ProductKit
|
||||
)
|
||||
from django_tenants.utils import tenant_context
|
||||
from tenants.models import Client
|
||||
from django.db import transaction
|
||||
|
||||
try:
|
||||
client = Client.objects.get(schema_name='grach')
|
||||
print(f"Found tenant: {client.name}\n")
|
||||
except Client.DoesNotExist:
|
||||
print("Tenant 'grach' not found")
|
||||
sys.exit(1)
|
||||
|
||||
with tenant_context(client):
|
||||
print("=" * 70)
|
||||
print("TEST: Card-Based Attribute Interface")
|
||||
print("=" * 70)
|
||||
|
||||
# Step 1: Create a test product
|
||||
print("\n[1] Creating test product...")
|
||||
try:
|
||||
ConfigurableKitProduct.objects.filter(name__icontains="card-test").delete()
|
||||
|
||||
product = ConfigurableKitProduct.objects.create(
|
||||
name="Card Test Product",
|
||||
sku="CARD-TEST-001",
|
||||
description="Test card interface"
|
||||
)
|
||||
print(f" OK: Created product: {product.name}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Manually create attributes like the interface would
|
||||
print("\n[2] Creating attributes (simulating card interface)...")
|
||||
try:
|
||||
# Parameter 1: Dlina (3 values)
|
||||
attr_dlina_50 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Dlina",
|
||||
option="50",
|
||||
position=0,
|
||||
visible=True
|
||||
)
|
||||
attr_dlina_60 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Dlina",
|
||||
option="60",
|
||||
position=0,
|
||||
visible=True
|
||||
)
|
||||
attr_dlina_70 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Dlina",
|
||||
option="70",
|
||||
position=0,
|
||||
visible=True
|
||||
)
|
||||
print(f" OK: Created parameter 'Dlina' with 3 values: 50, 60, 70")
|
||||
|
||||
# Parameter 2: Upakovka (2 values)
|
||||
attr_pack_bez = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Upakovka",
|
||||
option="BEZ",
|
||||
position=1,
|
||||
visible=True
|
||||
)
|
||||
attr_pack_v = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Upakovka",
|
||||
option="V_UPAKOVKE",
|
||||
position=1,
|
||||
visible=True
|
||||
)
|
||||
print(f" OK: Created parameter 'Upakovka' with 2 values: BEZ, V_UPAKOVKE")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Verify the structure
|
||||
print("\n[3] Verifying attribute structure...")
|
||||
try:
|
||||
# Get unique parameter names
|
||||
params = product.parent_attributes.values_list('name', flat=True).distinct()
|
||||
print(f" OK: Found {params.count()} unique parameters:")
|
||||
|
||||
for param_name in params:
|
||||
values = product.parent_attributes.filter(name=param_name).values_list('option', flat=True)
|
||||
print(f" - {param_name}: {list(values)}")
|
||||
|
||||
# Verify counts
|
||||
assert product.parent_attributes.count() == 5, "Should have 5 total attributes"
|
||||
assert product.parent_attributes.filter(name="Dlina").count() == 3, "Should have 3 Dlina values"
|
||||
assert product.parent_attributes.filter(name="Upakovka").count() == 2, "Should have 2 Upakovka values"
|
||||
print(f" OK: All assertions passed!")
|
||||
|
||||
except AssertionError as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 4: Test data retrieval
|
||||
print("\n[4] Testing data retrieval...")
|
||||
try:
|
||||
# Get first parameter
|
||||
param = product.parent_attributes.first()
|
||||
print(f" OK: Retrieved attribute: {param.name} = {param.option}")
|
||||
|
||||
# Test ordering
|
||||
by_position = product.parent_attributes.values('name').distinct('name').order_by('position', 'name')
|
||||
print(f" OK: Can order by position and name")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("OK: CARD INTERFACE TEST PASSED!")
|
||||
print("=" * 70)
|
||||
print("\nNotes:")
|
||||
print("- The interface is designed to work with this attribute structure")
|
||||
print("- Each parameter can have multiple values")
|
||||
print("- Position is shared by all values of a parameter")
|
||||
print("- This allows clean grouping in the card interface")
|
||||
@@ -1,177 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Тестовый скрипт для проверки что JSONField работает корректно
|
||||
в модели ConfigurableKitOption (с поддержкой тенанта).
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from products.models.kits import ConfigurableKitProduct, ConfigurableKitOption, ProductKit
|
||||
from django_tenants.utils import tenant_context
|
||||
from tenants.models import Client
|
||||
|
||||
# Переходим в нужную схему (тенант)
|
||||
try:
|
||||
client = Client.objects.get(schema_name='grach')
|
||||
print(f"✅ Найден тенант: {client.name} (schema: {client.schema_name})\n")
|
||||
except Client.DoesNotExist:
|
||||
print("❌ Тенант 'grach' не найден")
|
||||
print("📝 Доступные тенанты:")
|
||||
for c in Client.objects.all():
|
||||
print(f" - {c.name} ({c.schema_name})")
|
||||
exit(1)
|
||||
|
||||
# Весь тест в контексте тенанта
|
||||
with tenant_context(client):
|
||||
print("=" * 70)
|
||||
print("ТЕСТ: JSONField в ConfigurableKitOption")
|
||||
print("=" * 70)
|
||||
|
||||
# Проверка 1: Создание вариативного товара
|
||||
print("\n1️⃣ Проверка создания ConfigurableKitProduct...")
|
||||
try:
|
||||
configurable = ConfigurableKitProduct.objects.filter(name__icontains="тест").first()
|
||||
if configurable:
|
||||
print(f" ✅ Найден существующий товар: {configurable.name}")
|
||||
else:
|
||||
configurable = ConfigurableKitProduct.objects.create(
|
||||
name="Тестовый букет JSON",
|
||||
sku="TEST-BUCKET-JSON",
|
||||
description="Тестовый товар для проверки JSON атрибутов"
|
||||
)
|
||||
print(f" ✅ Создан новый товар: {configurable.name}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Ошибка: {e}")
|
||||
exit(1)
|
||||
|
||||
# Проверка 2: Создание вариантов с JSON атрибутами
|
||||
print("\n2️⃣ Проверка создания ConfigurableKitOption с JSON атрибутами...")
|
||||
try:
|
||||
# Получаем первый комплект или создаём тестовый
|
||||
kit = ProductKit.objects.filter(name__icontains="тест").first()
|
||||
if not kit:
|
||||
kit = ProductKit.objects.first()
|
||||
if not kit:
|
||||
print(" ⚠️ В базе нет ProductKit, пропускаем этот тест")
|
||||
kit = None
|
||||
|
||||
if kit:
|
||||
print(f" ℹ️ Используем существующий комплект: {kit.name}")
|
||||
|
||||
# Проверяем есть ли уже вариант для этого комплекта
|
||||
option = ConfigurableKitOption.objects.filter(
|
||||
parent=configurable,
|
||||
kit=kit
|
||||
).first()
|
||||
|
||||
if option:
|
||||
print(f" ℹ️ Вариант уже существует, обновляю атрибуты...")
|
||||
# Обновляем существующий
|
||||
option.attributes = {"length": "60", "color": "red"}
|
||||
option.save()
|
||||
print(f" ✅ Обновлены атрибуты: {option.attributes}")
|
||||
else:
|
||||
# Создаём новый вариант с JSON атрибутами
|
||||
option = ConfigurableKitOption.objects.create(
|
||||
parent=configurable,
|
||||
kit=kit,
|
||||
attributes={"length": "60", "color": "red"},
|
||||
is_default=True
|
||||
)
|
||||
print(f" ✅ Создан вариант с JSON атрибутами:")
|
||||
print(f" - Parent: {option.parent.name}")
|
||||
print(f" - Kit: {option.kit.name}")
|
||||
print(f" - Attributes (JSON): {option.attributes}")
|
||||
print(f" - Type: {type(option.attributes)}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Ошибка: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
# Проверка 3: Получение и работа с JSON атрибутами
|
||||
print("\n3️⃣ Проверка получения JSON атрибутов из БД...")
|
||||
try:
|
||||
options = ConfigurableKitOption.objects.filter(parent=configurable)
|
||||
print(f" ℹ️ Найдено {options.count()} вариант(ов)")
|
||||
|
||||
for idx, opt in enumerate(options, 1):
|
||||
print(f"\n Вариант {idx}:")
|
||||
print(f" - ID: {opt.id}")
|
||||
print(f" - SKU комплекта: {opt.kit.sku}")
|
||||
print(f" - Атрибуты (JSON): {opt.attributes}")
|
||||
print(f" - Тип данных: {type(opt.attributes)}")
|
||||
|
||||
# Проверяем доступ к ключам JSON
|
||||
if opt.attributes:
|
||||
if isinstance(opt.attributes, dict):
|
||||
print(f" - Доступ к ключам JSON:")
|
||||
for key, value in opt.attributes.items():
|
||||
print(f" • {key}: {value}")
|
||||
print(f" ✅ JSON работает корректно!")
|
||||
else:
|
||||
print(f" ❌ Атрибуты не являются dict!")
|
||||
except Exception as e:
|
||||
print(f" ❌ Ошибка: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
# Проверка 4: Фильтрация по JSON атрибутам (PostgreSQL)
|
||||
print("\n4️⃣ Проверка фильтрации по JSON атрибутам...")
|
||||
try:
|
||||
# Попытка использовать JSON фильтрацию (работает в PostgreSQL)
|
||||
# Для SQLite это может не работать
|
||||
filtered = ConfigurableKitOption.objects.filter(
|
||||
parent=configurable,
|
||||
attributes__length="60"
|
||||
)
|
||||
print(f" ℹ️ Попытка фильтрации по attributes__length='60'")
|
||||
print(f" ℹ️ Найдено результатов: {filtered.count()}")
|
||||
|
||||
if filtered.count() > 0:
|
||||
print(f" ✅ JSON фильтрация работает!")
|
||||
else:
|
||||
print(f" ℹ️ JSON фильтрация может не поддерживаться в текущей БД")
|
||||
except Exception as e:
|
||||
print(f" ℹ️ JSON фильтрация не поддерживается: {type(e).__name__}")
|
||||
|
||||
# Проверка 5: Сложные JSON структуры
|
||||
print("\n5️⃣ Проверка сохранения сложных JSON структур...")
|
||||
try:
|
||||
complex_attrs = {
|
||||
"length": "70",
|
||||
"color": "white",
|
||||
"quantity": 15,
|
||||
"stems": ["rose1", "rose2", "rose3"],
|
||||
"metadata": {
|
||||
"fresh": True,
|
||||
"days_available": 7
|
||||
}
|
||||
}
|
||||
|
||||
# Обновляем атрибуты сложной структурой
|
||||
if options.exists():
|
||||
opt = options.first()
|
||||
opt.attributes = complex_attrs
|
||||
opt.save()
|
||||
|
||||
# Проверяем что сохранилось правильно
|
||||
opt_reloaded = ConfigurableKitOption.objects.get(pk=opt.pk)
|
||||
print(f" ✅ Сохранены сложные JSON атрибуты:")
|
||||
print(f" {opt_reloaded.attributes}")
|
||||
|
||||
# Проверяем вложенность
|
||||
if opt_reloaded.attributes.get("metadata", {}).get("fresh"):
|
||||
print(f" ✅ Доступ к вложенным полям JSON работает!")
|
||||
except Exception as e:
|
||||
print(f" ❌ Ошибка: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ ВСЕ ТЕСТЫ ПРОЙДЕНЫ! JSONField работает корректно!")
|
||||
print("=" * 70)
|
||||
@@ -1,131 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Prostoy test skript dlya proverki ConfigurableKitOptionAttribute
|
||||
bez Unicode simvolov
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from products.models.kits import (
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitOption,
|
||||
ConfigurableKitProductAttribute,
|
||||
ConfigurableKitOptionAttribute,
|
||||
ProductKit
|
||||
)
|
||||
from django_tenants.utils import tenant_context
|
||||
from tenants.models import Client
|
||||
|
||||
try:
|
||||
client = Client.objects.get(schema_name='grach')
|
||||
print(f"OK: Found tenant: {client.name} (schema: {client.schema_name})\n")
|
||||
except Client.DoesNotExist:
|
||||
print("ERROR: Tenant 'grach' not found")
|
||||
print("Available tenants:")
|
||||
for c in Client.objects.all():
|
||||
print(f" - {c.name} ({c.schema_name})")
|
||||
sys.exit(1)
|
||||
|
||||
with tenant_context(client):
|
||||
print("=" * 70)
|
||||
print("TEST: ConfigurableKitOptionAttribute M2M Model")
|
||||
print("=" * 70)
|
||||
|
||||
# Test 1: Check models exist
|
||||
print("\n1. Checking if models exist...")
|
||||
try:
|
||||
# Try to get a ConfigurableKitProduct
|
||||
products = ConfigurableKitProduct.objects.filter(name__icontains="test").first()
|
||||
if products:
|
||||
print(f" OK: Found ConfigurableKitProduct: {products.name}")
|
||||
else:
|
||||
print(" INFO: No test ConfigurableKitProduct found")
|
||||
|
||||
# Check ConfigurableKitProductAttribute exists
|
||||
attrs = ConfigurableKitProductAttribute.objects.all()
|
||||
print(f" OK: ConfigurableKitProductAttribute model exists. Count: {attrs.count()}")
|
||||
|
||||
# Check ConfigurableKitOptionAttribute exists
|
||||
opt_attrs = ConfigurableKitOptionAttribute.objects.all()
|
||||
print(f" OK: ConfigurableKitOptionAttribute model exists. Count: {opt_attrs.count()}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Test 2: Check M2M relationships
|
||||
print("\n2. Checking M2M relationships...")
|
||||
try:
|
||||
# Get a sample variant
|
||||
option = ConfigurableKitOption.objects.first()
|
||||
if option:
|
||||
print(f" OK: Found option: {option.id} for parent: {option.parent.name}")
|
||||
|
||||
# Check if we can access attributes_set
|
||||
attr_set = option.attributes_set.all()
|
||||
print(f" OK: Can access attributes_set. Count: {attr_set.count()}")
|
||||
|
||||
# Check if we can reverse access
|
||||
if attr_set.exists():
|
||||
opt_attr = attr_set.first()
|
||||
print(f" OK: Can access option_attr.option: {opt_attr.option.id}")
|
||||
print(f" OK: Can access option_attr.attribute: {opt_attr.attribute.id}")
|
||||
else:
|
||||
print(" INFO: No ConfigurableKitOption found")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Test 3: Check form validation logic
|
||||
print("\n3. Checking form validation setup...")
|
||||
try:
|
||||
from products.forms import ConfigurableKitOptionForm
|
||||
|
||||
# Create a test form with instance
|
||||
option = ConfigurableKitOption.objects.filter(
|
||||
parent__parent_attributes__isnull=False
|
||||
).first()
|
||||
|
||||
if option:
|
||||
form = ConfigurableKitOptionForm(instance=option)
|
||||
print(f" OK: Form created for option with parent: {option.parent.name}")
|
||||
|
||||
# Check dynamically generated fields
|
||||
dynamic_fields = [f for f in form.fields if f.startswith('attribute_')]
|
||||
print(f" OK: Found {len(dynamic_fields)} dynamic attribute fields:")
|
||||
for field_name in dynamic_fields:
|
||||
print(f" - {field_name}")
|
||||
else:
|
||||
print(" INFO: No option with parent attributes found")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Test 4: Check view integration
|
||||
print("\n4. Checking view imports...")
|
||||
try:
|
||||
from products.views.configurablekit_views import (
|
||||
ConfigurableKitProductCreateView,
|
||||
ConfigurableKitProductUpdateView
|
||||
)
|
||||
print(" OK: Views imported successfully")
|
||||
print(" OK: ConfigurableKitProductCreateView available")
|
||||
print(" OK: ConfigurableKitProductUpdateView available")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("OK: ALL TESTS PASSED! Implementation is ready for testing.")
|
||||
print("=" * 70)
|
||||
@@ -1,3 +0,0 @@
|
||||
Тестовый файл с кириллическими символами для проверки кодировки в PowerShell.
|
||||
|
||||
This is a test file with Cyrillic characters to verify encoding.
|
||||
@@ -1,236 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test kit binding for ConfigurableKitProduct attributes
|
||||
Verifies that each attribute value can be bound to a specific ProductKit
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from products.models.kits import (
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitProductAttribute,
|
||||
ProductKit
|
||||
)
|
||||
from django_tenants.utils import tenant_context
|
||||
from tenants.models import Client
|
||||
from django.db import transaction
|
||||
|
||||
try:
|
||||
client = Client.objects.get(schema_name='grach')
|
||||
print(f"Found tenant: {client.name}\n")
|
||||
except Client.DoesNotExist:
|
||||
print("Tenant 'grach' not found")
|
||||
sys.exit(1)
|
||||
|
||||
with tenant_context(client):
|
||||
print("=" * 80)
|
||||
print("TEST: Kit Binding for ConfigurableKitProduct Attributes")
|
||||
print("=" * 80)
|
||||
|
||||
# Step 1: Create or get ProductKits
|
||||
print("\n[1] Setting up ProductKits...")
|
||||
try:
|
||||
# Clean up old test kits
|
||||
ProductKit.objects.filter(name__icontains="test-kit").delete()
|
||||
|
||||
kits = []
|
||||
for i, name in enumerate(['Test Kit A', 'Test Kit B', 'Test Kit C']):
|
||||
kit, created = ProductKit.objects.get_or_create(
|
||||
name=name,
|
||||
defaults={
|
||||
'sku': f'TEST-KIT-{i}',
|
||||
'status': 'active',
|
||||
'is_temporary': False
|
||||
}
|
||||
)
|
||||
kits.append(kit)
|
||||
status = "Created" if created else "Found"
|
||||
print(f" {status}: {kit.name} (ID: {kit.id})")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Create a test product
|
||||
print("\n[2] Creating test ConfigurableKitProduct...")
|
||||
try:
|
||||
ConfigurableKitProduct.objects.filter(name__icontains="kit-binding-test").delete()
|
||||
|
||||
product = ConfigurableKitProduct.objects.create(
|
||||
name="Kit Binding Test Product",
|
||||
sku="KIT-BINDING-TEST-001",
|
||||
description="Test product with kit-bound attributes"
|
||||
)
|
||||
print(f" OK: Created product: {product.name} (ID: {product.id})")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Create attributes with kit bindings
|
||||
print("\n[3] Creating attributes with kit bindings...")
|
||||
try:
|
||||
# Параметр "Длина" с 3 значениями, каждое привязано к своему комплекту
|
||||
attrs = []
|
||||
|
||||
attr1 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Длина",
|
||||
option="50",
|
||||
position=0,
|
||||
visible=True,
|
||||
kit=kits[0] # Kit A
|
||||
)
|
||||
attrs.append(attr1)
|
||||
print(" OK: Created Dlina=50 -> " + kits[0].name)
|
||||
|
||||
attr2 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Длина",
|
||||
option="60",
|
||||
position=0,
|
||||
visible=True,
|
||||
kit=kits[1] # Kit B
|
||||
)
|
||||
attrs.append(attr2)
|
||||
print(" OK: Created Dlina=60 -> " + kits[1].name)
|
||||
|
||||
attr3 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Длина",
|
||||
option="70",
|
||||
position=0,
|
||||
visible=True,
|
||||
kit=kits[2] # Kit C
|
||||
)
|
||||
attrs.append(attr3)
|
||||
print(" OK: Created Dlina=70 -> " + kits[2].name)
|
||||
|
||||
# Parametr "Upakovka" s 2 znacheniyami (odin bez komplekta)
|
||||
attr4 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Упаковка",
|
||||
option="БЕЗ",
|
||||
position=1,
|
||||
visible=True,
|
||||
kit=kits[0] # Kit A
|
||||
)
|
||||
attrs.append(attr4)
|
||||
print(" OK: Created Upakovka=BEZ -> " + kits[0].name)
|
||||
|
||||
attr5 = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name="Упаковка",
|
||||
option="В УПАКОВКЕ",
|
||||
position=1,
|
||||
visible=True
|
||||
# Kit is NULL for this one
|
||||
)
|
||||
attrs.append(attr5)
|
||||
print(" OK: Created Upakovka=V_UPAKOVKE -> (no kit)")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 4: Verify the structure
|
||||
print("\n[4] Verifying attribute structure...")
|
||||
try:
|
||||
# Get unique parameter names
|
||||
params = product.parent_attributes.values_list('name', flat=True).distinct().order_by('name')
|
||||
print(f" OK: Found {len(list(params))} unique parameters")
|
||||
|
||||
for param_name in product.parent_attributes.values_list('name', flat=True).distinct().order_by('name'):
|
||||
param_attrs = product.parent_attributes.filter(name=param_name)
|
||||
print("\n Parameter: " + param_name)
|
||||
for attr in param_attrs:
|
||||
kit_name = attr.kit.name if attr.kit else "(no kit)"
|
||||
print(" - " + param_name + "=" + attr.option + " -> " + kit_name)
|
||||
|
||||
# Verify relationships
|
||||
print("\n Verifying relationships...")
|
||||
assert product.parent_attributes.count() == 5, f"Should have 5 total attributes, got {product.parent_attributes.count()}"
|
||||
print(" [OK] Total attributes: " + str(product.parent_attributes.count()))
|
||||
|
||||
assert product.parent_attributes.filter(name="Длина").count() == 3, "Should have 3 Dlina values"
|
||||
print(" [OK] Dlina values: " + str(product.parent_attributes.filter(name='Длина').count()))
|
||||
|
||||
assert product.parent_attributes.filter(name="Упаковка").count() == 2, "Should have 2 Upakovka values"
|
||||
print(" [OK] Upakovka values: " + str(product.parent_attributes.filter(name='Упаковка').count()))
|
||||
|
||||
# Check kit bindings
|
||||
kit_bound = product.parent_attributes.filter(kit__isnull=False).count()
|
||||
assert kit_bound == 4, f"Should have 4 kit-bound attributes, got {kit_bound}"
|
||||
print(" [OK] Kit-bound attributes: " + str(kit_bound))
|
||||
|
||||
kit_unbound = product.parent_attributes.filter(kit__isnull=True).count()
|
||||
assert kit_unbound == 1, f"Should have 1 unbound attribute, got {kit_unbound}"
|
||||
print(" [OK] Unbound attributes: " + str(kit_unbound))
|
||||
|
||||
except AssertionError as e:
|
||||
print(f" ERROR: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Test querying by kit
|
||||
print("\n[5] Testing queries by kit binding...")
|
||||
try:
|
||||
for kit in kits:
|
||||
attrs_for_kit = ConfigurableKitProductAttribute.objects.filter(kit=kit)
|
||||
print(" Attributes for " + kit.name + ":")
|
||||
for attr in attrs_for_kit:
|
||||
print(" - " + attr.name + "=" + attr.option)
|
||||
|
||||
# Reverse query: get kit for a specific attribute value
|
||||
attr_value = "60"
|
||||
attr = product.parent_attributes.get(option=attr_value)
|
||||
if attr.kit:
|
||||
print("\n Attribute value '" + attr_value + "' is bound to: " + attr.kit.name)
|
||||
else:
|
||||
print("\n Attribute value '" + attr_value + "' has no kit binding")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 6: Test FK relationship integrity
|
||||
print("\n[6] Testing FK relationship integrity...")
|
||||
try:
|
||||
# Verify that kit field is properly populated
|
||||
kit_a = kits[0]
|
||||
attrs_with_kit_a = ConfigurableKitProductAttribute.objects.filter(kit=kit_a)
|
||||
print(" Attributes linked to " + kit_a.name + ": " + str(attrs_with_kit_a.count()))
|
||||
|
||||
# Verify NULL kit is allowed
|
||||
null_kit_attrs = ConfigurableKitProductAttribute.objects.filter(kit__isnull=True)
|
||||
print(" Attributes with NULL kit: " + str(null_kit_attrs.count()))
|
||||
|
||||
assert null_kit_attrs.count() > 0, "Should have at least one NULL kit attribute"
|
||||
print(" [OK] FK relationship integrity verified")
|
||||
|
||||
except Exception as e:
|
||||
print(" ERROR: " + str(e))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print("OK: KIT BINDING TEST PASSED!")
|
||||
print("=" * 80)
|
||||
print("\nSummary:")
|
||||
print("[OK] ProductKit creation and retrieval")
|
||||
print("[OK] Attribute creation with kit FK")
|
||||
print("[OK] Mixed kit-bound and unbound attributes")
|
||||
print("[OK] Querying attributes by kit")
|
||||
print("[OK] FK cascade deletion on kit delete")
|
||||
print("[OK] Reverse queries (get kit for attribute value)")
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Тест для проверки настройки статусов при создании и редактировании заказа.
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from orders.models import OrderStatus, Order
|
||||
from orders.forms import OrderForm
|
||||
|
||||
|
||||
def test_default_status():
|
||||
"""Проверяет настройки статусов для новых и существующих заказов"""
|
||||
|
||||
print("\n=== ТЕСТ: Настройки статусов заказов ===\n")
|
||||
|
||||
# Проверяем наличие статуса "Черновик"
|
||||
try:
|
||||
draft_status = OrderStatus.objects.get(code='draft', is_system=True)
|
||||
print(f"✓ Статус 'Черновик' найден: {draft_status.name} (ID: {draft_status.pk})")
|
||||
except OrderStatus.DoesNotExist:
|
||||
print("✗ ОШИБКА: Статус 'draft' не найден в БД!")
|
||||
print(" Создайте системные статусы командой:")
|
||||
print(" python manage.py shell -c \"from orders.services.order_status_service import OrderStatusService; OrderStatusService.create_default_statuses()\"")
|
||||
return False
|
||||
|
||||
# Проверяем финальные статусы
|
||||
final_positive = OrderStatus.objects.filter(is_positive_end=True)
|
||||
final_negative = OrderStatus.objects.filter(is_negative_end=True)
|
||||
intermediate = OrderStatus.objects.filter(is_positive_end=False, is_negative_end=False)
|
||||
|
||||
print(f"\n📊 Статистика статусов:")
|
||||
print(f" - Промежуточные (доступны при создании): {intermediate.count()}")
|
||||
print(f" - Положительные финальные: {final_positive.count()}")
|
||||
print(f" - Отрицательные финальные: {final_negative.count()}")
|
||||
|
||||
print(f"\n📋 Промежуточные статусы:")
|
||||
for status in intermediate.order_by('order'):
|
||||
print(f" - {status.name} ({status.code})")
|
||||
|
||||
print(f"\n🚫 Финальные статусы (недоступны при создании):")
|
||||
for status in final_positive:
|
||||
print(f" - {status.name} ({status.code}) [положительный]")
|
||||
for status in final_negative:
|
||||
print(f" - {status.name} ({status.code}) [отрицательный]")
|
||||
|
||||
# === ТЕСТ 1: Создание нового заказа ===
|
||||
print("\n=== ТЕСТ 1: Создание нового заказа ===\n")
|
||||
|
||||
form_new = OrderForm()
|
||||
status_field = form_new.fields['status']
|
||||
|
||||
# Проверяем начальное значение
|
||||
initial_value = status_field.initial
|
||||
print(f"✓ Начальное значение поля 'status': {initial_value}")
|
||||
print(f"✓ Ожидаемое значение (ID статуса 'Черновик'): {draft_status.pk}")
|
||||
|
||||
if initial_value == draft_status.pk:
|
||||
print("✓ УСПЕХ: Статус 'Черновик' установлен по умолчанию!")
|
||||
else:
|
||||
print(f"✗ ОШИБКА: Начальное значение {initial_value} != {draft_status.pk}")
|
||||
return False
|
||||
|
||||
# Проверяем queryset (только промежуточные статусы)
|
||||
available_statuses = list(status_field.queryset)
|
||||
print(f"\n✓ Доступно статусов при создании: {len(available_statuses)}")
|
||||
|
||||
has_final_positive = any(s.is_positive_end for s in available_statuses)
|
||||
has_final_negative = any(s.is_negative_end for s in available_statuses)
|
||||
|
||||
if not has_final_positive and not has_final_negative:
|
||||
print("✓ УСПЕХ: Финальные статусы исключены из выбора!")
|
||||
else:
|
||||
print("✗ ОШИБКА: В списке есть финальные статусы!")
|
||||
return False
|
||||
|
||||
# Проверяем, что поле обязательно
|
||||
if status_field.required:
|
||||
print("✓ Поле 'status' обязательно (required=True)")
|
||||
else:
|
||||
print("✗ ОШИБКА: Поле 'status' не обязательно!")
|
||||
return False
|
||||
|
||||
# Проверяем, что пустой выбор убран
|
||||
if status_field.empty_label is None:
|
||||
print("✓ Пустой выбор '-------' убран (empty_label=None)")
|
||||
else:
|
||||
print(f"✗ ОШИБКА: Пустой выбор не убран! empty_label={status_field.empty_label}")
|
||||
return False
|
||||
|
||||
# === ТЕСТ 2: Редактирование существующего заказа ===
|
||||
print("\n=== ТЕСТ 2: Редактирование существующего заказа ===\n")
|
||||
|
||||
# Создаем мок-объект заказа с pk (чтобы форма считала его существующим)
|
||||
class MockOrder:
|
||||
pk = 999
|
||||
status = draft_status
|
||||
customer = None
|
||||
delivery_address = None
|
||||
|
||||
form_edit = OrderForm(instance=MockOrder())
|
||||
status_field_edit = form_edit.fields['status']
|
||||
|
||||
# Проверяем queryset (все статусы)
|
||||
all_statuses = list(status_field_edit.queryset)
|
||||
print(f"✓ Доступно статусов при редактировании: {len(all_statuses)}")
|
||||
|
||||
has_final_in_edit = any(s.is_positive_end or s.is_negative_end for s in all_statuses)
|
||||
|
||||
if has_final_in_edit:
|
||||
print("✓ УСПЕХ: При редактировании доступны ВСЕ статусы (включая финальные)!")
|
||||
else:
|
||||
print("⚠ ВНИМАНИЕ: При редактировании финальные статусы отсутствуют")
|
||||
|
||||
print("\n=== ВСЕ ТЕСТЫ ПРОЙДЕНЫ! ===\n")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = test_default_status()
|
||||
exit(0 if success else 1)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,232 +0,0 @@
|
||||
"""
|
||||
Тест для проверки исправления двойного возврата товара и резервов.
|
||||
|
||||
Проблема: При смене статуса с 'completed' на нейтральный возвращается
|
||||
двойное количество товара и резервов.
|
||||
|
||||
Решение: Использовать update() вместо save() для резервов, чтобы избежать
|
||||
повторного вызова сигнала update_stock_on_reservation_change.
|
||||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from decimal import Decimal
|
||||
from django.db import transaction
|
||||
from orders.models import Order, OrderStatus
|
||||
from inventory.models import Sale, Reservation, Stock, StockBatch
|
||||
|
||||
|
||||
def print_state(order, title):
|
||||
"""Выводит текущее состояние заказа, резервов, товара и Stock"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"{title}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
print(f"Заказ #{order.order_number}: status={order.status.code if order.status else None}")
|
||||
|
||||
# Резервы
|
||||
print("\nРезервы:")
|
||||
reservations = Reservation.objects.filter(order_item__order=order).select_related('product')
|
||||
for res in reservations:
|
||||
print(f" {res.product.sku}: qty={res.quantity}, status={res.status}")
|
||||
|
||||
# Sale
|
||||
print("\nSale:")
|
||||
sales = Sale.objects.filter(order=order).select_related('product')
|
||||
for sale in sales:
|
||||
print(f" {sale.product.sku}: qty={sale.quantity}")
|
||||
|
||||
if not sales.exists():
|
||||
print(" (нет Sale)")
|
||||
|
||||
# Stock и Batches
|
||||
print("\nStock и StockBatch:")
|
||||
for item in order.items.all():
|
||||
product = item.product if item.product else item.product_kit
|
||||
warehouse = order.pickup_warehouse
|
||||
|
||||
if not product or not warehouse:
|
||||
continue
|
||||
|
||||
# Stock
|
||||
try:
|
||||
stock = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
print(f" {product.sku}:")
|
||||
print(f" Stock: available={stock.quantity_available}, reserved={stock.quantity_reserved}")
|
||||
except Stock.DoesNotExist:
|
||||
print(f" {product.sku}: Stock не найден")
|
||||
continue
|
||||
|
||||
# Batches
|
||||
batches = StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
).order_by('created_at')
|
||||
|
||||
total_batch_qty = sum(b.quantity for b in batches)
|
||||
print(f" Batches (всего {batches.count()}): total_qty={total_batch_qty}")
|
||||
|
||||
for batch in batches:
|
||||
print(f" Batch #{batch.id}: qty={batch.quantity}, cost={batch.cost_price}")
|
||||
|
||||
|
||||
def test_status_change_rollback():
|
||||
"""
|
||||
Тест: Проверка отката при смене статуса completed → draft
|
||||
|
||||
Шаги:
|
||||
1. Найти заказ в статусе 'draft' с товарами
|
||||
2. Записать начальное состояние Stock/Batches
|
||||
3. Перевести в 'completed' (создаются Sale, списывается товар)
|
||||
4. Вернуть в 'draft' (откат Sale, восстановление товара)
|
||||
5. Проверить, что количество вернулось к исходному (без дублирования)
|
||||
"""
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("ТЕСТ: Проверка отката при смене статуса completed → draft")
|
||||
print("="*80)
|
||||
|
||||
# Найти заказ для теста
|
||||
draft_status = OrderStatus.objects.get(code='draft')
|
||||
completed_status = OrderStatus.objects.get(code='completed')
|
||||
|
||||
order = Order.objects.filter(status=draft_status).exclude(items__isnull=True).first()
|
||||
|
||||
if not order:
|
||||
print("❌ Не найден заказ в статусе 'draft' для теста")
|
||||
return
|
||||
|
||||
print(f"Тестовый заказ: #{order.order_number}")
|
||||
|
||||
# Получаем товар и склад для проверки
|
||||
item = order.items.first()
|
||||
product = item.product if item.product else item.product_kit
|
||||
warehouse = order.pickup_warehouse
|
||||
|
||||
if not product or not warehouse:
|
||||
print("❌ У заказа нет товара или склада")
|
||||
return
|
||||
|
||||
# === ШАГ 1: Записываем начальное состояние ===
|
||||
print_state(order, "ШАГ 1: Начальное состояние (draft)")
|
||||
|
||||
try:
|
||||
stock_initial = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
initial_available = stock_initial.quantity_available
|
||||
initial_reserved = stock_initial.quantity_reserved
|
||||
except Stock.DoesNotExist:
|
||||
print("❌ Stock не найден для товара")
|
||||
return
|
||||
|
||||
batches_initial = list(
|
||||
StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
).values('id', 'quantity')
|
||||
)
|
||||
|
||||
print(f"\n📊 Записано начальное состояние:")
|
||||
print(f" Stock: available={initial_available}, reserved={initial_reserved}")
|
||||
print(f" Batches: {len(batches_initial)} партий")
|
||||
|
||||
# === ШАГ 2: Переводим в 'completed' ===
|
||||
print(f"\n{'='*60}")
|
||||
print("ШАГ 2: Переводим заказ в 'completed'")
|
||||
print(f"{'='*60}")
|
||||
|
||||
with transaction.atomic():
|
||||
order.status = completed_status
|
||||
order.save()
|
||||
|
||||
print_state(order, "Состояние после перехода в 'completed'")
|
||||
|
||||
# === ШАГ 3: Возвращаем в 'draft' ===
|
||||
print(f"\n{'='*60}")
|
||||
print("ШАГ 3: Возвращаем заказ в 'draft' (ОТКАТ)")
|
||||
print(f"{'='*60}")
|
||||
|
||||
with transaction.atomic():
|
||||
order.status = draft_status
|
||||
order.save()
|
||||
|
||||
print_state(order, "Состояние после возврата в 'draft'")
|
||||
|
||||
# === ШАГ 4: Проверка результатов ===
|
||||
print(f"\n{'='*60}")
|
||||
print("ШАГ 4: Проверка результатов")
|
||||
print(f"{'='*60}")
|
||||
|
||||
stock_final = Stock.objects.get(product=product, warehouse=warehouse)
|
||||
final_available = stock_final.quantity_available
|
||||
final_reserved = stock_final.quantity_reserved
|
||||
|
||||
batches_final = list(
|
||||
StockBatch.objects.filter(
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
is_active=True
|
||||
).values('id', 'quantity')
|
||||
)
|
||||
|
||||
print(f"\n📊 Сравнение начального и конечного состояния:")
|
||||
print(f" Stock available: {initial_available} → {final_available}")
|
||||
print(f" Stock reserved: {initial_reserved} → {final_reserved}")
|
||||
print(f" Batches count: {len(batches_initial)} → {len(batches_final)}")
|
||||
|
||||
# Проверки
|
||||
errors = []
|
||||
|
||||
if final_available != initial_available:
|
||||
errors.append(
|
||||
f"❌ Stock.quantity_available не совпадает! "
|
||||
f"Ожидалось {initial_available}, получено {final_available}"
|
||||
)
|
||||
else:
|
||||
print(f"✅ Stock.quantity_available вернулся к исходному: {final_available}")
|
||||
|
||||
if final_reserved != initial_reserved:
|
||||
errors.append(
|
||||
f"❌ Stock.quantity_reserved не совпадает! "
|
||||
f"Ожидалось {initial_reserved}, получено {final_reserved}"
|
||||
)
|
||||
else:
|
||||
print(f"✅ Stock.quantity_reserved вернулся к исходному: {final_reserved}")
|
||||
|
||||
# Проверяем количество в партиях
|
||||
for batch_init in batches_initial:
|
||||
batch_final = next((b for b in batches_final if b['id'] == batch_init['id']), None)
|
||||
if not batch_final:
|
||||
errors.append(f"❌ Партия #{batch_init['id']} исчезла после отката!")
|
||||
elif batch_final['quantity'] != batch_init['quantity']:
|
||||
errors.append(
|
||||
f"❌ Партия #{batch_init['id']}: количество не совпадает! "
|
||||
f"Ожидалось {batch_init['quantity']}, получено {batch_final['quantity']}"
|
||||
)
|
||||
|
||||
if not errors:
|
||||
print("\n✅ ТЕСТ ПРОЙДЕН: Все данные вернулись к исходному состоянию!")
|
||||
else:
|
||||
print("\n❌ ТЕСТ ПРОВАЛЕН:")
|
||||
for error in errors:
|
||||
print(f" {error}")
|
||||
|
||||
# === ШАГ 5: Откатываем изменения (возвращаем заказ в исходное состояние) ===
|
||||
print(f"\n{'='*60}")
|
||||
print("Откатываем тестовые изменения...")
|
||||
print(f"{'='*60}")
|
||||
|
||||
with transaction.atomic():
|
||||
# Заказ уже в draft, ничего не делаем
|
||||
pass
|
||||
|
||||
print("Тест завершен.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_status_change_rollback()
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Тест для проверки работы фильтра smart_quantity
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from decimal import Decimal
|
||||
from inventory.templatetags.inventory_filters import smart_quantity
|
||||
|
||||
# Тестовые случаи
|
||||
test_cases = [
|
||||
(5.0, "5"), # Целое число
|
||||
(5.000, "5"), # Целое с нулями
|
||||
(10, "10"), # int
|
||||
(2.5, "2,5"), # Простая дробь
|
||||
(2.500, "2,5"), # Дробь с лишними нулями
|
||||
(3.140, "3,14"), # Два знака после запятой
|
||||
(3.125, "3,125"), # Три знака после запятой
|
||||
(0.5, "0,5"), # Меньше единицы
|
||||
(100.0, "100"), # Большое целое
|
||||
(Decimal("5.000"), "5"), # Decimal целое
|
||||
(Decimal("2.500"), "2,5"), # Decimal дробное
|
||||
(Decimal("3.12500"), "3,125"), # Decimal с лишними нулями
|
||||
]
|
||||
|
||||
print("Testing smart_quantity filter:\n")
|
||||
print(f"{'Input value':<20} {'Expected':<15} {'Result':<15} {'Status'}")
|
||||
print("-" * 70)
|
||||
|
||||
all_passed = True
|
||||
for input_val, expected in test_cases:
|
||||
result = smart_quantity(input_val)
|
||||
status = "[PASS]" if result == expected else "[FAIL]"
|
||||
if result != expected:
|
||||
all_passed = False
|
||||
print(f"{str(input_val):<20} {expected:<15} {result:<15} {status}")
|
||||
|
||||
print("-" * 70)
|
||||
if all_passed:
|
||||
print("\n[SUCCESS] All tests passed!")
|
||||
else:
|
||||
print("\n[ERROR] Some tests failed")
|
||||
|
||||
print("\nПримеры использования в шаблоне:")
|
||||
print(" {{ batch.quantity|smart_quantity }}")
|
||||
print(" {{ item.quantity|smart_quantity }}")
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test template syntax without errors
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django.template import Template, Context
|
||||
|
||||
# Test if the template syntax is valid
|
||||
try:
|
||||
# Minimal template to check syntax
|
||||
test_template = """
|
||||
{% for field in form %}
|
||||
{% if "attribute_" in field.name %}
|
||||
<div>{{ field.label }}</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
t = Template(test_template)
|
||||
print("OK: Template syntax is valid!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Тест системы кошелька клиента
|
||||
"""
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
from customers.models import Customer, WalletTransaction
|
||||
from orders.models import Order, PaymentMethod
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("ТЕСТ СИСТЕМЫ КОШЕЛЬКА КЛИЕНТА")
|
||||
print("="*60)
|
||||
|
||||
with schema_context('buba'):
|
||||
# 1. Проверяем способ оплаты
|
||||
try:
|
||||
method = PaymentMethod.objects.get(code='account_balance')
|
||||
print(f"\n✓ Способ оплаты найден: {method.name}")
|
||||
print(f" Описание: {method.description}")
|
||||
print(f" Порядок: {method.order}")
|
||||
except PaymentMethod.DoesNotExist:
|
||||
print("\n✗ Способ оплаты 'account_balance' не найден!")
|
||||
|
||||
# 2. Проверяем клиентов
|
||||
customers = Customer.objects.filter(is_system_customer=False)
|
||||
print(f"\n✓ Всего клиентов: {customers.count()}")
|
||||
|
||||
if customers.exists():
|
||||
customer = customers.first()
|
||||
print(f"\n Тестовый клиент: {customer.name}")
|
||||
print(f" Баланс кошелька: {customer.wallet_balance} руб.")
|
||||
print(f" Всего покупок: {customer.total_spent} руб.")
|
||||
|
||||
# Транзакции
|
||||
txn_count = customer.wallet_transactions.count()
|
||||
print(f" Транзакций кошелька: {txn_count}")
|
||||
|
||||
if txn_count > 0:
|
||||
print("\n Последние транзакции:")
|
||||
for txn in customer.wallet_transactions.all()[:5]:
|
||||
print(f" - {txn.created_at.strftime('%d.%m.%Y %H:%M')}: "
|
||||
f"{txn.get_transaction_type_display()} "
|
||||
f"{txn.amount} руб.")
|
||||
|
||||
# 3. Проверяем заказы
|
||||
orders = Order.objects.all()
|
||||
print(f"\n✓ Всего заказов: {orders.count()}")
|
||||
|
||||
if orders.exists():
|
||||
order = orders.first()
|
||||
print(f"\n Тестовый заказ: #{order.order_number}")
|
||||
print(f" Клиент: {order.customer.name}")
|
||||
print(f" Сумма: {order.total_amount} руб.")
|
||||
print(f" Оплачено: {order.amount_paid} руб.")
|
||||
print(f" К оплате: {order.amount_due} руб.")
|
||||
print(f" Статус оплаты: {order.get_payment_status_display()}")
|
||||
|
||||
# Платежи
|
||||
payments = order.payments.all()
|
||||
if payments.exists():
|
||||
print(f"\n Платежи по заказу:")
|
||||
for payment in payments:
|
||||
print(f" - {payment.payment_method.name}: {payment.amount} руб.")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("ТЕСТ ЗАВЕРШЁН")
|
||||
print("="*60 + "\n")
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Workflow test: Create a full configurable product with attributes and variants
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||||
django.setup()
|
||||
|
||||
from products.models.kits import (
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitOption,
|
||||
ConfigurableKitProductAttribute,
|
||||
ConfigurableKitOptionAttribute,
|
||||
ProductKit
|
||||
)
|
||||
from django_tenants.utils import tenant_context
|
||||
from tenants.models import Client
|
||||
from django.db import transaction
|
||||
|
||||
try:
|
||||
client = Client.objects.get(schema_name='grach')
|
||||
print(f"Found tenant: {client.name}\n")
|
||||
except Client.DoesNotExist:
|
||||
print("Tenant 'grach' not found")
|
||||
sys.exit(1)
|
||||
|
||||
with tenant_context(client):
|
||||
print("=" * 70)
|
||||
print("WORKFLOW TEST: Complete ConfigurableKitProduct Creation")
|
||||
print("=" * 70)
|
||||
|
||||
# Step 1: Create ConfigurableKitProduct
|
||||
print("\n[1] Creating ConfigurableKitProduct...")
|
||||
with transaction.atomic():
|
||||
try:
|
||||
# Delete old test products
|
||||
ConfigurableKitProduct.objects.filter(name__icontains="workflow").delete()
|
||||
|
||||
product = ConfigurableKitProduct.objects.create(
|
||||
name="Workflow Test Product",
|
||||
sku="WORKFLOW-TEST-001",
|
||||
description="Test product for workflow validation"
|
||||
)
|
||||
print(f" OK: Created product: {product.name} (ID: {product.id})")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Create attributes with values
|
||||
print("\n[2] Creating product attributes...")
|
||||
try:
|
||||
# Delete old attributes
|
||||
ConfigurableKitProductAttribute.objects.filter(parent=product).delete()
|
||||
|
||||
attrs_data = [
|
||||
("Dlina", ["50", "60", "70"]),
|
||||
("Упаковка", ["BEZ", "V_UPAKOVKE"])
|
||||
]
|
||||
|
||||
created_attrs = {}
|
||||
for attr_name, values in attrs_data:
|
||||
print(f" Creating attribute: {attr_name}")
|
||||
created_attrs[attr_name] = []
|
||||
|
||||
for pos, value in enumerate(values):
|
||||
attr = ConfigurableKitProductAttribute.objects.create(
|
||||
parent=product,
|
||||
name=attr_name,
|
||||
option=value,
|
||||
position=pos,
|
||||
visible=True
|
||||
)
|
||||
created_attrs[attr_name].append(attr)
|
||||
print(f" - Created value: {value}")
|
||||
|
||||
print(f" OK: Created {len(created_attrs)} attribute(s)")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Get or create ProductKits
|
||||
print("\n[3] Getting ProductKits for variants...")
|
||||
try:
|
||||
kits = ProductKit.objects.all()[:3]
|
||||
if kits.count() == 0:
|
||||
print(" WARNING: No ProductKit found in database")
|
||||
print(" INFO: Skipping variant creation (need ProductKits in DB)")
|
||||
print("\n To complete testing:")
|
||||
print(" 1. Create some ProductKit objects in admin")
|
||||
print(" 2. Then run this script again")
|
||||
else:
|
||||
print(f" OK: Found {kits.count()} ProductKit(s)")
|
||||
for kit in kits:
|
||||
print(f" - {kit.name} (SKU: {kit.sku})")
|
||||
|
||||
# Step 4: Create variants with attribute values
|
||||
print("\n[4] Creating ConfigurableKitOption variants...")
|
||||
try:
|
||||
# Delete old options
|
||||
ConfigurableKitOption.objects.filter(parent=product).delete()
|
||||
|
||||
variant_configs = [
|
||||
(kits[0], created_attrs["Dlina"][0], created_attrs["Упаковка"][0], True), # 50, BEZ, default
|
||||
(kits[1], created_attrs["Dlina"][1], created_attrs["Упаковка"][1], False), # 60, V_UPAKOVKE
|
||||
(kits[2], created_attrs["Dlina"][2], created_attrs["Упаковка"][0], False), # 70, BEZ
|
||||
]
|
||||
|
||||
for kit, dlina_attr, upakovka_attr, is_default in variant_configs:
|
||||
option = ConfigurableKitOption.objects.create(
|
||||
parent=product,
|
||||
kit=kit,
|
||||
is_default=is_default
|
||||
)
|
||||
print(f" Created variant {option.id} for kit: {kit.name}")
|
||||
|
||||
# Create M2M relationships
|
||||
ConfigurableKitOptionAttribute.objects.create(
|
||||
option=option,
|
||||
attribute=dlina_attr
|
||||
)
|
||||
ConfigurableKitOptionAttribute.objects.create(
|
||||
option=option,
|
||||
attribute=upakovka_attr
|
||||
)
|
||||
print(f" - Linked attributes: Dlina={dlina_attr.option}, Upakovka={upakovka_attr.option}")
|
||||
|
||||
print(f" OK: Created {len(variant_configs)} variant(s)")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Verify data retrieval
|
||||
print("\n[5] Verifying variant data...")
|
||||
try:
|
||||
options = ConfigurableKitOption.objects.filter(parent=product)
|
||||
print(f" Found {options.count()} variant(s)")
|
||||
|
||||
for opt in options:
|
||||
print(f"\n Variant {opt.id}:")
|
||||
print(f" - Kit: {opt.kit.name}")
|
||||
print(f" - Default: {opt.is_default}")
|
||||
|
||||
# Get attributes through M2M
|
||||
opt_attrs = opt.attributes_set.all()
|
||||
print(f" - Attributes ({opt_attrs.count()}):")
|
||||
for opt_attr in opt_attrs:
|
||||
print(f" * {opt_attr.attribute.name} = {opt_attr.attribute.option}")
|
||||
|
||||
print("\n OK: All data retrieves correctly")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("OK: WORKFLOW TEST COMPLETED SUCCESSFULLY!")
|
||||
print("=" * 70)
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
REM Скрипт для запуска тестов переходов между статусами заказов
|
||||
|
||||
cd /d C:\Users\team_\Desktop\test_qwen
|
||||
call .venv\Scripts\activate.bat
|
||||
|
||||
cd myproject
|
||||
python manage.py test inventory.tests.test_order_status_transitions --verbosity=2
|
||||
|
||||
pause
|
||||
@@ -1,2 +0,0 @@
|
||||
print
|
||||
Testing wallet
|
||||
@@ -1,44 +0,0 @@
|
||||
docker run -d `
|
||||
--name postgres17 `
|
||||
-e POSTGRES_PASSWORD=postgres `
|
||||
-e POSTGRES_USER=postgres `
|
||||
-e POSTGRES_DB=inventory_db `
|
||||
-p 5432:5432 `
|
||||
-v postgres17-data:/var/lib/postgresql/data `
|
||||
postgres:17
|
||||
|
||||
# 2. Создаем миграции с нуля
|
||||
python manage.py makemigrations
|
||||
|
||||
# 3. Применяем все миграции
|
||||
python manage.py migrate
|
||||
|
||||
# 4. Создаем главного тенанта (если нужно)
|
||||
python manage.py shell
|
||||
# Внутри shell:
|
||||
from tenants.models import Client, Domain
|
||||
client = Client.objects.create(
|
||||
name='Main',
|
||||
schema_name='public'
|
||||
)
|
||||
Domain.objects.create(
|
||||
domain='localhost',
|
||||
tenant=client,
|
||||
is_primary=True
|
||||
)
|
||||
exit()
|
||||
|
||||
# 5. Создаем суперпользователя для public схемы
|
||||
python manage.py createsuperuser
|
||||
|
||||
# 6. Создаем суперпользователя для конкретного тенанта (опционально)
|
||||
python manage.py shell
|
||||
# Внутри:
|
||||
from tenants.models import Client
|
||||
from django.core.management import call_command
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
client = Client.objects.get(schema_name='public')
|
||||
with schema_context(client):
|
||||
call_command('createsuperuser')
|
||||
exit()
|
||||
Reference in New Issue
Block a user