mashrur950 commited on
Commit
a7365ce
Β·
1 Parent(s): 50e460a

Add order and driver management tools with update and delete functionalities

Browse files
Files changed (3) hide show
  1. chat/providers/gemini_provider.py +77 -0
  2. chat/tools.py +502 -0
  3. ui/app.py +334 -0
chat/providers/gemini_provider.py CHANGED
@@ -83,6 +83,10 @@ You: [geocode_address] β†’ "OK geocoded, now creating..." ❌ WRONG!
83
  - geocode_address: Convert address to GPS coordinates
84
  - create_order: Create customer delivery order (REQUIRES geocoded address)
85
 
 
 
 
 
86
  **Order Querying (INTERACTIVE):**
87
  - count_orders: Count orders with optional filters
88
  - fetch_orders: Fetch N orders with pagination and filters
@@ -93,6 +97,10 @@ You: [geocode_address] β†’ "OK geocoded, now creating..." ❌ WRONG!
93
  **Driver Creation:**
94
  - create_driver: Add new driver/delivery man to fleet
95
 
 
 
 
 
96
  **Driver Querying (INTERACTIVE):**
97
  - count_drivers: Count drivers with optional filters
98
  - fetch_drivers: Fetch N drivers with pagination and filters
@@ -532,6 +540,75 @@ You: [get_driver_details with driver_id=DRV-20251114163800] β†’ [Display complet
532
  },
533
  required=[]
534
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
  )
536
  ]
537
  )
 
83
  - geocode_address: Convert address to GPS coordinates
84
  - create_order: Create customer delivery order (REQUIRES geocoded address)
85
 
86
+ **Order Management:**
87
+ - update_order: Update existing order's details (status, priority, address, etc.)
88
+ - delete_order: Permanently delete an order (requires confirm=true)
89
+
90
  **Order Querying (INTERACTIVE):**
91
  - count_orders: Count orders with optional filters
92
  - fetch_orders: Fetch N orders with pagination and filters
 
97
  **Driver Creation:**
98
  - create_driver: Add new driver/delivery man to fleet
99
 
100
+ **Driver Management:**
101
+ - update_driver: Update existing driver's details (name, status, vehicle, location, etc.)
102
+ - delete_driver: Permanently delete a driver (requires confirm=true)
103
+
104
  **Driver Querying (INTERACTIVE):**
105
  - count_drivers: Count drivers with optional filters
106
  - fetch_drivers: Fetch N drivers with pagination and filters
 
540
  },
541
  required=[]
542
  )
543
+ ),
544
+ genai.protos.FunctionDeclaration(
545
+ name="update_order",
546
+ description="Update an existing order's details. You can update any combination of fields. Only provide the fields you want to change.",
547
+ parameters=genai.protos.Schema(
548
+ type=genai.protos.Type.OBJECT,
549
+ properties={
550
+ "order_id": genai.protos.Schema(type=genai.protos.Type.STRING, description="Order ID to update"),
551
+ "customer_name": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated customer name"),
552
+ "customer_phone": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated customer phone"),
553
+ "customer_email": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated customer email"),
554
+ "delivery_address": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated delivery address"),
555
+ "delivery_lat": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated delivery latitude"),
556
+ "delivery_lng": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated delivery longitude"),
557
+ "status": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated order status (pending/assigned/in_transit/delivered/failed/cancelled)"),
558
+ "priority": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated priority (standard/express/urgent)"),
559
+ "special_instructions": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated special instructions"),
560
+ "time_window_end": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated delivery deadline"),
561
+ "payment_status": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated payment status (pending/paid/cod)"),
562
+ "weight_kg": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated weight in kg"),
563
+ "order_value": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated order value")
564
+ },
565
+ required=["order_id"]
566
+ )
567
+ ),
568
+ genai.protos.FunctionDeclaration(
569
+ name="delete_order",
570
+ description="Permanently delete an order from the database. This action cannot be undone. Use with caution.",
571
+ parameters=genai.protos.Schema(
572
+ type=genai.protos.Type.OBJECT,
573
+ properties={
574
+ "order_id": genai.protos.Schema(type=genai.protos.Type.STRING, description="Order ID to delete"),
575
+ "confirm": genai.protos.Schema(type=genai.protos.Type.BOOLEAN, description="Must be true to confirm deletion")
576
+ },
577
+ required=["order_id", "confirm"]
578
+ )
579
+ ),
580
+ genai.protos.FunctionDeclaration(
581
+ name="update_driver",
582
+ description="Update an existing driver's details. You can update any combination of fields. Only provide the fields you want to change.",
583
+ parameters=genai.protos.Schema(
584
+ type=genai.protos.Type.OBJECT,
585
+ properties={
586
+ "driver_id": genai.protos.Schema(type=genai.protos.Type.STRING, description="Driver ID to update"),
587
+ "name": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated driver name"),
588
+ "phone": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated phone"),
589
+ "email": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated email"),
590
+ "status": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated status (active/busy/offline/unavailable)"),
591
+ "vehicle_type": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated vehicle type"),
592
+ "vehicle_plate": genai.protos.Schema(type=genai.protos.Type.STRING, description="Updated vehicle plate"),
593
+ "capacity_kg": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated capacity in kg"),
594
+ "capacity_m3": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated capacity in mΒ³"),
595
+ "current_lat": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated current latitude"),
596
+ "current_lng": genai.protos.Schema(type=genai.protos.Type.NUMBER, description="Updated current longitude")
597
+ },
598
+ required=["driver_id"]
599
+ )
600
+ ),
601
+ genai.protos.FunctionDeclaration(
602
+ name="delete_driver",
603
+ description="Permanently delete a driver from the database. This action cannot be undone. Use with caution.",
604
+ parameters=genai.protos.Schema(
605
+ type=genai.protos.Type.OBJECT,
606
+ properties={
607
+ "driver_id": genai.protos.Schema(type=genai.protos.Type.STRING, description="Driver ID to delete"),
608
+ "confirm": genai.protos.Schema(type=genai.protos.Type.BOOLEAN, description="Must be true to confirm deletion")
609
+ },
610
+ required=["driver_id", "confirm"]
611
+ )
612
  )
