""" Warehouse Worker Management Functions Handles worker-manager bindings and zone assignments for the warehouse module """ from app.database import get_db import logging logger = logging.getLogger(__name__) def assign_worker_to_manager(manager_id, worker_id, warehouse_zone=None): """ Assign a warehouse worker to a manager with optional zone restriction Args: manager_id (int): Manager user ID worker_id (int): Worker user ID warehouse_zone (str, optional): Zone restriction (e.g., "Cold Storage", "High Shelf") Returns: tuple: (success: bool, message: str) """ try: if manager_id == worker_id: return False, "Cannot assign a worker to themselves" db = get_db() cursor = db.cursor() # Check if binding already exists cursor.execute(""" SELECT id FROM worker_manager_bindings WHERE manager_id = %s AND worker_id = %s """, (manager_id, worker_id)) existing = cursor.fetchone() if existing: # Update existing binding cursor.execute(""" UPDATE worker_manager_bindings SET warehouse_zone = %s, is_active = 1, updated_at = NOW() WHERE manager_id = %s AND worker_id = %s """, (warehouse_zone, manager_id, worker_id)) message = f"Worker assignment updated (zone: {warehouse_zone or 'all zones'})" else: # Create new binding cursor.execute(""" INSERT INTO worker_manager_bindings (manager_id, worker_id, warehouse_zone) VALUES (%s, %s, %s) """, (manager_id, worker_id, warehouse_zone)) message = f"Worker assigned to manager (zone: {warehouse_zone or 'all zones'})" db.commit() logger.info(f"✓ {message} - Manager ID: {manager_id}, Worker ID: {worker_id}") return True, message except Exception as e: logger.error(f"Error assigning worker to manager: {e}") return False, f"Error: {str(e)}" def unassign_worker_from_manager(manager_id, worker_id, soft_delete=True): """ Remove a worker from a manager's supervision Args: manager_id (int): Manager user ID worker_id (int): Worker user ID soft_delete (bool): If True, set is_active=0; if False, delete record Returns: tuple: (success: bool, message: str) """ try: db = get_db() cursor = db.cursor() if soft_delete: cursor.execute(""" UPDATE worker_manager_bindings SET is_active = 0, updated_at = NOW() WHERE manager_id = %s AND worker_id = %s """, (manager_id, worker_id)) else: cursor.execute(""" DELETE FROM worker_manager_bindings WHERE manager_id = %s AND worker_id = %s """, (manager_id, worker_id)) db.commit() logger.info(f"✓ Worker unassigned - Manager ID: {manager_id}, Worker ID: {worker_id}") return True, "Worker unassigned successfully" except Exception as e: logger.error(f"Error unassigning worker: {e}") return False, f"Error: {str(e)}" def get_worker_binding_info(worker_id): """ Get binding information for a worker (who manages them, what zone) Args: worker_id (int): Worker user ID Returns: dict: {manager_id, manager_username, manager_name, zone, is_active} or None """ try: db = get_db() cursor = db.cursor() cursor.execute(""" SELECT m.id, m.username, m.full_name, wmb.warehouse_zone, wmb.is_active FROM worker_manager_bindings wmb JOIN users m ON wmb.manager_id = m.id WHERE wmb.worker_id = %s LIMIT 1 """, (worker_id,)) result = cursor.fetchone() if result: return { 'manager_id': result[0], 'manager_username': result[1], 'manager_name': result[2], 'zone': result[3], 'is_active': result[4] } return None except Exception as e: logger.error(f"Error getting worker binding info: {e}") return None def get_available_workers_for_assignment(exclude_manager_id=None): """ Get all warehouse_worker users available for assignment Args: exclude_manager_id (int, optional): Manager ID to exclude from results Returns: list: List of dicts {id, username, full_name, current_manager} """ try: db = get_db() cursor = db.cursor() query = """ SELECT u.id, u.username, u.full_name, (SELECT m.full_name FROM worker_manager_bindings wmb JOIN users m ON wmb.manager_id = m.id WHERE wmb.worker_id = u.id AND wmb.is_active = 1 LIMIT 1) as current_manager FROM users u WHERE u.role = 'warehouse_worker' ORDER BY u.full_name """ cursor.execute(query) results = [] for row in cursor.fetchall(): results.append({ 'id': row[0], 'username': row[1], 'full_name': row[2], 'current_manager': row[3] }) return results except Exception as e: logger.error(f"Error getting available workers: {e}") return [] def get_manager_assigned_workers(manager_id, include_inactive=False): """ Get all workers assigned to a specific manager Args: manager_id (int): Manager user ID include_inactive (bool): Whether to include inactive bindings Returns: list: List of dicts {id, username, full_name, zone, is_active} """ try: db = get_db() cursor = db.cursor() query = """ SELECT u.id, u.username, u.full_name, wmb.warehouse_zone, wmb.is_active FROM worker_manager_bindings wmb JOIN users u ON wmb.worker_id = u.id WHERE wmb.manager_id = %s """ params = [manager_id] if not include_inactive: query += " AND wmb.is_active = 1" query += " ORDER BY u.full_name" cursor.execute(query, params) results = [] for row in cursor.fetchall(): results.append({ 'id': row[0], 'username': row[1], 'full_name': row[2], 'zone': row[3], 'is_active': row[4] }) return results except Exception as e: logger.error(f"Error getting manager's workers: {e}") return [] def validate_zone_name(zone_name): """ Validate zone name format (alphanumeric, spaces, hyphens, underscores allowed) Args: zone_name (str): Zone name to validate Returns: tuple: (is_valid: bool, message: str) """ if not zone_name: return True, "No zone restriction" # NULL zone is valid (all zones) if not isinstance(zone_name, str): return False, "Zone must be a string" zone_name = zone_name.strip() if len(zone_name) > 100: return False, "Zone name too long (max 100 characters)" if len(zone_name) < 2: return False, "Zone name too short (min 2 characters)" # Allow alphanumeric, spaces, hyphens, underscores import re if not re.match(r'^[a-zA-Z0-9\s\-_]+$', zone_name): return False, "Zone name contains invalid characters" return True, "Valid zone name" def get_warehouse_zones(): """ Get list of all warehouse zones in use Returns: list: List of zone names currently assigned to workers """ try: db = get_db() cursor = db.cursor() cursor.execute(""" SELECT DISTINCT warehouse_zone FROM worker_manager_bindings WHERE is_active = 1 AND warehouse_zone IS NOT NULL ORDER BY warehouse_zone """) zones = [row[0] for row in cursor.fetchall()] return zones except Exception as e: logger.error(f"Error getting warehouse zones: {e}") return [] def reassign_worker(worker_id, new_manager_id, warehouse_zone=None): """ Reassign a worker from current manager to a new manager Args: worker_id (int): Worker user ID new_manager_id (int): New manager user ID warehouse_zone (str, optional): New zone restriction Returns: tuple: (success: bool, message: str) """ try: if worker_id == new_manager_id: return False, "Cannot assign a worker to themselves" db = get_db() cursor = db.cursor() # Deactivate old binding cursor.execute(""" UPDATE worker_manager_bindings SET is_active = 0 WHERE worker_id = %s AND is_active = 1 """, (worker_id,)) # Create new binding cursor.execute(""" INSERT INTO worker_manager_bindings (manager_id, worker_id, warehouse_zone) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE is_active = 1, warehouse_zone = %s, updated_at = NOW() """, (new_manager_id, worker_id, warehouse_zone, warehouse_zone)) db.commit() logger.info(f"✓ Worker reassigned - Worker ID: {worker_id}, New Manager ID: {new_manager_id}") return True, "Worker reassigned successfully" except Exception as e: logger.error(f"Error reassigning worker: {e}") return False, f"Error: {str(e)}"