Abhishek commited on
Commit
e086837
·
1 Parent(s): 5369e8c

Added dummy sqlite DB

Browse files
.dummyenv CHANGED
@@ -4,9 +4,19 @@ OPENAI_API_KEY=""
4
  # Weather API Key
5
  WEATHER_API_KEY = ""
6
 
 
 
 
 
7
  # PostgreSQL Database Configuration
8
  POSTGRES_HOST = ""
9
  POSTGRES_DB = ""
10
  POSTGRES_USER = ""
11
  POSTGRES_PASSWORD = ""
12
- POSTGRES_PORT = "5432"
 
 
 
 
 
 
 
4
  # Weather API Key
5
  WEATHER_API_KEY = ""
6
 
7
+ # Database Configuration
8
+ ERP_DB_TYPE = "sqlite" # Options: "sqlite" or "postgres"
9
+ SQLITE_DB_PATH = "./data/erp_db.sqlite"
10
+
11
  # PostgreSQL Database Configuration
12
  POSTGRES_HOST = ""
13
  POSTGRES_DB = ""
14
  POSTGRES_USER = ""
15
  POSTGRES_PASSWORD = ""
16
+ POSTGRES_PORT = "5432"
17
+
18
+ # ERP Postgres DB
19
+ ERP_DB_TYPE = "postgres" # Options: "postgres" or "sqlite"
20
+
21
+ # Disruption DB
22
+ DISRUPTIONS_DB_TYPE = "postgres" # Options: "postgres" or "sqlite"
.gitignore CHANGED
@@ -195,5 +195,4 @@ cython_debug/
195
  .env
196
  .vscode
197
 
198
- data
199
  notebooks
 
195
  .env
196
  .vscode
197
 
 
198
  notebooks
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
README.md CHANGED
@@ -21,6 +21,11 @@ tags:
21
 
22
  **TrackMate AI** is a Gradio-powered autonomous agent designed to interface with an ERP system via MCP tools. It connects to a custom MCP Server and lets users chat with an LLM to place, modify, or track orders — all through natural language.
23
 
 
 
 
 
 
24
  ---
25
 
26
  ## 📺 Demo Video
 
21
 
22
  **TrackMate AI** is a Gradio-powered autonomous agent designed to interface with an ERP system via MCP tools. It connects to a custom MCP Server and lets users chat with an LLM to place, modify, or track orders — all through natural language.
23
 
24
+ Designed for seamless interaction with ERP infrastructure, TrackMate AI empowers users to manage operational workflows via a conversational interface — no dashboards, no dropdowns, just intelligent dialogue.
25
+
26
+ Whether you're tracking an order, generating invoices, or accessing global risk intelligence, TrackMate AI brings an intuitive, natural language layer to your business operations.
27
+
28
+
29
  ---
30
 
31
  ## 📺 Demo Video
app.py CHANGED
@@ -89,49 +89,319 @@ def reset_conversation():
89
 
90
  # ----- Gradio Interface -----
91
  def create_interface():
92
- with gr.Blocks(title="TrackMate AI") as app:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  agent_state = gr.State(value=None)
94
- # Header
 
95
  gr.HTML("""
96
- <div style="text-align: center; padding: 1rem 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 2rem;">
97
- <h1 style="margin: 0; font-size: 2.5rem;">🧠 TrackMate AI</h1>
98
- <p style="margin: 0; opacity: 0.9;">Your smart companion for tracking and managing orders.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  </div>
100
  """)
101
- with gr.Row():
 
 
 
102
  with gr.Column(scale=1):
