Add boxes/crates management system to warehouse module
- Created boxes_crates table with 8-digit auto-generated box numbers - Added manage_boxes route and full CRUD operations - Implemented box status tracking (open/closed) - Added location assignment functionality - Integrated QZ Tray printing with barcode labels - Created manage_boxes.html with table, preview, and print features - Added 'Manage Boxes/Crates' card to warehouse main page - Boxes can be created without location and assigned later - Includes delete functionality for admin/management roles - Added box statistics display
This commit is contained in:
@@ -44,6 +44,30 @@ def ensure_warehouse_locations_table():
|
||||
except Exception as e:
|
||||
print(f"Error ensuring warehouse_locations table: {e}")
|
||||
|
||||
def ensure_boxes_crates_table():
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
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
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error ensuring boxes_crates table: {e}")
|
||||
|
||||
# Add warehouse-specific functions below
|
||||
def add_location(location_code, size, description):
|
||||
conn = get_db_connection()
|
||||
@@ -268,9 +292,158 @@ def update_location(location_id, location_code, size, description):
|
||||
except Exception as e:
|
||||
print(f"Error updating location: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
print(f"Error updating location: {e}")
|
||||
|
||||
# ============================================================================
|
||||
# Boxes/Crates Functions
|
||||
# ============================================================================
|
||||
|
||||
def generate_box_number():
|
||||
"""Generate next box number with 8 digits (00000001, 00000002, etc.)"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT MAX(CAST(box_number AS UNSIGNED)) FROM boxes_crates")
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if result and result[0]:
|
||||
next_number = int(result[0]) + 1
|
||||
else:
|
||||
next_number = 1
|
||||
|
||||
return str(next_number).zfill(8)
|
||||
|
||||
def add_box(location_id=None, created_by=None):
|
||||
"""Add a new box/crate"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
box_number = generate_box_number()
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
"INSERT INTO boxes_crates (box_number, status, location_id, created_by) VALUES (%s, %s, %s, %s)",
|
||||
(box_number, 'open', location_id if location_id else None, created_by)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return f"Box {box_number} created successfully"
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return f"Error creating box: {e}"
|
||||
|
||||
def get_boxes():
|
||||
"""Get all boxes with location information"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
b.id,
|
||||
b.box_number,
|
||||
b.status,
|
||||
l.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.id DESC
|
||||
""")
|
||||
boxes = cursor.fetchall()
|
||||
conn.close()
|
||||
return boxes
|
||||
|
||||
def update_box_status(box_id, status):
|
||||
"""Update box status (open/closed)"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"UPDATE boxes_crates SET status = %s WHERE id = %s",
|
||||
(status, box_id)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"success": True, "message": f"Box status updated to {status}"}
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def update_box_location(box_id, location_id):
|
||||
"""Update box location"""
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"UPDATE boxes_crates SET location_id = %s WHERE id = %s",
|
||||
(location_id if location_id else None, box_id)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return {"success": True, "message": "Box location updated"}
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def delete_boxes_by_ids(ids_str):
|
||||
"""Delete boxes by comma-separated IDs"""
|
||||
ids = [id.strip() for id in ids_str.split(',') if id.strip().isdigit()]
|
||||
if not ids:
|
||||
return "No valid IDs provided."
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
deleted = 0
|
||||
for id in ids:
|
||||
cursor.execute("DELETE FROM boxes_crates WHERE id = %s", (id,))
|
||||
if cursor.rowcount:
|
||||
deleted += 1
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return f"Deleted {deleted} box(es)."
|
||||
|
||||
def manage_boxes_handler():
|
||||
"""Handler for boxes/crates management page"""
|
||||
try:
|
||||
# Ensure table exists
|
||||
ensure_boxes_crates_table()
|
||||
ensure_warehouse_locations_table()
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action")
|
||||
|
||||
if action == "delete_boxes":
|
||||
ids_str = request.form.get("delete_ids", "")
|
||||
message = delete_boxes_by_ids(ids_str)
|
||||
session['flash_message'] = message
|
||||
elif action == "add_box":
|
||||
created_by = session.get('user', 'Unknown')
|
||||
message = add_box(None, created_by) # Create box without location
|
||||
session['flash_message'] = message
|
||||
elif action == "update_status":
|
||||
box_id = request.form.get("box_id")
|
||||
new_status = request.form.get("new_status")
|
||||
message = update_box_status(box_id, new_status)
|
||||
session['flash_message'] = message
|
||||
elif action == "update_location":
|
||||
box_id = request.form.get("box_id")
|
||||
new_location_id = request.form.get("new_location_id")
|
||||
message = update_box_location(box_id, new_location_id)
|
||||
session['flash_message'] = message
|
||||
|
||||
return redirect(url_for('warehouse.manage_boxes'))
|
||||
|
||||
# Get flash message from session if any
|
||||
message = session.pop('flash_message', None)
|
||||
boxes = get_boxes()
|
||||
locations = get_locations()
|
||||
return render_template("manage_boxes.html", boxes=boxes, locations=locations, message=message)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
print(f"Error in manage_boxes_handler: {e}")
|
||||
print(error_trace)
|
||||
return f"<h1>Error loading boxes management</h1><pre>{error_trace}</pre>", 500
|
||||
|
||||
def delete_location_by_id(location_id):
|
||||
"""Delete a warehouse location by ID"""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user