Files
lego-instructions-manager/app/services/moc_generator_updated.py

145 lines
5.0 KiB
Python

"""MOC Number Generator Service
Handles automatic generation and validation of MOC (My Own Creation) set numbers.
"""
from app.models import Set
from app import db
class MOCNumberGenerator:
"""Service for generating sequential MOC numbers with leading zeros"""
DEFAULT_PREFIX = 'MOC'
DEFAULT_START = 1 # Start from 1 for MOC-0001, MOC-0002, etc.
NUMBER_PADDING = 4 # Format as 4 digits with leading zeros (0001, 0002, etc.)
@staticmethod
def generate_next_number(prefix='MOC', user_id=None):
"""
Generate the next available MOC number with leading zeros.
Format: MOC-0001, MOC-0002, MOC-0003, etc.
Args:
prefix (str): MOC prefix (default: 'MOC')
user_id (int, optional): User ID for user-scoped numbering.
None for global numbering (default).
Returns:
str: Next MOC number (e.g., 'MOC-0012')
Examples:
>>> MOCNumberGenerator.generate_next_number()
'MOC-0001'
>>> MOCNumberGenerator.generate_next_number(prefix='CUSTOM')
'CUSTOM-0001'
"""
try:
# Build query for existing MOC numbers with this prefix
query = Set.query.filter(
Set.is_moc == True,
Set.set_number.like(f'{prefix}-%')
)
# Add user filter if user-scoped
if user_id is not None:
query = query.filter(Set.user_id == user_id)
# Get the highest existing MOC number
latest_set = query.order_by(Set.set_number.desc()).first()
if latest_set:
# Extract the number from the set_number (e.g., "MOC-0012" -> 12)
try:
number_part = latest_set.set_number.split('-')[-1]
current_num = int(number_part)
next_num = current_num + 1
except (ValueError, IndexError):
# If parsing fails, start from default
next_num = MOCNumberGenerator.DEFAULT_START
else:
# No existing MOCs, start from default
next_num = MOCNumberGenerator.DEFAULT_START
# Format with leading zeros (e.g., 12 -> "0012")
formatted_number = str(next_num).zfill(MOCNumberGenerator.NUMBER_PADDING)
return f'{prefix}-{formatted_number}'
except Exception as e:
# On any error, return a safe default
formatted_default = str(MOCNumberGenerator.DEFAULT_START).zfill(
MOCNumberGenerator.NUMBER_PADDING
)
return f'{prefix}-{formatted_default}'
@staticmethod
def validate_moc_number(set_number, user_id=None):
"""
Check if a MOC number is available (not already in use).
Args:
set_number (str): MOC number to validate
user_id (int, optional): User ID for user-scoped validation.
None for global validation.
Returns:
bool: True if available, False if already exists
Examples:
>>> MOCNumberGenerator.validate_moc_number('MOC-0012')
True
>>> MOCNumberGenerator.validate_moc_number('MOC-0001') # If exists
False
"""
query = Set.query.filter(
Set.is_moc == True,
Set.set_number == set_number
)
# Add user filter if user-scoped
if user_id is not None:
query = query.filter(Set.user_id == user_id)
existing = query.first()
return existing is None # Available if no existing set found
@staticmethod
def is_moc_number(set_number, prefix='MOC'):
"""
Check if a set number follows the MOC format.
Format: PREFIX-NNNN (where NNNN is 4 digits with leading zeros)
Args:
set_number (str): Set number to check
prefix (str): Expected prefix (default: 'MOC')
Returns:
bool: True if matches MOC format, False otherwise
Examples:
>>> MOCNumberGenerator.is_moc_number('MOC-0012')
True
>>> MOCNumberGenerator.is_moc_number('10497')
False
>>> MOCNumberGenerator.is_moc_number('CUSTOM-0001', prefix='CUSTOM')
True
"""
if not set_number or not isinstance(set_number, str):
return False
# Check if it starts with the prefix followed by a hyphen
if not set_number.startswith(f'{prefix}-'):
return False
# Extract the number part
try:
number_part = set_number.split('-', 1)[1]
# Check if it's a valid number (will work with or without leading zeros)
int(number_part)
return True
except (ValueError, IndexError):
return False