- Add management command to recalculate quantity_reserved for all Stock records - Add signals to automatically update Stock when Reservation changes - Implement post_save signal for Reservation creation/updates - Implement post_delete signal for Reservation deletion - Both signals call Stock.refresh_from_batches() to recalculate quantities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
4.8 KiB
Python
107 lines
4.8 KiB
Python
"""
|
||
Management команда для пересчета quantity_reserved для всех Stock записей
|
||
Используется после добавления сигналов для Reservation
|
||
"""
|
||
from django.core.management.base import BaseCommand
|
||
from django.db import connection, transaction
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = 'Пересчитывает quantity_reserved для всех Stock записей'
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
'--schema',
|
||
type=str,
|
||
default='grach',
|
||
help='Схема базы данных (tenant) для работы'
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
schema_name = options['schema']
|
||
|
||
self.stdout.write(f'[НАЧАЛО] Пересчет quantity_reserved для всех Stock записей в схеме {schema_name}...')
|
||
|
||
updated_count = 0
|
||
reserved_count = 0
|
||
|
||
# Используем прямой SQL для пересчета quantity_reserved
|
||
# Это обходит проблему с tenant routing в Django ORM
|
||
with connection.cursor() as cursor, transaction.atomic():
|
||
# Устанавливаем схему
|
||
cursor.execute(f'SET search_path TO {schema_name}')
|
||
|
||
# Получаем общее количество Stock записей
|
||
cursor.execute(f'SELECT COUNT(*) FROM {schema_name}.inventory_stock')
|
||
total_count = cursor.fetchone()[0]
|
||
|
||
self.stdout.write(f'[INFO] Найдено Stock записей: {total_count}')
|
||
|
||
# Получаем все Stock записи с информацией о продукте
|
||
cursor.execute(f"""
|
||
SELECT
|
||
s.id as stock_id,
|
||
s.product_id,
|
||
p.name as product_name,
|
||
s.warehouse_id
|
||
FROM {schema_name}.inventory_stock s
|
||
JOIN {schema_name}.products_product p ON p.id = s.product_id
|
||
""")
|
||
stocks = cursor.fetchall()
|
||
|
||
# Обрабатываем каждый Stock
|
||
for stock_id, product_id, product_name, warehouse_id in stocks:
|
||
try:
|
||
# Пересчитываем quantity_reserved и quantity_available
|
||
# Логика из Stock.refresh_from_batches()
|
||
|
||
# 1. Считаем общее количество из активных партий
|
||
cursor.execute(f"""
|
||
SELECT COALESCE(SUM(quantity), 0)
|
||
FROM {schema_name}.inventory_stockbatch
|
||
WHERE product_id = %s
|
||
AND warehouse_id = %s
|
||
AND is_active = true
|
||
""", [product_id, warehouse_id])
|
||
total_qty = cursor.fetchone()[0]
|
||
|
||
# 2. Считаем зарезервированное количество
|
||
cursor.execute(f"""
|
||
SELECT COALESCE(SUM(quantity), 0)
|
||
FROM {schema_name}.inventory_reservation
|
||
WHERE product_id = %s
|
||
AND warehouse_id = %s
|
||
AND status = 'reserved'
|
||
""", [product_id, warehouse_id])
|
||
total_reserved = cursor.fetchone()[0]
|
||
|
||
# 3. Обновляем Stock
|
||
cursor.execute(f"""
|
||
UPDATE {schema_name}.inventory_stock
|
||
SET quantity_available = %s,
|
||
quantity_reserved = %s,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = %s
|
||
""", [total_qty, total_reserved, stock_id])
|
||
|
||
updated_count += 1
|
||
|
||
if total_reserved > 0:
|
||
reserved_count += 1
|
||
self.stdout.write(
|
||
f' [OK] Stock #{stock_id}: {product_name} - '
|
||
f'зарезервировано: {total_reserved}'
|
||
)
|
||
|
||
except Exception as e:
|
||
self.stdout.write(
|
||
self.style.ERROR(f' [ОШИБКА] Stock #{stock_id}: {str(e)}')
|
||
)
|
||
|
||
self.stdout.write('\n' + '=' * 60)
|
||
self.stdout.write(self.style.SUCCESS('[ЗАВЕРШЕНО] Результаты:'))
|
||
self.stdout.write(f' Всего Stock записей: {total_count}')
|
||
self.stdout.write(f' Обновлено записей: {updated_count}')
|
||
self.stdout.write(f' Записей с резервами: {reserved_count}')
|
||
self.stdout.write(self.style.SUCCESS('\n[OK] Все резервы пересчитаны!'))
|