Add MOC generator with leading zeros support (MOC-0001 format)

This commit is contained in:
2025-12-15 09:23:53 +11:00
parent 3aa81f5b6d
commit cd0b5f17f5

View File

@@ -0,0 +1,144 @@
"""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