613
  ]
614
  )
chat/tools.py CHANGED
@@ -375,6 +375,171 @@ TOOLS_SCHEMA = [
375
  },
376
  "required": []
377
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  }
379
  ]
380
 
@@ -417,6 +582,14 @@ def execute_tool(tool_name: str, tool_input: dict) -> dict:
417
  return handle_search_drivers(tool_input)
418
  elif tool_name == "get_available_drivers":
419
  return handle_get_available_drivers(tool_input)
 
 
 
 
 
 
 
 
420
  else:
421
  return {
422
  "success": False,
@@ -647,6 +820,335 @@ def handle_create_driver(tool_input: dict) -> dict:
647
  }
648
 
649
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  def handle_count_orders(tool_input: dict) -> dict:
651
  """
652
  Execute count orders tool
 
375
  },
376
  "required": []
377
  }
378
+ },
379
+ {
380
+ "name": "update_order",
381
+ "description": "Update an existing order's details. You can update any combination of fields. Only provide the fields you want to change.",
382
+ "input_schema": {
383
+ "type": "object",
384
+ "properties": {
385
+ "order_id": {
386
+ "type": "string",
387
+ "description": "Order ID to update (e.g., 'ORD-20250114123456')"
388
+ },
389
+ "customer_name": {
390
+ "type": "string",
391
+ "description": "Updated customer name"
392
+ },
393
+ "customer_phone": {
394
+ "type": "string",
395
+ "description": "Updated customer phone number"
396
+ },
397
+ "customer_email": {
398
+ "type": "string",
399
+ "description": "Updated customer email address"
400
+ },
401
+ "delivery_address": {
402
+ "type": "string",
403
+ "description": "Updated delivery address"
404
+ },
405
+ "delivery_lat": {
406
+ "type": "number",
407
+ "description": "Updated delivery latitude (required if updating address)"
408
+ },
409
+ "delivery_lng": {
410
+ "type": "number",
411
+ "description": "Updated delivery longitude (required if updating address)"
412
+ },
413
+ "status": {
414
+ "type": "string",
415
+ "description": "Updated order status",
416
+ "enum": ["pending", "assigned", "in_transit", "delivered", "failed", "cancelled"]
417
+ },
418
+ "priority": {
419
+ "type": "string",
420
+ "description": "Updated priority level",
421
+ "enum": ["standard", "express", "urgent"]
422
+ },
423
+ "special_instructions": {
424
+ "type": "string",
425
+ "description": "Updated special delivery instructions"
426
+ },
427
+ "time_window_end": {
428
+ "type": "string",
429
+ "description": "Updated delivery deadline (ISO format datetime)"
430
+ },
431
+ "payment_status": {
432
+ "type": "string",
433
+ "description": "Updated payment status",
434
+ "enum": ["pending", "paid", "cod"]
435
+ },
436
+ "weight_kg": {
437
+ "type": "number",
438
+ "description": "Updated package weight in kilograms"
439
+ },
440
+ "order_value": {
441
+ "type": "number",
442
+ "description": "Updated order value in currency"
443
+ }
444
+ },
445
+ "required": ["order_id"]
446
+ }
447
+ },
448
+ {
449
+ "name": "delete_order",
450
+ "description": "Permanently delete an order from the database. This action cannot be undone. Use with caution.",
451
+ "input_schema": {
452
+ "type": "object",
453
+ "properties": {
454
+ "order_id": {
455
+ "type": "string",
456
+ "description": "Order ID to delete (e.g., 'ORD-20250114123456')"
457
+ },
458
+ "confirm": {
459
+ "type": "boolean",
460
+ "description": "Must be set to true to confirm deletion"
461
+ }
462
+ },
463
+ "required": ["order_id", "confirm"]
464
+ }
465
+ },
466
+ {
467
+ "name": "update_driver",
468
+ "description": "Update an existing driver's details. You can update any combination of fields. Only provide the fields you want to change.",
469
+ "input_schema": {
470
+ "type": "object",
471
+ "properties": {
472
+ "driver_id": {
473
+ "type": "string",
474
+ "description": "Driver ID to update (e.g., 'DRV-20250114123456')"
475
+ },
476
+ "name": {
477
+ "type": "string",
478
+ "description": "Updated driver name"
479
+ },
480
+ "phone": {
481
+ "type": "string",
482
+ "description": "Updated phone number"
483
+ },
484
+ "email": {
485
+ "type": "string",
486
+ "description": "Updated email address"
487
+ },
488
+ "status": {
489
+ "type": "string",
490
+ "description": "Updated driver status",
491
+ "enum": ["active", "busy", "offline", "unavailable"]
492
+ },
493
+ "vehicle_type": {
494
+ "type": "string",
495
+ "description": "Updated vehicle type"
496
+ },
497
+ "vehicle_plate": {
498
+ "type": "string",
499
+ "description": "Updated vehicle license plate"
500
+ },
501
+ "capacity_kg": {
502
+ "type": "number",
503
+ "description": "Updated cargo capacity in kilograms"
504
+ },
505
+ "capacity_m3": {
506
+ "type": "number",
507
+ "description": "Updated cargo capacity in cubic meters"
508
+ },
509
+ "skills": {
510
+ "type": "array",
511
+ "items": {"type": "string"},
512
+ "description": "Updated list of driver skills/certifications"
513
+ },
514
+ "current_lat": {
515
+ "type": "number",
516
+ "description": "Updated current latitude"
517
+ },
518
+ "current_lng": {
519
+ "type": "number",
520
+ "description": "Updated current longitude"
521
+ }
522
+ },
523
+ "required": ["driver_id"]
524
+ }
525
+ },
526
+ {
527
+ "name": "delete_driver",
528
+ "description": "Permanently delete a driver from the database. This action cannot be undone. Use with caution.",
529
+ "input_schema": {
530
+ "type": "object",
531
+ "properties": {
532
+ "driver_id": {
533
+ "type": "string",
534
+ "description": "Driver ID to delete (e.g., 'DRV-20250114123456')"
535
+ },
536
+ "confirm": {
537
+ "type": "boolean",
538
+ "description": "Must be set to true to confirm deletion"
539
+ }
540
+ },
541
+ "required": ["driver_id", "confirm"]
542
+ }
543
  }
544
  ]
545
 
 
582
  return handle_search_drivers(tool_input)
583
  elif tool_name == "get_available_drivers":
584
  return handle_get_available_drivers(tool_input)
585
+ elif tool_name == "update_order":
586
+ return handle_update_order(tool_input)
587
+ elif tool_name == "delete_order":
588
+ return handle_delete_order(tool_input)
589
+ elif tool_name == "update_driver":
590
+ return handle_update_driver(tool_input)
591
+ elif tool_name == "delete_driver":
592
+ return handle_delete_driver(tool_input)
593
  else:
594
  return {
595
  "success": False,
 
820
  }
821
 
822
 
823
+ def handle_update_order(tool_input: dict) -> dict:
824
+ """
825
+ Execute order update tool
826
+
827
+ Args:
828
+ tool_input: Dict with order_id and fields to update
829
+
830
+ Returns:
831
+ Update result
832
+ """
833
+ import json
834
+
835
+ order_id = tool_input.get("order_id")
836
+
837
+ # Validate required field
838
+ if not order_id:
839
+ return {
840
+ "success": False,
841
+ "error": "Missing required field: order_id"
842
+ }
843
+
844
+ # Check if order exists
845
+ check_query = "SELECT order_id FROM orders WHERE order_id = %s"
846
+ existing = execute_query(check_query, (order_id,))
847
+
848
+ if not existing:
849
+ return {
850
+ "success": False,
851
+ "error": f"Order {order_id} not found"
852
+ }
853
+
854
+ # Auto-geocode if delivery address is updated without coordinates
855
+ if "delivery_address" in tool_input and ("delivery_lat" not in tool_input or "delivery_lng" not in tool_input):
856
+ from chat.geocoding import GeocodingService
857
+ geocoding_service = GeocodingService()
858
+
859
+ try:
860
+ geocode_result = geocoding_service.geocode(tool_input["delivery_address"])
861
+ tool_input["delivery_lat"] = geocode_result["lat"]
862
+ tool_input["delivery_lng"] = geocode_result["lng"]
863
+ logger.info(f"Auto-geocoded delivery address: {geocode_result['formatted_address']}")
864
+ except Exception as e:
865
+ logger.warning(f"Failed to geocode address, skipping coordinate update: {e}")
866
+
867
+ # Build UPDATE query dynamically based on provided fields
868
+ update_fields = []
869
+ params = []
870
+
871
+ # Map of field names to their database columns
872
+ updateable_fields = {
873
+ "customer_name": "customer_name",
874
+ "customer_phone": "customer_phone",
875
+ "customer_email": "customer_email",
876
+ "delivery_address": "delivery_address",
877
+ "delivery_lat": "delivery_lat",
878
+ "delivery_lng": "delivery_lng",
879
+ "status": "status",
880
+ "priority": "priority",
881
+ "special_instructions": "special_instructions",
882
+ "time_window_end": "time_window_end",
883
+ "payment_status": "payment_status",
884
+ "weight_kg": "weight_kg",
885
+ "order_value": "order_value"
886
+ }
887
+
888
+ for field, column in updateable_fields.items():
889
+ if field in tool_input:
890
+ update_fields.append(f"{column} = %s")
891
+ params.append(tool_input[field])
892
+
893
+ if not update_fields:
894
+ return {
895
+ "success": False,
896
+ "error": "No fields provided to update"
897
+ }
898
+
899
+ # Always update the updated_at timestamp
900
+ update_fields.append("updated_at = %s")
901
+ params.append(datetime.now())
902
+
903
+ # Add order_id for WHERE clause
904
+ params.append(order_id)
905
+
906
+ # Execute update
907
+ query = f"""
908
+ UPDATE orders
909
+ SET {', '.join(update_fields)}
910
+ WHERE order_id = %s
911
+ """
912
+
913
+ try:
914
+ execute_write(query, tuple(params))
915
+ logger.info(f"Order updated: {order_id}")
916
+
917
+ return {
918
+ "success": True,
919
+ "order_id": order_id,
920
+ "updated_fields": list(updateable_fields.keys() & tool_input.keys()),
921
+ "message": f"Order {order_id} updated successfully!"
922
+ }
923
+ except Exception as e:
924
+ logger.error(f"Database error updating order: {e}")
925
+ return {
926
+ "success": False,
927
+ "error": f"Failed to update order: {str(e)}"
928
+ }
929
+
930
+
931
+ def handle_delete_order(tool_input: dict) -> dict:
932
+ """
933
+ Execute order deletion tool
934
+
935
+ Args:
936
+ tool_input: Dict with order_id and confirm flag
937
+
938
+ Returns:
939
+ Deletion result
940
+ """
941
+ order_id = tool_input.get("order_id")
942
+ confirm = tool_input.get("confirm", False)
943
+
944
+ # Validate required fields
945
+ if not order_id:
946
+ return {
947
+ "success": False,
948
+ "error": "Missing required field: order_id"
949
+ }
950
+
951
+ if not confirm:
952
+ return {
953
+ "success": False,
954
+ "error": "Deletion not confirmed. Set confirm=true to proceed."
955
+ }
956
+
957
+ # Check if order exists
958
+ check_query = "SELECT order_id, status FROM orders WHERE order_id = %s"
959
+ existing = execute_query(check_query, (order_id,))
960
+
961
+ if not existing:
962
+ return {
963
+ "success": False,
964
+ "error": f"Order {order_id} not found"
965
+ }
966
+
967
+ # Delete the order
968
+ query = "DELETE FROM orders WHERE order_id = %s"
969
+
970
+ try:
971
+ execute_write(query, (order_id,))
972
+ logger.info(f"Order deleted: {order_id}")
973
+
974
+ return {
975
+ "success": True,
976
+ "order_id": order_id,
977
+ "message": f"Order {order_id} has been permanently deleted."
978
+ }
979
+ except Exception as e:
980
+ logger.error(f"Database error deleting order: {e}")
981
+ return {
982
+ "success": False,
983
+ "error": f"Failed to delete order: {str(e)}"
984
+ }
985
+
986
+
987
+ def handle_update_driver(tool_input: dict) -> dict:
988
+ """
989
+ Execute driver update tool
990
+
991
+ Args:
992
+ tool_input: Dict with driver_id and fields to update
993
+
994
+ Returns:
995
+ Update result
996
+ """
997
+ import json
998
+
999
+ driver_id = tool_input.get("driver_id")
1000
+
1001
+ # Validate required field
1002
+ if not driver_id:
1003
+ return {
1004
+ "success": False,
1005
+ "error": "Missing required field: driver_id"
1006
+ }
1007
+
1008
+ # Check if driver exists
1009
+ check_query = "SELECT driver_id FROM drivers WHERE driver_id = %s"
1010
+ existing = execute_query(check_query, (driver_id,))
1011
+
1012
+ if not existing:
1013
+ return {
1014
+ "success": False,
1015
+ "error": f"Driver {driver_id} not found"
1016
+ }
1017
+
1018
+ # Build UPDATE query dynamically based on provided fields
1019
+ update_fields = []
1020
+ params = []
1021
+
1022
+ # Map of field names to their database columns
1023
+ updateable_fields = {
1024
+ "name": "name",
1025
+ "phone": "phone",
1026
+ "email": "email",
1027
+ "status": "status",
1028
+ "vehicle_type": "vehicle_type",
1029
+ "vehicle_plate": "vehicle_plate",
1030
+ "capacity_kg": "capacity_kg",
1031
+ "capacity_m3": "capacity_m3",
1032
+ "current_lat": "current_lat",
1033
+ "current_lng": "current_lng"
1034
+ }
1035
+
1036
+ for field, column in updateable_fields.items():
1037
+ if field in tool_input:
1038
+ update_fields.append(f"{column} = %s")
1039
+ params.append(tool_input[field])
1040
+
1041
+ # Handle skills array specially (convert to JSON)
1042
+ if "skills" in tool_input:
1043
+ skills = list(tool_input.get("skills", []))
1044
+ update_fields.append("skills = %s")
1045
+ params.append(json.dumps(skills))
1046
+
1047
+ if not update_fields:
1048
+ return {
1049
+ "success": False,
1050
+ "error": "No fields provided to update"
1051
+ }
1052
+
1053
+ # Always update the updated_at timestamp
1054
+ update_fields.append("updated_at = %s")
1055
+ params.append(datetime.now())
1056
+
1057
+ # Update location timestamp if lat/lng changed
1058
+ if "current_lat" in tool_input or "current_lng" in tool_input:
1059
+ update_fields.append("last_location_update = %s")
1060
+ params.append(datetime.now())
1061
+
1062
+ # Add driver_id for WHERE clause
1063
+ params.append(driver_id)
1064
+
1065
+ # Execute update
1066
+ query = f"""
1067
+ UPDATE drivers
1068
+ SET {', '.join(update_fields)}
1069
+ WHERE driver_id = %s
1070
+ """
1071
+
1072
+ try:
1073
+ execute_write(query, tuple(params))
1074
+ logger.info(f"Driver updated: {driver_id}")
1075
+
1076
+ updated_list = list(updateable_fields.keys() & tool_input.keys())
1077
+ if "skills" in tool_input:
1078
+ updated_list.append("skills")
1079
+
1080
+ return {
1081
+ "success": True,
1082
+ "driver_id": driver_id,
1083
+ "updated_fields": updated_list,
1084
+ "message": f"Driver {driver_id} updated successfully!"
1085
+ }
1086
+ except Exception as e:
1087
+ logger.error(f"Database error updating driver: {e}")
1088
+ return {
1089
+ "success": False,
1090
+ "error": f"Failed to update driver: {str(e)}"
1091
+ }
1092
+
1093
+
1094
+ def handle_delete_driver(tool_input: dict) -> dict:
1095
+ """
1096
+ Execute driver deletion tool
1097
+
1098
+ Args:
1099
+ tool_input: Dict with driver_id and confirm flag
1100
+
1101
+ Returns:
1102
+ Deletion result
1103
+ """
1104
+ driver_id = tool_input.get("driver_id")
1105
+ confirm = tool_input.get("confirm", False)
1106
+
1107
+ # Validate required fields
1108
+ if not driver_id:
1109
+ return {
1110
+ "success": False,
1111
+ "error": "Missing required field: driver_id"
1112
+ }
1113
+
1114
+ if not confirm:
1115
+ return {
1116
+ "success": False,
1117
+ "error": "Deletion not confirmed. Set confirm=true to proceed."
1118
+ }
1119
+
1120
+ # Check if driver exists
1121
+ check_query = "SELECT driver_id, name FROM drivers WHERE driver_id = %s"
1122
+ existing = execute_query(check_query, (driver_id,))
1123
+
1124
+ if not existing:
1125
+ return {
1126
+ "success": False,
1127
+ "error": f"Driver {driver_id} not found"
1128
+ }
1129
+
1130
+ driver_name = existing[0]["name"]
1131
+
1132
+ # Delete the driver
1133
+ query = "DELETE FROM drivers WHERE driver_id = %s"
1134
+
1135
+ try:
1136
+ execute_write(query, (driver_id,))
1137
+ logger.info(f"Driver deleted: {driver_id}")
1138
+
1139
+ return {
1140
+ "success": True,
1141
+ "driver_id": driver_id,
1142
+ "message": f"Driver {driver_id} ({driver_name}) has been permanently deleted."
1143
+ }
1144
+ except Exception as e:
1145
+ logger.error(f"Database error deleting driver: {e}")
1146
+ return {
1147
+ "success": False,
1148
+ "error": f"Failed to delete driver: {str(e)}"
1149
+ }
1150
+
1151
+
1152
  def handle_count_orders(tool_input: dict) -> dict:
1153
  """
