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 @@
- +
-

Store Articles

+

Set Boxes Locations

Add or update articles in the warehouse inventory.

- Go to Store Articles + Go to Set Boxes Locations
diff --git a/py_app/app/templates/print_lost_labels.html b/py_app/app/templates/print_lost_labels.html index dba7b33..9892034 100644 --- a/py_app/app/templates/print_lost_labels.html +++ b/py_app/app/templates/print_lost_labels.html @@ -86,7 +86,7 @@
-
INNOFA ROMANIA SRL
+
diff --git a/py_app/app/templates/print_module.html b/py_app/app/templates/print_module.html index 2e242f2..cb87997 100644 --- a/py_app/app/templates/print_module.html +++ b/py_app/app/templates/print_module.html @@ -34,9 +34,9 @@
- +
- INNOFA ROMANIA SRL +
@@ -1228,7 +1228,7 @@ function generateHTMLLabel(orderData, pieceNumber, totalPieces) {
-
INNOFA ROMANIA SRL
+
${customer_name}
diff --git a/py_app/app/templates/store_articles.html b/py_app/app/templates/store_articles.html index da0e7e7..89cc841 100644 --- a/py_app/app/templates/store_articles.html +++ b/py_app/app/templates/store_articles.html @@ -1,6 +1,1098 @@ {% extends "base.html" %} -{% block title %}Store Articles{% endblock %} +{% block title %}Set Boxes Locations{% endblock %} + {% block content %} -

Store Articles

-

This is the Store Articles page for the warehouse module.

+ + +
+
+ +
+ + +
+ + +
+
+

📦 Scan/Enter Box Number

+ +
+ +
+ + +
+ + + + +
+

Box Details

+
+ Box Number: + - +
+
+ Status: + + - + +
+
+ Current Location: + - +
+ + +
+
+ + + +
+ + +
+
+

📍 Scan/Enter Location Code

+ +
+ +
+ + +
+ + + + +
+

Location Details

+
+ Location Code: + - +
+
+ Boxes Count: + - +
+
+ + +
+

Boxes in This Location

+
+
+
+ + + +
+
+
+ + {% endblock %} diff --git a/py_app/app/warehouse.py b/py_app/app/warehouse.py index b4c8c60..e81bf1b 100644 --- a/py_app/app/warehouse.py +++ b/py_app/app/warehouse.py @@ -710,3 +710,270 @@ def view_warehouse_inventory_handler(): return f"

Error loading warehouse inventory

{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 + +