Files
octopus/myproject/inventory/utils/document_generator.py
Andrey Smakotin c534e27c41 refactor: подготовка к стандартизации Transfer моделей
Текущее состояние перед рефакторингом Transfer → TransferDocument.
Все изменения с последнего коммита по улучшению системы поступлений.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 19:55:50 +03:00

152 lines
6.0 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.
"""
Генератор номеров документов для различных операций в 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 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
# Собираем все номера документов из IncomingDocument
all_numbers = list(IncomingDocument.objects.filter(
document_number__startswith='IN-'
).values_list('document_number', flat=True))
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
максимальным номером из существующих документов 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}"