diff --git a/backups/backups_metadata.json b/backups/backups_metadata.json index 9934c71..8b8b467 100644 --- a/backups/backups_metadata.json +++ b/backups/backups_metadata.json @@ -118,5 +118,11 @@ "size": 539493, "timestamp": "2025-11-22T03:00:00.182628", "database": "trasabilitate" + }, + { + "filename": "data_only_scheduled_20251227_030000.sql", + "size": 16038, + "timestamp": "2025-12-27T03:00:00.088164", + "database": "trasabilitate" } ] \ No newline at end of file diff --git a/py_app/app/pdf_generator.py b/py_app/app/pdf_generator.py index 42e218f..fd769dc 100644 --- a/py_app/app/pdf_generator.py +++ b/py_app/app/pdf_generator.py @@ -159,10 +159,10 @@ class LabelPDFGenerator: # Calculate row positions from top current_y = self.content_y + self.content_height - # Row 1: Company Header - "INNOFA RROMANIA SRL" + # Row 1: Company Header - (removed) row_y = current_y - self.row_height canvas.setFont("Helvetica-Bold", 10) - text = "INNOFA RROMANIA SRL" + text = "" text_width = canvas.stringWidth(text, "Helvetica-Bold", 10) x_centered = self.content_x + (self.content_width - text_width) / 2 canvas.drawString(x_centered, row_y + self.row_height/3, text) diff --git a/py_app/app/routes.py b/py_app/app/routes.py index 04c4ff7..8f37284 100644 --- a/py_app/app/routes.py +++ b/py_app/app/routes.py @@ -6,7 +6,14 @@ from flask import Blueprint, render_template, redirect, url_for, request, flash, from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas import csv -from .warehouse import add_location +from .warehouse import ( + add_location, + search_box_by_number, + assign_box_to_location, + search_location_with_boxes, + move_box_to_new_location, + change_box_status +) from app.settings import ( settings_handler, role_permissions_handler, @@ -5473,3 +5480,98 @@ def restore_single_table(): }), 500 +# Warehouse Box Location Management API Routes + +@bp.route('/api/warehouse/box/search', methods=['POST']) +@requires_warehouse_module +def api_search_box(): + """Search for a box by box number""" + data = request.get_json() + box_number = data.get('box_number', '').strip() + + success, response_data, status_code = search_box_by_number(box_number) + + return jsonify({ + 'success': success, + **response_data + }), status_code + + +@bp.route('/api/warehouse/box/assign-location', methods=['POST']) +@requires_warehouse_module +def api_assign_box_to_location(): + """Assign a box to a warehouse location (only if box is closed)""" + data = request.get_json() + box_id = data.get('box_id') + location_code = data.get('location_code', '').strip() + + # Additional check: verify box is closed before assigning + if box_id: + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute("SELECT status FROM boxes_crates WHERE id = %s", (box_id,)) + result = cursor.fetchone() + conn.close() + + if result and result[0] == 'open': + return jsonify({ + 'success': False, + 'message': 'Cannot assign an open box to a location. Please close the box first.' + }), 400 + except Exception as e: + pass # Continue to the main function which will handle other errors + + success, response_data, status_code = assign_box_to_location(box_id, location_code) + + return jsonify({ + 'success': success, + **response_data + }), status_code + + +@bp.route('/api/warehouse/box/change-status', methods=['POST']) +@requires_warehouse_module +def api_change_box_status(): + """Change the status of a box (open/closed)""" + data = request.get_json() + box_id = data.get('box_id') + new_status = data.get('new_status', '').strip() + + success, response_data, status_code = change_box_status(box_id, new_status) + + return jsonify({ + 'success': success, + **response_data + }), status_code + + +@bp.route('/api/warehouse/location/search', methods=['POST']) +@requires_warehouse_module +def api_search_location(): + """Search for a location and get all boxes in it""" + data = request.get_json() + location_code = data.get('location_code', '').strip() + + success, response_data, status_code = search_location_with_boxes(location_code) + + return jsonify({ + 'success': success, + **response_data + }), status_code + + +@bp.route('/api/warehouse/box/move-location', methods=['POST']) +@requires_warehouse_module +def api_move_box_to_location(): + """Move a box from one location to another""" + data = request.get_json() + box_id = data.get('box_id') + new_location_code = data.get('new_location_code', '').strip() + + success, response_data, status_code = move_box_to_new_location(box_id, new_location_code) + + return jsonify({ + 'success': success, + **response_data + }), status_code diff --git a/py_app/app/templates/base.html b/py_app/app/templates/base.html index c544c79..9a52421 100644 --- a/py_app/app/templates/base.html +++ b/py_app/app/templates/base.html @@ -50,7 +50,7 @@ {% if request.endpoint in ['main.etichete', 'main.upload_data', 'main.view_orders', 'main.print_module', 'main.print_lost_labels'] %} Labels Module {% endif %} - {% if request.endpoint.startswith('warehouse.') %} + {% if request.endpoint.startswith('warehouse.') or request.endpoint in ['main.store_articles', 'main.warehouse_reports'] %} Warehouse Main {% endif %} Go to Dashboard diff --git a/py_app/app/templates/main_page_warehouse.html b/py_app/app/templates/main_page_warehouse.html index 406d56a..234f78c 100644 --- a/py_app/app/templates/main_page_warehouse.html +++ b/py_app/app/templates/main_page_warehouse.html @@ -9,11 +9,11 @@
Add or update articles in the warehouse inventory.
- Go to Store Articles + Go to Set Boxes LocationsThis is the Store Articles page for the warehouse module.
+ + +{error_trace}", 500
+# Box Location Management Functions
+
+def search_box_by_number(box_number):
+ """
+ Search for a box by box number and return its details including location
+
+ Args:
+ box_number (str): The box number to search for
+
+ Returns:
+ tuple: (success: bool, data: dict, status_code: int)
+ """
+ try:
+ if not box_number:
+ return False, {'message': 'Box number is required'}, 400
+
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ # Search for the box and get its location info
+ cursor.execute("""
+ SELECT
+ b.id,
+ b.box_number,
+ b.status,
+ b.location_id,
+ w.location_code
+ FROM boxes_crates b
+ LEFT JOIN warehouse_locations w ON b.location_id = w.id
+ WHERE b.box_number = %s
+ """, (box_number,))
+
+ result = cursor.fetchone()
+ conn.close()
+
+ if result:
+ return True, {
+ 'box': {
+ 'id': result[0],
+ 'box_number': result[1],
+ 'status': result[2],
+ 'location_id': result[3],
+ 'location_code': result[4]
+ }
+ }, 200
+ else:
+ return False, {'message': f'Box "{box_number}" not found in the system'}, 404
+
+ except Exception as e:
+ return False, {'message': f'Error searching for box: {str(e)}'}, 500
+
+
+def assign_box_to_location(box_id, location_code):
+ """
+ Assign a box to a warehouse location
+
+ Args:
+ box_id (int): The ID of the box to assign
+ location_code (str): The location code to assign the box to
+
+ Returns:
+ tuple: (success: bool, data: dict, status_code: int)
+ """
+ try:
+ if not box_id or not location_code:
+ return False, {'message': 'Box ID and location code are required'}, 400
+
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ # Check if location exists
+ cursor.execute("SELECT id FROM warehouse_locations WHERE location_code = %s", (location_code,))
+ location_result = cursor.fetchone()
+
+ if not location_result:
+ conn.close()
+ return False, {'message': f'Location "{location_code}" not found in the system'}, 404
+
+ location_id = location_result[0]
+
+ # Update box location
+ cursor.execute("""
+ UPDATE boxes_crates
+ SET location_id = %s, updated_at = NOW()
+ WHERE id = %s
+ """, (location_id, box_id))
+
+ conn.commit()
+ conn.close()
+
+ return True, {'message': f'Box successfully assigned to location "{location_code}"'}, 200
+
+ except Exception as e:
+ return False, {'message': f'Error assigning box to location: {str(e)}'}, 500
+
+
+def search_location_with_boxes(location_code):
+ """
+ Search for a location and get all boxes assigned to it
+
+ Args:
+ location_code (str): The location code to search for
+
+ Returns:
+ tuple: (success: bool, data: dict, status_code: int)
+ """
+ try:
+ if not location_code:
+ return False, {'message': 'Location code is required'}, 400
+
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ # Search for the location
+ cursor.execute("""
+ SELECT id, location_code, size, description
+ FROM warehouse_locations
+ WHERE location_code = %s
+ """, (location_code,))
+
+ location_result = cursor.fetchone()
+
+ if not location_result:
+ conn.close()
+ return False, {'message': f'Location "{location_code}" not found in the system'}, 404
+
+ location_id = location_result[0]
+
+ # Get all boxes assigned to this location
+ cursor.execute("""
+ SELECT
+ b.id,
+ b.box_number,
+ b.status,
+ b.created_at
+ FROM boxes_crates b
+ WHERE b.location_id = %s
+ ORDER BY b.box_number
+ """, (location_id,))
+
+ boxes_results = cursor.fetchall()
+ conn.close()
+
+ boxes = []
+ for box in boxes_results:
+ boxes.append({
+ 'id': box[0],
+ 'box_number': box[1],
+ 'status': box[2],
+ 'created_at': box[3].strftime('%Y-%m-%d %H:%M:%S') if box[3] else None
+ })
+
+ return True, {
+ 'location': {
+ 'id': location_result[0],
+ 'location_code': location_result[1],
+ 'size': location_result[2],
+ 'description': location_result[3]
+ },
+ 'boxes': boxes
+ }, 200
+
+ except Exception as e:
+ return False, {'message': f'Error searching for location: {str(e)}'}, 500
+
+
+def move_box_to_new_location(box_id, new_location_code):
+ """
+ Move a box from its current location to a new location
+
+ Args:
+ box_id (int): The ID of the box to move
+ new_location_code (str): The new location code
+
+ Returns:
+ tuple: (success: bool, data: dict, status_code: int)
+ """
+ try:
+ if not box_id or not new_location_code:
+ return False, {'message': 'Box ID and new location code are required'}, 400
+
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ # Check if new location exists
+ cursor.execute("SELECT id FROM warehouse_locations WHERE location_code = %s", (new_location_code,))
+ location_result = cursor.fetchone()
+
+ if not location_result:
+ conn.close()
+ return False, {'message': f'Location "{new_location_code}" not found in the system'}, 404
+
+ new_location_id = location_result[0]
+
+ # Get box number for response message
+ cursor.execute("SELECT box_number FROM boxes_crates WHERE id = %s", (box_id,))
+ box_result = cursor.fetchone()
+
+ if not box_result:
+ conn.close()
+ return False, {'message': 'Box not found'}, 404
+
+ box_number = box_result[0]
+
+ # Update box location
+ cursor.execute("""
+ UPDATE boxes_crates
+ SET location_id = %s, updated_at = NOW()
+ WHERE id = %s
+ """, (new_location_id, box_id))
+
+ conn.commit()
+ conn.close()
+
+ return True, {'message': f'Box "{box_number}" successfully moved to location "{new_location_code}"'}, 200
+
+ except Exception as e:
+ return False, {'message': f'Error moving box to new location: {str(e)}'}, 500
+
+
+def change_box_status(box_id, new_status):
+ """
+ Change the status of a box (open/closed)
+
+ Args:
+ box_id (int): The ID of the box
+ new_status (str): The new status ('open' or 'closed')
+
+ Returns:
+ tuple: (success: bool, data: dict, status_code: int)
+ """
+ try:
+ if not box_id:
+ return False, {'message': 'Box ID is required'}, 400
+
+ if new_status not in ['open', 'closed']:
+ return False, {'message': 'Invalid status. Must be "open" or "closed"'}, 400
+
+ conn = get_db_connection()
+ cursor = conn.cursor()
+
+ # Get box number for response message
+ cursor.execute("SELECT box_number FROM boxes_crates WHERE id = %s", (box_id,))
+ box_result = cursor.fetchone()
+
+ if not box_result:
+ conn.close()
+ return False, {'message': 'Box not found'}, 404
+
+ box_number = box_result[0]
+
+ # Update box status
+ cursor.execute("""
+ UPDATE boxes_crates
+ SET status = %s, updated_at = NOW()
+ WHERE id = %s
+ """, (new_status, box_id))
+
+ conn.commit()
+ conn.close()
+
+ return True, {'message': f'Box "{box_number}" status changed to "{new_status}"'}, 200
+
+ except Exception as e:
+ return False, {'message': f'Error changing box status: {str(e)}'}, 500
+
+