1154
  Execute count orders tool
ui/app.py CHANGED
@@ -351,6 +351,66 @@ def get_driver_details(driver_id):
351
  return f"Error fetching driver details: {str(e)}"
352
 
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  # ============================================
355
  # CHAT FUNCTIONS
356
  # ============================================
@@ -443,12 +503,21 @@ def create_interface():
443
 
444
  # Quick Action Buttons
445
  gr.Markdown("**Quick Actions:**")
 
 
446
  with gr.Row():
447
  quick_create_order = gr.Button("πŸ“¦ Create Order", size="sm")
448
  quick_view_orders = gr.Button("πŸ“‹ View Orders", size="sm")
449
  quick_view_drivers = gr.Button("πŸ‘₯ View Drivers", size="sm")
450
  quick_check_status = gr.Button("πŸ“Š Check Status", size="sm")
451
 
 
 
 
 
 
 
 
452
  # Chat interface
453
  chatbot = gr.Chatbot(
454
  label="Chat with AI Assistant",
@@ -515,6 +584,26 @@ def create_interface():
515
  outputs=msg_input
516
  )
517
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
  send_btn.click(
519
  fn=send_message,
520
  inputs=[msg_input, session_id_state],
@@ -603,6 +692,42 @@ def create_interface():
603
  gr.Markdown("**Order Details:**")
604
  order_details = gr.Markdown("*Select an order from the table above to view full details*")
605
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  # Event handlers
607
  def filter_and_update_orders(status, priority, payment, search):
608
  return get_all_orders(status, priority, payment, search)
@@ -643,6 +768,93 @@ def create_interface():
643
  outputs=order_details
644
  )
