Files
octopus/myproject/inventory/utils/document_generator.py
Andrey Smakotin a8ba5ce780 Улучшения инвентаризации: автоматическое проведение документов, оптимизация запросов и улучшения UI
- Автоматическое проведение документов списания и оприходования после завершения инвентаризации
- Оптимизация SQL-запросов: устранение N+1, bulk-операции для Stock, агрегация для StockBatch и Reservation
- Изменение формулы расчета разницы: (quantity_fact + quantity_reserved) - quantity_available
- Переименование поля 'По факту' в 'Подсчитано (факт, свободные)'
- Добавлены столбцы 'В резервах' и 'Всего на складе' в таблицу инвентаризации
- Перемещение столбца 'В системе (свободно)' после 'В резервах' с визуальным выделением
- Центральное выравнивание значений в столбцах таблицы
- Автоматическое выделение текста при фокусе на поле ввода количества
- Исправление форматирования разницы (убраны лишние нули)
- Изменение статуса 'Не обработана' на 'Не проведено'
- Добавление номера документа для инвентаризаций (INV-XXXXXX)
- Отображение всех типов списаний в debug-странице (WriteOff, WriteOffDocument, WriteOffDocumentItem)
- Улучшение отображения документов в детальном просмотре инвентаризации с возможностью перехода к ним
2025-12-21 23:59:02 +03:00

162 lines
6.4 KiB
Python
Raw 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.
"""
Генератор номеров документов для различных операций в inventory.
"""
from inventory.models import DocumentCounter
def generate_writeoff_document_number():
"""
Генерирует уникальный номер документа списания.
Формат: WO-XXXXXX (6 цифр)
Thread-safe через DocumentCounter.
Returns:
str: Сгенерированный номер документа (например, WO-000001)
"""
next_number = DocumentCounter.get_next_value('writeoff')
return f"WO-{next_number:06d}"
def generate_transfer_document_number():
"""
Генерирует уникальный номер документа перемещения.
Формат: MOVE-XXXXXX (6 цифр)
Returns:
str: Сгенерированный номер документа (например, MOVE-000001)
"""
next_number = DocumentCounter.get_next_value('transfer')
return f"MOVE-{next_number:06d}"
def _extract_number_from_document_number(doc_number):
"""
Извлекает числовое значение из номера документа.
Поддерживает оба формата: IN-0000-0003 и IN-000002.
Для старого формата IN-XXXX-YYYY извлекается YYYY (последние 4 цифры).
Для нового формата IN-XXXXXX извлекается XXXXXX (6 цифр).
Args:
doc_number: строка номера документа (например, 'IN-0000-0003' или 'IN-000002')
Returns:
int: числовое значение или 0 если не удалось распарсить
"""
try:
# Убираем префикс 'IN-'
if not doc_number.startswith('IN-'):
return 0
parts = doc_number[3:].split('-') # ['0000', '0003'] или ['000002']
if len(parts) == 2:
# Старый формат: IN-0000-0003
# Берем последнюю часть (0003) и конвертируем в число
return int(parts[1])
elif len(parts) == 1:
# Новый формат: IN-000002
return int(parts[0])
else:
return 0
except (ValueError, IndexError):
return 0
def _initialize_incoming_counter_if_needed():
"""
Инициализирует DocumentCounter для 'incoming' максимальным номером
из существующих документов, если счетчик еще не инициализирован.
Вызывается только если счетчик равен 0 (не инициализирован).
Thread-safe через select_for_update.
"""
from inventory.models import IncomingBatch, IncomingDocument
from django.db import transaction
# Быстрая проверка без блокировки - если счетчик существует и > 0, выходим
if DocumentCounter.objects.filter(
counter_type='incoming',
current_value__gt=0
).exists():
return
# Только если счетчик не инициализирован - делаем полную проверку с блокировкой
with transaction.atomic():
counter = DocumentCounter.objects.select_for_update().filter(
counter_type='incoming'
).first()
# Двойная проверка: возможно другой поток уже инициализировал
if counter and counter.current_value > 0:
return
# Собираем все номера документов
all_numbers = []
# Номера из IncomingBatch
batch_numbers = IncomingBatch.objects.filter(
document_number__startswith='IN-'
).values_list('document_number', flat=True)
all_numbers.extend(batch_numbers)
# Номера из IncomingDocument
doc_numbers = IncomingDocument.objects.filter(
document_number__startswith='IN-'
).values_list('document_number', flat=True)
all_numbers.extend(doc_numbers)
if all_numbers:
# Извлекаем максимальный номер из всех форматов
max_number = max(_extract_number_from_document_number(num) for num in all_numbers)
else:
# Нет существующих документов - начинаем с 0
max_number = 0
# Создаем или обновляем счетчик
if not counter:
DocumentCounter.objects.create(
counter_type='incoming',
current_value=max_number
)
elif counter.current_value == 0:
counter.current_value = max_number
counter.save(update_fields=['current_value'])
def generate_incoming_document_number():
"""
Генерирует уникальный номер документа поступления.
Формат: IN-XXXXXX (6 цифр) - унифицирован с WO-000001 и MOVE-000001.
Thread-safe через DocumentCounter.
При первом использовании автоматически инициализирует DocumentCounter
максимальным номером из существующих документов (IncomingBatch и IncomingDocument).
Returns:
str: Сгенерированный номер документа (например, IN-000001)
"""
# Инициализируем счетчик, если нужно (только если он равен 0)
_initialize_incoming_counter_if_needed()
# Используем стандартный метод, как и другие функции
next_number = DocumentCounter.get_next_value('incoming')
return f"IN-{next_number:06d}"
def generate_inventory_document_number():
"""
Генерирует уникальный номер документа инвентаризации.
Формат: INV-XXXXXX (6 цифр)
Thread-safe через DocumentCounter.
Returns:
str: Сгенерированный номер документа (например, INV-000001)
"""
next_number = DocumentCounter.get_next_value('inventory')
return f"INV-{next_number:06d}"