Spaces:
Sleeping
Sleeping
| """ | |
| Organization Code Generator - Creates unique, memorable codes for organizations | |
| Format: [Initials(3)][Year(2)][Sequence(3)] = e.g., "FWL25001" | |
| """ | |
| from datetime import datetime | |
| from sqlalchemy.orm import Session | |
| from app.models.client import Client | |
| from app.models.contractor import Contractor | |
| def extract_initials(org_name: str, max_length: int = 3) -> str: | |
| """ | |
| Extract initials from organization name. | |
| Rules: | |
| - Take first letter of each word (up to max_length) | |
| - If single word, take first max_length characters | |
| - Convert to uppercase | |
| - Remove special characters | |
| Examples: | |
| "FiberWorks Ltd" -> "FWL" | |
| "TechInstall" -> "TEC" | |
| "ABC Corp" -> "ABC" | |
| "M" -> "M" | |
| """ | |
| # Remove special characters and extra spaces | |
| cleaned = ''.join(c if c.isalnum() or c.isspace() else ' ' for c in org_name) | |
| words = cleaned.split() | |
| if not words: | |
| return "ORG" # Fallback | |
| # If multiple words, take first letter of each | |
| if len(words) > 1: | |
| initials = ''.join(word[0].upper() for word in words if word)[:max_length] | |
| else: | |
| # Single word: take first N characters | |
| initials = words[0][:max_length].upper() | |
| # Ensure minimum length of 1 | |
| return initials if initials else "ORG" | |
| def find_next_sequence(prefix: str, year_suffix: str, db: Session) -> int: | |
| """ | |
| Find the next available sequence number for the given prefix+year. | |
| Searches both clients and contractors tables for codes matching pattern. | |
| Returns next available sequence number (1-999). | |
| Args: | |
| prefix: The initials prefix (e.g., "FWL") | |
| year_suffix: Two-digit year (e.g., "25") | |
| db: Database session | |
| Returns: | |
| Next available sequence number (1-999) | |
| """ | |
| pattern = f"{prefix}{year_suffix}%" | |
| # Find all codes matching this pattern in both tables | |
| client_codes = db.query(Client.swiftops_code).filter( | |
| Client.swiftops_code.like(pattern) | |
| ).all() | |
| contractor_codes = db.query(Contractor.swiftops_code).filter( | |
| Contractor.swiftops_code.like(pattern) | |
| ).all() | |
| # Extract sequence numbers | |
| existing_sequences = set() | |
| all_codes = [c[0] for c in client_codes] + [c[0] for c in contractor_codes] | |
| for code in all_codes: | |
| if code and len(code) >= 8: # Expected format: XXX25NNN | |
| try: | |
| # Extract last 3 digits | |
| sequence = int(code[-3:]) | |
| existing_sequences.add(sequence) | |
| except ValueError: | |
| continue | |
| # Find next available sequence (1-999) | |
| for seq in range(1, 1000): | |
| if seq not in existing_sequences: | |
| return seq | |
| # If all 999 slots are taken, raise error | |
| raise ValueError(f"All sequence numbers exhausted for prefix {prefix}{year_suffix}") | |
| def generate_org_code(org_name: str, db: Session) -> str: | |
| """ | |
| Generate a unique SwiftOps code for an organization. | |
| Format: [Initials(3)][Year(2)][Sequence(3)] | |
| Example: "FWL25001" for FiberWorks Ltd in 2025 | |
| Args: | |
| org_name: The organization name | |
| db: Database session for checking uniqueness | |
| Returns: | |
| Unique 8-character organization code | |
| Raises: | |
| ValueError: If unable to generate unique code | |
| """ | |
| # Extract initials (up to 3 characters) | |
| initials = extract_initials(org_name, max_length=3) | |
| # Get current year suffix (last 2 digits) | |
| year_suffix = str(datetime.now().year)[-2:] | |
| # Find next available sequence number | |
| sequence = find_next_sequence(initials, year_suffix, db) | |
| # Format code | |
| code = f"{initials}{year_suffix}{sequence:03d}" | |
| return code | |
| def validate_org_code_format(code: str) -> bool: | |
| """ | |
| Validate that a code follows the expected format. | |
| Format: [Initials(1-3)][Year(2)][Sequence(3)] | |
| Length: 6-8 characters | |
| Args: | |
| code: The code to validate | |
| Returns: | |
| True if valid format, False otherwise | |
| """ | |
| if not code or not isinstance(code, str): | |
| return False | |
| # Length should be 6-8 characters | |
| if not (6 <= len(code) <= 8): | |
| return False | |
| # Should be alphanumeric | |
| if not code.isalnum(): | |
| return False | |
| # Should be uppercase | |
| if not code.isupper(): | |
| return False | |
| # Last 3 characters should be digits | |
| if not code[-3:].isdigit(): | |
| return False | |
| # Characters before last 5 should be letters (initials) | |
| if len(code) >= 5: | |
| initials_part = code[:-5] | |
| if initials_part and not initials_part.isalpha(): | |
| return False | |
| return True | |
| def is_code_available(code: str, db: Session, exclude_id: int = None, org_type: str = None) -> bool: | |
| """ | |
| Check if an organization code is available (not in use). | |
| Args: | |
| code: The code to check | |
| db: Database session | |
| exclude_id: Optional ID to exclude (for updates) | |
| org_type: Optional org type filter ('client' or 'contractor') | |
| Returns: | |
| True if code is available, False if already in use | |
| """ | |
| # Check clients | |
| if org_type != 'contractor': | |
| query = db.query(Client).filter(Client.swiftops_code == code) | |
| if exclude_id: | |
| query = query.filter(Client.id != exclude_id) | |
| if query.first(): | |
| return False | |
| # Check contractors | |
| if org_type != 'client': | |
| query = db.query(Contractor).filter(Contractor.swiftops_code == code) | |
| if exclude_id: | |
| query = query.filter(Contractor.id != exclude_id) | |
| if query.first(): | |
| return False | |
| return True | |