645
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  # ==========================================
647
  # TAB 3: DRIVERS
648
  # ==========================================
@@ -707,6 +919,38 @@ def create_interface():
707
  gr.Markdown("**Driver Details:**")
708
  driver_details = gr.Markdown("*Select a driver from the table above to view full details*")
709
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  # Event handlers
711
  def filter_and_update_drivers(status, vehicle, search):
712
  return get_all_drivers(status, vehicle, search)
@@ -747,6 +991,96 @@ def create_interface():
747
  outputs=driver_details
748
  )
749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  gr.Markdown("---")
751
  gr.Markdown("*FleetMind v1.0 - AI-Powered Dispatch Coordination*")
752
 
 
351
  return f"Error fetching driver details: {str(e)}"
352
 
353
 
354
+ def update_order_ui(order_id, **fields):
355
+ """Update order via UI"""
356
+ from chat.tools import execute_tool
357
+
358
+ if not order_id:
359
+ return {"success": False, "message": "Order ID is required"}
360
+
361
+ # Build tool input
362
+ tool_input = {"order_id": order_id}
363
+ tool_input.update(fields)
364
+
365
+ # Call update tool
366
+ result = execute_tool("update_order", tool_input)
367
+
368
+ return result
369
+
370
+
371
+ def delete_order_ui(order_id):
372
+ """Delete order via UI"""
373
+ from chat.tools import execute_tool
374
+
375
+ if not order_id:
376
+ return {"success": False, "message": "Order ID is required"}
377
+
378
+ # Call delete tool with confirmation
379
+ result = execute_tool("delete_order", {"order_id": order_id, "confirm": True})
380
+
381
+ return result
382
+
383
+
384
+ def update_driver_ui(driver_id, **fields):
385
+ """Update driver via UI"""
386
+ from chat.tools import execute_tool
387
+
388
+ if not driver_id:
389
+ return {"success": False, "message": "Driver ID is required"}
390
+
391
+ # Build tool input
392
+ tool_input = {"driver_id": driver_id}
393
+ tool_input.update(fields)
394
+
395
+ # Call update tool
396
+ result = execute_tool("update_driver", tool_input)
397
+
398
+ return result
399
+
400
+
401
+ def delete_driver_ui(driver_id):
402
+ """Delete driver via UI"""
403
+ from chat.tools import execute_tool
404
+
405
+ if not driver_id:
406
+ return {"success": False, "message": "Driver ID is required"}
407
+
408
+ # Call delete tool with confirmation
409
+ result = execute_tool("delete_driver", {"driver_id": driver_id, "confirm": True})
410
+
411
+ return result
412
+
413
+
414
  # ============================================