103
- model_name = gr.Dropdown(
104
- choices=["gpt-4o-mini", "gpt-4o", "gpt-4"],
105
- value="gpt-4o",
106
- label="Model"
107
- )
108
- temperature = gr.Slider(0.0, 1.0, value=0.7, step=0.1, label="Temperature")
109
- max_tokens = gr.Number(value=1000, minimum=100, maximum=4000, step=100, label="Max Tokens")
110
- api_key = gr.Textbox(
111
- value="",
112
- label="API Key",
113
- placeholder="Enter your OpenAI API key",
114
- type="password",
115
- interactive=True
116
- )
117
- with gr.Accordion("System Prompt", open=False):
118
- system_prompt = gr.Textbox(
119
- value=get_default_system_prompt(),
120
- lines=10,
121
- label="System Prompt",
122
- interactive=True
123
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- init_btn = gr.Button("🚀 Initialize Agent")
126
- status_display = gr.Textbox(label="Status", interactive=False)
127
- agent_status = gr.Textbox(label="Agent Status", interactive=False)
 
 
 
 
 
 
 
 
 
 
128
 
 
129
  with gr.Column(scale=3, min_width=700):
130
- chatbot = gr.Chatbot(label="TrackMate AI Chat", height=500)
131
- msg_input = gr.Textbox(label="Message")
132
- send_btn = gr.Button("Send")
133
- reset_btn = gr.Button("🔄 Reset Chat")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
 
135
  init_btn.click(
136
  fn=initialize_agent_handler,
137
  inputs=[model_name, temperature, max_tokens, system_prompt, api_key],
 
89
 
90
  # ----- Gradio Interface -----
91
  def create_interface():
92
+ with gr.Blocks(
93
+ title="TrackMate AI",
94
+ theme=gr.themes.Soft(),
95
+ css="""
96
+ /* Dark theme colors */
97
+ :root {
98
+ --bg-primary: #121212;
99
+ --bg-secondary: #1e1e1e;
100
+ --bg-tertiary: #252525;
101
+ --text-primary: #e0e0e0;
102
+ --text-secondary: #a0a0a0;
103
+ --accent-primary: #3a506b;
104
+ --accent-secondary: #1c2541;
105
+ --accent-tertiary: #0b132b;
106
+ --highlight: #5bc0be;
107
+ --error: #ff6b6b;
108
+ --success: #6bd425;
109
+ }
110
+
111
+ .documentation-container {
112
+ background-color: var(--bg-secondary);
113
+ color: var(--text-primary);
114
+ padding: 2rem;
115
+ border-radius: 15px;
116
+ margin-bottom: 2rem;
117
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
118
+ }
119
+
120
+ .documentation-header {
121
+ text-align: center;
122
+ margin-bottom: 1.5rem;
123
+ }
124
+
125
+ .documentation-section {
126
+ background-color: var(--bg-tertiary);
127
+ padding: 1.5rem;
128
+ border-radius: 10px;
129
+ margin-bottom: 1.5rem;
130
+ border-left: 4px solid var(--highlight);
131
+ }
132
+
133
+ .config-panel {
134
+ background-color: var(--bg-secondary);
135
+ padding: 1.5rem;
136
+ border-radius: 15px;
137
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
138
+ height: 100%;
139
+ display: flex;
140
+ flex-direction: column;
141
+ }
142
+
143
+ .chat-panel {
144
+ background-color: var(--bg-secondary);
145
+ padding: 1.5rem;
146
+ border-radius: 15px;
147
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
148
+ height: 100%;
149
+ display: flex;
150
+ flex-direction: column;
151
+ }
152
+
153
+ .panel-header {
154
+ text-align: center;
155
+ padding: 1rem;
156
+ background-color: var(--accent-primary);
157
+ color: var(--text-primary);
158
+ border-radius: 10px;
159
+ margin-bottom: 1rem;
160
+ }
161
+
162
+ .tools-table {
163
+ width: 100%;
164
+ border-collapse: collapse;
165
+ margin-top: 1rem;
166
+ }
167
+
168
+ .tools-table th, .tools-table td {
169
+ padding: 0.75rem;
170
+ text-align: left;
171
+ border-bottom: 1px solid var(--accent-secondary);
172
+ }
173
+
174
+ .tools-table th {
175
+ font-weight: bold;
176
+ background-color: var(--accent-tertiary);
177
+ }
178
+
179
+ .feature-list {
180
+ display: grid;
181
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
182
+ gap: 0.5rem;
183
+ margin-top: 1rem;
184
+ }
185
+
186
+ .feature-item {
187
+ background-color: var(--accent-secondary);
188
+ padding: 0.75rem;
189
+ border-radius: 10px;
190
+ }
191
+
192
+ a {
193
+ color: var(--highlight);
194
+ text-decoration: none;
195
+ font-weight: bold;
196
+ }
197
+
198
+ a:hover {
199
+ text-decoration: underline;
200
+ }
201
+
202
+ /* Chat avatar styling */
203
+ .chatbot .avatar {
204
+ display: flex !important;
205
+ align-items: center;
206
+ justify-content: center;
207
+ width: 40px !important;
208
+ height: 40px !important;
209
+ border-radius: 50%;
210
+ background-color: var(--accent-secondary);
211
+ color: var(--text-primary);
212
+ font-size: 20px;
213
+ margin-right: 10px;
214
+ }
215
+
216
+ /* Ensure chat messages have proper contrast */
217
+ .chatbot .user-message {
218
+ background-color: var(--accent-primary) !important;
219
+ color: var(--text-primary) !important;
220
+ border-radius: 10px !important;
221
+ }
222
+
223
+ .chatbot .bot-message {
224
+ background-color: var(--accent-secondary) !important;
225
+ color: var(--text-primary) !important;
226
+ border-radius: 10px !important;
227
+ }
228
+
229
+ /* Override any theme styles that might hide avatars */
230
+ .chatbot .message-container {
231
+ display: flex !important;
232
+ align-items: flex-start !important;
233
+ }
234
+ """
235
+ ) as app:
236
  agent_state = gr.State(value=None)
237
+
238
+ # Consolidated Documentation Section
239
  gr.HTML("""
240
+ <div class="documentation-container">
241
+ <div class="documentation-header">
242
+ <h1 style="margin: 0; font-size: 3rem; font-weight: 700;">🤖 TrackMate AI</h1>
243
+ <p style="margin: 0.5rem 0 0 0; font-size: 1.2rem; opacity: 0.9;">Your smart companion for tracking and managing orders.</p>
244
+ </div>
245
+
246
+ <div class="documentation-section">
247
+ <h2 style="margin: 0 0 1rem 0; font-size: 1.8rem;">📋 About TrackMate AI</h2>
248
+ <p style="margin: 0; font-size: 1.1rem; line-height: 1.6;">
249
+ <strong>TrackMate AI</strong> is a Gradio-powered autonomous agent designed to interface with an ERP system via MCP tools.
250
+ It connects to a custom MCP Server and lets users chat with an LLM to place, modify, or track orders — all through natural language. Designed for seamless interaction with ERP infrastructure, TrackMate AI empowers users to manage operational workflows via a conversational interface — no dashboards, no dropdowns, just intelligent dialogue. Whether you're tracking an order, generating invoices, or accessing global risk intelligence, TrackMate AI brings an intuitive, natural language layer to your business operations.
251
+ </p>
252
+
253
+ <h3 style="margin: 1.5rem 0 0.5rem 0; font-size: 1.4rem;">📺 Demo Video</h3>
254
+ <p style="margin: 0; font-size: 1.1rem;">
255
+ <a href="https://youtu.be/hzaNbPRrrz8" target="_blank">
256
+ 📹 Watch Demo Video - TrackMate AI Agent
257
+ </a>
258
+ </p>
259
+
260
+ <h3 style="margin: 1.5rem 0 0.5rem 0; font-size: 1.4rem;">✨ What It Can Do</h3>
261
+ <div class="feature-list">
262
+ <div class="feature-item">🔍 Query live ERP data (orders, invoices, customers)</div>
263
+ <div class="feature-item">�� Place or cancel orders</div>
264
+ <div class="feature-item">🌐 Check for geopolitical disruptions</div>
265
+ <div class="feature-item">🧠 Leverage multiple tools via MCP + Gradio agent framework</div>
266
+ <div class="feature-item">🧮 Use a calculator for quick computations</div>
267
+ <div class="feature-item">☀️🌧️ Get live weather updates for any location</div>
268
+ </div>
269
+
270
+ <h3 style="margin: 1.5rem 0 0.5rem 0; font-size: 1.4rem;">🔌 Connected Tools</h3>
271
+ <p style="margin: 0 0 1rem 0; font-size: 1.1rem;">
272
+ TrackMate AI uses a bunch of tools provided by the
273
+ <a href="https://huggingface.co/spaces/Agents-MCP-Hackathon/TrackMate-AI-MCP-Server" target="_blank">
274
+ TrackMate AI MCP Server
275
+ </a>:
276
+ </p>
277
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid white;">
278
+ <thead>
279
+ <tr>
280
+ <th style="border: 1px solid white; padding: 8px; background-color: #333; color: white;">Tool Name</th>
281
+ <th style="border: 1px solid white; padding: 8px; background-color: #333; color: white;">Description</th>
282
+ </tr>
283
+ </thead>
284
+ <tbody>
285
+ <tr>
286
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>list_tables</code></td>
287
+ <td style="border: 1px solid white; padding: 8px; color: white;">Lists all ERP database tables</td>
288
+ </tr>
289
+ <tr>
290
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>execute_query</code></td>
291
+ <td style="border: 1px solid white; padding: 8px; color: white;">Executes a custom SQL query</td>
292
+ </tr>
293
+ <tr>
294
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>get_order_status</code></td>
295
+ <td style="border: 1px solid white; padding: 8px; color: white;">Gets status of an order</td>
296
+ </tr>
297
+ <tr>
298
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>place_order</code></td>
299
+ <td style="border: 1px solid white; padding: 8px; color: white;">Places a new order</td>
300
+ </tr>
301
+ <tr>
302
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>cancel_order</code></td>
303
+ <td style="border: 1px solid white; padding: 8px; color: white;">Cancels an existing order</td>
304
+ </tr>
305
+ <tr>
306
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>get_active_disruptions</code></td>
307
+ <td style="border: 1px solid white; padding: 8px; color: white;">Returns geopolitical disruptions between nations</td>
308
+ </tr>
309
+ <tr>
310
+ <td style="border: 1px solid white; padding: 8px; color: white;"><code>add, subtract, multiple</code></td>
311
+ <td style="border: 1px solid white; padding: 8px; color: white;">Use a simple calculator</td>
312
+ </tr>
313
+ </tbody>
314
+ </table>
315
+
316
+ </div>
317
  </div>
318
  """)
319
+
320
+ # Main Interface
321
+ with gr.Row(equal_height=True):
322
+ # Configuration Panel
323
  with gr.Column(scale=1):
324
+ with gr.Group(elem_classes="config-panel"):
325
+ gr.HTML('<div class="panel-header"><h3 style="margin: 0;">⚙️ Agent Configuration</h3></div>')
326
+
327
+ model_name = gr.Dropdown(
328
+ choices=["gpt-4o-mini", "gpt-4o", "gpt-4"],
329
+ value="gpt-4o",
330
+ label="🤖 Model",
331
+ info="Select the OpenAI model to use"
 
 
 
 
 
 
 
 
 
 
 
 
332
  )
333
+ temperature = gr.Slider(
334
+ 0.0, 1.0,
335
+ value=0.7,
336
+ step=0.1,
337
+ label="🌡️ Temperature",
338
+ info="Controls randomness (0=deterministic, 1=creative)"
339
+ )
340
+ max_tokens = gr.Number(
341
+ value=1000,
342
+ minimum=100,
343
+ maximum=4000,
344
+ step=100,
345
+ label="📏 Max Tokens",
346
+ info="Maximum response length"
347
+ )
348
+ api_key = gr.Textbox(
349
+ value="",
350
+ label="🔑 API Key",
351
+ placeholder="Enter your OpenAI API key",
352
+ type="password",
353
+ interactive=True,
354
+ info="Your OpenAI API key (kept secure)"
355
+ )
356
+
357
+ with gr.Accordion("📝 System Prompt", open=False):
358
+ system_prompt = gr.Textbox(
359
+ value=get_default_system_prompt(),
360
+ lines=10,
361
+ label="System Prompt",
362
+ interactive=True,
363
+ info="Customize the agent's behavior and instructions"
364
+ )
365
 
366
+ init_btn = gr.Button("🚀 Initialize Agent", variant="primary", size="lg")
367
+
368
+ with gr.Group():
369
+ status_display = gr.Textbox(
370
+ label="📊 Status",
371
+ interactive=False,
372
+ info="Current operation status"
373
+ )
374
+ agent_status = gr.Textbox(
375
+ label="🤖 Agent Status",
376
+ interactive=False,
377
+ info="Agent readiness indicator"
378
+ )
379
 
380
+ # Chat Panel
381
  with gr.Column(scale=3, min_width=700):
382
+ with gr.Group(elem_classes="chat-panel"):
383
+ gr.HTML('<div class="panel-header"><h3 style="margin: 0;">💬 Chat Interface</h3></div>')
384
+
385
+ chatbot = gr.Chatbot(
386
+ label="TrackMate AI Chat",
387
+ height=650,
388
+ show_label=False,
389
+ show_copy_button=True,
390
+ elem_classes="chatbot"
391
+ )
392
+
393
+ # Make the message input take full width
394
+ msg_input = gr.Textbox(
395
+ label="💭 Message",
396
+ placeholder="Ask me about orders, inventory, weather, or anything else...",
397
+ show_label=False
398
+ )
399
+
400
+ send_btn = gr.Button("📤 Send", variant="primary", scale=1)
401
+
402
+ reset_btn = gr.Button("🔄 Reset Chat", variant="secondary", scale=1)
403
 
404
+ # Event handlers
405
  init_btn.click(
406
  fn=initialize_agent_handler,
407
  inputs=[model_name, temperature, max_tokens, system_prompt, api_key],
core/mcp_tools/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()
core/mcp_tools/erp_server.py CHANGED
@@ -1,64 +1,129 @@
1
  # erp_server.py
2
- from typing import Dict, Any, List, Optional
3
- import psycopg2
4
- from psycopg2.extras import RealDictCursor
5
- from mcp.server.fastmcp import FastMCP
6
  import os
 
 
 
 
 
 
 
7
  from dotenv import load_dotenv
8
- import json
9
- from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  load_dotenv()
12
 
13
  mcp = FastMCP("ERP Database")
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def get_db_connection():
16
- """Get database connection using environment variables."""
17
- try:
18
- conn = psycopg2.connect(
19
- host=os.getenv("POSTGRES_HOST", "localhost"),
20
- database=os.getenv("POSTGRES_DB", "erp_db"),
21
- user=os.getenv("POSTGRES_USER", "postgres"),
22
- password=os.getenv("POSTGRES_PASSWORD", ""),
23
- port=os.getenv("POSTGRES_PORT", "5432")
24
- )
25
- return conn
26
- except Exception as e:
27
- raise Exception(f"Database connection failed: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  @mcp.tool()
30
  async def execute_query(query: str, params: Optional[List] = None) -> Dict[str, Any]:
31
  """
32
  Execute a custom SQL query on the ERP database.
 
33
 
34
  Args:
35
- query (str): SQL query to execute
36
  params (List, optional): Parameters for parameterized queries
37
 
38
  Returns:
39
  dict: Query results or error message
40
  """
41
  try:
42
- conn = get_db_connection()
43
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  if params:
46
  cursor.execute(query, params)
47
  else:
48
  cursor.execute(query)
49
 
50
- # Check if it's a SELECT query
51
- if query.strip().upper().startswith('SELECT'):
52
- results = cursor.fetchall()
53
- # Convert RealDictRow to regular dict for JSON serialization
54
  results = [dict(row) for row in results]
55
- if not results:
56
- return "No results found"
57
- return results
58
- else:
59
- # For INSERT, UPDATE, DELETE queries
60
- conn.commit()
61
- return f"Query executed successfully. {cursor.rowcount} rows affected."
62
 
63
  except Exception as e:
64
  return {
@@ -77,21 +142,33 @@ async def list_erp_tables() -> Dict[str, Any]:
77
  Returns:
78
  dict: List of ERP table names
79
  """
80
- query = """
81
- SELECT table_name
82
- FROM information_schema.tables
83
- WHERE table_schema = 'public'
84
- AND table_name LIKE 'erp_%'
85
- ORDER BY table_name;
86
- """
87
-
88
  try:
89
- conn = get_db_connection()
90
- cursor = conn.cursor(cursor_factory=RealDictCursor)
91
- cursor.execute(query)
92
- results = cursor.fetchall()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- table_names = [row['table_name'] for row in results]
95
  if not table_names:
96
  return {
97
  "success": False,
@@ -125,17 +202,24 @@ async def get_order_status(order_id: int) -> Dict[str, Any]:
125
  Returns:
126
  dict: Order status information including shipping and destination countries
127
  """
128
- query = """
129
- SELECT o.*, c.name as customer_name, c.email as customer_email,
130
- o.shipping_country, o.destination_country
131
- FROM erp_orders o
132
- JOIN erp_customers c ON o.customer_id = c.customer_id
133
- WHERE o.order_id = %s;
134
- """
135
-
136
  try:
137
- conn = get_db_connection()
138
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  cursor.execute(query, [order_id])
140
  order = cursor.fetchone()
141
 
@@ -146,28 +230,28 @@ async def get_order_status(order_id: int) -> Dict[str, Any]:
146
  }
147
 
148
  # Get order items
149
- items_query = """
150
  SELECT oi.order_item_id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price, oi.subtotal,
151
  p.product_name, p.sku
152
  FROM erp_order_items oi
153
  JOIN erp_products p ON oi.product_id = p.product_id
154
- WHERE oi.order_id = %s;
155
  """
156
  cursor.execute(items_query, [order_id])
157
  items = cursor.fetchall()
158
 
159
  # Get order history
160
- history_query = """
161
  SELECT * FROM erp_order_history
162
- WHERE order_id = %s
163
  ORDER BY timestamp DESC;
164
  """
165
  cursor.execute(history_query, [order_id])
166
  history = cursor.fetchall()
167
 
168
- order_dict = dict(order)
169
- items_list = [dict(item) for item in items]
170
- history_list = [dict(entry) for entry in history]
171
 
172
  # Return JSON formatted response
173
  return {
@@ -221,13 +305,27 @@ async def place_new_order(
221
  dict: New order information
222
  """
223
  try:
224
- conn = get_db_connection()
225
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  # Calculate total amount based on the total items and their prices
228
  total_amount = 0
229
  for item in items:
230
- product_query = "SELECT price FROM erp_products WHERE product_id = %s;"
231
  cursor.execute(product_query, [item['product_id']])
232
  product = cursor.fetchone()
233
  if not product:
@@ -238,21 +336,26 @@ async def place_new_order(
238
  total_amount += product['price'] * item['quantity']
239
 
240
  # Insert new order into the erp_orders table for the customer
241
- order_query = """
242
  INSERT INTO erp_orders (
243
  customer_id, order_date, total_amount, status,
244
  shipping_address, shipping_country, destination_country, previous_order_id,
245
  estimated_delivery, payment_status
246
  ) VALUES (
247
- %s, CURRENT_DATE, %s, 'Processing',
248
- %s, %s, %s, %s,
249
- CURRENT_DATE + INTERVAL '7 days', 'Pending'
250
- ) RETURNING order_id;
251
  """
 
252
  cursor.execute(order_query, [
253
  customer_id, total_amount, shipping_address, shipping_country, destination_country, previous_order_id
254
  ])
255
- new_order_id = cursor.fetchone()['order_id']
 
 
 
 
256
 
257
  # Insert order items into erp_order_items table for the new order
258
  for item in items:
@@ -262,11 +365,11 @@ async def place_new_order(
262
  unit_price = product['price']
263
  subtotal = unit_price * item['quantity']
264
 
265
- item_query = """
266
  INSERT INTO erp_order_items (
267
  order_id, product_id, quantity, unit_price, subtotal
268
  ) VALUES (
269
- %s, %s, %s, %s, %s
270
  );
271
  """
272
  cursor.execute(item_query, [
@@ -275,52 +378,66 @@ async def place_new_order(
275
  ])
276
 
277
  # Update product stock
278
- update_stock_query = """
279
  UPDATE erp_products
280
- SET stock_quantity = stock_quantity - %s
281
- WHERE product_id = %s;
282
  """
283
  cursor.execute(update_stock_query, [item['quantity'], item['product_id']])
284
 
285
  # Create order history entry into the erp_order_history for the new order
286
- history_query = """
287
  INSERT INTO erp_order_history (
288
  order_id, timestamp, status_change, notes, updated_by
289
  ) VALUES (
290
- %s, CURRENT_TIMESTAMP, 'Order Created', 'New order placed', 'System'
291
  );
292
  """
293
  cursor.execute(history_query, [new_order_id])
294
 
295
  # If this is a replacement order, add a note to the previous order
296
  if previous_order_id:
297
- prev_order_note_query = """
298
  INSERT INTO erp_order_history (
299
  order_id, timestamp, status_change, notes, updated_by
300
  ) VALUES (
301
- %s, CURRENT_TIMESTAMP, 'Replaced', 'Order replaced by order #%s', 'System'
302
  );
303
  """
304
  cursor.execute(prev_order_note_query, [previous_order_id, new_order_id])
305
 
306
  # Generate invoice for the new order
307
- invoice_query = """
308
  INSERT INTO erp_invoices (
309
  order_id, invoice_date, amount, payment_terms, due_date, is_paid, invoice_number
310
  ) VALUES (
311
- %s, CURRENT_DATE, %s, 'Net 30', CURRENT_DATE + INTERVAL '30 days', FALSE, 'INV-' || %s
312
- ) RETURNING invoice_id;
313
  """
314
- cursor.execute(invoice_query, [new_order_id, total_amount, new_order_id])
315
- invoice_id = cursor.fetchone()['invoice_id']
 
 
 
 
316
 
317
  conn.commit()
318
 
319
  # Get the complete new order details
320
- cursor.execute("SELECT * FROM erp_orders WHERE order_id = %s;", [new_order_id])
321
  order = cursor.fetchone()
322
 
323
- order_dict = dict(order)
 
 
 
 
 
 
 
 
 
 
324
 
325
  # Return JSON formatted response
326
  return {
@@ -330,7 +447,7 @@ async def place_new_order(
330
  "invoice_id": invoice_id,
331
  "total_amount": float(total_amount),
332
  "status": order_dict['status'],
333
- "estimated_delivery": order_dict['estimated_delivery'].strftime("%Y-%m-%d") if order_dict['estimated_delivery'] else None,
334
  "customer_id": customer_id,
335
  "shipping_address": shipping_address,
336
  "shipping_country": shipping_country,
@@ -363,12 +480,18 @@ async def cancel_order(order_id: int, reason: str) -> Dict[str, Any]:
363
  dict: Result of the cancellation operation
364
  """
365
  try:
366
- conn = get_db_connection()
367
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
368
 
369
  # Check if order exists and can be cancelled
370
- check_query = """
371
- SELECT status, customer_id FROM erp_orders WHERE order_id = %s;
372
  """
373
  cursor.execute(check_query, [order_id])
374
  order = cursor.fetchone()
@@ -386,43 +509,43 @@ async def cancel_order(order_id: int, reason: str) -> Dict[str, Any]:
386
  }
387
 
388
  # Get order items to restore stock
389
- items_query = """
390
- SELECT product_id, quantity FROM erp_order_items WHERE order_id = %s;
391
  """
392
  cursor.execute(items_query, [order_id])
393
  items = cursor.fetchall()
394
 
395
  # Update order status to Cancelled
396
- update_query = """
397
  UPDATE erp_orders SET status = 'Cancelled', payment_status = 'Cancelled'
398
- WHERE order_id = %s;
399
  """
400
  cursor.execute(update_query, [order_id])
401
 
402
  # Add entry to order history
403
- history_query = """
404
  INSERT INTO erp_order_history (
405
  order_id, timestamp, status_change, notes, updated_by
406
  ) VALUES (
407
- %s, CURRENT_TIMESTAMP, 'Cancelled', %s, 'System'
408
  );
409
  """
410
  cursor.execute(history_query, [order_id, f"Order cancelled: {reason}"])
411
 
412
  # Restore product stock quantities
413
  for item in items:
414
- restore_stock_query = """
415
  UPDATE erp_products
416
- SET stock_quantity = stock_quantity + %s
417
- WHERE product_id = %s;
418
  """
419
  cursor.execute(restore_stock_query, [item['quantity'], item['product_id']])
420
 
421
  # Update invoice if exists
422
- invoice_query = """
423
  UPDATE erp_invoices
424
- SET is_paid = FALSE
425
- WHERE order_id = %s;
426
  """
427
  cursor.execute(invoice_query, [order_id])
428
 
@@ -465,27 +588,33 @@ async def get_invoice_details(invoice_id: Optional[int] = None, order_id: Option
465
  }
466
 
467
  try:
468
- conn = get_db_connection()
469
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
470
 
471
  if invoice_id:
472
- query = """
473
  SELECT i.*, o.order_date, o.status as order_status,
474
  c.name as customer_name, c.email as customer_email, c.address as customer_address
475
  FROM erp_invoices i
476
  JOIN erp_orders o ON i.order_id = o.order_id
477
  JOIN erp_customers c ON o.customer_id = c.customer_id
478
- WHERE i.invoice_id = %s;
479
  """
480
  cursor.execute(query, [invoice_id])
481
  else:
482
- query = """
483
  SELECT i.*, o.order_date, o.status as order_status,
484
  c.name as customer_name, c.email as customer_email, c.address as customer_address
485
  FROM erp_invoices i
486
  JOIN erp_orders o ON i.order_id = o.order_id
487
  JOIN erp_customers c ON o.customer_id = c.customer_id
488
- WHERE i.order_id = %s;
489
  """
490
  cursor.execute(query, [order_id])
491
 
@@ -498,17 +627,36 @@ async def get_invoice_details(invoice_id: Optional[int] = None, order_id: Option
498
  }
499
 
500
  # Get order items
501
- items_query = """
502
  SELECT oi.*, p.product_name, p.sku
503
  FROM erp_order_items oi
504
  JOIN erp_products p ON oi.product_id = p.product_id
505
- WHERE oi.order_id = %s;
506
  """
507
  cursor.execute(items_query, [invoice['order_id']])
508
  items = cursor.fetchall()
509
 
510
- invoice_dict = dict(invoice)
511
- items_list = [dict(item) for item in items]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
  # Return JSON formatted response
514
  return {
@@ -517,10 +665,10 @@ async def get_invoice_details(invoice_id: Optional[int] = None, order_id: Option
517
  "invoice_id": invoice_dict['invoice_id'],
518
  "invoice_number": invoice_dict.get('invoice_number', ''),
519
  "order_id": invoice_dict['order_id'],
520
- "order_date": invoice_dict['order_date'].strftime("%Y-%m-%d") if invoice_dict['order_date'] else None,
521
  "order_status": invoice_dict['order_status'],
522
  "amount": float(invoice_dict['amount']),
523
- "due_date": invoice_dict['due_date'].strftime("%Y-%m-%d") if invoice_dict['due_date'] else None,
524
  "payment_status": "Paid" if invoice_dict['is_paid'] else "Unpaid",
525
  "customer": {
526
  "name": invoice_dict['customer_name'],
 
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
+ from typing import Dict, Any, List, Optional
10
  from dotenv import load_dotenv
11
+ from datetime import datetime, date
12
+ import pathlib, logging
13
+
14
+ # Initialize logging
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
+
17
+ # Import both database libraries
18
+ import sqlite3
19
+ try:
20
+ import psycopg2
21
+ from psycopg2.extras import RealDictCursor
22
+ POSTGRES_AVAILABLE = True
23
+ except ImportError:
24
+ POSTGRES_AVAILABLE = False
25
+
26
+ # Import SQLite initialization function
27
+ from core.mcp_tools.erp_db_init import init_sqlite_db
28
+
29
+ from mcp.server.fastmcp import FastMCP
30
 
31
  load_dotenv()
32
 
33
  mcp = FastMCP("ERP Database")
34
 
35
+ # Helper function to convert SQLite row to dict
36
+ def dict_factory(cursor, row):
37
+ d = {}
38
+ for idx, col in enumerate(cursor.description):
39
+ d[col[0]] = row[idx]
40
+ return d
41
+
42
+ # Helper function to handle date serialization for JSON
43
+ def serialize_dates(obj):
44
+ if isinstance(obj, (date, datetime)):
45
+ return obj.isoformat()
46
+ return obj
47
+
48
  def get_db_connection():
49
+ """Get database connection based on configuration."""
50
+ # Default to SQLite unless explicitly set to use PostgreSQL
51
+ db_type = os.getenv("ERP_DB_TYPE", "sqlite").lower()
52
+
53
+ logging.info(f"Connecting to {db_type} database...")
54
+
55
+ if db_type == "postgres" and POSTGRES_AVAILABLE:
56
+ try:
57
+ conn = psycopg2.connect(
58
+ host=os.getenv("POSTGRES_HOST", "localhost"),
59
+ database=os.getenv("POSTGRES_DB", "erp_db"),
60
+ user=os.getenv("POSTGRES_USER", "postgres"),
61
+ password=os.getenv("POSTGRES_PASSWORD", ""),
62
+ port=os.getenv("POSTGRES_PORT", "5432")
63
+ )
64
+ return conn, "postgres"
65
+ except Exception as e:
66
+ raise Exception(f"PostgreSQL connection failed: {str(e)}")
67
+ else:
68
+ try:
69
+ # Get SQLite database path from environment or use default
70
+ db_path = os.getenv("SQLITE_DB_PATH", "./data/erp_db.sqlite")
71
+
72
+ # Ensure database is initialized
73
+ db_dir = pathlib.Path(os.path.dirname(db_path))
74
+ if not db_dir.exists() or not pathlib.Path(db_path).exists():
75
+ init_sqlite_db(db_path)
76
+
77
+ conn = sqlite3.connect(db_path)
78
+ conn.row_factory = dict_factory
79
+
80
+ # Enable foreign keys
81
+ conn.execute("PRAGMA foreign_keys = ON")
82
+
83
+ return conn, "sqlite"
84
+ except Exception as e:
85
+ raise Exception(f"SQLite connection failed: {str(e)}")
86
 
87
  @mcp.tool()
88
  async def execute_query(query: str, params: Optional[List] = None) -> Dict[str, Any]:
89
  """
90
  Execute a custom SQL query on the ERP database.
91
+ Only SELECT statements are allowed for security reasons.
92
 
93
  Args:
94
+ query (str): SQL query to execute (must be a SELECT statement)
95
  params (List, optional): Parameters for parameterized queries
96
 
97
  Returns:
98
  dict: Query results or error message
99
  """
100
  try:
101
+ # Check if it's a SELECT query
102
+ if not query.strip().upper().startswith('SELECT'):
103
+ return {
104
+ "success": False,
105
+ "error": "Only SELECT statements are allowed for security reasons."
106
+ }
107
+
108
+ conn, db_type = get_db_connection()
109
+
110
+ if db_type == "postgres":
111
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
112
+ else: # sqlite
113
+ cursor = conn.cursor()
114
 
115
  if params:
116
  cursor.execute(query, params)
117
  else:
118
  cursor.execute(query)
119
 
120
+ results = cursor.fetchall()
121
+ # Convert results to regular dict for JSON serialization
122
+ if db_type == "postgres":
 
123
  results = [dict(row) for row in results]
124
+ if not results:
125
+ return "No results found"
126
+ return results
 
 
 
 
127
 
128
  except Exception as e:
129
  return {
 
142
  Returns:
143
  dict: List of ERP table names
144
  """
 
 
 
 
 
 
 
 
145
  try:
146
+ conn, db_type = get_db_connection()
147
+
148
+ if db_type == "postgres":
149
+ query = """
150
+ SELECT table_name
151
+ FROM information_schema.tables
152
+ WHERE table_schema = 'public'
153
+ AND table_name LIKE 'erp_%'
154
+ ORDER BY table_name;
155
+ """
156
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
157
+ cursor.execute(query)
158
+ results = cursor.fetchall()
159
+ table_names = [row['table_name'] for row in results]
160
+ else: # sqlite
161
+ query = """
162
+ SELECT name as table_name
163
+ FROM sqlite_master
164
+ WHERE type='table' AND name LIKE 'erp_%'
165
+ ORDER BY name;
166
+ """
167
+ cursor = conn.cursor()
168
+ cursor.execute(query)
169
+ results = cursor.fetchall()
170
+ table_names = [row['table_name'] for row in results]
171
 
 
172
  if not table_names:
173
  return {
174
  "success": False,
 
202
  Returns:
203
  dict: Order status information including shipping and destination countries
204
  """
 
 
 
 
 
 
 
 
205
  try:
206
+ conn, db_type = get_db_connection()
207
+
208
+ if db_type == "postgres":
209
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
210
+ param_placeholder = "%s"
211
+ else: # sqlite
212
+ cursor = conn.cursor()
213
+ param_placeholder = "?"
214
+
215
+ query = f"""
216
+ SELECT o.*, c.name as customer_name, c.email as customer_email,
217
+ o.shipping_country, o.destination_country
218
+ FROM erp_orders o
219
+ JOIN erp_customers c ON o.customer_id = c.customer_id
220
+ WHERE o.order_id = {param_placeholder};
221
+ """
222
+
223
  cursor.execute(query, [order_id])
224
  order = cursor.fetchone()
225
 
 
230
  }
231
 
232
  # Get order items
233
+ items_query = f"""
234
  SELECT oi.order_item_id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price, oi.subtotal,
235
  p.product_name, p.sku
236
  FROM erp_order_items oi
237
  JOIN erp_products p ON oi.product_id = p.product_id
238
+ WHERE oi.order_id = {param_placeholder};
239
  """
240
  cursor.execute(items_query, [order_id])
241
  items = cursor.fetchall()
242
 
243
  # Get order history
244
+ history_query = f"""
245
  SELECT * FROM erp_order_history
246
+ WHERE order_id = {param_placeholder}
247
  ORDER BY timestamp DESC;
248
  """
249
  cursor.execute(history_query, [order_id])
250
  history = cursor.fetchall()
251
 
252
+ order_dict = order if db_type == "sqlite" else dict(order)
253
+ items_list = items if db_type == "sqlite" else [dict(item) for item in items]
254
+ history_list = history if db_type == "sqlite" else [dict(entry) for entry in history]
255
 
256
  # Return JSON formatted response
257
  return {
 
305
  dict: New order information
306
  """
307
  try:
308
+ conn, db_type = get_db_connection()
309
+
310
+ if db_type == "postgres":
311
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
312
+ param_placeholder = "%s"
313
+ returning_clause = "RETURNING order_id"
314
+ date_interval = "CURRENT_DATE + INTERVAL '7 days'"
315
+ due_date_interval = "CURRENT_DATE + INTERVAL '30 days'"
316
+ concat_op = "||"
317
+ else: # sqlite
318
+ cursor = conn.cursor()
319
+ param_placeholder = "?"
320
+ returning_clause = ""
321
+ date_interval = "date('now', '+7 days')"
322
+ due_date_interval = "date('now', '+30 days')"
323
+ concat_op = "||"
324
 
325
  # Calculate total amount based on the total items and their prices
326
  total_amount = 0
327
  for item in items:
328
+ product_query = f"SELECT price FROM erp_products WHERE product_id = {param_placeholder};"
329
  cursor.execute(product_query, [item['product_id']])
330
  product = cursor.fetchone()
331
  if not product:
 
336
  total_amount += product['price'] * item['quantity']
337
 
338
  # Insert new order into the erp_orders table for the customer
339
+ order_query = f"""
340
  INSERT INTO erp_orders (
341
  customer_id, order_date, total_amount, status,
342
  shipping_address, shipping_country, destination_country, previous_order_id,
343
  estimated_delivery, payment_status
344
  ) VALUES (
345
+ {param_placeholder}, CURRENT_DATE, {param_placeholder}, 'Processing',
346
+ {param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder},
347
+ {date_interval}, 'Pending'
348
+ ) {returning_clause};
349
  """
350
+
351
  cursor.execute(order_query, [
352
  customer_id, total_amount, shipping_address, shipping_country, destination_country, previous_order_id
353
  ])
354
+
355
+ if db_type == "postgres":
356
+ new_order_id = cursor.fetchone()['order_id']
357
+ else: # sqlite
358
+ new_order_id = cursor.lastrowid
359
 
360
  # Insert order items into erp_order_items table for the new order
361
  for item in items:
 
365
  unit_price = product['price']
366
  subtotal = unit_price * item['quantity']
367
 
368
+ item_query = f"""
369
  INSERT INTO erp_order_items (
370
  order_id, product_id, quantity, unit_price, subtotal
371
  ) VALUES (
372
+ {param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder}, {param_placeholder}
373
  );
374
  """
375
  cursor.execute(item_query, [
 
378
  ])
379
 
380
  # Update product stock
381
+ update_stock_query = f"""
382
  UPDATE erp_products
383
+ SET stock_quantity = stock_quantity - {param_placeholder}
384
+ WHERE product_id = {param_placeholder};
385
  """
386
  cursor.execute(update_stock_query, [item['quantity'], item['product_id']])
387
 
388
  # Create order history entry into the erp_order_history for the new order
389
+ history_query = f"""
390
  INSERT INTO erp_order_history (
391
  order_id, timestamp, status_change, notes, updated_by
392
  ) VALUES (
393
+ {param_placeholder}, CURRENT_TIMESTAMP, 'Order Created', 'New order placed', 'System'
394
  );
395
  """
396
  cursor.execute(history_query, [new_order_id])
397
 
398
  # If this is a replacement order, add a note to the previous order
399
  if previous_order_id:
400
+ prev_order_note_query = f"""
401
  INSERT INTO erp_order_history (
402
  order_id, timestamp, status_change, notes, updated_by
403
  ) VALUES (
404
+ {param_placeholder}, CURRENT_TIMESTAMP, 'Replaced', 'Order replaced by order #' {concat_op} {param_placeholder}, 'System'
405
  );
406
  """
407
  cursor.execute(prev_order_note_query, [previous_order_id, new_order_id])
408
 
409
  # Generate invoice for the new order
410
+ invoice_query = f"""
411
  INSERT INTO erp_invoices (
412
  order_id, invoice_date, amount, payment_terms, due_date, is_paid, invoice_number
413
  ) VALUES (
414
+ {param_placeholder}, CURRENT_DATE, {param_placeholder}, 'Net 30', {due_date_interval}, 0, 'INV-' {concat_op} {param_placeholder}
415
+ ) {returning_clause};
416
  """
417
+ cursor.execute(invoice_query, [new_order_id, total_amount, str(new_order_id)])
418
+
419
+ if db_type == "postgres":
420
+ invoice_id = cursor.fetchone()['invoice_id']
421
+ else: # sqlite
422
+ invoice_id = cursor.lastrowid
423
 
424
  conn.commit()
425
 
426
  # Get the complete new order details
427
+ cursor.execute(f"SELECT * FROM erp_orders WHERE order_id = {param_placeholder};", [new_order_id])
428
  order = cursor.fetchone()
429
 
430
+ order_dict = order
431
+
432
+ # Format date for response
433
+ estimated_delivery = order_dict.get('estimated_delivery')
434
+ if estimated_delivery:
435
+ if isinstance(estimated_delivery, str):
436
+ estimated_delivery_str = estimated_delivery
437
+ else:
438
+ estimated_delivery_str = estimated_delivery.strftime("%Y-%m-%d") if hasattr(estimated_delivery, 'strftime') else str(estimated_delivery)
439
+ else:
440
+ estimated_delivery_str = None
441
 
442
  # Return JSON formatted response
443
  return {
 
447
  "invoice_id": invoice_id,
448
  "total_amount": float(total_amount),
449
  "status": order_dict['status'],
450
+ "estimated_delivery": estimated_delivery_str,
451
  "customer_id": customer_id,
452
  "shipping_address": shipping_address,
453
  "shipping_country": shipping_country,
 
480
  dict: Result of the cancellation operation
481
  """
482
  try:
483
+ conn, db_type = get_db_connection()
484
+
485
+ if db_type == "postgres":
486
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
487
+ param_placeholder = "%s"
488
+ else: # sqlite
489
+ cursor = conn.cursor()
490
+ param_placeholder = "?"
491
 
492
  # Check if order exists and can be cancelled
493
+ check_query = f"""
494
+ SELECT status, customer_id FROM erp_orders WHERE order_id = {param_placeholder};
495
  """
496
  cursor.execute(check_query, [order_id])
497
  order = cursor.fetchone()
 
509
  }
510
 
511
  # Get order items to restore stock
512
+ items_query = f"""
513
+ SELECT product_id, quantity FROM erp_order_items WHERE order_id = {param_placeholder};
514
  """
515
  cursor.execute(items_query, [order_id])
516
  items = cursor.fetchall()
517
 
518
  # Update order status to Cancelled
519
+ update_query = f"""
520
  UPDATE erp_orders SET status = 'Cancelled', payment_status = 'Cancelled'
521
+ WHERE order_id = {param_placeholder};
522
  """
523
  cursor.execute(update_query, [order_id])
524
 
525
  # Add entry to order history
526
+ history_query = f"""
527
  INSERT INTO erp_order_history (
528
  order_id, timestamp, status_change, notes, updated_by
529
  ) VALUES (
530
+ {param_placeholder}, CURRENT_TIMESTAMP, 'Cancelled', {param_placeholder}, 'System'
531
  );
532
  """
533
  cursor.execute(history_query, [order_id, f"Order cancelled: {reason}"])
534
 
535
  # Restore product stock quantities
536
  for item in items:
537
+ restore_stock_query = f"""
538
  UPDATE erp_products
539
+ SET stock_quantity = stock_quantity + {param_placeholder}
540
+ WHERE product_id = {param_placeholder};
541
  """
542
  cursor.execute(restore_stock_query, [item['quantity'], item['product_id']])
543
 
544
  # Update invoice if exists
545
+ invoice_query = f"""
546
  UPDATE erp_invoices
547
+ SET is_paid = 0
548
+ WHERE order_id = {param_placeholder};
549
  """
550
  cursor.execute(invoice_query, [order_id])
551
 
 
588
  }
589
 
590
  try:
591
+ conn, db_type = get_db_connection()
592
+
593
+ if db_type == "postgres":
594
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
595
+ param_placeholder = "%s"
596
+ else: # sqlite
597
+ cursor = conn.cursor()
598
+ param_placeholder = "?"
599
 
600
  if invoice_id:
601
+ query = f"""
602
  SELECT i.*, o.order_date, o.status as order_status,
603
  c.name as customer_name, c.email as customer_email, c.address as customer_address
604
  FROM erp_invoices i
605
  JOIN erp_orders o ON i.order_id = o.order_id
606
  JOIN erp_customers c ON o.customer_id = c.customer_id
607
+ WHERE i.invoice_id = {param_placeholder};
608
  """
609
  cursor.execute(query, [invoice_id])
610
  else:
611
+ query = f"""
612
  SELECT i.*, o.order_date, o.status as order_status,
613
  c.name as customer_name, c.email as customer_email, c.address as customer_address
614
  FROM erp_invoices i
615
  JOIN erp_orders o ON i.order_id = o.order_id
616
  JOIN erp_customers c ON o.customer_id = c.customer_id
617
+ WHERE i.order_id = {param_placeholder};
618
  """
619
  cursor.execute(query, [order_id])
620
 
 
627
  }
628
 
629
  # Get order items
630
+ items_query = f"""
631
  SELECT oi.*, p.product_name, p.sku
632
  FROM erp_order_items oi
633
  JOIN erp_products p ON oi.product_id = p.product_id
634
+ WHERE oi.order_id = {param_placeholder};
635
  """
636
  cursor.execute(items_query, [invoice['order_id']])
637
  items = cursor.fetchall()
638
 
639
+ invoice_dict = invoice
640
+ items_list = items
641
+
642
+ # Format dates for response
643
+ order_date = invoice_dict.get('order_date')
644
+ if order_date:
645
+ if isinstance(order_date, str):
646
+ order_date_str = order_date
647
+ else:
648
+ order_date_str = order_date.strftime("%Y-%m-%d") if hasattr(order_date, 'strftime') else str(order_date)
649
+ else:
650
+ order_date_str = None
651
+
652
+ due_date = invoice_dict.get('due_date')
653
+ if due_date:
654
+ if isinstance(due_date, str):
655
+ due_date_str = due_date
656
+ else:
657
+ due_date_str = due_date.strftime("%Y-%m-%d") if hasattr(due_date, 'strftime') else str(due_date)
658
+ else:
659
+ due_date_str = None
660
 
661
  # Return JSON formatted response
662
  return {
 
665
  "invoice_id": invoice_dict['invoice_id'],
666
  "invoice_number": invoice_dict.get('invoice_number', ''),
667
  "order_id": invoice_dict['order_id'],
668
+ "order_date": order_date_str,
669
  "order_status": invoice_dict['order_status'],
670
  "amount": float(invoice_dict['amount']),
671
+ "due_date": due_date_str,
672
  "payment_status": "Paid" if invoice_dict['is_paid'] else "Unpaid",
673
  "customer": {
674
  "name": invoice_dict['customer_name'],
core/mcp_tools/global_disruptions_server.py CHANGED
@@ -1,29 +1,133 @@
1
  # global_disruptions_server.py
 
 
 
 
 
 
 
 
2
  from typing import Dict, Any, List, Optional
3
- import psycopg2
4
- from psycopg2.extras import RealDictCursor
5
- from mcp.server.fastmcp import FastMCP
6
  import os
 
 
7
  from dotenv import load_dotenv
8
  from datetime import datetime, date
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  load_dotenv()
11
 
12
  mcp = FastMCP("Global Disruptions Database")
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  def get_db_connection():
15
- """Get database connection using environment variables."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  try:
17
- conn = psycopg2.connect(
18
- host=os.getenv("POSTGRES_HOST", "localhost"),
19
- database=os.getenv("POSTGRES_DB", "erp_db"),
20
- user=os.getenv("POSTGRES_USER", "postgres"),
21
- password=os.getenv("POSTGRES_PASSWORD", ""),
22
- port=os.getenv("POSTGRES_PORT", "5432")
23
- )
24
- return conn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  except Exception as e:
26
- raise Exception(f"Database connection failed: {str(e)}")
 
 
 
 
 
 
27
 
28
  @mcp.tool()
29
  async def get_active_disruptions(source_country: Optional[str] = None, destination_country: Optional[str] = None) -> Dict[str, Any]:
@@ -38,11 +142,19 @@ async def get_active_disruptions(source_country: Optional[str] = None, destinati
38
  dict: Information about current disruptions that might affect shipping
39
  """
40
  try:
41
- conn = get_db_connection()
42
- cursor = conn.cursor(cursor_factory=RealDictCursor)
 
 
 
 
 
 
 
 
43
 
44
  # Base query to get disruptions
45
- query = """
46
  SELECT
47
  disruption_id,
48
  source_country,
@@ -60,29 +172,29 @@ async def get_active_disruptions(source_country: Optional[str] = None, destinati
60
  FROM
61
  live_global_disruptions
62
  WHERE
63
- is_active = TRUE
64
  """
65
 
66
  params = []
67
 
68
  # Add filters for specific countries if provided
69
  if source_country and destination_country:
70
- query += """
71
  AND (
72
- (source_country = %s AND destination_country = %s) OR
73
- (source_country = %s) OR
74
- (destination_country = %s)
75
  )
76
  """
77
  params.extend([source_country, destination_country, source_country, destination_country])
78
  elif source_country:
79
- query += """
80
- AND (source_country = %s)
81
  """
82
  params.append(source_country)
83
  elif destination_country:
84
- query += """
85
- AND (destination_country = %s)
86
  """
87
  params.append(destination_country)
88
 
@@ -92,8 +204,6 @@ async def get_active_disruptions(source_country: Optional[str] = None, destinati
92
  severity DESC,
93
  updated_at DESC;
94
  """
95
-
96
- print(query, params) # Debugging line to see the query and parameters
97
 
98
  cursor.execute(query, params)
99
  disruptions = cursor.fetchall()
@@ -126,7 +236,7 @@ async def get_active_disruptions(source_country: Optional[str] = None, destinati
126
  # Format the response
127
  formatted_disruptions = []
128
  for disruption in disruptions:
129
- d = dict(disruption)
130
 
131
  # Format severity level as text
132
  severity = d['severity']
 
1
  # global_disruptions_server.py
2
+
3
+ import os
4
+ import sys
5
+ from typing import TypedDict, List, Union
6
+
7
+ # Add the project root directory to the Python path
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
9
+
10
  from typing import Dict, Any, List, Optional
 
 
 
11
  import os
12
+ import sys
13
+ import pathlib
14
  from dotenv import load_dotenv
15
  from datetime import datetime, date
16
 
17
+ # Import both database libraries
18
+ import sqlite3
19
+ try:
20
+ import psycopg2
21
+ from psycopg2.extras import RealDictCursor
22
+ POSTGRES_AVAILABLE = True
23
+ except ImportError:
24
+ POSTGRES_AVAILABLE = False
25
+
26
+ # Import SQLite initialization function
27
+ from core.mcp_tools.erp_db_init import init_sqlite_db
28
+ from mcp.server.fastmcp import FastMCP
29
+
30
  load_dotenv()
31
 
32
  mcp = FastMCP("Global Disruptions 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("DISRUPTIONS_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 Global Disruptions database.
88
+
89
+ Args:
90
+ query (str): SQL query to execute
91
+ params (List, optional): Parameters for parameterized queries
92
+
93
+ Returns:
94
+ dict: Query results or error message
95
+ """
96
  try:
97
+ conn, db_type = get_db_connection()
98
+
99
+ if db_type == "postgres":
100
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
101
+ else: # sqlite
102
+ cursor = conn.cursor()
103
+
104
+ if params:
105
+ cursor.execute(query, params)
106
+ else:
107
+ cursor.execute(query)
108
+
109
+ # Check if it's a SELECT query
110
+ if query.strip().upper().startswith('SELECT'):
111
+ results = cursor.fetchall()
112
+ # Convert results to regular dict for JSON serialization
113
+ if db_type == "postgres":
114
+ results = [dict(row) for row in results]
115
+ if not results:
116
+ return "No results found"
117
+ return results
118
+ else:
119
+ # For INSERT, UPDATE, DELETE queries
120
+ conn.commit()
121
+ return f"Query executed successfully. {cursor.rowcount} rows affected."
122
+
123
  except Exception as e:
124
+ return {
125
+ "success": False,
126
+ "error": str(e)
127
+ }
128
+ finally:
129
+ if 'conn' in locals():
130
+ conn.close()
131
 
132
  @mcp.tool()
133
  async def get_active_disruptions(source_country: Optional[str] = None, destination_country: Optional[str] = None) -> Dict[str, Any]:
 
142
  dict: Information about current disruptions that might affect shipping
143
  """
144
  try:
145
+ conn, db_type = get_db_connection()
146
+
147
+ if db_type == "postgres":
148
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
149
+ param_placeholder = "%s"
150
+ true_value = "TRUE"
151
+ else: # sqlite
152
+ cursor = conn.cursor()
153
+ param_placeholder = "?"
154
+ true_value = "1"
155
 
156
  # Base query to get disruptions
157
+ query = f"""
158
  SELECT
159
  disruption_id,
160
  source_country,
 
172
  FROM
173
  live_global_disruptions
174
  WHERE
175
+ is_active = {true_value}
176
  """
177
 
178
  params = []
179
 
180
  # Add filters for specific countries if provided
181
  if source_country and destination_country:
182
+ query += f"""
183
  AND (
184
+ (source_country = {param_placeholder} AND destination_country = {param_placeholder}) OR
185
+ (source_country = {param_placeholder}) OR
186
+ (destination_country = {param_placeholder})
187
  )
188
  """
189
  params.extend([source_country, destination_country, source_country, destination_country])
190
  elif source_country:
191
+ query += f"""
192
+ AND (source_country = {param_placeholder})
193
  """
194
  params.append(source_country)
195
  elif destination_country:
196
+ query += f"""
197
+ AND (destination_country = {param_placeholder})
198
  """
199
  params.append(destination_country)
200
 
 
204
  severity DESC,
205
  updated_at DESC;
206
  """
 
 
207
 
208
  cursor.execute(query, params)
209
  disruptions = cursor.fetchall()
 
236
  # Format the response
237
  formatted_disruptions = []
238
  for disruption in disruptions:
239
+ d = disruption if db_type == "sqlite" else dict(disruption)
240
 
241
  # Format severity level as text
242
  severity = d['severity']
core/mcp_tools/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 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()
core/mcp_tools/reset_erp_db.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # reset_erp_db.py
2
+ import os
3
+ import pathlib
4
+ from erp_db_init import init_sqlite_db
5
+ from 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()
data/erp_db.sqlite ADDED
Binary file (98.3 kB). View file