Files
octopus/myproject/inventory/management/commands/refresh_stock_reservations.py
Andrey Smakotin d2f15b5126 Add stock reservation auto-update system
- 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>
2025-11-08 11:07:35 +03:00

107 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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] Все резервы пересчитаны!'))