145 lines
5.0 KiB
Python
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
|