Abhishek commited on
Commit
3b382fd
·
1 Parent(s): 64021ce

Added dummy sqlite DB

Browse files
.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
- 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,
@@ -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 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:
@@ -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
- %s, %s, %s, %s, %s
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 - %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'],
@@ -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 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,
@@ -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()