Spaces:
Runtime error
Runtime error
Abhishek commited on
Commit ·
3b382fd
1
Parent(s): 64021ce
Added dummy sqlite DB
Browse files- .dummyenv +2 -0
- README.md +1 -0
- app.py +379 -220
- data/__init__.py +0 -0
- data/__pycache__/__init__.cpython-310.pyc +0 -0
- data/erp_db.sqlite +0 -0
- data/utils/__init__.py +0 -0
- data/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- data/utils/__pycache__/erp_db_init.cpython-310.pyc +0 -0
- data/utils/__pycache__/populate_erp_db.cpython-310.pyc +0 -0
- data/utils/__pycache__/reset_erp_db.cpython-310.pyc +0 -0
- data/utils/erp_db_init.py +145 -0
- data/utils/populate_erp_db.py +415 -0
- data/utils/reset_erp_db.py +47 -0
.dummyenv
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ERP Postgres DB
|
| 2 |
+
ERP_DB_TYPE = "sqlite" # Options: "postgres" or "sqlite"
|
README.md
CHANGED
|
@@ -48,6 +48,7 @@ This backend is designed to be consumed by autonomous agents for real-world task
|
|
| 48 |
| `place_order` | Places a new order |
|
| 49 |
| `cancel_order` | Cancels an existing order |
|
| 50 |
| `get_invoice_details` | Returns invoice details for an order |
|
|
|
|
| 51 |
| `get_active_disruptions` | Returns geopolitical disruptions between nations |
|
| 52 |
|
| 53 |
---
|
|
|
|
| 48 |
| `place_order` | Places a new order |
|
| 49 |
| `cancel_order` | Cancels an existing order |
|
| 50 |
| `get_invoice_details` | Returns invoice details for an order |
|
| 51 |
+
| `reset_database` | Resets the SQLite Database |
|
| 52 |
| `get_active_disruptions` | Returns geopolitical disruptions between nations |
|
| 53 |
|
| 54 |
---
|
app.py
CHANGED
|
@@ -1,186 +1,257 @@
|
|
| 1 |
-
|
| 2 |
-
import psycopg2
|
| 3 |
-
from psycopg2.extras import RealDictCursor
|
| 4 |
import os
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from typing import Dict, Any, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
load_dotenv()
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
def get_db_connection():
|
| 11 |
-
"""Get database connection
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
|
|
|
| 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
|
| 31 |
|
| 32 |
Returns:
|
| 33 |
-
Query results or error message
|
| 34 |
"""
|
| 35 |
try:
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
|
| 44 |
-
if
|
| 45 |
-
cursor.execute(query,
|
| 46 |
else:
|
| 47 |
cursor.execute(query)
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# Convert RealDictRow to regular dict for JSON serialization
|
| 53 |
results = [dict(row) for row in results]
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 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
|
| 77 |
"success": False,
|
| 78 |
"error": str(e)
|
| 79 |
-
}
|
| 80 |
finally:
|
| 81 |
if 'conn' in locals():
|
| 82 |
conn.close()
|
| 83 |
|
| 84 |
-
|
|
|
|
| 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 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
table_names = [row['table_name'] for row in results]
|
| 106 |
if not table_names:
|
| 107 |
-
return
|
| 108 |
"success": False,
|
| 109 |
"message": "No ERP tables found",
|
| 110 |
"tables": []
|
| 111 |
-
}
|
| 112 |
-
return
|
| 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
|
| 121 |
"success": False,
|
| 122 |
"error": str(e)
|
| 123 |
-
}
|
| 124 |
finally:
|
| 125 |
if 'conn' in locals():
|
| 126 |
conn.close()
|
| 127 |
|
| 128 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
cursor.execute(query, [order_id])
|
| 150 |
order = cursor.fetchone()
|
| 151 |
|
| 152 |
if not order:
|
| 153 |
-
return
|
| 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 =
|
| 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 =
|
| 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
|
| 184 |
"success": True,
|
| 185 |
"order": {
|
| 186 |
"order_id": order_id,
|
|
@@ -196,90 +267,92 @@ def get_order_status(order_id: int):
|
|
| 196 |
"items": items_list,
|
| 197 |
"history": history_list
|
| 198 |
}
|
| 199 |
-
}
|
| 200 |
|
| 201 |
except Exception as e:
|
| 202 |
-
return
|
| 203 |
"success": False,
|
| 204 |
"error": str(e)
|
| 205 |
-
}
|
| 206 |
finally:
|
| 207 |
if 'conn' in locals():
|
| 208 |
conn.close()
|
| 209 |
|
| 210 |
-
|
|
|
|
| 211 |
customer_id: int,
|
| 212 |
-
|
| 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 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
previous_order_id: ID of a previous order this is replacing (optional)
|
| 230 |
|
| 231 |
Returns:
|
| 232 |
-
New order information
|
| 233 |
"""
|
| 234 |
try:
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 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 =
|
| 258 |
cursor.execute(product_query, [item['product_id']])
|
| 259 |
product = cursor.fetchone()
|
| 260 |
if not product:
|
| 261 |
-
return
|
| 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 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
)
|
| 278 |
"""
|
|
|
|
| 279 |
cursor.execute(order_query, [
|
| 280 |
customer_id, total_amount, shipping_address, shipping_country, destination_country, previous_order_id
|
| 281 |
])
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
# Insert order items into erp_order_items table for the new order
|
| 285 |
for item in items:
|
|
@@ -289,11 +362,11 @@ def place_new_order(
|
|
| 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 |
-
|
| 297 |
);
|
| 298 |
"""
|
| 299 |
cursor.execute(item_query, [
|
|
@@ -302,252 +375,297 @@ def place_new_order(
|
|
| 302 |
])
|
| 303 |
|
| 304 |
# Update product stock
|
| 305 |
-
update_stock_query = """
|
| 306 |
UPDATE erp_products
|
| 307 |
-
SET stock_quantity = stock_quantity -
|
| 308 |
-
WHERE product_id =
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 339 |
-
)
|
| 340 |
"""
|
| 341 |
-
cursor.execute(invoice_query, [new_order_id, total_amount, new_order_id])
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
conn.commit()
|
| 345 |
|
| 346 |
# Get the complete new order details
|
| 347 |
-
cursor.execute("SELECT * FROM erp_orders WHERE order_id =
|
| 348 |
order = cursor.fetchone()
|
| 349 |
|
| 350 |
-
order_dict =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
# Return JSON formatted response
|
| 353 |
-
return
|
| 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":
|
| 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
|
| 374 |
"success": False,
|
| 375 |
"error": str(e)
|
| 376 |
-
}
|
| 377 |
finally:
|
| 378 |
if 'conn' in locals():
|
| 379 |
conn.close()
|
| 380 |
|
| 381 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
# Check if order exists and can be cancelled
|
| 397 |
-
check_query = """
|
| 398 |
-
SELECT status, customer_id FROM erp_orders WHERE order_id =
|
| 399 |
"""
|
| 400 |
cursor.execute(check_query, [order_id])
|
| 401 |
order = cursor.fetchone()
|
| 402 |
|
| 403 |
if not order:
|
| 404 |
-
return
|
| 405 |
"success": False,
|
| 406 |
"error": f"Order with ID {order_id} not found"
|
| 407 |
-
}
|
| 408 |
|
| 409 |
if order['status'] in ['Delivered', 'Cancelled']:
|
| 410 |
-
return
|
| 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 =
|
| 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 =
|
| 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 |
-
|
| 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 +
|
| 444 |
-
WHERE product_id =
|
| 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 =
|
| 452 |
-
WHERE order_id =
|
| 453 |
"""
|
| 454 |
cursor.execute(invoice_query, [order_id])
|
| 455 |
|
| 456 |
conn.commit()
|
| 457 |
|
| 458 |
-
return
|
| 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
|
| 470 |
"success": False,
|
| 471 |
"error": str(e)
|
| 472 |
-
}
|
| 473 |
finally:
|
| 474 |
if 'conn' in locals():
|
| 475 |
conn.close()
|
| 476 |
|
| 477 |
-
|
|
|
|
| 478 |
"""
|
| 479 |
Get invoice details by invoice ID or order ID.
|
| 480 |
|
| 481 |
Args:
|
| 482 |
-
invoice_id: ID of the invoice
|
| 483 |
-
order_id: ID of the order
|
| 484 |
|
| 485 |
Returns:
|
| 486 |
-
Invoice details including customer and order information
|
| 487 |
"""
|
| 488 |
if not invoice_id and not order_id:
|
| 489 |
-
return
|
| 490 |
"success": False,
|
| 491 |
"error": "Either invoice_id or order_id must be provided"
|
| 492 |
-
}
|
| 493 |
|
| 494 |
try:
|
| 495 |
-
conn = get_db_connection()
|
| 496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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 =
|
| 516 |
"""
|
| 517 |
cursor.execute(query, [order_id])
|
| 518 |
|
| 519 |
invoice = cursor.fetchone()
|
| 520 |
|
| 521 |
if not invoice:
|
| 522 |
-
return
|
| 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 =
|
| 533 |
"""
|
| 534 |
cursor.execute(items_query, [invoice['order_id']])
|
| 535 |
items = cursor.fetchall()
|
| 536 |
|
| 537 |
-
invoice_dict =
|
| 538 |
-
items_list =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
|
| 540 |
# Return JSON formatted response
|
| 541 |
-
return
|
| 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":
|
| 548 |
"order_status": invoice_dict['order_status'],
|
| 549 |
"amount": float(invoice_dict['amount']),
|
| 550 |
-
"due_date":
|
| 551 |
"payment_status": "Paid" if invoice_dict['is_paid'] else "Unpaid",
|
| 552 |
"customer": {
|
| 553 |
"name": invoice_dict['customer_name'],
|
|
@@ -556,17 +674,48 @@ def get_invoice_details(invoice_id: int = None, order_id: int = None):
|
|
| 556 |
},
|
| 557 |
"items": items_list
|
| 558 |
}
|
| 559 |
-
}
|
| 560 |
|
| 561 |
except Exception as e:
|
| 562 |
-
return
|
| 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,
|
|
@@ -633,6 +782,14 @@ invoice_details_interface = gr.Interface(
|
|
| 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 |
[
|
|
@@ -641,7 +798,8 @@ demo = gr.TabbedInterface(
|
|
| 641 |
order_status_interface,
|
| 642 |
place_order_interface,
|
| 643 |
cancel_order_interface,
|
| 644 |
-
invoice_details_interface
|
|
|
|
| 645 |
],
|
| 646 |
[
|
| 647 |
"Execute Query",
|
|
@@ -649,7 +807,8 @@ demo = gr.TabbedInterface(
|
|
| 649 |
"Order Status",
|
| 650 |
"Place Order",
|
| 651 |
"Cancel Order",
|
| 652 |
-
"Invoice Details"
|
|
|
|
| 653 |
],
|
| 654 |
title="ERP System Tools"
|
| 655 |
)
|
|
|
|
| 1 |
+
# erp_server.py
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
+
import sys
|
| 4 |
+
from typing import TypedDict, List, Union
|
| 5 |
+
|
| 6 |
+
# Add the project root directory to the Python path
|
| 7 |
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
| 8 |
+
|
| 9 |
+
import gradio as gr
|
| 10 |
from typing import Dict, Any, List, Optional
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
from datetime import datetime, date
|
| 13 |
+
import pathlib
|
| 14 |
+
|
| 15 |
+
# Import both database libraries
|
| 16 |
+
import sqlite3
|
| 17 |
+
try:
|
| 18 |
+
import psycopg2
|
| 19 |
+
from psycopg2.extras import RealDictCursor
|
| 20 |
+
POSTGRES_AVAILABLE = True
|
| 21 |
+
except ImportError:
|
| 22 |
+
POSTGRES_AVAILABLE = False
|
| 23 |
+
|
| 24 |
+
# Import SQLite initialization and reset functions
|
| 25 |
+
from data.utils.erp_db_init import init_sqlite_db
|
| 26 |
+
from data.utils.reset_erp_db import reset_erp_db
|
| 27 |
+
|
| 28 |
+
from mcp.server.fastmcp import FastMCP
|
| 29 |
|
| 30 |
load_dotenv()
|
| 31 |
|
| 32 |
+
mcp = FastMCP("ERP Database")
|
| 33 |
+
|
| 34 |
+
# Helper function to convert SQLite row to dict
|
| 35 |
+
def dict_factory(cursor, row):
|
| 36 |
+
d = {}
|
| 37 |
+
for idx, col in enumerate(cursor.description):
|
| 38 |
+
d[col[0]] = row[idx]
|
| 39 |
+
return d
|
| 40 |
+
|
| 41 |
+
# Helper function to handle date serialization for JSON
|
| 42 |
+
def serialize_dates(obj):
|
| 43 |
+
if isinstance(obj, (date, datetime)):
|
| 44 |
+
return obj.isoformat()
|
| 45 |
+
return obj
|
| 46 |
+
|
| 47 |
def get_db_connection():
|
| 48 |
+
"""Get database connection based on configuration."""
|
| 49 |
+
# Default to SQLite unless explicitly set to use PostgreSQL
|
| 50 |
+
db_type = os.getenv("ERP_DB_TYPE", "sqlite").lower()
|
| 51 |
+
|
| 52 |
+
if db_type == "postgres" and POSTGRES_AVAILABLE:
|
| 53 |
+
try:
|
| 54 |
+
conn = psycopg2.connect(
|
| 55 |
+
host=os.getenv("POSTGRES_HOST", "localhost"),
|
| 56 |
+
database=os.getenv("POSTGRES_DB", "erp_db"),
|
| 57 |
+
user=os.getenv("POSTGRES_USER", "postgres"),
|
| 58 |
+
password=os.getenv("POSTGRES_PASSWORD", ""),
|
| 59 |
+
port=os.getenv("POSTGRES_PORT", "5432")
|
| 60 |
+
)
|
| 61 |
+
return conn, "postgres"
|
| 62 |
+
except Exception as e:
|
| 63 |
+
raise Exception(f"PostgreSQL connection failed: {str(e)}")
|
| 64 |
+
else:
|
| 65 |
+
try:
|
| 66 |
+
# Get SQLite database path from environment or use default
|
| 67 |
+
db_path = os.getenv("SQLITE_DB_PATH", "./data/erp_db.sqlite")
|
| 68 |
+
|
| 69 |
+
# Ensure database is initialized
|
| 70 |
+
db_dir = pathlib.Path(os.path.dirname(db_path))
|
| 71 |
+
if not db_dir.exists() or not pathlib.Path(db_path).exists():
|
| 72 |
+
init_sqlite_db(db_path)
|
| 73 |
+
|
| 74 |
+
conn = sqlite3.connect(db_path)
|
| 75 |
+
conn.row_factory = dict_factory
|
| 76 |
+
|
| 77 |
+
# Enable foreign keys
|
| 78 |
+
conn.execute("PRAGMA foreign_keys = ON")
|
| 79 |
+
|
| 80 |
+
return conn, "sqlite"
|
| 81 |
+
except Exception as e:
|
| 82 |
+
raise Exception(f"SQLite connection failed: {str(e)}")
|
| 83 |
|
| 84 |
+
@mcp.tool()
|
| 85 |
+
async def execute_query(query: str, params: Optional[List] = None) -> Dict[str, Any]:
|
| 86 |
"""
|
| 87 |
Execute a custom SQL query on the ERP database.
|
| 88 |
+
Only SELECT statements are allowed for security reasons.
|
| 89 |
|
| 90 |
Args:
|
| 91 |
+
query (str): SQL query to execute (must be a SELECT statement)
|
| 92 |
+
params (List, optional): Parameters for parameterized queries
|
| 93 |
|
| 94 |
Returns:
|
| 95 |
+
dict: Query results or error message
|
| 96 |
"""
|
| 97 |
try:
|
| 98 |
+
# Check if it's a SELECT query
|
| 99 |
+
if not query.strip().upper().startswith('SELECT'):
|
| 100 |
+
return {
|
| 101 |
+
"success": False,
|
| 102 |
+
"error": "Only SELECT statements are allowed for security reasons."
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
conn, db_type = get_db_connection()
|
| 106 |
|
| 107 |
+
if db_type == "postgres":
|
| 108 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 109 |
+
else: # sqlite
|
| 110 |
+
cursor = conn.cursor()
|
| 111 |
|
| 112 |
+
if params:
|
| 113 |
+
cursor.execute(query, params)
|
| 114 |
else:
|
| 115 |
cursor.execute(query)
|
| 116 |
|
| 117 |
+
results = cursor.fetchall()
|
| 118 |
+
# Convert results to regular dict for JSON serialization
|
| 119 |
+
if db_type == "postgres":
|
|
|
|
| 120 |
results = [dict(row) for row in results]
|
| 121 |
+
if not results:
|
| 122 |
+
return "No results found"
|
| 123 |
+
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
except Exception as e:
|
| 126 |
+
return {
|
| 127 |
"success": False,
|
| 128 |
"error": str(e)
|
| 129 |
+
}
|
| 130 |
finally:
|
| 131 |
if 'conn' in locals():
|
| 132 |
conn.close()
|
| 133 |
|
| 134 |
+
@mcp.tool()
|
| 135 |
+
async def list_erp_tables() -> Dict[str, Any]:
|
| 136 |
"""
|
| 137 |
List all ERP tables in the database.
|
| 138 |
|
| 139 |
Returns:
|
| 140 |
+
dict: List of ERP table names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
"""
|
|
|
|
| 142 |
try:
|
| 143 |
+
conn, db_type = get_db_connection()
|
| 144 |
+
|
| 145 |
+
if db_type == "postgres":
|
| 146 |
+
query = """
|
| 147 |
+
SELECT table_name
|
| 148 |
+
FROM information_schema.tables
|
| 149 |
+
WHERE table_schema = 'public'
|
| 150 |
+
AND table_name LIKE 'erp_%'
|
| 151 |
+
ORDER BY table_name;
|
| 152 |
+
"""
|
| 153 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 154 |
+
cursor.execute(query)
|
| 155 |
+
results = cursor.fetchall()
|
| 156 |
+
table_names = [row['table_name'] for row in results]
|
| 157 |
+
else: # sqlite
|
| 158 |
+
query = """
|
| 159 |
+
SELECT name as table_name
|
| 160 |
+
FROM sqlite_master
|
| 161 |
+
WHERE type='table' AND name LIKE 'erp_%'
|
| 162 |
+
ORDER BY name;
|
| 163 |
+
"""
|
| 164 |
+
cursor = conn.cursor()
|
| 165 |
+
cursor.execute(query)
|
| 166 |
+
results = cursor.fetchall()
|
| 167 |
+
table_names = [row['table_name'] for row in results]
|
| 168 |
|
|
|
|
| 169 |
if not table_names:
|
| 170 |
+
return {
|
| 171 |
"success": False,
|
| 172 |
"message": "No ERP tables found",
|
| 173 |
"tables": []
|
| 174 |
+
}
|
| 175 |
+
return {
|
| 176 |
"success": True,
|
| 177 |
"message": f"Found {len(table_names)} tables",
|
| 178 |
"count": len(table_names),
|
| 179 |
"tables": table_names
|
| 180 |
+
}
|
| 181 |
|
| 182 |
except Exception as e:
|
| 183 |
+
return {
|
| 184 |
"success": False,
|
| 185 |
"error": str(e)
|
| 186 |
+
}
|
| 187 |
finally:
|
| 188 |
if 'conn' in locals():
|
| 189 |
conn.close()
|
| 190 |
|
| 191 |
+
@mcp.tool()
|
| 192 |
+
async def get_order_status(order_id: int) -> Dict[str, Any]:
|
| 193 |
"""
|
| 194 |
Get the status of an order by order ID.
|
| 195 |
|
| 196 |
Args:
|
| 197 |
+
order_id (int): The ID of the order to check
|
| 198 |
|
| 199 |
Returns:
|
| 200 |
+
dict: Order status information including shipping and destination countries
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
"""
|
|
|
|
| 202 |
try:
|
| 203 |
+
conn, db_type = get_db_connection()
|
| 204 |
+
|
| 205 |
+
if db_type == "postgres":
|
| 206 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 207 |
+
param_placeholder = "%s"
|
| 208 |
+
else: # sqlite
|
| 209 |
+
cursor = conn.cursor()
|
| 210 |
+
param_placeholder = "?"
|
| 211 |
+
|
| 212 |
+
query = f"""
|
| 213 |
+
SELECT o.*, c.name as customer_name, c.email as customer_email,
|
| 214 |
+
o.shipping_country, o.destination_country
|
| 215 |
+
FROM erp_orders o
|
| 216 |
+
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 217 |
+
WHERE o.order_id = {param_placeholder};
|
| 218 |
+
"""
|
| 219 |
+
|
| 220 |
cursor.execute(query, [order_id])
|
| 221 |
order = cursor.fetchone()
|
| 222 |
|
| 223 |
if not order:
|
| 224 |
+
return {
|
| 225 |
"success": False,
|
| 226 |
"message": f"Order with ID {order_id} not found"
|
| 227 |
+
}
|
| 228 |
|
| 229 |
# Get order items
|
| 230 |
+
items_query = f"""
|
| 231 |
SELECT oi.order_item_id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price, oi.subtotal,
|
| 232 |
p.product_name, p.sku
|
| 233 |
FROM erp_order_items oi
|
| 234 |
JOIN erp_products p ON oi.product_id = p.product_id
|
| 235 |
+
WHERE oi.order_id = {param_placeholder};
|
| 236 |
"""
|
| 237 |
cursor.execute(items_query, [order_id])
|
| 238 |
items = cursor.fetchall()
|
| 239 |
|
| 240 |
# Get order history
|
| 241 |
+
history_query = f"""
|
| 242 |
SELECT * FROM erp_order_history
|
| 243 |
+
WHERE order_id = {param_placeholder}
|
| 244 |
ORDER BY timestamp DESC;
|
| 245 |
"""
|
| 246 |
cursor.execute(history_query, [order_id])
|
| 247 |
history = cursor.fetchall()
|
| 248 |
|
| 249 |
+
order_dict = order if db_type == "sqlite" else dict(order)
|
| 250 |
+
items_list = items if db_type == "sqlite" else [dict(item) for item in items]
|
| 251 |
+
history_list = history if db_type == "sqlite" else [dict(entry) for entry in history]
|
| 252 |
|
| 253 |
# Return JSON formatted response
|
| 254 |
+
return {
|
| 255 |
"success": True,
|
| 256 |
"order": {
|
| 257 |
"order_id": order_id,
|
|
|
|
| 267 |
"items": items_list,
|
| 268 |
"history": history_list
|
| 269 |
}
|
| 270 |
+
}
|
| 271 |
|
| 272 |
except Exception as e:
|
| 273 |
+
return {
|
| 274 |
"success": False,
|
| 275 |
"error": str(e)
|
| 276 |
+
}
|
| 277 |
finally:
|
| 278 |
if 'conn' in locals():
|
| 279 |
conn.close()
|
| 280 |
|
| 281 |
+
@mcp.tool()
|
| 282 |
+
async def place_new_order(
|
| 283 |
customer_id: int,
|
| 284 |
+
items: List[Dict[str, Any]],
|
|
|
|
| 285 |
shipping_address: str,
|
| 286 |
shipping_country: str,
|
| 287 |
destination_country: str,
|
| 288 |
+
previous_order_id: Optional[int] = None
|
| 289 |
+
) -> Dict[str, Any]:
|
| 290 |
"""
|
| 291 |
+
Place a new order in the ERP system, after ensuring there are no global disruptions affecting the shipment.
|
| 292 |
|
| 293 |
Args:
|
| 294 |
+
customer_id (int): ID of the customer placing the order
|
| 295 |
+
items (List[Dict]): List of items to order, each with product_id and quantity
|
| 296 |
+
shipping_address (str): Shipping address for the order
|
| 297 |
+
shipping_country (str): Country where the order will be shipped from
|
| 298 |
+
destination_country (str): Country where the order will be delivered to
|
| 299 |
+
previous_order_id (int, optional): ID of a previous order this is replacing
|
|
|
|
| 300 |
|
| 301 |
Returns:
|
| 302 |
+
dict: New order information
|
| 303 |
"""
|
| 304 |
try:
|
| 305 |
+
conn, db_type = get_db_connection()
|
| 306 |
+
|
| 307 |
+
if db_type == "postgres":
|
| 308 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 309 |
+
param_placeholder = "%s"
|
| 310 |
+
returning_clause = "RETURNING order_id"
|
| 311 |
+
date_interval = "CURRENT_DATE + INTERVAL '7 days'"
|
| 312 |
+
due_date_interval = "CURRENT_DATE + INTERVAL '30 days'"
|
| 313 |
+
concat_op = "||"
|
| 314 |
+
else: # sqlite
|
| 315 |
+
cursor = conn.cursor()
|
| 316 |
+
param_placeholder = "?"
|
| 317 |
+
returning_clause = ""
|
| 318 |
+
date_interval = "date('now', '+7 days')"
|
| 319 |
+
due_date_interval = "date('now', '+30 days')"
|
| 320 |
+
concat_op = "||"
|
|
|
|
|
|
|
| 321 |
|
| 322 |
# Calculate total amount based on the total items and their prices
|
| 323 |
total_amount = 0
|
| 324 |
for item in items:
|
| 325 |
+
product_query = f"SELECT price FROM erp_products WHERE product_id = {param_placeholder};"
|
| 326 |
cursor.execute(product_query, [item['product_id']])
|
| 327 |
product = cursor.fetchone()
|
| 328 |
if not product:
|
| 329 |
+
return {
|
| 330 |
"success": False,
|
| 331 |
"error": f"Product with ID {item['product_id']} not found"
|
| 332 |
+
}
|
| 333 |
total_amount += product['price'] * item['quantity']
|
| 334 |
|
| 335 |
# Insert new order into the erp_orders table for the customer
|
| 336 |
+
order_query = f"""
|
| 337 |
INSERT INTO erp_orders (
|
| 338 |
customer_id, order_date, total_amount, status,
|
| 339 |
shipping_address, shipping_country, destination_country, previous_order_id,
|
| 340 |
estimated_delivery, payment_status
|
| 341 |
) VALUES (
|
| 342 |
+
{param_placeholder}, CURRENT_DATE, {param_placeholder}, 'Processing',
|
| 343 |
+
{param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder},
|
| 344 |
+
{date_interval}, 'Pending'
|
| 345 |
+
) {returning_clause};
|
| 346 |
"""
|
| 347 |
+
|
| 348 |
cursor.execute(order_query, [
|
| 349 |
customer_id, total_amount, shipping_address, shipping_country, destination_country, previous_order_id
|
| 350 |
])
|
| 351 |
+
|
| 352 |
+
if db_type == "postgres":
|
| 353 |
+
new_order_id = cursor.fetchone()['order_id']
|
| 354 |
+
else: # sqlite
|
| 355 |
+
new_order_id = cursor.lastrowid
|
| 356 |
|
| 357 |
# Insert order items into erp_order_items table for the new order
|
| 358 |
for item in items:
|
|
|
|
| 362 |
unit_price = product['price']
|
| 363 |
subtotal = unit_price * item['quantity']
|
| 364 |
|
| 365 |
+
item_query = f"""
|
| 366 |
INSERT INTO erp_order_items (
|
| 367 |
order_id, product_id, quantity, unit_price, subtotal
|
| 368 |
) VALUES (
|
| 369 |
+
{param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder}
|
| 370 |
);
|
| 371 |
"""
|
| 372 |
cursor.execute(item_query, [
|
|
|
|
| 375 |
])
|
| 376 |
|
| 377 |
# Update product stock
|
| 378 |
+
update_stock_query = f"""
|
| 379 |
UPDATE erp_products
|
| 380 |
+
SET stock_quantity = stock_quantity - {param_placeholder}
|
| 381 |
+
WHERE product_id = {param_placeholder};
|
| 382 |
"""
|
| 383 |
cursor.execute(update_stock_query, [item['quantity'], item['product_id']])
|
| 384 |
|
| 385 |
# Create order history entry into the erp_order_history for the new order
|
| 386 |
+
history_query = f"""
|
| 387 |
INSERT INTO erp_order_history (
|
| 388 |
order_id, timestamp, status_change, notes, updated_by
|
| 389 |
) VALUES (
|
| 390 |
+
{param_placeholder}, CURRENT_TIMESTAMP, 'Order Created', 'New order placed', 'System'
|
| 391 |
);
|
| 392 |
"""
|
| 393 |
cursor.execute(history_query, [new_order_id])
|
| 394 |
|
| 395 |
# If this is a replacement order, add a note to the previous order
|
| 396 |
if previous_order_id:
|
| 397 |
+
prev_order_note_query = f"""
|
| 398 |
INSERT INTO erp_order_history (
|
| 399 |
order_id, timestamp, status_change, notes, updated_by
|
| 400 |
) VALUES (
|
| 401 |
+
{param_placeholder}, CURRENT_TIMESTAMP, 'Replaced', 'Order replaced by order #' {concat_op} {param_placeholder}, 'System'
|
| 402 |
);
|
| 403 |
"""
|
| 404 |
cursor.execute(prev_order_note_query, [previous_order_id, new_order_id])
|
| 405 |
|
| 406 |
# Generate invoice for the new order
|
| 407 |
+
invoice_query = f"""
|
| 408 |
INSERT INTO erp_invoices (
|
| 409 |
order_id, invoice_date, amount, payment_terms, due_date, is_paid, invoice_number
|
| 410 |
) VALUES (
|
| 411 |
+
{param_placeholder}, CURRENT_DATE, {param_placeholder}, 'Net 30', {due_date_interval}, 0, 'INV-' {concat_op} {param_placeholder}
|
| 412 |
+
) {returning_clause};
|
| 413 |
"""
|
| 414 |
+
cursor.execute(invoice_query, [new_order_id, total_amount, str(new_order_id)])
|
| 415 |
+
|
| 416 |
+
if db_type == "postgres":
|
| 417 |
+
invoice_id = cursor.fetchone()['invoice_id']
|
| 418 |
+
else: # sqlite
|
| 419 |
+
invoice_id = cursor.lastrowid
|
| 420 |
|
| 421 |
conn.commit()
|
| 422 |
|
| 423 |
# Get the complete new order details
|
| 424 |
+
cursor.execute(f"SELECT * FROM erp_orders WHERE order_id = {param_placeholder};", [new_order_id])
|
| 425 |
order = cursor.fetchone()
|
| 426 |
|
| 427 |
+
order_dict = order
|
| 428 |
+
|
| 429 |
+
# Format date for response
|
| 430 |
+
estimated_delivery = order_dict.get('estimated_delivery')
|
| 431 |
+
if estimated_delivery:
|
| 432 |
+
if isinstance(estimated_delivery, str):
|
| 433 |
+
estimated_delivery_str = estimated_delivery
|
| 434 |
+
else:
|
| 435 |
+
estimated_delivery_str = estimated_delivery.strftime("%Y-%m-%d") if hasattr(estimated_delivery, 'strftime') else str(estimated_delivery)
|
| 436 |
+
else:
|
| 437 |
+
estimated_delivery_str = None
|
| 438 |
|
| 439 |
# Return JSON formatted response
|
| 440 |
+
return {
|
| 441 |
"success": True,
|
| 442 |
"order": {
|
| 443 |
"order_id": new_order_id,
|
| 444 |
"invoice_id": invoice_id,
|
| 445 |
"total_amount": float(total_amount),
|
| 446 |
"status": order_dict['status'],
|
| 447 |
+
"estimated_delivery": estimated_delivery_str,
|
| 448 |
"customer_id": customer_id,
|
| 449 |
"shipping_address": shipping_address,
|
| 450 |
"shipping_country": shipping_country,
|
| 451 |
"destination_country": destination_country,
|
| 452 |
+
"previous_order_id": previous_order_id
|
|
|
|
| 453 |
}
|
| 454 |
+
}
|
| 455 |
|
| 456 |
except Exception as e:
|
| 457 |
if 'conn' in locals():
|
| 458 |
conn.rollback()
|
| 459 |
+
return {
|
| 460 |
"success": False,
|
| 461 |
"error": str(e)
|
| 462 |
+
}
|
| 463 |
finally:
|
| 464 |
if 'conn' in locals():
|
| 465 |
conn.close()
|
| 466 |
|
| 467 |
+
@mcp.tool()
|
| 468 |
+
async def cancel_order(order_id: int, reason: str) -> Dict[str, Any]:
|
| 469 |
"""
|
| 470 |
Cancel an existing order in the ERP system.
|
| 471 |
|
| 472 |
Args:
|
| 473 |
+
order_id (int): ID of the order to cancel
|
| 474 |
+
reason (str): Reason for cancellation
|
| 475 |
|
| 476 |
Returns:
|
| 477 |
+
dict: Result of the cancellation operation
|
| 478 |
"""
|
| 479 |
try:
|
| 480 |
+
conn, db_type = get_db_connection()
|
| 481 |
+
|
| 482 |
+
if db_type == "postgres":
|
| 483 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 484 |
+
param_placeholder = "%s"
|
| 485 |
+
else: # sqlite
|
| 486 |
+
cursor = conn.cursor()
|
| 487 |
+
param_placeholder = "?"
|
| 488 |
|
| 489 |
# Check if order exists and can be cancelled
|
| 490 |
+
check_query = f"""
|
| 491 |
+
SELECT status, customer_id FROM erp_orders WHERE order_id = {param_placeholder};
|
| 492 |
"""
|
| 493 |
cursor.execute(check_query, [order_id])
|
| 494 |
order = cursor.fetchone()
|
| 495 |
|
| 496 |
if not order:
|
| 497 |
+
return {
|
| 498 |
"success": False,
|
| 499 |
"error": f"Order with ID {order_id} not found"
|
| 500 |
+
}
|
| 501 |
|
| 502 |
if order['status'] in ['Delivered', 'Cancelled']:
|
| 503 |
+
return {
|
| 504 |
"success": False,
|
| 505 |
"error": f"Cannot cancel order with status '{order['status']}'"
|
| 506 |
+
}
|
| 507 |
|
| 508 |
# Get order items to restore stock
|
| 509 |
+
items_query = f"""
|
| 510 |
+
SELECT product_id, quantity FROM erp_order_items WHERE order_id = {param_placeholder};
|
| 511 |
"""
|
| 512 |
cursor.execute(items_query, [order_id])
|
| 513 |
items = cursor.fetchall()
|
| 514 |
|
| 515 |
# Update order status to Cancelled
|
| 516 |
+
update_query = f"""
|
| 517 |
UPDATE erp_orders SET status = 'Cancelled', payment_status = 'Cancelled'
|
| 518 |
+
WHERE order_id = {param_placeholder};
|
| 519 |
"""
|
| 520 |
cursor.execute(update_query, [order_id])
|
| 521 |
|
| 522 |
# Add entry to order history
|
| 523 |
+
history_query = f"""
|
| 524 |
INSERT INTO erp_order_history (
|
| 525 |
order_id, timestamp, status_change, notes, updated_by
|
| 526 |
) VALUES (
|
| 527 |
+
{param_placeholder}, CURRENT_TIMESTAMP, 'Cancelled', {param_placeholder}, 'System'
|
| 528 |
);
|
| 529 |
"""
|
| 530 |
cursor.execute(history_query, [order_id, f"Order cancelled: {reason}"])
|
| 531 |
|
| 532 |
# Restore product stock quantities
|
| 533 |
for item in items:
|
| 534 |
+
restore_stock_query = f"""
|
| 535 |
UPDATE erp_products
|
| 536 |
+
SET stock_quantity = stock_quantity + {param_placeholder}
|
| 537 |
+
WHERE product_id = {param_placeholder};
|
| 538 |
"""
|
| 539 |
cursor.execute(restore_stock_query, [item['quantity'], item['product_id']])
|
| 540 |
|
| 541 |
# Update invoice if exists
|
| 542 |
+
invoice_query = f"""
|
| 543 |
UPDATE erp_invoices
|
| 544 |
+
SET is_paid = 0
|
| 545 |
+
WHERE order_id = {param_placeholder};
|
| 546 |
"""
|
| 547 |
cursor.execute(invoice_query, [order_id])
|
| 548 |
|
| 549 |
conn.commit()
|
| 550 |
|
| 551 |
+
return {
|
| 552 |
"success": True,
|
| 553 |
"message": f"Order #{order_id} has been successfully cancelled",
|
|
|
|
| 554 |
"reason": reason,
|
| 555 |
"items_restored": len(items)
|
| 556 |
+
}
|
| 557 |
|
| 558 |
except Exception as e:
|
| 559 |
if 'conn' in locals():
|
| 560 |
conn.rollback()
|
| 561 |
+
return {
|
| 562 |
"success": False,
|
| 563 |
"error": str(e)
|
| 564 |
+
}
|
| 565 |
finally:
|
| 566 |
if 'conn' in locals():
|
| 567 |
conn.close()
|
| 568 |
|
| 569 |
+
@mcp.tool()
|
| 570 |
+
async def get_invoice_details(invoice_id: Optional[int] = None, order_id: Optional[int] = None) -> Dict[str, Any]:
|
| 571 |
"""
|
| 572 |
Get invoice details by invoice ID or order ID.
|
| 573 |
|
| 574 |
Args:
|
| 575 |
+
invoice_id (int, optional): ID of the invoice
|
| 576 |
+
order_id (int, optional): ID of the order
|
| 577 |
|
| 578 |
Returns:
|
| 579 |
+
dict: Invoice details including customer and order information
|
| 580 |
"""
|
| 581 |
if not invoice_id and not order_id:
|
| 582 |
+
return {
|
| 583 |
"success": False,
|
| 584 |
"error": "Either invoice_id or order_id must be provided"
|
| 585 |
+
}
|
| 586 |
|
| 587 |
try:
|
| 588 |
+
conn, db_type = get_db_connection()
|
| 589 |
+
|
| 590 |
+
if db_type == "postgres":
|
| 591 |
+
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
| 592 |
+
param_placeholder = "%s"
|
| 593 |
+
else: # sqlite
|
| 594 |
+
cursor = conn.cursor()
|
| 595 |
+
param_placeholder = "?"
|
| 596 |
|
| 597 |
if invoice_id:
|
| 598 |
+
query = f"""
|
| 599 |
SELECT i.*, o.order_date, o.status as order_status,
|
| 600 |
c.name as customer_name, c.email as customer_email, c.address as customer_address
|
| 601 |
FROM erp_invoices i
|
| 602 |
JOIN erp_orders o ON i.order_id = o.order_id
|
| 603 |
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 604 |
+
WHERE i.invoice_id = {param_placeholder};
|
| 605 |
"""
|
| 606 |
cursor.execute(query, [invoice_id])
|
| 607 |
else:
|
| 608 |
+
query = f"""
|
| 609 |
SELECT i.*, o.order_date, o.status as order_status,
|
| 610 |
c.name as customer_name, c.email as customer_email, c.address as customer_address
|
| 611 |
FROM erp_invoices i
|
| 612 |
JOIN erp_orders o ON i.order_id = o.order_id
|
| 613 |
JOIN erp_customers c ON o.customer_id = c.customer_id
|
| 614 |
+
WHERE i.order_id = {param_placeholder};
|
| 615 |
"""
|
| 616 |
cursor.execute(query, [order_id])
|
| 617 |
|
| 618 |
invoice = cursor.fetchone()
|
| 619 |
|
| 620 |
if not invoice:
|
| 621 |
+
return {
|
| 622 |
"success": False,
|
| 623 |
"error": f"Invoice not found for the provided {'invoice_id' if invoice_id else 'order_id'}"
|
| 624 |
+
}
|
| 625 |
|
| 626 |
# Get order items
|
| 627 |
+
items_query = f"""
|
| 628 |
SELECT oi.*, p.product_name, p.sku
|
| 629 |
FROM erp_order_items oi
|
| 630 |
JOIN erp_products p ON oi.product_id = p.product_id
|
| 631 |
+
WHERE oi.order_id = {param_placeholder};
|
| 632 |
"""
|
| 633 |
cursor.execute(items_query, [invoice['order_id']])
|
| 634 |
items = cursor.fetchall()
|
| 635 |
|
| 636 |
+
invoice_dict = invoice
|
| 637 |
+
items_list = items
|
| 638 |
+
|
| 639 |
+
# Format dates for response
|
| 640 |
+
order_date = invoice_dict.get('order_date')
|
| 641 |
+
if order_date:
|
| 642 |
+
if isinstance(order_date, str):
|
| 643 |
+
order_date_str = order_date
|
| 644 |
+
else:
|
| 645 |
+
order_date_str = order_date.strftime("%Y-%m-%d") if hasattr(order_date, 'strftime') else str(order_date)
|
| 646 |
+
else:
|
| 647 |
+
order_date_str = None
|
| 648 |
+
|
| 649 |
+
due_date = invoice_dict.get('due_date')
|
| 650 |
+
if due_date:
|
| 651 |
+
if isinstance(due_date, str):
|
| 652 |
+
due_date_str = due_date
|
| 653 |
+
else:
|
| 654 |
+
due_date_str = due_date.strftime("%Y-%m-%d") if hasattr(due_date, 'strftime') else str(due_date)
|
| 655 |
+
else:
|
| 656 |
+
due_date_str = None
|
| 657 |
|
| 658 |
# Return JSON formatted response
|
| 659 |
+
return {
|
| 660 |
"success": True,
|
| 661 |
"invoice": {
|
| 662 |
"invoice_id": invoice_dict['invoice_id'],
|
| 663 |
"invoice_number": invoice_dict.get('invoice_number', ''),
|
| 664 |
"order_id": invoice_dict['order_id'],
|
| 665 |
+
"order_date": order_date_str,
|
| 666 |
"order_status": invoice_dict['order_status'],
|
| 667 |
"amount": float(invoice_dict['amount']),
|
| 668 |
+
"due_date": due_date_str,
|
| 669 |
"payment_status": "Paid" if invoice_dict['is_paid'] else "Unpaid",
|
| 670 |
"customer": {
|
| 671 |
"name": invoice_dict['customer_name'],
|
|
|
|
| 674 |
},
|
| 675 |
"items": items_list
|
| 676 |
}
|
| 677 |
+
}
|
| 678 |
|
| 679 |
except Exception as e:
|
| 680 |
+
return {
|
| 681 |
"success": False,
|
| 682 |
"error": str(e)
|
| 683 |
+
}
|
| 684 |
finally:
|
| 685 |
if 'conn' in locals():
|
| 686 |
conn.close()
|
| 687 |
|
| 688 |
+
@mcp.tool()
|
| 689 |
+
async def reset_database() -> Dict[str, Any]:
|
| 690 |
+
"""
|
| 691 |
+
Reset the ERP database by deleting it and recreating it with fresh data.
|
| 692 |
+
|
| 693 |
+
Returns:
|
| 694 |
+
dict: Result of the database reset operation
|
| 695 |
+
"""
|
| 696 |
+
try:
|
| 697 |
+
# Get SQLite database path from environment or use default
|
| 698 |
+
db_path = os.getenv("SQLITE_DB_PATH", "./data/erp_db.sqlite")
|
| 699 |
+
|
| 700 |
+
# Reset the database
|
| 701 |
+
result = reset_erp_db(db_path)
|
| 702 |
+
|
| 703 |
+
if result:
|
| 704 |
+
return {
|
| 705 |
+
"success": True,
|
| 706 |
+
"message": "Database reset successfully. All tables have been recreated with fresh sample data."
|
| 707 |
+
}
|
| 708 |
+
else:
|
| 709 |
+
return {
|
| 710 |
+
"success": False,
|
| 711 |
+
"error": "Failed to reset database. Check server logs for details."
|
| 712 |
+
}
|
| 713 |
+
except Exception as e:
|
| 714 |
+
return {
|
| 715 |
+
"success": False,
|
| 716 |
+
"error": f"Error resetting database: {str(e)}"
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
# Create Gradio interfaces for each function
|
| 720 |
execute_query_interface = gr.Interface(
|
| 721 |
fn=execute_query,
|
|
|
|
| 782 |
description="Get invoice details by invoice ID or order ID"
|
| 783 |
)
|
| 784 |
|
| 785 |
+
reset_database_interface = gr.Interface(
|
| 786 |
+
fn=reset_database,
|
| 787 |
+
inputs=[],
|
| 788 |
+
outputs=gr.Textbox(lines=5),
|
| 789 |
+
title="Reset Database",
|
| 790 |
+
description="Reset the ERP database by deleting it and recreating it with fresh data"
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
# Create a Gradio TabItem for each interface
|
| 794 |
demo = gr.TabbedInterface(
|
| 795 |
[
|
|
|
|
| 798 |
order_status_interface,
|
| 799 |
place_order_interface,
|
| 800 |
cancel_order_interface,
|
| 801 |
+
invoice_details_interface,
|
| 802 |
+
reset_database_interface
|
| 803 |
],
|
| 804 |
[
|
| 805 |
"Execute Query",
|
|
|
|
| 807 |
"Order Status",
|
| 808 |
"Place Order",
|
| 809 |
"Cancel Order",
|
| 810 |
+
"Invoice Details",
|
| 811 |
+
"Reset Database"
|
| 812 |
],
|
| 813 |
title="ERP System Tools"
|
| 814 |
)
|
data/__init__.py
ADDED
|
File without changes
|
data/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (173 Bytes). View file
|
|
|
data/erp_db.sqlite
ADDED
|
Binary file (98.3 kB). View file
|
|
|
data/utils/__init__.py
ADDED
|
File without changes
|
data/utils/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (179 Bytes). View file
|
|
|
data/utils/__pycache__/erp_db_init.cpython-310.pyc
ADDED
|
Binary file (5.09 kB). View file
|
|
|
data/utils/__pycache__/populate_erp_db.cpython-310.pyc
ADDED
|
Binary file (11.4 kB). View file
|
|
|
data/utils/__pycache__/reset_erp_db.cpython-310.pyc
ADDED
|
Binary file (1.43 kB). View file
|
|
|
data/utils/erp_db_init.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# erp_db_init.py
|
| 2 |
+
import sqlite3
|
| 3 |
+
import os
|
| 4 |
+
import pathlib
|
| 5 |
+
|
| 6 |
+
def init_sqlite_db(db_path='./data/erp_db.sqlite'):
|
| 7 |
+
"""Initialize SQLite database with ERP tables."""
|
| 8 |
+
try:
|
| 9 |
+
# Create the database directory if it doesn't exist
|
| 10 |
+
db_dir = pathlib.Path(os.path.dirname(db_path))
|
| 11 |
+
db_dir.mkdir(exist_ok=True)
|
| 12 |
+
|
| 13 |
+
# Connect to SQLite database (will be created if it doesn't exist)
|
| 14 |
+
conn = sqlite3.connect(db_path)
|
| 15 |
+
cursor = conn.cursor()
|
| 16 |
+
|
| 17 |
+
# Enable foreign keys
|
| 18 |
+
conn.execute("PRAGMA foreign_keys = ON")
|
| 19 |
+
|
| 20 |
+
# Create customers table
|
| 21 |
+
cursor.execute('''
|
| 22 |
+
CREATE TABLE IF NOT EXISTS erp_customers (
|
| 23 |
+
customer_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 24 |
+
name TEXT NOT NULL,
|
| 25 |
+
email TEXT NOT NULL UNIQUE,
|
| 26 |
+
phone TEXT,
|
| 27 |
+
address TEXT,
|
| 28 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 29 |
+
)
|
| 30 |
+
''')
|
| 31 |
+
|
| 32 |
+
# Create products table
|
| 33 |
+
cursor.execute('''
|
| 34 |
+
CREATE TABLE IF NOT EXISTS erp_products (
|
| 35 |
+
product_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 36 |
+
product_name TEXT NOT NULL,
|
| 37 |
+
description TEXT,
|
| 38 |
+
category TEXT,
|
| 39 |
+
price REAL NOT NULL,
|
| 40 |
+
stock_quantity INTEGER NOT NULL DEFAULT 0,
|
| 41 |
+
sku TEXT UNIQUE
|
| 42 |
+
)
|
| 43 |
+
''')
|
| 44 |
+
|
| 45 |
+
# Create orders table
|
| 46 |
+
cursor.execute('''
|
| 47 |
+
CREATE TABLE IF NOT EXISTS erp_orders (
|
| 48 |
+
order_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 49 |
+
customer_id INTEGER,
|
| 50 |
+
order_date DATE DEFAULT CURRENT_DATE,
|
| 51 |
+
total_amount REAL NOT NULL,
|
| 52 |
+
status TEXT NOT NULL DEFAULT 'Processing',
|
| 53 |
+
previous_order_id INTEGER,
|
| 54 |
+
estimated_delivery DATE,
|
| 55 |
+
actual_delivery DATE,
|
| 56 |
+
payment_status TEXT DEFAULT 'Pending',
|
| 57 |
+
shipping_address TEXT NOT NULL,
|
| 58 |
+
shipping_country TEXT,
|
| 59 |
+
destination_country TEXT,
|
| 60 |
+
FOREIGN KEY (customer_id) REFERENCES erp_customers (customer_id),
|
| 61 |
+
FOREIGN KEY (previous_order_id) REFERENCES erp_orders (order_id)
|
| 62 |
+
)
|
| 63 |
+
''')
|
| 64 |
+
|
| 65 |
+
# Create order items table
|
| 66 |
+
cursor.execute('''
|
| 67 |
+
CREATE TABLE IF NOT EXISTS erp_order_items (
|
| 68 |
+
order_item_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 69 |
+
order_id INTEGER,
|
| 70 |
+
product_id INTEGER,
|
| 71 |
+
quantity INTEGER NOT NULL,
|
| 72 |
+
unit_price REAL NOT NULL,
|
| 73 |
+
subtotal REAL NOT NULL,
|
| 74 |
+
FOREIGN KEY (order_id) REFERENCES erp_orders (order_id),
|
| 75 |
+
FOREIGN KEY (product_id) REFERENCES erp_products (product_id)
|
| 76 |
+
)
|
| 77 |
+
''')
|
| 78 |
+
|
| 79 |
+
# Create order history table
|
| 80 |
+
cursor.execute('''
|
| 81 |
+
CREATE TABLE IF NOT EXISTS erp_order_history (
|
| 82 |
+
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 83 |
+
order_id INTEGER,
|
| 84 |
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 85 |
+
status_change TEXT,
|
| 86 |
+
notes TEXT,
|
| 87 |
+
updated_by TEXT,
|
| 88 |
+
FOREIGN KEY (order_id) REFERENCES erp_orders (order_id)
|
| 89 |
+
)
|
| 90 |
+
''')
|
| 91 |
+
|
| 92 |
+
# Create invoices table
|
| 93 |
+
cursor.execute('''
|
| 94 |
+
CREATE TABLE IF NOT EXISTS erp_invoices (
|
| 95 |
+
invoice_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 96 |
+
order_id INTEGER,
|
| 97 |
+
invoice_date DATE DEFAULT CURRENT_DATE,
|
| 98 |
+
amount REAL NOT NULL,
|
| 99 |
+
payment_terms TEXT,
|
| 100 |
+
due_date DATE,
|
| 101 |
+
is_paid BOOLEAN DEFAULT 0,
|
| 102 |
+
invoice_number TEXT UNIQUE,
|
| 103 |
+
FOREIGN KEY (order_id) REFERENCES erp_orders (order_id)
|
| 104 |
+
)
|
| 105 |
+
''')
|
| 106 |
+
|
| 107 |
+
# Create global disruptions table
|
| 108 |
+
cursor.execute('''
|
| 109 |
+
CREATE TABLE IF NOT EXISTS live_global_disruptions (
|
| 110 |
+
disruption_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 111 |
+
source_country TEXT NOT NULL,
|
| 112 |
+
destination_country TEXT NOT NULL,
|
| 113 |
+
disruption_type TEXT NOT NULL,
|
| 114 |
+
severity INTEGER NOT NULL CHECK (severity >= 1 AND severity <= 5),
|
| 115 |
+
start_date DATE NOT NULL,
|
| 116 |
+
expected_end_date DATE,
|
| 117 |
+
actual_end_date DATE,
|
| 118 |
+
is_active BOOLEAN DEFAULT 1,
|
| 119 |
+
description TEXT,
|
| 120 |
+
impact_hours INTEGER,
|
| 121 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 122 |
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 123 |
+
UNIQUE (source_country, destination_country, disruption_type, start_date)
|
| 124 |
+
)
|
| 125 |
+
''')
|
| 126 |
+
|
| 127 |
+
# Create indexes
|
| 128 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_orders_customer ON erp_orders (customer_id)')
|
| 129 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_items_order ON erp_order_items (order_id)')
|
| 130 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_items_product ON erp_order_items (product_id)')
|
| 131 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_order_history_order ON erp_order_history (order_id)')
|
| 132 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_invoices_order ON erp_invoices (order_id)')
|
| 133 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_global_disruptions_active ON live_global_disruptions (is_active)')
|
| 134 |
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_global_disruptions_countries ON live_global_disruptions (source_country, destination_country)')
|
| 135 |
+
|
| 136 |
+
conn.commit()
|
| 137 |
+
conn.close()
|
| 138 |
+
|
| 139 |
+
return True
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"Error initializing SQLite database: {str(e)}")
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
init_sqlite_db()
|
data/utils/populate_erp_db.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# populate_erp_db.py
|
| 2 |
+
import sqlite3
|
| 3 |
+
import os
|
| 4 |
+
import pathlib
|
| 5 |
+
import datetime
|
| 6 |
+
import random
|
| 7 |
+
from data.utils.erp_db_init import init_sqlite_db
|
| 8 |
+
|
| 9 |
+
def populate_erp_db(db_path='./data/erp_db.sqlite'):
|
| 10 |
+
"""Populate SQLite database with sample ERP data."""
|
| 11 |
+
try:
|
| 12 |
+
# Ensure database exists
|
| 13 |
+
if not os.path.exists(db_path):
|
| 14 |
+
init_sqlite_db(db_path)
|
| 15 |
+
|
| 16 |
+
# Connect to SQLite database
|
| 17 |
+
conn = sqlite3.connect(db_path)
|
| 18 |
+
cursor = conn.cursor()
|
| 19 |
+
|
| 20 |
+
# Sample data for customers
|
| 21 |
+
customers = [
|
| 22 |
+
('John Doe', 'john.doe@example.com', '555-123-4567', '123 Main St, Anytown, USA'),
|
| 23 |
+
('Jane Smith', 'jane.smith@example.com', '555-234-5678', '456 Oak Ave, Somewhere, USA'),
|
| 24 |
+
('Robert Johnson', 'robert.j@example.com', '555-345-6789', '789 Pine Rd, Nowhere, USA'),
|
| 25 |
+
('Emily Davis', 'emily.davis@example.com', '555-456-7890', '101 Maple Dr, Anywhere, USA'),
|
| 26 |
+
('Michael Wilson', 'michael.w@example.com', '555-567-8901', '202 Cedar Ln, Everywhere, USA'),
|
| 27 |
+
('Sarah Brown', 'sarah.b@example.com', '555-678-9012', '303 Birch Blvd, Somewhere, USA'),
|
| 28 |
+
('David Miller', 'david.m@example.com', '555-789-0123', '404 Elm St, Anytown, USA'),
|
| 29 |
+
('Jennifer Taylor', 'jennifer.t@example.com', '555-890-1234', '505 Walnut Ave, Nowhere, USA'),
|
| 30 |
+
('Christopher Anderson', 'chris.a@example.com', '555-901-2345', '606 Spruce Rd, Anywhere, USA'),
|
| 31 |
+
('Lisa Thomas', 'lisa.t@example.com', '555-012-3456', '707 Fir Dr, Everywhere, USA'),
|
| 32 |
+
('Daniel Jackson', 'daniel.j@example.com', '555-123-7890', '808 Pine St, Somewhere, USA'),
|
| 33 |
+
('Michelle White', 'michelle.w@example.com', '555-234-8901', '909 Oak Rd, Anytown, USA')
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
# Sample data for products
|
| 37 |
+
products = [
|
| 38 |
+
('Laptop Pro', 'High-performance laptop for professionals', 'Electronics', 1299.99, 50, 'LP-001'),
|
| 39 |
+
('Smartphone X', 'Latest smartphone with advanced features', 'Electronics', 899.99, 100, 'SP-001'),
|
| 40 |
+
('Office Chair', 'Ergonomic office chair', 'Furniture', 199.99, 30, 'OC-001'),
|
| 41 |
+
('Desk Lamp', 'LED desk lamp with adjustable brightness', 'Home', 49.99, 75, 'DL-001'),
|
| 42 |
+
('Coffee Maker', 'Programmable coffee maker', 'Appliances', 89.99, 40, 'CM-001'),
|
| 43 |
+
('Wireless Headphones', 'Noise-cancelling wireless headphones', 'Electronics', 149.99, 60, 'WH-001'),
|
| 44 |
+
('Tablet Mini', 'Compact tablet for on-the-go use', 'Electronics', 399.99, 45, 'TM-001'),
|
| 45 |
+
('External Hard Drive', '2TB external hard drive', 'Electronics', 129.99, 55, 'EH-001'),
|
| 46 |
+
('Wireless Mouse', 'Ergonomic wireless mouse', 'Electronics', 29.99, 80, 'WM-001'),
|
| 47 |
+
('Bluetooth Speaker', 'Portable Bluetooth speaker', 'Electronics', 79.99, 65, 'BS-001'),
|
| 48 |
+
('Monitor 27"', '27-inch 4K monitor', 'Electronics', 349.99, 35, 'MN-001'),
|
| 49 |
+
('Keyboard', 'Mechanical gaming keyboard', 'Electronics', 119.99, 70, 'KB-001'),
|
| 50 |
+
('Desk', 'Adjustable standing desk', 'Furniture', 299.99, 25, 'DK-001'),
|
| 51 |
+
('Bookshelf', 'Modern 5-tier bookshelf', 'Furniture', 149.99, 20, 'BS-002')
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
# Insert customers
|
| 55 |
+
cursor.executemany('''
|
| 56 |
+
INSERT INTO erp_customers (name, email, phone, address)
|
| 57 |
+
VALUES (?, ?, ?, ?)
|
| 58 |
+
''', customers)
|
| 59 |
+
|
| 60 |
+
# Insert products
|
| 61 |
+
cursor.executemany('''
|
| 62 |
+
INSERT INTO erp_products (product_name, description, category, price, stock_quantity, sku)
|
| 63 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
| 64 |
+
''', products)
|
| 65 |
+
|
| 66 |
+
# Get customer IDs for reference
|
| 67 |
+
cursor.execute('SELECT customer_id FROM erp_customers')
|
| 68 |
+
customer_ids = [row[0] for row in cursor.fetchall()]
|
| 69 |
+
|
| 70 |
+
# Get product IDs for reference
|
| 71 |
+
cursor.execute('SELECT product_id, price FROM erp_products')
|
| 72 |
+
product_data = cursor.fetchall()
|
| 73 |
+
product_ids = [row[0] for row in product_data]
|
| 74 |
+
product_prices = {row[0]: row[1] for row in product_data}
|
| 75 |
+
|
| 76 |
+
# Sample data for orders
|
| 77 |
+
orders = []
|
| 78 |
+
order_items = []
|
| 79 |
+
invoices = []
|
| 80 |
+
order_history = []
|
| 81 |
+
|
| 82 |
+
# Generate 15 orders
|
| 83 |
+
for i in range(1, 16):
|
| 84 |
+
# Randomly select a customer
|
| 85 |
+
customer_id = random.choice(customer_ids)
|
| 86 |
+
|
| 87 |
+
# Get customer address for shipping
|
| 88 |
+
cursor.execute('SELECT address FROM erp_customers WHERE customer_id = ?', (customer_id,))
|
| 89 |
+
address = cursor.fetchone()[0]
|
| 90 |
+
|
| 91 |
+
# Generate order date (within the last 60 days)
|
| 92 |
+
days_ago = random.randint(1, 60)
|
| 93 |
+
order_date = (datetime.datetime.now() - datetime.timedelta(days=days_ago)).strftime('%Y-%m-%d')
|
| 94 |
+
|
| 95 |
+
# Generate estimated delivery (3-10 days after order)
|
| 96 |
+
est_delivery = (datetime.datetime.now() - datetime.timedelta(days=days_ago) +
|
| 97 |
+
datetime.timedelta(days=random.randint(3, 10))).strftime('%Y-%m-%d')
|
| 98 |
+
|
| 99 |
+
# Determine if order has been delivered
|
| 100 |
+
delivered = random.random() > 0.3 # 70% chance of being delivered
|
| 101 |
+
actual_delivery = None
|
| 102 |
+
if delivered:
|
| 103 |
+
# Delivery occurred 0-2 days after estimated delivery
|
| 104 |
+
delivery_offset = random.randint(-1, 2) # Can be early or late
|
| 105 |
+
actual_delivery = (datetime.datetime.now() - datetime.timedelta(days=days_ago) +
|
| 106 |
+
datetime.timedelta(days=random.randint(3, 10) + delivery_offset)).strftime('%Y-%m-%d')
|
| 107 |
+
|
| 108 |
+
# Determine order status
|
| 109 |
+
if days_ago <= 1:
|
| 110 |
+
status = 'Processing'
|
| 111 |
+
elif days_ago <= 3:
|
| 112 |
+
status = 'Shipped'
|
| 113 |
+
elif delivered:
|
| 114 |
+
status = 'Delivered'
|
| 115 |
+
else:
|
| 116 |
+
status = random.choice(['Processing', 'Shipped', 'In Transit'])
|
| 117 |
+
|
| 118 |
+
# Determine payment status
|
| 119 |
+
payment_status = random.choice(['Paid', 'Pending', 'Paid', 'Paid']) # 75% chance of being paid
|
| 120 |
+
|
| 121 |
+
# Generate shipping and destination countries
|
| 122 |
+
shipping_country = random.choice([
|
| 123 |
+
'USA', 'Canada', 'UK', 'Germany', 'France', 'Australia',
|
| 124 |
+
'China', 'Japan', 'India', 'Brazil', 'Mexico', 'South Africa',
|
| 125 |
+
'Italy', 'Spain', 'Russia', 'South Korea', 'Singapore', 'UAE',
|
| 126 |
+
'Netherlands', 'Sweden'
|
| 127 |
+
])
|
| 128 |
+
destination_country = shipping_country # Usually the same
|
| 129 |
+
|
| 130 |
+
# Previous order (for some customers)
|
| 131 |
+
previous_order_id = None
|
| 132 |
+
if i > 5 and random.random() > 0.7: # 30% chance of having a previous order
|
| 133 |
+
previous_order_id = random.randint(1, i-1)
|
| 134 |
+
|
| 135 |
+
# Add to orders list
|
| 136 |
+
orders.append((
|
| 137 |
+
customer_id, order_date, 0, # Total amount will be updated later
|
| 138 |
+
status, previous_order_id, est_delivery, actual_delivery,
|
| 139 |
+
payment_status, address, shipping_country, destination_country
|
| 140 |
+
))
|
| 141 |
+
|
| 142 |
+
# Insert orders
|
| 143 |
+
cursor.executemany('''
|
| 144 |
+
INSERT INTO erp_orders (
|
| 145 |
+
customer_id, order_date, total_amount, status, previous_order_id,
|
| 146 |
+
estimated_delivery, actual_delivery, payment_status, shipping_address,
|
| 147 |
+
shipping_country, destination_country
|
| 148 |
+
)
|
| 149 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 150 |
+
''', orders)
|
| 151 |
+
|
| 152 |
+
# Get order IDs
|
| 153 |
+
cursor.execute('SELECT order_id FROM erp_orders')
|
| 154 |
+
order_ids = [row[0] for row in cursor.fetchall()]
|
| 155 |
+
|
| 156 |
+
# Generate order items for each order
|
| 157 |
+
for order_id in order_ids:
|
| 158 |
+
# Each order has 1-5 items
|
| 159 |
+
num_items = random.randint(1, 5)
|
| 160 |
+
order_total = 0
|
| 161 |
+
|
| 162 |
+
# Select random products for this order
|
| 163 |
+
selected_products = random.sample(product_ids, num_items)
|
| 164 |
+
|
| 165 |
+
for product_id in selected_products:
|
| 166 |
+
quantity = random.randint(1, 3)
|
| 167 |
+
unit_price = product_prices[product_id]
|
| 168 |
+
subtotal = quantity * unit_price
|
| 169 |
+
order_total += subtotal
|
| 170 |
+
|
| 171 |
+
# Add to order items list
|
| 172 |
+
order_items.append((order_id, product_id, quantity, unit_price, subtotal))
|
| 173 |
+
|
| 174 |
+
# Update order total
|
| 175 |
+
cursor.execute('UPDATE erp_orders SET total_amount = ? WHERE order_id = ?', (order_total, order_id))
|
| 176 |
+
|
| 177 |
+
# Generate invoice for paid orders
|
| 178 |
+
cursor.execute('SELECT payment_status FROM erp_orders WHERE order_id = ?', (order_id,))
|
| 179 |
+
payment_status = cursor.fetchone()[0]
|
| 180 |
+
|
| 181 |
+
if payment_status == 'Paid':
|
| 182 |
+
invoice_date = (datetime.datetime.now() - datetime.timedelta(days=random.randint(1, 30))).strftime('%Y-%m-%d')
|
| 183 |
+
due_date = (datetime.datetime.now() - datetime.timedelta(days=random.randint(1, 15))).strftime('%Y-%m-%d')
|
| 184 |
+
invoice_number = f'INV-{order_id}-{random.randint(1000, 9999)}'
|
| 185 |
+
|
| 186 |
+
invoices.append((order_id, invoice_date, order_total, 'Net 30', due_date, 1, invoice_number))
|
| 187 |
+
|
| 188 |
+
# Generate order history entries
|
| 189 |
+
# Initial status
|
| 190 |
+
order_history.append((
|
| 191 |
+
order_id,
|
| 192 |
+
(datetime.datetime.now() - datetime.timedelta(days=random.randint(50, 60))).strftime('%Y-%m-%d %H:%M:%S'),
|
| 193 |
+
'Order Created',
|
| 194 |
+
'New order placed',
|
| 195 |
+
'System'
|
| 196 |
+
))
|
| 197 |
+
|
| 198 |
+
# Processing status
|
| 199 |
+
order_history.append((
|
| 200 |
+
order_id,
|
| 201 |
+
(datetime.datetime.now() - datetime.timedelta(days=random.randint(40, 49))).strftime('%Y-%m-%d %H:%M:%S'),
|
| 202 |
+
'Processing',
|
| 203 |
+
'Order is being processed',
|
| 204 |
+
'System'
|
| 205 |
+
))
|
| 206 |
+
|
| 207 |
+
# Additional statuses based on current order status
|
| 208 |
+
cursor.execute('SELECT status FROM erp_orders WHERE order_id = ?', (order_id,))
|
| 209 |
+
current_status = cursor.fetchone()[0]
|
| 210 |
+
|
| 211 |
+
if current_status in ['Shipped', 'In Transit', 'Delivered']:
|
| 212 |
+
order_history.append((
|
| 213 |
+
order_id,
|
| 214 |
+
(datetime.datetime.now() - datetime.timedelta(days=random.randint(30, 39))).strftime('%Y-%m-%d %H:%M:%S'),
|
| 215 |
+
'Shipped',
|
| 216 |
+
'Order has been shipped',
|
| 217 |
+
'Shipping Dept'
|
| 218 |
+
))
|
| 219 |
+
|
| 220 |
+
if current_status in ['In Transit', 'Delivered']:
|
| 221 |
+
order_history.append((
|
| 222 |
+
order_id,
|
| 223 |
+
(datetime.datetime.now() - datetime.timedelta(days=random.randint(20, 29))).strftime('%Y-%m-%d %H:%M:%S'),
|
| 224 |
+
'In Transit',
|
| 225 |
+
'Order is in transit',
|
| 226 |
+
'Shipping Carrier'
|
| 227 |
+
))
|
| 228 |
+
|
| 229 |
+
if current_status == 'Delivered':
|
| 230 |
+
order_history.append((
|
| 231 |
+
order_id,
|
| 232 |
+
(datetime.datetime.now() - datetime.timedelta(days=random.randint(1, 19))).strftime('%Y-%m-%d %H:%M:%S'),
|
| 233 |
+
'Delivered',
|
| 234 |
+
'Order has been delivered',
|
| 235 |
+
'Shipping Carrier'
|
| 236 |
+
))
|
| 237 |
+
|
| 238 |
+
# Insert order items
|
| 239 |
+
cursor.executemany('''
|
| 240 |
+
INSERT INTO erp_order_items (order_id, product_id, quantity, unit_price, subtotal)
|
| 241 |
+
VALUES (?, ?, ?, ?, ?)
|
| 242 |
+
''', order_items)
|
| 243 |
+
|
| 244 |
+
# Insert invoices
|
| 245 |
+
cursor.executemany('''
|
| 246 |
+
INSERT INTO erp_invoices (order_id, invoice_date, amount, payment_terms, due_date, is_paid, invoice_number)
|
| 247 |
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
| 248 |
+
''', invoices)
|
| 249 |
+
|
| 250 |
+
# Insert order history
|
| 251 |
+
cursor.executemany('''
|
| 252 |
+
INSERT INTO erp_order_history (order_id, timestamp, status_change, notes, updated_by)
|
| 253 |
+
VALUES (?, ?, ?, ?, ?)
|
| 254 |
+
''', order_history)
|
| 255 |
+
|
| 256 |
+
# Sample data for global disruptions
|
| 257 |
+
disruption_types = [
|
| 258 |
+
'Natural Disaster', 'Political Unrest', 'Labor Strike',
|
| 259 |
+
'Transportation Issue', 'Customs Delay', 'Weather Event',
|
| 260 |
+
'Port Congestion', 'Regulatory Change', 'Supply Shortage',
|
| 261 |
+
'Infrastructure Failure', 'Security Threat', 'Health Crisis'
|
| 262 |
+
]
|
| 263 |
+
|
| 264 |
+
countries = [
|
| 265 |
+
'USA', 'Canada', 'UK', 'Germany', 'France', 'Australia',
|
| 266 |
+
'China', 'Japan', 'India', 'Brazil', 'Mexico', 'South Africa',
|
| 267 |
+
'Italy', 'Spain', 'Russia', 'South Korea', 'Singapore', 'UAE',
|
| 268 |
+
'Netherlands', 'Sweden'
|
| 269 |
+
]
|
| 270 |
+
|
| 271 |
+
global_disruptions = []
|
| 272 |
+
|
| 273 |
+
# Generate 15 global disruptions
|
| 274 |
+
for i in range(1, 16):
|
| 275 |
+
# Select random countries for source and destination
|
| 276 |
+
source_country = random.choice(countries)
|
| 277 |
+
# Ensure destination is different from source
|
| 278 |
+
destination_options = [c for c in countries if c != source_country]
|
| 279 |
+
destination_country = random.choice(destination_options)
|
| 280 |
+
|
| 281 |
+
# Select random disruption type
|
| 282 |
+
disruption_type = random.choice(disruption_types)
|
| 283 |
+
|
| 284 |
+
# Generate severity (1-5)
|
| 285 |
+
severity = random.randint(1, 5)
|
| 286 |
+
|
| 287 |
+
# Generate start date (within the last 90 days)
|
| 288 |
+
days_ago = random.randint(1, 90)
|
| 289 |
+
start_date = (datetime.datetime.now() - datetime.timedelta(days=days_ago)).strftime('%Y-%m-%d')
|
| 290 |
+
|
| 291 |
+
# Generate expected end date (1-30 days after start)
|
| 292 |
+
expected_days_duration = random.randint(1, 30)
|
| 293 |
+
expected_end_date = (datetime.datetime.now() - datetime.timedelta(days=days_ago) +
|
| 294 |
+
datetime.timedelta(days=expected_days_duration)).strftime('%Y-%m-%d')
|
| 295 |
+
|
| 296 |
+
# Determine if disruption has ended
|
| 297 |
+
is_ended = random.random() > 0.6 # 40% chance of being ended
|
| 298 |
+
actual_end_date = None
|
| 299 |
+
if is_ended:
|
| 300 |
+
# Actual end occurred 0-5 days after/before expected end
|
| 301 |
+
end_offset = random.randint(-5, 5)
|
| 302 |
+
actual_end_date = (datetime.datetime.now() - datetime.timedelta(days=days_ago) +
|
| 303 |
+
datetime.timedelta(days=expected_days_duration + end_offset)).strftime('%Y-%m-%d')
|
| 304 |
+
|
| 305 |
+
# Determine if disruption is active
|
| 306 |
+
is_active = not is_ended
|
| 307 |
+
|
| 308 |
+
# Generate impact hours based on severity
|
| 309 |
+
impact_hours = severity * random.randint(10, 48)
|
| 310 |
+
|
| 311 |
+
# Generate description
|
| 312 |
+
descriptions = {
|
| 313 |
+
'Natural Disaster': [
|
| 314 |
+
f"Severe flooding in {source_country} affecting shipments to {destination_country}",
|
| 315 |
+
f"Earthquake in {source_country} disrupting supply chain to {destination_country}",
|
| 316 |
+
f"Hurricane impacting shipping routes between {source_country} and {destination_country}"
|
| 317 |
+
],
|
| 318 |
+
'Political Unrest': [
|
| 319 |
+
f"Protests in {source_country} affecting exports to {destination_country}",
|
| 320 |
+
f"Trade dispute between {source_country} and {destination_country}",
|
| 321 |
+
f"Political tensions causing delays in shipments from {source_country} to {destination_country}"
|
| 322 |
+
],
|
| 323 |
+
'Labor Strike': [
|
| 324 |
+
f"Port workers strike in {source_country} affecting shipments to {destination_country}",
|
| 325 |
+
f"Transportation union strike impacting deliveries between {source_country} and {destination_country}",
|
| 326 |
+
f"Warehouse workers strike in {source_country} delaying orders to {destination_country}"
|
| 327 |
+
],
|
| 328 |
+
'Transportation Issue': [
|
| 329 |
+
f"Major highway closure between {source_country} and {destination_country}",
|
| 330 |
+
f"Shipping container shortage affecting routes from {source_country} to {destination_country}",
|
| 331 |
+
f"Fuel shortage in {source_country} impacting deliveries to {destination_country}"
|
| 332 |
+
],
|
| 333 |
+
'Customs Delay': [
|
| 334 |
+
f"New customs regulations in {destination_country} causing delays from {source_country}",
|
| 335 |
+
f"Increased inspection rates at {destination_country} border for goods from {source_country}",
|
| 336 |
+
f"Documentation issues for shipments from {source_country} to {destination_country}"
|
| 337 |
+
],
|
| 338 |
+
'Weather Event': [
|
| 339 |
+
f"Severe snowstorm in {source_country} delaying shipments to {destination_country}",
|
| 340 |
+
f"Fog at major ports in {source_country} affecting vessels bound for {destination_country}",
|
| 341 |
+
f"Extreme heat causing transportation issues between {source_country} and {destination_country}"
|
| 342 |
+
],
|
| 343 |
+
'Port Congestion': [
|
| 344 |
+
f"Backlog at {source_country} ports affecting shipments to {destination_country}",
|
| 345 |
+
f"Vessel scheduling issues at {source_country} ports for routes to {destination_country}",
|
| 346 |
+
f"Limited berthing capacity at {destination_country} ports for vessels from {source_country}"
|
| 347 |
+
],
|
| 348 |
+
'Regulatory Change': [
|
| 349 |
+
f"New import regulations in {destination_country} affecting goods from {source_country}",
|
| 350 |
+
f"Export restrictions in {source_country} for shipments to {destination_country}",
|
| 351 |
+
f"Changed documentation requirements between {source_country} and {destination_country}"
|
| 352 |
+
],
|
| 353 |
+
'Supply Shortage': [
|
| 354 |
+
f"Raw material shortage in {source_country} affecting production for {destination_country}",
|
| 355 |
+
f"Component shortage impacting products shipped from {source_country} to {destination_country}",
|
| 356 |
+
f"Limited availability of goods in {source_country} for export to {destination_country}"
|
| 357 |
+
],
|
| 358 |
+
'Infrastructure Failure': [
|
| 359 |
+
f"Bridge collapse on major route between {source_country} and {destination_country}",
|
| 360 |
+
f"Power outage in {source_country} affecting production for {destination_country}",
|
| 361 |
+
f"IT system failure impacting customs processing between {source_country} and {destination_country}"
|
| 362 |
+
],
|
| 363 |
+
'Security Threat': [
|
| 364 |
+
f"Increased piracy risk on shipping routes from {source_country} to {destination_country}",
|
| 365 |
+
f"Security concerns at {source_country} borders affecting shipments to {destination_country}",
|
| 366 |
+
f"Cybersecurity incident affecting logistics between {source_country} and {destination_country}"
|
| 367 |
+
],
|
| 368 |
+
'Health Crisis': [
|
| 369 |
+
f"Disease outbreak in {source_country} affecting workforce for exports to {destination_country}",
|
| 370 |
+
f"Quarantine requirements delaying shipments from {source_country} to {destination_country}",
|
| 371 |
+
f"Health screening causing delays at {destination_country} border for goods from {source_country}"
|
| 372 |
+
]
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
description = random.choice(descriptions.get(disruption_type, [f"{disruption_type} affecting shipments from {source_country} to {destination_country}"]))
|
| 376 |
+
|
| 377 |
+
# Add to global disruptions list
|
| 378 |
+
global_disruptions.append((
|
| 379 |
+
source_country, destination_country, disruption_type, severity,
|
| 380 |
+
start_date, expected_end_date, actual_end_date, is_active,
|
| 381 |
+
description, impact_hours
|
| 382 |
+
))
|
| 383 |
+
|
| 384 |
+
# Insert global disruptions
|
| 385 |
+
cursor.executemany('''
|
| 386 |
+
INSERT INTO live_global_disruptions (
|
| 387 |
+
source_country, destination_country, disruption_type, severity,
|
| 388 |
+
start_date, expected_end_date, actual_end_date, is_active,
|
| 389 |
+
description, impact_hours
|
| 390 |
+
)
|
| 391 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 392 |
+
''', global_disruptions)
|
| 393 |
+
|
| 394 |
+
# Commit changes and close connection
|
| 395 |
+
conn.commit()
|
| 396 |
+
conn.close()
|
| 397 |
+
|
| 398 |
+
print(f"Successfully populated ERP database with sample data.")
|
| 399 |
+
|
| 400 |
+
# Print summary of inserted data
|
| 401 |
+
print(f"Inserted {len(customers)} customers")
|
| 402 |
+
print(f"Inserted {len(products)} products")
|
| 403 |
+
print(f"Inserted {len(orders)} orders")
|
| 404 |
+
print(f"Inserted {len(order_items)} order items")
|
| 405 |
+
print(f"Inserted {len(invoices)} invoices")
|
| 406 |
+
print(f"Inserted {len(order_history)} order history entries")
|
| 407 |
+
print(f"Inserted {len(global_disruptions)} global disruptions")
|
| 408 |
+
|
| 409 |
+
return True
|
| 410 |
+
except Exception as e:
|
| 411 |
+
print(f"Error populating SQLite database: {str(e)}")
|
| 412 |
+
return False
|
| 413 |
+
|
| 414 |
+
if __name__ == "__main__":
|
| 415 |
+
populate_erp_db()
|
data/utils/reset_erp_db.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# reset_erp_db.py
|
| 2 |
+
import os
|
| 3 |
+
import pathlib
|
| 4 |
+
from data.utils.erp_db_init import init_sqlite_db
|
| 5 |
+
from data.utils.populate_erp_db import populate_erp_db
|
| 6 |
+
|
| 7 |
+
def reset_erp_db(db_path='./data/erp_db.sqlite'):
|
| 8 |
+
"""Reset the ERP database by deleting it and recreating it with fresh data."""
|
| 9 |
+
try:
|
| 10 |
+
print(f"Resetting ERP database at {db_path}...")
|
| 11 |
+
|
| 12 |
+
# Check if database file exists
|
| 13 |
+
if os.path.exists(db_path):
|
| 14 |
+
print(f"Removing existing database file...")
|
| 15 |
+
os.remove(db_path)
|
| 16 |
+
print(f"Database file removed.")
|
| 17 |
+
else:
|
| 18 |
+
print(f"No existing database file found.")
|
| 19 |
+
|
| 20 |
+
# Create database directory if it doesn't exist
|
| 21 |
+
db_dir = pathlib.Path(os.path.dirname(db_path))
|
| 22 |
+
db_dir.mkdir(exist_ok=True)
|
| 23 |
+
|
| 24 |
+
# Initialize the database
|
| 25 |
+
print(f"Initializing new database...")
|
| 26 |
+
init_result = init_sqlite_db(db_path)
|
| 27 |
+
if not init_result:
|
| 28 |
+
print(f"Failed to initialize database.")
|
| 29 |
+
return False
|
| 30 |
+
print(f"Database initialized successfully.")
|
| 31 |
+
|
| 32 |
+
# Populate the database with sample data
|
| 33 |
+
print(f"Populating database with sample data...")
|
| 34 |
+
populate_result = populate_erp_db(db_path)
|
| 35 |
+
if not populate_result:
|
| 36 |
+
print(f"Failed to populate database.")
|
| 37 |
+
return False
|
| 38 |
+
print(f"Database populated successfully.")
|
| 39 |
+
|
| 40 |
+
print(f"Database reset complete.")
|
| 41 |
+
return True
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"Error resetting database: {str(e)}")
|
| 44 |
+
return False
|
| 45 |
+
|
| 46 |
+
if __name__ == "__main__":
|
| 47 |
+
reset_erp_db()
|