415
  # CHAT FUNCTIONS
416
  # ============================================
 
503
 
504
  # Quick Action Buttons
505
  gr.Markdown("**Quick Actions:**")
506
+
507
+ # Row 1: Create and View
508
  with gr.Row():
509
  quick_create_order = gr.Button("πŸ“¦ Create Order", size="sm")
510
  quick_view_orders = gr.Button("πŸ“‹ View Orders", size="sm")
511
  quick_view_drivers = gr.Button("πŸ‘₯ View Drivers", size="sm")
512
  quick_check_status = gr.Button("πŸ“Š Check Status", size="sm")
513
 
514
+ # Row 2: Order Management
515
+ with gr.Row():
516
+ quick_update_order = gr.Button("✏️ Update Order", size="sm", variant="secondary")
517
+ quick_delete_order = gr.Button("πŸ—‘οΈ Delete Order", size="sm", variant="secondary")
518
+ quick_update_driver = gr.Button("✏️ Update Driver", size="sm", variant="secondary")
519
+ quick_delete_driver = gr.Button("πŸ—‘οΈ Delete Driver", size="sm", variant="secondary")
520
+
521
  # Chat interface
522
  chatbot = gr.Chatbot(
523
  label="Chat with AI Assistant",
 
584
  outputs=msg_input
585
  )
