From cd0b5f17f54af15f24f4afecaa161d7a639c8ecb Mon Sep 17 00:00:00 2001 From: jessikitty Date: Mon, 15 Dec 2025 09:23:53 +1100 Subject: [PATCH] Add MOC generator with leading zeros support (MOC-0001 format) --- app/services/moc_generator_updated.py | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 app/services/moc_generator_updated.py diff --git a/app/services/moc_generator_updated.py b/app/services/moc_generator_updated.py new file mode 100644 index 0000000..33d05b9 --- /dev/null +++ b/app/services/moc_generator_updated.py @@ -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