Repository navigation
Expand file tree
/
Copy pathdatabase.py
More file actions
354 lines (316 loc) · 14.2 KB
/
Copy pathdatabase.py
File metadata and controls
354 lines (316 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
import asyncpg
import asyncio
from datetime import datetime, date
from typing import Optional, Dict, List
import logging
import uuid
logger = logging.getLogger(__name__)
class Database:
def __init__(self, database_url: str):
self.database_url = database_url
self.pool = None
async def initialize(self):
"""Initialize database connection pool and create tables"""
try:
self.pool = await asyncpg.create_pool(
self.database_url,
min_size=1,
max_size=10,
command_timeout=60,
statement_cache_size=0 # Disable prepared statement cache to avoid conflicts
)
await self.create_tables()
logger.info("Database initialized successfully")
except Exception as e:
logger.error(f"Failed to initialize database: {str(e)}")
raise
async def create_tables(self):
"""Create necessary database tables"""
async with self.pool.acquire() as conn:
# Create shipments table
await conn.execute('''
CREATE TABLE IF NOT EXISTS shipments (
tracking_id VARCHAR(20) PRIMARY KEY,
customer_name VARCHAR(255) NOT NULL,
pickup_address TEXT NOT NULL,
delivery_address TEXT NOT NULL,
delivery_date DATE NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Create call_logs table for tracking interactions
await conn.execute('''
CREATE TABLE IF NOT EXISTS call_logs (
id SERIAL PRIMARY KEY,
call_sid VARCHAR(255) UNIQUE NOT NULL,
from_number VARCHAR(20),
action VARCHAR(50),
tracking_id VARCHAR(20),
details JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
logger.info("Database tables created successfully")
async def close(self):
"""Close database connection pool"""
if self.pool:
await self.pool.close()
async def get_shipment(self, tracking_id: str) -> Optional[Dict]:
"""Get shipment information by tracking ID"""
try:
async with self.pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT * FROM shipments WHERE tracking_id = $1",
tracking_id
)
if row:
return {
'tracking_id': row['tracking_id'],
'customer_name': row['customer_name'],
'pickup_address': row['pickup_address'],
'delivery_address': row['delivery_address'],
'delivery_date': row['delivery_date'].isoformat(),
'status': row['status'],
'created_at': row['created_at'].isoformat(),
'updated_at': row['updated_at'].isoformat()
}
return None
except Exception as e:
logger.error(f"Error getting shipment {tracking_id}: {str(e)}")
raise
async def book_shipment(
self,
customer_name: str,
pickup_address: str,
delivery_address: str,
delivery_date: str
) -> Dict:
"""Book a new shipment"""
try:
# Generate simple numeric tracking ID (8 digits)
import random
tracking_id = str(random.randint(10000000, 99999999))
# Convert delivery_date string to date object
delivery_date_obj = datetime.strptime(delivery_date, "%Y-%m-%d").date()
async with self.pool.acquire() as conn:
await conn.execute(
"""
INSERT INTO shipments
(tracking_id, customer_name, pickup_address, delivery_address, delivery_date, status)
VALUES ($1, $2, $3, $4, $5, $6)
""",
tracking_id,
customer_name,
pickup_address,
delivery_address,
delivery_date_obj,
'booked'
)
return {
'tracking_id': tracking_id,
'customer_name': customer_name,
'pickup_address': pickup_address,
'delivery_address': delivery_address,
'delivery_date': delivery_date,
'status': 'booked',
'confirmation_message': f'Your shipment has been booked successfully with tracking ID {tracking_id}'
}
except Exception as e:
logger.error(f"Error booking shipment: {str(e)}")
raise
async def update_shipment(self, tracking_id: str, new_date: str) -> Dict:
"""Update shipment delivery date"""
try:
# Convert new_date string to date object
new_date_obj = datetime.strptime(new_date, "%Y-%m-%d").date()
async with self.pool.acquire() as conn:
# Check if shipment exists
existing = await conn.fetchrow(
"SELECT tracking_id, customer_name FROM shipments WHERE tracking_id = $1",
tracking_id
)
if not existing:
raise ValueError(f"Shipment with tracking ID {tracking_id} not found")
# Update the shipment
await conn.execute(
"""
UPDATE shipments
SET delivery_date = $1, status = 'rescheduled', updated_at = CURRENT_TIMESTAMP
WHERE tracking_id = $2
""",
new_date_obj,
tracking_id
)
return {
'tracking_id': tracking_id,
'customer_name': existing['customer_name'],
'new_delivery_date': new_date,
'status': 'rescheduled',
'confirmation_message': f'Your shipment {tracking_id} has been rescheduled for delivery on {new_date}'
}
except Exception as e:
logger.error(f"Error updating shipment {tracking_id}: {str(e)}")
raise
async def log_call_interaction(
self,
call_sid: str,
from_number: str,
action: str,
tracking_id: Optional[str] = None,
details: Optional[Dict] = None
):
"""Log call interaction for analytics"""
try:
async with self.pool.acquire() as conn:
await conn.execute(
"""
INSERT INTO call_logs (call_sid, from_number, action, tracking_id, details)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (call_sid) DO UPDATE SET
action = EXCLUDED.action,
tracking_id = EXCLUDED.tracking_id,
details = EXCLUDED.details
""",
call_sid,
from_number,
action,
tracking_id,
details
)
except Exception as e:
logger.error(f"Error logging call interaction: {str(e)}")
async def get_shipments_by_customer(self, customer_name: str) -> List[Dict]:
"""Get all shipments for a customer (used for identity verification)"""
try:
async with self.pool.acquire() as conn:
rows = await conn.fetch(
"SELECT * FROM shipments WHERE LOWER(customer_name) = LOWER($1) ORDER BY created_at DESC",
customer_name
)
shipments = []
for row in rows:
shipments.append({
'tracking_id': row['tracking_id'],
'customer_name': row['customer_name'],
'pickup_address': row['pickup_address'],
'delivery_address': row['delivery_address'],
'delivery_date': row['delivery_date'].isoformat(),
'status': row['status'],
'created_at': row['created_at'].isoformat()
})
return shipments
except Exception as e:
logger.error(f"Error getting shipments for customer {customer_name}: {str(e)}")
raise
async def cancel_shipment(self, tracking_id: str) -> Dict:
"""Cancel a shipment"""
try:
async with self.pool.acquire() as conn:
# Check if shipment exists
existing = await conn.fetchrow(
"SELECT tracking_id, customer_name, status FROM shipments WHERE tracking_id = $1",
tracking_id
)
if not existing:
raise ValueError(f"Shipment with tracking ID {tracking_id} not found")
if existing['status'] == 'cancelled':
raise ValueError(f"Shipment {tracking_id} is already cancelled")
# Cancel the shipment
await conn.execute(
"""
UPDATE shipments
SET status = 'cancelled', updated_at = CURRENT_TIMESTAMP
WHERE tracking_id = $1
""",
tracking_id
)
return {
'tracking_id': tracking_id,
'customer_name': existing['customer_name'],
'previous_status': existing['status'],
'new_status': 'cancelled',
'confirmation_message': f'Your shipment {tracking_id} has been successfully cancelled'
}
except Exception as e:
logger.error(f"Error cancelling shipment {tracking_id}: {str(e)}")
raise
async def update_shipment_address(
self,
tracking_id: str,
address_type: str,
new_address: str
) -> Dict:
"""Update pickup or delivery address"""
try:
if address_type not in ['pickup', 'delivery']:
raise ValueError("Address type must be 'pickup' or 'delivery'")
field_name = f"{address_type}_address"
async with self.pool.acquire() as conn:
# Check if shipment exists
existing = await conn.fetchrow(
f"SELECT tracking_id, customer_name, {field_name} FROM shipments WHERE tracking_id = $1",
tracking_id
)
if not existing:
raise ValueError(f"Shipment with tracking ID {tracking_id} not found")
# Update the address
await conn.execute(
f"""
UPDATE shipments
SET {field_name} = $1, status = 'modified', updated_at = CURRENT_TIMESTAMP
WHERE tracking_id = $2
""",
new_address,
tracking_id
)
return {
'tracking_id': tracking_id,
'customer_name': existing['customer_name'],
'address_type': address_type,
'old_address': existing[field_name],
'new_address': new_address,
'confirmation_message': f'Your {address_type} address for shipment {tracking_id} has been updated to {new_address}'
}
except Exception as e:
logger.error(f"Error updating {address_type} address for {tracking_id}: {str(e)}")
raise
async def update_delivery_time(self, tracking_id: str, new_time: str, new_date: str = None) -> Dict:
"""Update delivery date/time"""
try:
async with self.pool.acquire() as conn:
# Check if shipment exists
existing = await conn.fetchrow(
"SELECT tracking_id, customer_name, delivery_date FROM shipments WHERE tracking_id = $1",
tracking_id
)
if not existing:
raise ValueError(f"Shipment with tracking ID {tracking_id} not found")
# Use provided date or keep existing date
if new_date:
new_date_obj = datetime.strptime(new_date, "%Y-%m-%d").date()
else:
new_date_obj = existing['delivery_date']
new_date = existing['delivery_date'].isoformat()
# Update the delivery date
await conn.execute(
"""
UPDATE shipments
SET delivery_date = $1, status = 'rescheduled', updated_at = CURRENT_TIMESTAMP
WHERE tracking_id = $2
""",
new_date_obj,
tracking_id
)
return {
'tracking_id': tracking_id,
'customer_name': existing['customer_name'],
'old_date': existing['delivery_date'].isoformat(),
'new_date': new_date,
'new_time': new_time,
'confirmation_message': f'Your delivery for shipment {tracking_id} has been rescheduled to {new_date} at {new_time}'
}
except Exception as e:
logger.error(f"Error updating delivery time for {tracking_id}: {str(e)}")
raise