""" Boxes Management Module Handles CRUD operations for warehouse boxes Uses boxes_crates table matching the old app structure """ from app.database import get_db import logging logger = logging.getLogger(__name__) def ensure_boxes_table(): """Create boxes_crates table if it doesn't exist""" try: conn = get_db() cursor = conn.cursor() cursor.execute("SHOW TABLES LIKE 'boxes_crates'") result = cursor.fetchone() if not result: cursor.execute(''' CREATE TABLE IF NOT EXISTS boxes_crates ( id BIGINT AUTO_INCREMENT PRIMARY KEY, box_number VARCHAR(8) NOT NULL UNIQUE, status ENUM('open', 'closed') DEFAULT 'open', location_id BIGINT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_by VARCHAR(100), FOREIGN KEY (location_id) REFERENCES warehouse_locations(id) ON DELETE SET NULL, INDEX idx_box_number (box_number), INDEX idx_status (status) ) ''') conn.commit() cursor.close() logger.info("boxes_crates table ensured") return True except Exception as e: logger.error(f"Error ensuring boxes_crates table: {e}") return False def generate_box_number(): """Generate next box number with 8 digits (00000001, 00000002, etc.)""" try: conn = get_db() cursor = conn.cursor() cursor.execute("SELECT MAX(CAST(box_number AS UNSIGNED)) FROM boxes_crates") result = cursor.fetchone() cursor.close() if result and result[0]: next_number = int(result[0]) + 1 else: next_number = 1 return str(next_number).zfill(8) except Exception as e: logger.error(f"Error generating box number: {e}") return "00000001" def add_box(created_by=None): """Add a new box/crate with auto-generated number""" try: ensure_boxes_table() box_number = generate_box_number() conn = get_db() cursor = conn.cursor() cursor.execute( "INSERT INTO boxes_crates (box_number, status, created_by) VALUES (%s, %s, %s)", (box_number, 'open', created_by) ) conn.commit() cursor.close() logger.info(f"Box {box_number} created successfully") return True, f"Box {box_number} created successfully" except Exception as e: logger.error(f"Error adding box: {e}") return False, f"Error creating box: {str(e)}" def get_all_boxes(): """Get all boxes with their location information""" try: ensure_boxes_table() conn = get_db() cursor = conn.cursor() cursor.execute(''' SELECT b.id, b.box_number, b.status, COALESCE(l.location_code, 'Not assigned') as location_code, b.created_at, b.updated_at, b.created_by, b.location_id FROM boxes_crates b LEFT JOIN warehouse_locations l ON b.location_id = l.id ORDER BY b.created_at DESC ''') boxes = cursor.fetchall() cursor.close() return boxes if boxes else [] except Exception as e: logger.error(f"Error getting all boxes: {e}") return [] def get_box_by_id(box_id): """Get a single box by ID""" try: conn = get_db() cursor = conn.cursor() cursor.execute(''' SELECT b.id, b.box_number, b.status, b.location_id, b.created_by, b.created_at, b.updated_at FROM boxes_crates b WHERE b.id = %s ''', (box_id,)) box = cursor.fetchone() cursor.close() return box except Exception as e: logger.error(f"Error getting box by ID: {e}") return None def update_box(box_id, status=None, location_id=None): """Update box status or location""" try: conn = get_db() cursor = conn.cursor() if status and location_id is not None: cursor.execute( "UPDATE boxes_crates SET status = %s, location_id = %s WHERE id = %s", (status, location_id if location_id else None, box_id) ) elif status: cursor.execute( "UPDATE boxes_crates SET status = %s WHERE id = %s", (status, box_id) ) elif location_id is not None: cursor.execute( "UPDATE boxes_crates SET location_id = %s WHERE id = %s", (location_id if location_id else None, box_id) ) conn.commit() cursor.close() logger.info(f"Box {box_id} updated successfully") return True, "Box updated successfully" except Exception as e: logger.error(f"Error updating box: {e}") return False, f"Error updating box: {str(e)}" def delete_box(box_id): """Delete a single box""" try: conn = get_db() cursor = conn.cursor() cursor.execute("DELETE FROM boxes_crates WHERE id = %s", (box_id,)) conn.commit() cursor.close() logger.info(f"Box {box_id} deleted successfully") return True, "Box deleted successfully" except Exception as e: logger.error(f"Error deleting box: {e}") return False, f"Error deleting box: {str(e)}" def delete_multiple_boxes(box_ids_str): """Delete multiple boxes""" try: if not box_ids_str: return False, "No boxes selected" # Parse box IDs box_ids = [int(x) for x in box_ids_str.split(',') if x.strip()] if not box_ids: return False, "No valid box IDs provided" conn = get_db() cursor = conn.cursor() placeholders = ','.join(['%s'] * len(box_ids)) cursor.execute(f"DELETE FROM boxes_crates WHERE id IN ({placeholders})", box_ids) conn.commit() cursor.close() logger.info(f"Deleted {len(box_ids)} boxes") return True, f"Deleted {len(box_ids)} box(es) successfully" except Exception as e: logger.error(f"Error deleting multiple boxes: {e}") return False, f"Error deleting boxes: {str(e)}" def get_box_stats(): """Get box statistics""" try: ensure_boxes_table() conn = get_db() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM boxes_crates") total = cursor.fetchone()[0] if cursor.fetchone() else 0 cursor.execute("SELECT COUNT(*) FROM boxes_crates WHERE status = 'open'") result = cursor.fetchone() open_count = result[0] if result else 0 cursor.execute("SELECT COUNT(*) FROM boxes_crates WHERE status = 'closed'") result = cursor.fetchone() closed_count = result[0] if result else 0 cursor.close() return { 'total': total, 'open': open_count, 'closed': closed_count } except Exception as e: logger.error(f"Error getting box statistics: {e}") return {'total': 0, 'open': 0, 'closed': 0}