"""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