586
 
587
+ quick_update_order.click(
588
+ fn=lambda: "Update order [ORDER_ID] - change status to [STATUS]",
589
+ outputs=msg_input
590
+ )
591
+
592
+ quick_delete_order.click(
593
+ fn=lambda: "Delete order [ORDER_ID]",
594
+ outputs=msg_input
595
+ )
596
+
597
+ quick_update_driver.click(
598
+ fn=lambda: "Update driver [DRIVER_ID] - change status to [STATUS]",
599
+ outputs=msg_input
600
+ )
601
+
602
+ quick_delete_driver.click(
603
+ fn=lambda: "Delete driver [DRIVER_ID]",
604
+ outputs=msg_input
605
+ )
606
+
607
  send_btn.click(
608
  fn=send_message,
609
  inputs=[msg_input, session_id_state],
 
692
  gr.Markdown("**Order Details:**")
693
  order_details = gr.Markdown("*Select an order from the table above to view full details*")
694
 
695
+ # Edit/Delete Actions
696
+ gr.Markdown("---")
697
+ gr.Markdown("**Order Actions:**")
698
+
699
+ with gr.Row():
700
+ selected_order_id_edit = gr.Textbox(label="Order ID to Edit", placeholder="ORD-XXXXXXXX", scale=2)
701
+ edit_order_btn = gr.Button("✏️ Edit Order", variant="secondary", scale=1)
702
+ delete_order_btn = gr.Button("πŸ—‘οΈ Delete Order", variant="stop", scale=1)
703
+
704
+ # Edit Form (in accordion)
705
+ with gr.Accordion("Edit Order Form", open=False) as edit_accordion:
706
+ with gr.Row():
707
+ edit_customer_name = gr.Textbox(label="Customer Name")
708
+ edit_customer_phone = gr.Textbox(label="Customer Phone")
709
+ with gr.Row():
710
+ edit_status = gr.Dropdown(
711
+ choices=["pending", "assigned", "in_transit", "delivered", "failed", "cancelled"],
712
+ label="Status"
713
+ )
714
+ edit_priority = gr.Dropdown(
715
+ choices=["standard", "express", "urgent"],
716
+ label="Priority"
717
+ )
718
+ with gr.Row():
719
+ edit_payment_status = gr.Dropdown(
720
+ choices=["pending", "paid", "cod"],
721
+ label="Payment Status"
722
+ )
723
+ edit_weight_kg = gr.Number(label="Weight (kg)")
724
+ edit_special_instructions = gr.Textbox(label="Special Instructions", lines=2)
725
+
726
+ save_order_btn = gr.Button("πŸ’Ύ Save Changes", variant="primary")
727
+
728
+ # Action Results
729
+ action_result = gr.Markdown("")
730
+
731
  # Event handlers
732
  def filter_and_update_orders(status, priority, payment, search):
733
  return get_all_orders(status, priority, payment, search)
 
768
  outputs=order_details
769
  )
