Spaces:
Runtime error
Runtime error
Update app.py
Browse filesAdded the gradio MCP server app.
app.py
CHANGED
|
@@ -1,7 +1,658 @@
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
return "Hello " + name + "!!"
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import psycopg2
|
| 3 |
+
from psycopg2.extras import RealDictCursor
|
| 4 |
+
import os
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
from typing import Dict, Any, List, Optional
|
| 7 |
|
| 8 |
+
load_dotenv()
|
|
|
|
| 9 |
|
| 10 |
+
def get_db_connection():
|
| 11 |
+
"""Get database connection using environment variables."""
|
| 12 |
+
try:
|
| 13 |
+
conn = psycopg2.connect(
|
| 14 |
+
host=os.getenv("POSTGRES_HOST", "localhost"),
|
| 15 |
+
database=os.getenv("POSTGRES_DB", "erp_db"),
|
| 16 |
+
user=os.getenv("POSTGRES_USER", "postgres"),
|
| 17 |
+
password=os.getenv("POSTGRES_PASSWORD", ""),
|
| 18 |
+
port=os.getenv("POSTGRES_PORT", "5432")
|
| 19 |
+
)
|
| 20 |
+
return conn
|
| 21 |
+
except Exception as e:
|
| 22 |
+
raise Exception(f"Database connection failed: {str(e)}")
|
| 23 |
+
|
| 24 |
+
def execute_query(query: str, params: str = None):
|
| 25 |
+
"""
|
| 26 |
+
Execute a custom SQL query on the ERP database.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
query: SQL query to execute
|
| 30 |
+
params: Parameters for parameterized queries (as a comma-separated string)
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
Query results or error message
|
| 34 |
+
"""
|
| 35 |
+
try:
|
| 36 |
+
conn = get_db_connection()
|
| 37 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 38 |
+
|
| 39 |
+
# Convert string params to list if provided
|
| 40 |
+
param_list = None
|
| 41 |
+
if params and params.strip():
|
| 42 |
+
param_list = [p.strip() for p in params.split(',')]
|
| 43 |
+
|
| 44 |
+
if param_list:
|
| 45 |
+
cursor.execute(query, param_list)
|
| 46 |
+
else:
|
| 47 |
+
cursor.execute(query)
|
| 48 |
+
|
| 49 |
+
# Check if it's a SELECT query
|
| 50 |
+
if query.strip().upper().startswith('SELECT'):
|
| 51 |
+
results = cursor.fetchall()
|
| 52 |
+
# Convert RealDictRow to regular dict for JSON serialization
|
| 53 |
+
results = [dict(row) for row in results]
|
| 54 |
+
if not results:
|
| 55 |
+
return str({
|
| 56 |
+
"success": True,
|
| 57 |
+
"message": "No results found",
|
| 58 |
+
"results": []
|
| 59 |
+
})
|
| 60 |
+
return str({
|
| 61 |
+
"success": True,
|
| 62 |
+
"message": f"Query returned {len(results)} results",
|
| 63 |
+
"count": len(results),
|
| 64 |
+
"results": results
|
| 65 |
+
})
|
| 66 |
+
else:
|
| 67 |
+
# For INSERT, UPDATE, DELETE queries
|
| 68 |
+
conn.commit()
|
| 69 |
+
return str({
|
| 70 |
+
"success": True,
|
| 71 |
+
"message": f"Query executed successfully",
|
| 72 |
+
"rows_affected": cursor.rowcount
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
return str({
|
| 77 |
+
"success": False,
|
| 78 |
+
"error": str(e)
|
| 79 |
+
})
|
| 80 |
+
finally:
|
| 81 |
+
if 'conn' in locals():
|
| 82 |
+
conn.close()
|
| 83 |
+
|
| 84 |
+
def list_erp_tables():
|
| 85 |
+
"""
|
| 86 |
+
List all ERP tables in the database.
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
List of ERP table names
|
| 90 |
+
"""
|
| 91 |
+
query = """
|
| 92 |
+
SELECT table_name
|
| 93 |
+
FROM information_schema.tables
|
| 94 |
+
WHERE table_schema = 'public'
|
| 95 |
+
AND table_name LIKE 'erp_%'
|
| 96 |
+
ORDER BY table_name;
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
conn = get_db_connection()
|
| 101 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 102 |
+
cursor.execute(query)
|
| 103 |
+
results = cursor.fetchall()
|
| 104 |
+
|
| 105 |
+
table_names = [row['table_name'] for row in results]
|
| 106 |
+
if not table_names:
|
| 107 |
+
return str({
|
| 108 |
+
"success": False,
|
| 109 |
+
"message": "No ERP tables found",
|
| 110 |
+
"tables": []
|
| 111 |
+
})
|
| 112 |
+
return str({
|
| 113 |
+
"success": True,
|
| 114 |
+
"message": f"Found {len(table_names)} tables",
|
| 115 |
+
"count": len(table_names),
|
| 116 |
+
"tables": table_names
|
| 117 |
+
})
|
| 118 |
+
|
| 119 |
+
except Exception as e:
|
| 120 |
+
return str({
|
| 121 |
+
"success": False,
|
| 122 |
+
"error": str(e)
|
| 123 |
+
})
|
| 124 |
+
finally:
|
| 125 |
+
if 'conn' in locals():
|
| 126 |
+
conn.close()
|
| 127 |
+
|
| 128 |
+
def get_order_status(order_id: int):
|
| 129 |
+
"""
|
| 130 |
+
Get the status of an order by order ID.
|
| 131 |
+
|
| 132 |
+
Args:
|
| 133 |
+
order_id: The ID of the order to check
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
Order status information including shipping and destination countries
|
| 137 |
+
"""
|
| 138 |
+
query = """
|
| 139 |
+
SELECT o.*, c.name as customer_name, c.email as customer_email,
|
| 140 |
+
o.shipping_country, o.destination_country
|
| 141 |
+
FROM erp_orders o
|
| 142 |
+
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 143 |
+
WHERE o.order_id = %s;
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
try:
|
| 147 |
+
conn = get_db_connection()
|
| 148 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 149 |
+
cursor.execute(query, [order_id])
|
| 150 |
+
order = cursor.fetchone()
|
| 151 |
+
|
| 152 |
+
if not order:
|
| 153 |
+
return str({
|
| 154 |
+
"success": False,
|
| 155 |
+
"message": f"Order with ID {order_id} not found"
|
| 156 |
+
})
|
| 157 |
+
|
| 158 |
+
# Get order items
|
| 159 |
+
items_query = """
|
| 160 |
+
SELECT oi.order_item_id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price, oi.subtotal,
|
| 161 |
+
p.product_name, p.sku
|
| 162 |
+
FROM erp_order_items oi
|
| 163 |
+
JOIN erp_products p ON oi.product_id = p.product_id
|
| 164 |
+
WHERE oi.order_id = %s;
|
| 165 |
+
"""
|
| 166 |
+
cursor.execute(items_query, [order_id])
|
| 167 |
+
items = cursor.fetchall()
|
| 168 |
+
|
| 169 |
+
# Get order history
|
| 170 |
+
history_query = """
|
| 171 |
+
SELECT * FROM erp_order_history
|
| 172 |
+
WHERE order_id = %s
|
| 173 |
+
ORDER BY timestamp DESC;
|
| 174 |
+
"""
|
| 175 |
+
cursor.execute(history_query, [order_id])
|
| 176 |
+
history = cursor.fetchall()
|
| 177 |
+
|
| 178 |
+
order_dict = dict(order)
|
| 179 |
+
items_list = [dict(item) for item in items]
|
| 180 |
+
history_list = [dict(entry) for entry in history]
|
| 181 |
+
|
| 182 |
+
# Return JSON formatted response
|
| 183 |
+
return str({
|
| 184 |
+
"success": True,
|
| 185 |
+
"order": {
|
| 186 |
+
"order_id": order_id,
|
| 187 |
+
"customer": {
|
| 188 |
+
"customer_id": order_dict['customer_id'],
|
| 189 |
+
"name": order_dict['customer_name'],
|
| 190 |
+
"email": order_dict['customer_email']
|
| 191 |
+
},
|
| 192 |
+
"status": order_dict['status'],
|
| 193 |
+
"shipping_address": order_dict.get('shipping_address', 'N/A'),
|
| 194 |
+
"shipping_country": order_dict.get('shipping_country', 'N/A'),
|
| 195 |
+
"destination_country": order_dict.get('destination_country', 'N/A'),
|
| 196 |
+
"items": items_list,
|
| 197 |
+
"history": history_list
|
| 198 |
+
}
|
| 199 |
+
})
|
| 200 |
+
|
| 201 |
+
except Exception as e:
|
| 202 |
+
return str({
|
| 203 |
+
"success": False,
|
| 204 |
+
"error": str(e)
|
| 205 |
+
})
|
| 206 |
+
finally:
|
| 207 |
+
if 'conn' in locals():
|
| 208 |
+
conn.close()
|
| 209 |
+
|
| 210 |
+
def place_new_order(
|
| 211 |
+
customer_id: int,
|
| 212 |
+
product_ids: str,
|
| 213 |
+
quantities: str,
|
| 214 |
+
shipping_address: str,
|
| 215 |
+
shipping_country: str,
|
| 216 |
+
destination_country: str,
|
| 217 |
+
previous_order_id: int = None
|
| 218 |
+
):
|
| 219 |
+
"""
|
| 220 |
+
Place a new order in the ERP system.
|
| 221 |
+
|
| 222 |
+
Args:
|
| 223 |
+
customer_id: ID of the customer placing the order
|
| 224 |
+
product_ids: Comma-separated list of product IDs
|
| 225 |
+
quantities: Comma-separated list of quantities corresponding to product IDs
|
| 226 |
+
shipping_address: Shipping address for the order
|
| 227 |
+
shipping_country: Country where the order will be shipped from
|
| 228 |
+
destination_country: Country where the order will be delivered to
|
| 229 |
+
previous_order_id: ID of a previous order this is replacing (optional)
|
| 230 |
+
|
| 231 |
+
Returns:
|
| 232 |
+
New order information
|
| 233 |
+
"""
|
| 234 |
+
try:
|
| 235 |
+
# Parse product IDs and quantities
|
| 236 |
+
product_id_list = [int(pid.strip()) for pid in product_ids.split(',')]
|
| 237 |
+
quantity_list = [int(qty.strip()) for qty in quantities.split(',')]
|
| 238 |
+
|
| 239 |
+
if len(product_id_list) != len(quantity_list):
|
| 240 |
+
return str({
|
| 241 |
+
"success": False,
|
| 242 |
+
"error": "Number of product IDs must match number of quantities"
|
| 243 |
+
})
|
| 244 |
+
|
| 245 |
+
# Create items list
|
| 246 |
+
items = [
|
| 247 |
+
{"product_id": pid, "quantity": qty}
|
| 248 |
+
for pid, qty in zip(product_id_list, quantity_list)
|
| 249 |
+
]
|
| 250 |
+
|
| 251 |
+
conn = get_db_connection()
|
| 252 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 253 |
+
|
| 254 |
+
# Calculate total amount based on the total items and their prices
|
| 255 |
+
total_amount = 0
|
| 256 |
+
for item in items:
|
| 257 |
+
product_query = "SELECT price FROM erp_products WHERE product_id = %s;"
|
| 258 |
+
cursor.execute(product_query, [item['product_id']])
|
| 259 |
+
product = cursor.fetchone()
|
| 260 |
+
if not product:
|
| 261 |
+
return str({
|
| 262 |
+
"success": False,
|
| 263 |
+
"error": f"Product with ID {item['product_id']} not found"
|
| 264 |
+
})
|
| 265 |
+
total_amount += product['price'] * item['quantity']
|
| 266 |
+
|
| 267 |
+
# Insert new order into the erp_orders table for the customer
|
| 268 |
+
order_query = """
|
| 269 |
+
INSERT INTO erp_orders (
|
| 270 |
+
customer_id, order_date, total_amount, status,
|
| 271 |
+
shipping_address, shipping_country, destination_country, previous_order_id,
|
| 272 |
+
estimated_delivery, payment_status
|
| 273 |
+
) VALUES (
|
| 274 |
+
%s, CURRENT_DATE, %s, 'Processing',
|
| 275 |
+
%s, %s, %s, %s,
|
| 276 |
+
CURRENT_DATE + INTERVAL '7 days', 'Pending'
|
| 277 |
+
) RETURNING order_id;
|
| 278 |
+
"""
|
| 279 |
+
cursor.execute(order_query, [
|
| 280 |
+
customer_id, total_amount, shipping_address, shipping_country, destination_country, previous_order_id
|
| 281 |
+
])
|
| 282 |
+
new_order_id = cursor.fetchone()['order_id']
|
| 283 |
+
|
| 284 |
+
# Insert order items into erp_order_items table for the new order
|
| 285 |
+
for item in items:
|
| 286 |
+
# Get product price
|
| 287 |
+
cursor.execute(product_query, [item['product_id']])
|
| 288 |
+
product = cursor.fetchone()
|
| 289 |
+
unit_price = product['price']
|
| 290 |
+
subtotal = unit_price * item['quantity']
|
| 291 |
+
|
| 292 |
+
item_query = """
|
| 293 |
+
INSERT INTO erp_order_items (
|
| 294 |
+
order_id, product_id, quantity, unit_price, subtotal
|
| 295 |
+
) VALUES (
|
| 296 |
+
%s, %s, %s, %s, %s
|
| 297 |
+
);
|
| 298 |
+
"""
|
| 299 |
+
cursor.execute(item_query, [
|
| 300 |
+
new_order_id, item['product_id'], item['quantity'],
|
| 301 |
+
unit_price, subtotal
|
| 302 |
+
])
|
| 303 |
+
|
| 304 |
+
# Update product stock
|
| 305 |
+
update_stock_query = """
|
| 306 |
+
UPDATE erp_products
|
| 307 |
+
SET stock_quantity = stock_quantity - %s
|
| 308 |
+
WHERE product_id = %s;
|
| 309 |
+
"""
|
| 310 |
+
cursor.execute(update_stock_query, [item['quantity'], item['product_id']])
|
| 311 |
+
|
| 312 |
+
# Create order history entry into the erp_order_history for the new order
|
| 313 |
+
history_query = """
|
| 314 |
+
INSERT INTO erp_order_history (
|
| 315 |
+
order_id, timestamp, status_change, notes, updated_by
|
| 316 |
+
) VALUES (
|
| 317 |
+
%s, CURRENT_TIMESTAMP, 'Order Created', 'New order placed', 'System'
|
| 318 |
+
);
|
| 319 |
+
"""
|
| 320 |
+
cursor.execute(history_query, [new_order_id])
|
| 321 |
+
|
| 322 |
+
# If this is a replacement order, add a note to the previous order
|
| 323 |
+
if previous_order_id:
|
| 324 |
+
prev_order_note_query = """
|
| 325 |
+
INSERT INTO erp_order_history (
|
| 326 |
+
order_id, timestamp, status_change, notes, updated_by
|
| 327 |
+
) VALUES (
|
| 328 |
+
%s, CURRENT_TIMESTAMP, 'Replaced', 'Order replaced by order #%s', 'System'
|
| 329 |
+
);
|
| 330 |
+
"""
|
| 331 |
+
cursor.execute(prev_order_note_query, [previous_order_id, new_order_id])
|
| 332 |
+
|
| 333 |
+
# Generate invoice for the new order
|
| 334 |
+
invoice_query = """
|
| 335 |
+
INSERT INTO erp_invoices (
|
| 336 |
+
order_id, invoice_date, amount, payment_terms, due_date, is_paid, invoice_number
|
| 337 |
+
) VALUES (
|
| 338 |
+
%s, CURRENT_DATE, %s, 'Net 30', CURRENT_DATE + INTERVAL '30 days', FALSE, 'INV-' || %s
|
| 339 |
+
) RETURNING invoice_id;
|
| 340 |
+
"""
|
| 341 |
+
cursor.execute(invoice_query, [new_order_id, total_amount, new_order_id])
|
| 342 |
+
invoice_id = cursor.fetchone()['invoice_id']
|
| 343 |
+
|
| 344 |
+
conn.commit()
|
| 345 |
+
|
| 346 |
+
# Get the complete new order details
|
| 347 |
+
cursor.execute("SELECT * FROM erp_orders WHERE order_id = %s;", [new_order_id])
|
| 348 |
+
order = cursor.fetchone()
|
| 349 |
+
|
| 350 |
+
order_dict = dict(order)
|
| 351 |
+
|
| 352 |
+
# Return JSON formatted response
|
| 353 |
+
return str({
|
| 354 |
+
"success": True,
|
| 355 |
+
"order": {
|
| 356 |
+
"order_id": new_order_id,
|
| 357 |
+
"invoice_id": invoice_id,
|
| 358 |
+
"total_amount": float(total_amount),
|
| 359 |
+
"status": order_dict['status'],
|
| 360 |
+
"estimated_delivery": str(order_dict['estimated_delivery']) if order_dict['estimated_delivery'] else None,
|
| 361 |
+
"customer_id": customer_id,
|
| 362 |
+
"shipping_address": shipping_address,
|
| 363 |
+
"shipping_country": shipping_country,
|
| 364 |
+
"destination_country": destination_country,
|
| 365 |
+
"previous_order_id": previous_order_id,
|
| 366 |
+
"items": [{"product_id": pid, "quantity": qty} for pid, qty in zip(product_id_list, quantity_list)]
|
| 367 |
+
}
|
| 368 |
+
})
|
| 369 |
+
|
| 370 |
+
except Exception as e:
|
| 371 |
+
if 'conn' in locals():
|
| 372 |
+
conn.rollback()
|
| 373 |
+
return str({
|
| 374 |
+
"success": False,
|
| 375 |
+
"error": str(e)
|
| 376 |
+
})
|
| 377 |
+
finally:
|
| 378 |
+
if 'conn' in locals():
|
| 379 |
+
conn.close()
|
| 380 |
+
|
| 381 |
+
def cancel_order(order_id: int, reason: str):
|
| 382 |
+
"""
|
| 383 |
+
Cancel an existing order in the ERP system.
|
| 384 |
+
|
| 385 |
+
Args:
|
| 386 |
+
order_id: ID of the order to cancel
|
| 387 |
+
reason: Reason for cancellation
|
| 388 |
+
|
| 389 |
+
Returns:
|
| 390 |
+
Result of the cancellation operation
|
| 391 |
+
"""
|
| 392 |
+
try:
|
| 393 |
+
conn = get_db_connection()
|
| 394 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 395 |
+
|
| 396 |
+
# Check if order exists and can be cancelled
|
| 397 |
+
check_query = """
|
| 398 |
+
SELECT status, customer_id FROM erp_orders WHERE order_id = %s;
|
| 399 |
+
"""
|
| 400 |
+
cursor.execute(check_query, [order_id])
|
| 401 |
+
order = cursor.fetchone()
|
| 402 |
+
|
| 403 |
+
if not order:
|
| 404 |
+
return str({
|
| 405 |
+
"success": False,
|
| 406 |
+
"error": f"Order with ID {order_id} not found"
|
| 407 |
+
})
|
| 408 |
+
|
| 409 |
+
if order['status'] in ['Delivered', 'Cancelled']:
|
| 410 |
+
return str({
|
| 411 |
+
"success": False,
|
| 412 |
+
"error": f"Cannot cancel order with status '{order['status']}'"
|
| 413 |
+
})
|
| 414 |
+
|
| 415 |
+
# Get order items to restore stock
|
| 416 |
+
items_query = """
|
| 417 |
+
SELECT product_id, quantity FROM erp_order_items WHERE order_id = %s;
|
| 418 |
+
"""
|
| 419 |
+
cursor.execute(items_query, [order_id])
|
| 420 |
+
items = cursor.fetchall()
|
| 421 |
+
|
| 422 |
+
# Update order status to Cancelled
|
| 423 |
+
update_query = """
|
| 424 |
+
UPDATE erp_orders SET status = 'Cancelled', payment_status = 'Cancelled'
|
| 425 |
+
WHERE order_id = %s;
|
| 426 |
+
"""
|
| 427 |
+
cursor.execute(update_query, [order_id])
|
| 428 |
+
|
| 429 |
+
# Add entry to order history
|
| 430 |
+
history_query = """
|
| 431 |
+
INSERT INTO erp_order_history (
|
| 432 |
+
order_id, timestamp, status_change, notes, updated_by
|
| 433 |
+
) VALUES (
|
| 434 |
+
%s, CURRENT_TIMESTAMP, 'Cancelled', %s, 'System'
|
| 435 |
+
);
|
| 436 |
+
"""
|
| 437 |
+
cursor.execute(history_query, [order_id, f"Order cancelled: {reason}"])
|
| 438 |
+
|
| 439 |
+
# Restore product stock quantities
|
| 440 |
+
for item in items:
|
| 441 |
+
restore_stock_query = """
|
| 442 |
+
UPDATE erp_products
|
| 443 |
+
SET stock_quantity = stock_quantity + %s
|
| 444 |
+
WHERE product_id = %s;
|
| 445 |
+
"""
|
| 446 |
+
cursor.execute(restore_stock_query, [item['quantity'], item['product_id']])
|
| 447 |
+
|
| 448 |
+
# Update invoice if exists
|
| 449 |
+
invoice_query = """
|
| 450 |
+
UPDATE erp_invoices
|
| 451 |
+
SET is_paid = FALSE
|
| 452 |
+
WHERE order_id = %s;
|
| 453 |
+
"""
|
| 454 |
+
cursor.execute(invoice_query, [order_id])
|
| 455 |
+
|
| 456 |
+
conn.commit()
|
| 457 |
+
|
| 458 |
+
return str({
|
| 459 |
+
"success": True,
|
| 460 |
+
"message": f"Order #{order_id} has been successfully cancelled",
|
| 461 |
+
"order_id": order_id,
|
| 462 |
+
"reason": reason,
|
| 463 |
+
"items_restored": len(items)
|
| 464 |
+
})
|
| 465 |
+
|
| 466 |
+
except Exception as e:
|
| 467 |
+
if 'conn' in locals():
|
| 468 |
+
conn.rollback()
|
| 469 |
+
return str({
|
| 470 |
+
"success": False,
|
| 471 |
+
"error": str(e)
|
| 472 |
+
})
|
| 473 |
+
finally:
|
| 474 |
+
if 'conn' in locals():
|
| 475 |
+
conn.close()
|
| 476 |
+
|
| 477 |
+
def get_invoice_details(invoice_id: int = None, order_id: int = None):
|
| 478 |
+
"""
|
| 479 |
+
Get invoice details by invoice ID or order ID.
|
| 480 |
+
|
| 481 |
+
Args:
|
| 482 |
+
invoice_id: ID of the invoice (optional)
|
| 483 |
+
order_id: ID of the order (optional)
|
| 484 |
+
|
| 485 |
+
Returns:
|
| 486 |
+
Invoice details including customer and order information
|
| 487 |
+
"""
|
| 488 |
+
if not invoice_id and not order_id:
|
| 489 |
+
return str({
|
| 490 |
+
"success": False,
|
| 491 |
+
"error": "Either invoice_id or order_id must be provided"
|
| 492 |
+
})
|
| 493 |
+
|
| 494 |
+
try:
|
| 495 |
+
conn = get_db_connection()
|
| 496 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 497 |
+
|
| 498 |
+
if invoice_id:
|
| 499 |
+
query = """
|
| 500 |
+
SELECT i.*, o.order_date, o.status as order_status,
|
| 501 |
+
c.name as customer_name, c.email as customer_email, c.address as customer_address
|
| 502 |
+
FROM erp_invoices i
|
| 503 |
+
JOIN erp_orders o ON i.order_id = o.order_id
|
| 504 |
+
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 505 |
+
WHERE i.invoice_id = %s;
|
| 506 |
+
"""
|
| 507 |
+
cursor.execute(query, [invoice_id])
|
| 508 |
+
else:
|
| 509 |
+
query = """
|
| 510 |
+
SELECT i.*, o.order_date, o.status as order_status,
|
| 511 |
+
c.name as customer_name, c.email as customer_email, c.address as customer_address
|
| 512 |
+
FROM erp_invoices i
|
| 513 |
+
JOIN erp_orders o ON i.order_id = o.order_id
|
| 514 |
+
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 515 |
+
WHERE i.order_id = %s;
|
| 516 |
+
"""
|
| 517 |
+
cursor.execute(query, [order_id])
|
| 518 |
+
|
| 519 |
+
invoice = cursor.fetchone()
|
| 520 |
+
|
| 521 |
+
if not invoice:
|
| 522 |
+
return str({
|
| 523 |
+
"success": False,
|
| 524 |
+
"error": f"Invoice not found for the provided {'invoice_id' if invoice_id else 'order_id'}"
|
| 525 |
+
})
|
| 526 |
+
|
| 527 |
+
# Get order items
|
| 528 |
+
items_query = """
|
| 529 |
+
SELECT oi.*, p.product_name, p.sku
|
| 530 |
+
FROM erp_order_items oi
|
| 531 |
+
JOIN erp_products p ON oi.product_id = p.product_id
|
| 532 |
+
WHERE oi.order_id = %s;
|
| 533 |
+
"""
|
| 534 |
+
cursor.execute(items_query, [invoice['order_id']])
|
| 535 |
+
items = cursor.fetchall()
|
| 536 |
+
|
| 537 |
+
invoice_dict = dict(invoice)
|
| 538 |
+
items_list = [dict(item) for item in items]
|
| 539 |
+
|
| 540 |
+
# Return JSON formatted response
|
| 541 |
+
return str({
|
| 542 |
+
"success": True,
|
| 543 |
+
"invoice": {
|
| 544 |
+
"invoice_id": invoice_dict['invoice_id'],
|
| 545 |
+
"invoice_number": invoice_dict.get('invoice_number', ''),
|
| 546 |
+
"order_id": invoice_dict['order_id'],
|
| 547 |
+
"order_date": str(invoice_dict['order_date']) if invoice_dict['order_date'] else None,
|
| 548 |
+
"order_status": invoice_dict['order_status'],
|
| 549 |
+
"amount": float(invoice_dict['amount']),
|
| 550 |
+
"due_date": str(invoice_dict['due_date']) if invoice_dict['due_date'] else None,
|
| 551 |
+
"payment_status": "Paid" if invoice_dict['is_paid'] else "Unpaid",
|
| 552 |
+
"customer": {
|
| 553 |
+
"name": invoice_dict['customer_name'],
|
| 554 |
+
"email": invoice_dict['customer_email'],
|
| 555 |
+
"address": invoice_dict['customer_address']
|
| 556 |
+
},
|
| 557 |
+
"items": items_list
|
| 558 |
+
}
|
| 559 |
+
})
|
| 560 |
+
|
| 561 |
+
except Exception as e:
|
| 562 |
+
return str({
|
| 563 |
+
"success": False,
|
| 564 |
+
"error": str(e)
|
| 565 |
+
})
|
| 566 |
+
finally:
|
| 567 |
+
if 'conn' in locals():
|
| 568 |
+
conn.close()
|
| 569 |
+
|
| 570 |
+
# Create Gradio interfaces for each function
|
| 571 |
+
execute_query_interface = gr.Interface(
|
| 572 |
+
fn=execute_query,
|
| 573 |
+
inputs=[
|
| 574 |
+
gr.Textbox(lines=5, label="SQL Query"),
|
| 575 |
+
gr.Textbox(label="Parameters (comma-separated)", placeholder="Optional")
|
| 576 |
+
],
|
| 577 |
+
outputs=gr.Textbox(lines=10),
|
| 578 |
+
title="Execute SQL Query",
|
| 579 |
+
description="Execute a custom SQL query on the ERP database"
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
list_tables_interface = gr.Interface(
|
| 583 |
+
fn=list_erp_tables,
|
| 584 |
+
inputs=[],
|
| 585 |
+
outputs=gr.Textbox(lines=10),
|
| 586 |
+
title="List ERP Tables",
|
| 587 |
+
description="List all ERP tables in the database"
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
order_status_interface = gr.Interface(
|
| 591 |
+
fn=get_order_status,
|
| 592 |
+
inputs=gr.Number(label="Order ID", precision=0),
|
| 593 |
+
outputs=gr.Textbox(lines=15),
|
| 594 |
+
title="Get Order Status",
|
| 595 |
+
description="Get the status of an order by order ID"
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
place_order_interface = gr.Interface(
|
| 599 |
+
fn=place_new_order,
|
| 600 |
+
inputs=[
|
| 601 |
+
gr.Number(label="Customer ID", precision=0),
|
| 602 |
+
gr.Textbox(label="Product IDs (comma-separated)", placeholder="1, 2, 3"),
|
| 603 |
+
gr.Textbox(label="Quantities (comma-separated)", placeholder="2, 1, 3"),
|
| 604 |
+
gr.Textbox(label="Shipping Address"),
|
| 605 |
+
gr.Textbox(label="Shipping Country"),
|
| 606 |
+
gr.Textbox(label="Destination Country"),
|
| 607 |
+
gr.Number(label="Previous Order ID (optional)", precision=0)
|
| 608 |
+
],
|
| 609 |
+
outputs=gr.Textbox(lines=10),
|
| 610 |
+
title="Place New Order",
|
| 611 |
+
description="Place a new order in the ERP system"
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
cancel_order_interface = gr.Interface(
|
| 615 |
+
fn=cancel_order,
|
| 616 |
+
inputs=[
|
| 617 |
+
gr.Number(label="Order ID", precision=0),
|
| 618 |
+
gr.Textbox(label="Reason for Cancellation")
|
| 619 |
+
],
|
| 620 |
+
outputs=gr.Textbox(lines=5),
|
| 621 |
+
title="Cancel Order",
|
| 622 |
+
description="Cancel an existing order in the ERP system"
|
| 623 |
+
)
|
| 624 |
+
|
| 625 |
+
invoice_details_interface = gr.Interface(
|
| 626 |
+
fn=get_invoice_details,
|
| 627 |
+
inputs=[
|
| 628 |
+
gr.Number(label="Invoice ID (optional)", precision=0),
|
| 629 |
+
gr.Number(label="Order ID (optional)", precision=0)
|
| 630 |
+
],
|
| 631 |
+
outputs=gr.Textbox(lines=15),
|
| 632 |
+
title="Get Invoice Details",
|
| 633 |
+
description="Get invoice details by invoice ID or order ID"
|
| 634 |
+
)
|
| 635 |
+
|
| 636 |
+
# Create a Gradio TabItem for each interface
|
| 637 |
+
demo = gr.TabbedInterface(
|
| 638 |
+
[
|
| 639 |
+
execute_query_interface,
|
| 640 |
+
list_tables_interface,
|
| 641 |
+
order_status_interface,
|
| 642 |
+
place_order_interface,
|
| 643 |
+
cancel_order_interface,
|
| 644 |
+
invoice_details_interface
|
| 645 |
+
],
|
| 646 |
+
[
|
| 647 |
+
"Execute Query",
|
| 648 |
+
"List Tables",
|
| 649 |
+
"Order Status",
|
| 650 |
+
"Place Order",
|
| 651 |
+
"Cancel Order",
|
| 652 |
+
"Invoice Details"
|
| 653 |
+
],
|
| 654 |
+
title="ERP System Tools"
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
# Launch the demo with MCP server enabled
|
| 658 |
+
demo.launch(mcp_server=True, server_port=7090)
|