feat: Add Set Orders on Boxes feature with debouncing and page refresh

- Created warehouse_orders.py module with 8 order management functions
- Added /warehouse/set-orders-on-boxes route and 7 API endpoints
- Implemented 4-tab interface: assign, find, move, and view orders
- Changed assign input from dropdown to text field with BOX validation
- Fixed location join issue in warehouse.py (use boxes_crates.location_id)
- Added debouncing flag to prevent multiple rapid form submissions
- Added page refresh after successful order assignment
- Disabled assign button during processing
- Added page refresh with 2 second delay for UX feedback
- Added CP code validation in inventory page
- Improved modal styling with theme support
- Fixed set_boxes_locations page to refresh box info after assignments
This commit is contained in:
Quality App Developer
2026-02-02 01:06:03 +02:00
parent 39a3a0084c
commit f54e1bebc3
7 changed files with 1986 additions and 52 deletions

View File

@@ -452,6 +452,7 @@ def get_cp_inventory_list(limit=100, offset=0):
cursor = conn.cursor()
# Get all CP codes with their box and location info (latest entries first)
# NOTE: Use box's CURRENT location (from boxes_crates), not the historical scan location
cursor.execute("""
SELECT
s.id,
@@ -468,7 +469,7 @@ def get_cp_inventory_list(limit=100, offset=0):
SUM(s.rejected_quantity) as total_rejected
FROM scanfg_orders s
LEFT JOIN boxes_crates bc ON s.box_id = bc.id
LEFT JOIN warehouse_locations wl ON s.location_id = wl.id
LEFT JOIN warehouse_locations wl ON bc.location_id = wl.id
GROUP BY SUBSTRING(s.CP_full_code, 1, 10), s.box_id
ORDER BY MAX(s.created_at) DESC
LIMIT %s OFFSET %s
@@ -476,7 +477,15 @@ def get_cp_inventory_list(limit=100, offset=0):
# Convert tuples to dicts using cursor description
columns = [col[0] for col in cursor.description]
results = [{columns[i]: row[i] for i in range(len(columns))} for row in cursor.fetchall()]
results = []
for row in cursor.fetchall():
row_dict = {columns[i]: row[i] for i in range(len(columns))}
# Convert time and date fields to strings to avoid JSON serialization issues
if row_dict.get('latest_date'):
row_dict['latest_date'] = str(row_dict['latest_date'])
if row_dict.get('latest_time'):
row_dict['latest_time'] = str(row_dict['latest_time'])
results.append(row_dict)
cursor.close()
return results if results else []
@@ -503,6 +512,7 @@ def search_cp_code(cp_code_search):
search_term = cp_code_search.replace('-', '').strip().upper()
# Search for matching CP codes
# NOTE: Use box's CURRENT location (from boxes_crates), not the historical scan location
cursor.execute("""
SELECT
s.id,
@@ -519,7 +529,7 @@ def search_cp_code(cp_code_search):
SUM(s.rejected_quantity) as total_rejected
FROM scanfg_orders s
LEFT JOIN boxes_crates bc ON s.box_id = bc.id
LEFT JOIN warehouse_locations wl ON s.location_id = wl.id
LEFT JOIN warehouse_locations wl ON bc.location_id = wl.id
WHERE REPLACE(s.CP_full_code, '-', '') LIKE %s
GROUP BY SUBSTRING(s.CP_full_code, 1, 10), s.box_id
ORDER BY MAX(s.created_at) DESC
@@ -527,7 +537,15 @@ def search_cp_code(cp_code_search):
# Convert tuples to dicts using cursor description
columns = [col[0] for col in cursor.description]
results = [{columns[i]: row[i] for i in range(len(columns))} for row in cursor.fetchall()]
results = []
for row in cursor.fetchall():
row_dict = {columns[i]: row[i] for i in range(len(columns))}
# Convert time and date fields to strings to avoid JSON serialization issues
if row_dict.get('latest_date'):
row_dict['latest_date'] = str(row_dict['latest_date'])
if row_dict.get('latest_time'):
row_dict['latest_time'] = str(row_dict['latest_time'])
results.append(row_dict)
cursor.close()
return results if results else []
@@ -569,7 +587,7 @@ def search_by_box_number(box_number_search):
s.created_at
FROM scanfg_orders s
LEFT JOIN boxes_crates bc ON s.box_id = bc.id
LEFT JOIN warehouse_locations wl ON s.location_id = wl.id
LEFT JOIN warehouse_locations wl ON bc.location_id = wl.id
WHERE bc.box_number LIKE %s
ORDER BY s.created_at DESC
LIMIT 500
@@ -577,7 +595,17 @@ def search_by_box_number(box_number_search):
# Convert tuples to dicts using cursor description
columns = [col[0] for col in cursor.description]
results = [{columns[i]: row[i] for i in range(len(columns))} for row in cursor.fetchall()]
results = []
for row in cursor.fetchall():
row_dict = {columns[i]: row[i] for i in range(len(columns))}
# Convert time and date fields to strings to avoid JSON serialization issues
if row_dict.get('date'):
row_dict['date'] = str(row_dict['date'])
if row_dict.get('time'):
row_dict['time'] = str(row_dict['time'])
if row_dict.get('created_at'):
row_dict['created_at'] = str(row_dict['created_at'])
results.append(row_dict)
cursor.close()
return results if results else []
@@ -602,6 +630,7 @@ def get_cp_details(cp_code):
cursor = conn.cursor()
# Search for all entries with this CP base
# NOTE: Use box's CURRENT location (from boxes_crates), not the historical scan location
cursor.execute("""
SELECT
s.id,
@@ -622,7 +651,7 @@ def get_cp_details(cp_code):
s.created_at
FROM scanfg_orders s
LEFT JOIN boxes_crates bc ON s.box_id = bc.id
LEFT JOIN warehouse_locations wl ON s.location_id = wl.id
LEFT JOIN warehouse_locations wl ON bc.location_id = wl.id
WHERE SUBSTRING(s.CP_full_code, 1, 10) = %s
ORDER BY s.created_at DESC
LIMIT 1000
@@ -630,7 +659,17 @@ def get_cp_details(cp_code):
# Convert tuples to dicts using cursor description
columns = [col[0] for col in cursor.description]
results = [{columns[i]: row[i] for i in range(len(columns))} for row in cursor.fetchall()]
results = []
for row in cursor.fetchall():
row_dict = {columns[i]: row[i] for i in range(len(columns))}
# Convert time and date fields to strings to avoid JSON serialization issues
if row_dict.get('date'):
row_dict['date'] = str(row_dict['date'])
if row_dict.get('time'):
row_dict['time'] = str(row_dict['time'])
if row_dict.get('created_at'):
row_dict['created_at'] = str(row_dict['created_at'])
results.append(row_dict)
cursor.close()
return results if results else []