Унификация генерации номеров документов и оптимизация кода

- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX
- Убрано дублирование функции _extract_number_from_document_number
- Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией
- Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py)
- Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
This commit is contained in:
2025-12-21 00:51:08 +03:00
parent 78dc9e9801
commit 375ec5366a
14 changed files with 1873 additions and 147 deletions

View File

@@ -32,73 +32,117 @@ def generate_transfer_document_number():
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-XXXX-XXXX'.
Генерирует уникальный номер документа поступления.
Алгоритм:
1. Ищет максимальный номер в БД с префиксом 'IN-'
2. Извлекает числовое значение из последней части (IN-XXXX-XXXX)
3. Увеличивает на 1 и форматирует в 'IN-XXXX-XXXX'
Формат: IN-XXXXXX (6 цифр) - унифицирован с WO-000001 и MOVE-000001.
Thread-safe через DocumentCounter.
Преимущества:
- Работает без SEQUENCE (не требует миграций)
- Гарантирует уникальность через unique constraint в модели
- Простая логика, легко отладить
- Работает с любым тенантом (django-tenants совместимо)
При первом использовании автоматически инициализирует DocumentCounter
максимальным номером из существующих документов (IncomingBatch и IncomingDocument).
Возвращает:
str: Номер вида 'IN-0000-0001', 'IN-0000-0002', итд
Returns:
str: Сгенерированный номер документа (например, IN-000001)
"""
from inventory.models import IncomingBatch
import logging
import os
# Настройка логирования
LOG_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs', 'incoming_sequence.log')
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
file_logger = logging.getLogger('incoming_sequence_file')
if not file_logger.handlers:
handler = logging.FileHandler(LOG_FILE, encoding='utf-8')
formatter = logging.Formatter(
'%(asctime)s | %(levelname)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
file_logger.addHandler(handler)
file_logger.setLevel(logging.DEBUG)
logger = logging.getLogger('inventory.incoming')
try:
# Найти все номера с префиксом IN-
existing_batches = IncomingBatch.objects.filter(
document_number__startswith='IN-'
).values_list('document_number', flat=True).order_by('document_number')
if not existing_batches:
# Если нет номеров - начинаем с 1
next_num = 1
file_logger.info(f"✓ No existing batches found, starting from 1")
else:
# Берем последний номер, извлекаем цифру и увеличиваем
last_number = existing_batches.last() # 'IN-0000-0005'
# Извлекаем последние 4 цифры
last_digits = int(last_number.split('-')[-1]) # 5
next_num = last_digits + 1
file_logger.info(f"✓ Last number was {last_number}, next: {next_num}")
# Форматируем в IN-XXXX-XXXX
combined_str = f"{next_num:08d}" # Гарантируем 8 цифр
first_part = combined_str[:4] # '0000' или '0001'
second_part = combined_str[4:] # '0001' или '0002'
result = f"IN-{first_part}-{second_part}"
file_logger.info(f"✓ Generated: {result}")
return result
except Exception as e:
file_logger.error(f"✗ Error generating number: {str(e)}")
raise
# Инициализируем счетчик, если нужно (только если он равен 0)
_initialize_incoming_counter_if_needed()
# Используем стандартный метод, как и другие функции
next_number = DocumentCounter.get_next_value('incoming')
return f"IN-{next_number:06d}"