swiftops-backend / src /app /utils /org_code_generator.py
kamau1's picture
feat: org ids for clients and contractors inside swiftops
2b747fd
"""
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