770
 
771
+ # Edit/Delete handlers
772
+ def handle_edit_order(order_id):
773
+ if not order_id:
774
+ return "Please enter an Order ID"
775
+ # Fetch order details and populate form
776
+ query = "SELECT * FROM orders WHERE order_id = %s"
777
+ from database.connection import execute_query
778
+ results = execute_query(query, (order_id,))
779
+ if not results:
780
+ return "Order not found"
781
+ order = results[0]
782
+ return (
783
+ order.get('customer_name', ''),
784
+ order.get('customer_phone', ''),
785
+ order.get('status', ''),
786
+ order.get('priority', ''),
787
+ order.get('payment_status', ''),
788
+ order.get('weight_kg', 0),
789
+ order.get('special_instructions', ''),
790
+ "Order loaded. Update fields and click Save Changes."
791
+ )
792
+
793
+ def handle_save_order(order_id, name, phone, status, priority, payment, weight, instructions, status_f, priority_f, payment_f, search):
794
+ if not order_id:
795
+ return "Please enter an Order ID", get_all_orders(status_f, priority_f, payment_f, search)
796
+
797
+ fields = {}
798
+ if name:
799
+ fields['customer_name'] = name
800
+ if phone:
801
+ fields['customer_phone'] = phone
802
+ if status:
803
+ fields['status'] = status
804
+ if priority:
805
+ fields['priority'] = priority
806
+ if payment:
807
+ fields['payment_status'] = payment
808
+ if weight:
809
+ fields['weight_kg'] = float(weight)
810
+ if instructions:
811
+ fields['special_instructions'] = instructions
812
+
813
+ result = update_order_ui(order_id, **fields)
814
+
815
+ # Refresh table
816
+ refreshed_table = get_all_orders(status_f, priority_f, payment_f, search)
817
+
818
+ if result['success']:
819
+ return f"βœ… {result['message']}", refreshed_table
820
+ else:
821
+ return f"❌ {result.get('error', 'Update failed')}", refreshed_table
822
+
823
+ def handle_delete_order(order_id, status_f, priority_f, payment_f, search):
824
+ if not order_id:
825
+ return "Please enter an Order ID", get_all_orders(status_f, priority_f, payment_f, search)
826
+
827
+ result = delete_order_ui(order_id)
828
+
829
+ # Refresh table
830
+ refreshed_table = get_all_orders(status_f, priority_f, payment_f, search)
831
+
832
+ if result['success']:
833
+ return f"βœ… {result['message']}", refreshed_table
834
+ else:
835
+ return f"❌ {result.get('error', 'Deletion failed')}", refreshed_table
836
+
837
+ edit_order_btn.click(
838
+ fn=handle_edit_order,
839
+ inputs=[selected_order_id_edit],
840
+ outputs=[edit_customer_name, edit_customer_phone, edit_status, edit_priority,
841
+ edit_payment_status, edit_weight_kg, edit_special_instructions, action_result]
842
+ )
843
+
844
+ save_order_btn.click(
845
+ fn=handle_save_order,
846
+ inputs=[selected_order_id_edit, edit_customer_name, edit_customer_phone, edit_status,
847
+ edit_priority, edit_payment_status, edit_weight_kg, edit_special_instructions,
848
+ status_filter, priority_filter, payment_filter, search_orders],
849
+ outputs=[action_result, orders_table]
850
+ )
851
+
852
+ delete_order_btn.click(
853
+ fn=handle_delete_order,
854
+ inputs=[selected_order_id_edit, status_filter, priority_filter, payment_filter, search_orders],
855
+ outputs=[action_result, orders_table]
856
+ )
857
+
858
  # ==========================================
859
  # TAB 3: DRIVERS
860
  # ==========================================
 
919
  gr.Markdown("**Driver Details:**")
920
  driver_details = gr.Markdown("*Select a driver from the table above to view full details*")
921
 
922
+ # Edit/Delete Actions
923
+ gr.Markdown("---")
924
+ gr.Markdown("**Driver Actions:**")
925
+
926
+ with gr.Row():
927
+ selected_driver_id_edit = gr.Textbox(label="Driver ID to Edit", placeholder="DRV-XXXXXXXX", scale=2)
928
+ edit_driver_btn = gr.Button("✏️ Edit Driver", variant="secondary", scale=1)
929
+ delete_driver_btn = gr.Button("πŸ—‘οΈ Delete Driver", variant="stop", scale=1)
930
+
931
+ # Edit Form (in accordion)
932
+ with gr.Accordion("Edit Driver Form", open=False) as driver_edit_accordion:
933
+ with gr.Row():
934
+ edit_driver_name = gr.Textbox(label="Driver Name")
935
+ edit_driver_phone = gr.Textbox(label="Phone")
936
+ with gr.Row():
937
+ edit_driver_email = gr.Textbox(label="Email")
938
+ edit_driver_status = gr.Dropdown(
939
+ choices=["active", "busy", "offline", "unavailable"],
940
+ label="Status"
941
+ )
942
+ with gr.Row():
943
+ edit_vehicle_type = gr.Textbox(label="Vehicle Type")
944
+ edit_vehicle_plate = gr.Textbox(label="Vehicle Plate")
945
+ with gr.Row():
946
+ edit_capacity_kg = gr.Number(label="Capacity (kg)")
947
+ edit_capacity_m3 = gr.Number(label="Capacity (mΒ³)")
948
+
949
+ save_driver_btn = gr.Button("πŸ’Ύ Save Changes", variant="primary")
950
+
951
+ # Action Results
952
+ driver_action_result = gr.Markdown("")
953
+
954
  # Event handlers
955
  def filter_and_update_drivers(status, vehicle, search):
956
  return get_all_drivers(status, vehicle, search)
 
991
  outputs=driver_details
992
  )
993
 
994
+ # Edit/Delete handlers
995
+ def handle_edit_driver(driver_id):
996
+ if not driver_id:
997
+ return "Please enter a Driver ID"
998
+ # Fetch driver details and populate form
999
+ query = "SELECT * FROM drivers WHERE driver_id = %s"
1000
+ from database.connection import execute_query
1001
+ results = execute_query(query, (driver_id,))
1002
+ if not results:
1003
+ return "Driver not found"
1004
+ driver = results[0]
1005
+ return (
1006
+ driver.get('name', ''),
1007
+ driver.get('phone', ''),
1008
+ driver.get('email', ''),
1009
+ driver.get('status', ''),
1010
+ driver.get('vehicle_type', ''),
1011
+ driver.get('vehicle_plate', ''),
1012
+ driver.get('capacity_kg', 0),
1013
+ driver.get('capacity_m3', 0),
1014
+ "Driver loaded. Update fields and click Save Changes."
1015
+ )
1016
+
1017
+ def handle_save_driver(driver_id, name, phone, email, status, vehicle_type, vehicle_plate, capacity_kg, capacity_m3, status_f, vehicle_f, search):
1018
+ if not driver_id:
1019
+ return "Please enter a Driver ID", get_all_drivers(status_f, vehicle_f, search)
1020
+
1021
+ fields = {}
1022
+ if name:
1023
+ fields['name'] = name
1024
+ if phone:
1025
+ fields['phone'] = phone
1026
+ if email:
1027
+ fields['email'] = email
1028
+ if status:
1029
+ fields['status'] = status
1030
+ if vehicle_type:
1031
+ fields['vehicle_type'] = vehicle_type
1032
+ if vehicle_plate:
1033
+ fields['vehicle_plate'] = vehicle_plate
1034
+ if capacity_kg:
1035
+ fields['capacity_kg'] = float(capacity_kg)
1036
+ if capacity_m3:
1037
+ fields['capacity_m3'] = float(capacity_m3)
1038
+
1039
+ result = update_driver_ui(driver_id, **fields)
1040
+
1041
+ # Refresh table
1042
+ refreshed_table = get_all_drivers(status_f, vehicle_f, search)
1043
+
1044
+ if result['success']:
1045
+ return f"βœ… {result['message']}", refreshed_table
1046
+ else:
1047
+ return f"❌ {result.get('error', 'Update failed')}", refreshed_table
1048
+
1049
+ def handle_delete_driver(driver_id, status_f, vehicle_f, search):
1050
+ if not driver_id:
1051
+ return "Please enter a Driver ID", get_all_drivers(status_f, vehicle_f, search)
1052
+
1053
+ result = delete_driver_ui(driver_id)
1054
+
1055
+ # Refresh table
1056
+ refreshed_table = get_all_drivers(status_f, vehicle_f, search)
1057
+
1058
+ if result['success']:
1059
+ return f"βœ… {result['message']}", refreshed_table
1060
+ else:
1061
+ return f"❌ {result.get('error', 'Deletion failed')}", refreshed_table
1062
+
1063
+ edit_driver_btn.click(
1064
+ fn=handle_edit_driver,
1065
+ inputs=[selected_driver_id_edit],
1066
+ outputs=[edit_driver_name, edit_driver_phone, edit_driver_email, edit_driver_status,
1067
+ edit_vehicle_type, edit_vehicle_plate, edit_capacity_kg, edit_capacity_m3, driver_action_result]
1068
+ )
1069
+
1070
+ save_driver_btn.click(
1071
+ fn=handle_save_driver,
1072
+ inputs=[selected_driver_id_edit, edit_driver_name, edit_driver_phone, edit_driver_email,
1073
+ edit_driver_status, edit_vehicle_type, edit_vehicle_plate, edit_capacity_kg, edit_capacity_m3,
1074
+ driver_status_filter, vehicle_filter, search_drivers],
1075
+ outputs=[driver_action_result, drivers_table]
1076
+ )
1077
+
1078
+ delete_driver_btn.click(
1079
+ fn=handle_delete_driver,
1080
+ inputs=[selected_driver_id_edit, driver_status_filter, vehicle_filter, search_drivers],
1081
+ outputs=[driver_action_result, drivers_table]
1082
+ )
1083
+
1084
  gr.Markdown("---")
1085
  gr.Markdown("*FleetMind v1.0 - AI-Powered Dispatch Coordination*")
1086