akiko19191 commited on
Commit
892f25f
·
verified ·
1 Parent(s): f0b4404

Update app/api.py

Browse files
Files changed (1) hide show
  1. app/api.py +391 -391
app/api.py CHANGED
@@ -1,392 +1,392 @@
1
- # your_app/api.py
2
-
3
- from flask import Blueprint, request, jsonify, current_app, redirect
4
- from bson.objectid import ObjectId
5
- from datetime import datetime
6
- from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
7
- from .extensions import bcrypt, mongo
8
- from .xero_utils import trigger_po_creation, trigger_contact_creation,sync_user_approval_from_xero
9
- from .email_utils import send_order_confirmation_email, send_registration_email, send_login_notification_email, send_cart_reminder_email
10
- from .general_utils import get_next_order_serial
11
-
12
-
13
- api_bp = Blueprint('api', __name__)
14
- @api_bp.route('/sync_xero_users', methods=['GET'])
15
- def sync_xero_users():
16
- sync_user_approval_from_xero()
17
- return "✅"
18
-
19
- @api_bp.route('/clear')
20
- def clear_all():
21
- mongo.db.users.delete_many({})
22
- mongo.db.orders.delete_many({})
23
- return "✅"
24
-
25
- @api_bp.route('/register', methods=['POST'])
26
- def register():
27
- data = request.get_json()
28
- email = data.get('email')
29
- password = data.get('password')
30
- company_name = data.get('businessName')
31
-
32
- if not all([email, password, company_name]):
33
- return jsonify({"msg": "Missing required fields: Email, Password, and Business Name"}), 400
34
- if mongo.db.users.find_one({'email': email}):
35
- return jsonify({"msg": "A user with this email already exists"}), 409
36
-
37
- hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
38
-
39
- user_document = data.copy()
40
- user_document['password'] = hashed_password
41
- user_document['company_name'] = company_name
42
- user_document['is_approved'] = True
43
- user_document['is_admin'] = False
44
-
45
- mongo.db.users.insert_one(user_document)
46
- trigger_contact_creation(data)
47
-
48
- try:
49
- send_registration_email(data)
50
- except Exception as e:
51
- current_app.logger.error(f"Failed to send registration email to {email}: {e}")
52
-
53
- return jsonify({"msg": "Registration successful! Your application is being processed."}), 201
54
-
55
- # ... (the rest of your api.py file remains unchanged)
56
- @api_bp.route('/login', methods=['POST'])
57
- def login():
58
- data = request.get_json()
59
- email, password = data.get('email'), data.get('password')
60
- user = mongo.db.users.find_one({'email': email})
61
-
62
- if user and user.get('password') and bcrypt.check_password_hash(user['password'], password):
63
- if not user.get('is_approved', False): return jsonify({"msg": "Account pending approval"}), 403
64
-
65
- try:
66
- send_login_notification_email(user)
67
- except Exception as e:
68
- current_app.logger.error(f"Failed to send login notification email to {email}: {e}")
69
-
70
- access_token = create_access_token(identity=email)
71
- return jsonify(access_token=access_token, email=user['email'], companyName=user['businessName'],contactPerson=user.get('contactPerson', '')) , 200
72
-
73
- return jsonify({"msg": "Bad email or password"}), 401
74
-
75
- @api_bp.route('/profile', methods=['GET'])
76
- @jwt_required()
77
- def get_user_profile():
78
- user_email = get_jwt_identity()
79
- user = mongo.db.users.find_one({'email': user_email})
80
-
81
- if not user:
82
- return jsonify({"msg": "User not found"}), 404
83
-
84
- profile_data = {
85
- 'deliveryAddress': user.get('businessAddress', ''),
86
- 'mobileNumber': user.get('phoneNumber', '')
87
- }
88
-
89
- return jsonify(profile_data), 200
90
-
91
- @api_bp.route('/products', methods=['GET'])
92
- def get_products():
93
- products = [{
94
- 'id': str(p['_id']), 'name': p.get('name'), 'category': p.get('category'),
95
- 'unit': p.get('unit'), 'image_url': p.get('image_url', ''), 'price': p.get('price', '')
96
- } for p in mongo.db.products.find()]
97
- return jsonify(products)
98
-
99
-
100
- @api_bp.route('/cart', methods=['GET', 'POST'])
101
- @jwt_required()
102
- def handle_cart():
103
- user_email = get_jwt_identity()
104
-
105
- if request.method == 'GET':
106
- cart = mongo.db.carts.find_one({'user_email': user_email})
107
- if not cart:
108
- return jsonify({'items': [], 'deliveryDate': None})
109
-
110
- populated_items = []
111
- if cart.get('items'):
112
- product_ids = [ObjectId(item['productId']) for item in cart['items']]
113
- if product_ids:
114
- products = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': product_ids}})}
115
- for item in cart['items']:
116
- details = products.get(item['productId'])
117
- if details:
118
- populated_items.append({
119
- 'product': {'id': str(details['_id']), 'name': details.get('name'), 'unit': details.get('unit'), 'image_url': details.get('image_url'), 'price': details.get('price')},
120
- 'quantity': item['quantity'],
121
- 'mode': item.get('mode', 'pieces')
122
- })
123
-
124
- return jsonify({
125
- 'items': populated_items,
126
- 'deliveryDate': cart.get('deliveryDate')
127
- })
128
-
129
- if request.method == 'POST':
130
- data = request.get_json()
131
-
132
- update_doc = {
133
- 'user_email': user_email,
134
- 'updated_at': datetime.utcnow()
135
- }
136
-
137
- if 'items' in data:
138
- update_doc['items'] = data['items']
139
-
140
- if 'deliveryDate' in data:
141
- update_doc['deliveryDate'] = data['deliveryDate']
142
-
143
- mongo.db.carts.update_one(
144
- {'user_email': user_email},
145
- {'$set': update_doc},
146
- upsert=True
147
- )
148
- return jsonify({"msg": "Cart updated successfully"})
149
-
150
- @api_bp.route('/orders', methods=['GET', 'POST'])
151
- @jwt_required()
152
- def handle_orders():
153
- user_email = get_jwt_identity()
154
-
155
- if request.method == 'POST':
156
- cart = mongo.db.carts.find_one({'user_email': user_email})
157
- if not cart or not cart.get('items'): return jsonify({"msg": "Your cart is empty"}), 400
158
-
159
- data = request.get_json()
160
- if not all([data.get('deliveryDate'), data.get('deliveryAddress'), data.get('mobileNumber')]): return jsonify({"msg": "Missing delivery information"}), 400
161
-
162
- user = mongo.db.users.find_one({'email': user_email})
163
- if not user:
164
- return jsonify({"msg": "User not found"}), 404
165
-
166
- order_doc = {
167
- 'user_email': user_email, 'items': cart['items'], 'delivery_date': data['deliveryDate'],
168
- 'delivery_address': data['deliveryAddress'], 'mobile_number': data['mobileNumber'],
169
- 'additional_info': data.get('additionalInfo'), 'total_amount': data.get('totalAmount'),
170
- 'status': 'pending', 'created_at': datetime.utcnow()
171
- }
172
- order_doc['serial_no'] = get_next_order_serial()
173
- order_id = mongo.db.orders.insert_one(order_doc).inserted_id
174
- order_doc['_id'] = order_id
175
-
176
- order_details_for_xero = {
177
- "order_id": str(order_id), "user_email": user_email, "items": cart['items'],
178
- "delivery_address": data['deliveryAddress'], "mobile_number": data['mobileNumber'],"deliverydate":data["deliveryDate"]
179
- }
180
- trigger_po_creation(order_details_for_xero)
181
-
182
- try:
183
- product_ids = [ObjectId(item['productId']) for item in cart['items']]
184
- products_map = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': product_ids}})}
185
-
186
- order_doc['populated_items'] = [{
187
- "name": products_map.get(item['productId'], {}).get('name', 'N/A'),
188
- "quantity": item['quantity'],
189
- "mode": item.get('mode', 'pieces')
190
- } for item in cart['items']]
191
-
192
- send_order_confirmation_email(order_doc, user)
193
-
194
- except Exception as e:
195
- current_app.logger.error(f"Failed to send confirmation email for order {order_id}: {e}")
196
-
197
- mongo.db.carts.delete_one({'user_email': user_email})
198
- return jsonify({"msg": "Order placed successfully! You will be redirected shortly to the Orders Page!", "orderId": str(order_id)}), 201
199
-
200
- if request.method == 'GET':
201
- user_orders = list(mongo.db.orders.find({'user_email': user_email}).sort('created_at', -1))
202
- if not user_orders: return jsonify([])
203
-
204
- all_product_ids = {ObjectId(item['productId']) for order in user_orders for item in order.get('items', [])}
205
- products = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': list(all_product_ids)}})}
206
-
207
- for order in user_orders:
208
- order['items'] = [
209
- {
210
- 'quantity': item['quantity'],
211
- 'mode': item.get('mode', 'pieces'),
212
- 'product': {
213
- 'id': str(p['_id']),
214
- 'name': p.get('name'),
215
- 'unit': p.get('unit'),
216
- 'image_url': p.get('image_url')
217
- }
218
- }
219
- for item in order.get('items', []) if (p := products.get(item['productId']))
220
- ]
221
- order['_id'] = str(order['_id'])
222
- order['created_at'] = order['created_at'].isoformat()
223
- order['delivery_date'] = order['delivery_date'] if isinstance(order['delivery_date'], str) else order['delivery_date'].isoformat()
224
- return jsonify(user_orders)
225
-
226
- @api_bp.route('/orders/<order_id>', methods=['GET'])
227
- @jwt_required()
228
- def get_order(order_id):
229
- user_email = get_jwt_identity()
230
- try:
231
- order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
232
- if not order:
233
- return jsonify({"msg": "Order not found or access denied"}), 404
234
-
235
- order['_id'] = str(order['_id'])
236
- return jsonify(order), 200
237
- except Exception as e:
238
- return jsonify({"msg": f"Invalid Order ID format: {e}"}), 400
239
-
240
- @api_bp.route('/orders/<order_id>', methods=['PUT'])
241
- @jwt_required()
242
- def update_order(order_id):
243
- user_email = get_jwt_identity()
244
-
245
- order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
246
- if not order:
247
- return jsonify({"msg": "Order not found or access denied"}), 404
248
-
249
- if order.get('status') not in ['pending', 'confirmed']:
250
- return jsonify({"msg": f"Order with status '{order.get('status')}' cannot be modified."}), 400
251
-
252
- cart = mongo.db.carts.find_one({'user_email': user_email})
253
- if not cart or not cart.get('items'):
254
- return jsonify({"msg": "Cannot update with an empty cart. Please add items."}), 400
255
-
256
- data = request.get_json()
257
- update_doc = {
258
- 'items': cart['items'],
259
- 'delivery_date': data['deliveryDate'],
260
- 'delivery_address': data['deliveryAddress'],
261
- 'mobile_number': data['mobileNumber'],
262
- 'additional_info': data.get('additionalInfo'),
263
- 'total_amount': data.get('totalAmount'),
264
- 'updated_at': datetime.utcnow()
265
- }
266
-
267
- mongo.db.orders.update_one({'_id': ObjectId(order_id)}, {'$set': update_doc})
268
- mongo.db.carts.delete_one({'user_email': user_email})
269
-
270
- return jsonify({"msg": "Order updated successfully!", "orderId": order_id}), 200
271
-
272
- @api_bp.route('/orders/<order_id>/cancel', methods=['POST'])
273
- @jwt_required()
274
- def cancel_order(order_id):
275
- user_email = get_jwt_identity()
276
- order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
277
-
278
- if not order:
279
- return jsonify({"msg": "Order not found or access denied"}), 404
280
-
281
- if order.get('status') in ['delivered', 'cancelled']:
282
- return jsonify({"msg": "This order can no longer be cancelled."}), 400
283
-
284
- mongo.db.orders.update_one(
285
- {'_id': ObjectId(order_id)},
286
- {'$set': {'status': 'cancelled', 'updated_at': datetime.utcnow()}}
287
- )
288
-
289
- return jsonify({"msg": "Order has been cancelled."}), 200
290
-
291
- @api_bp.route('/sendmail', methods=['GET'])
292
- def send_cart_reminders():
293
- try:
294
- carts_with_items = list(mongo.db.carts.find({'items': {'$exists': True, '$ne': []}}))
295
-
296
- if not carts_with_items:
297
- return jsonify({"msg": "No users with pending items in cart."}), 200
298
-
299
- user_emails = [cart['user_email'] for cart in carts_with_items]
300
- all_product_ids = {
301
- ObjectId(item['productId'])
302
- for cart in carts_with_items
303
- for item in cart.get('items', [])
304
- }
305
-
306
- users_cursor = mongo.db.users.find({'email': {'$in': user_emails}})
307
- products_cursor = mongo.db.products.find({'_id': {'$in': list(all_product_ids)}})
308
-
309
- users_map = {user['email']: user for user in users_cursor}
310
- products_map = {str(prod['_id']): prod for prod in products_cursor}
311
-
312
- emails_sent_count = 0
313
-
314
- for cart in carts_with_items:
315
- user = users_map.get(cart['user_email'])
316
- if not user:
317
- current_app.logger.warning(f"Cart found for non-existent user: {cart['user_email']}")
318
- continue
319
-
320
- populated_items = []
321
- for item in cart.get('items', []):
322
- product_details = products_map.get(item['productId'])
323
- if product_details:
324
- populated_items.append({
325
- 'product': {
326
- 'id': str(product_details['_id']),
327
- 'name': product_details.get('name'),
328
- },
329
- 'quantity': item['quantity']
330
- })
331
-
332
- if populated_items:
333
- try:
334
- send_cart_reminder_email(user, populated_items)
335
- emails_sent_count += 1
336
- except Exception as e:
337
- current_app.logger.error(f"Failed to send cart reminder to {user['email']}: {e}")
338
-
339
- return jsonify({"msg": f"Cart reminder process finished. Emails sent to {emails_sent_count} users."}), 200
340
-
341
- except Exception as e:
342
- current_app.logger.error(f"Error in /sendmail endpoint: {e}")
343
- return jsonify({"msg": "An internal error occurred while sending reminders."}), 500
344
-
345
- @api_bp.route('/admin/users/approve/<user_id>', methods=['POST'])
346
- @jwt_required()
347
- def approve_user(user_id):
348
- mongo.db.users.update_one({'_id': ObjectId(user_id)}, {'$set': {'is_approved': True}})
349
- return jsonify({"msg": f"User {user_id} approved"})
350
-
351
- # +++ START: NEW ENDPOINT FOR ITEM REQUESTS +++
352
- @api_bp.route('/request-item', methods=['POST'])
353
- @jwt_required()
354
- def request_item():
355
- """
356
- Allows a logged-in user to request an item that is not in the catalog.
357
- The request is saved to the database for admin review.
358
- """
359
- user_email = get_jwt_identity()
360
- data = request.get_json()
361
-
362
- if not data or not data.get('details'):
363
- return jsonify({"msg": "Item details are required."}), 400
364
-
365
- details = data.get('details').strip()
366
- if not details:
367
- return jsonify({"msg": "Item details cannot be empty."}), 400
368
-
369
- try:
370
- # Fetch user info for more context in the request
371
- user = mongo.db.users.find_one({'email': user_email}, {'company_name': 1})
372
- company_name = user.get('company_name', 'N/A') if user else 'N/A'
373
-
374
- request_doc = {
375
- 'user_email': user_email,
376
- 'company_name': company_name,
377
- 'details': details,
378
- 'status': 'new', # Possible statuses: 'new', 'reviewed', 'sourced', 'rejected'
379
- 'requested_at': datetime.utcnow()
380
- }
381
-
382
- # The collection 'item_requests' will be created if it doesn't exist
383
- mongo.db.item_requests.insert_one(request_doc)
384
-
385
- # Optional: Here you could add a call to an email utility to notify admins
386
- # For example: send_item_request_notification(user_email, company_name, details)
387
-
388
- return jsonify({"msg": "Your item request has been submitted. We will look into it!"}), 201
389
-
390
- except Exception as e:
391
- current_app.logger.error(f"Error processing item request for {user_email}: {e}")
392
  return jsonify({"msg": "An internal server error occurred."}), 500
 
1
+ # your_app/api.py
2
+
3
+ from flask import Blueprint, request, jsonify, current_app, redirect
4
+ from bson.objectid import ObjectId
5
+ from datetime import datetime
6
+ from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
7
+ from .extensions import bcrypt, mongo
8
+ from .xero_utils import trigger_po_creation, trigger_contact_creation,sync_user_approval_from_xero
9
+ from .email_utils import send_order_confirmation_email, send_registration_email, send_login_notification_email, send_cart_reminder_email
10
+ from .general_utils import get_next_order_serial
11
+
12
+
13
+ api_bp = Blueprint('api', __name__)
14
+ @api_bp.route('/sync_xero_users', methods=['GET'])
15
+ def sync_xero_users():
16
+ sync_user_approval_from_xero()
17
+ return "✅"
18
+
19
+ @api_bp.route('/clear')
20
+ def clear_all():
21
+ mongo.db.users.delete_many({})
22
+ mongo.db.orders.delete_many({})
23
+ return "✅"
24
+
25
+ @api_bp.route('/register', methods=['POST'])
26
+ def register():
27
+ data = request.get_json()
28
+ email = data.get('email')
29
+ password = data.get('password')
30
+ company_name = data.get('businessName')
31
+
32
+ if not all([email, password, company_name]):
33
+ return jsonify({"msg": "Missing required fields: Email, Password, and Business Name"}), 400
34
+ if mongo.db.users.find_one({'email': email}):
35
+ return jsonify({"msg": "A user with this email already exists"}), 409
36
+
37
+ hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
38
+
39
+ user_document = data.copy()
40
+ user_document['password'] = hashed_password
41
+ user_document['company_name'] = company_name
42
+ user_document['is_approved'] = False
43
+ user_document['is_admin'] = False
44
+
45
+ mongo.db.users.insert_one(user_document)
46
+ trigger_contact_creation(data)
47
+
48
+ try:
49
+ send_registration_email(data)
50
+ except Exception as e:
51
+ current_app.logger.error(f"Failed to send registration email to {email}: {e}")
52
+
53
+ return jsonify({"msg": "Registration successful! Your application is being processed."}), 201
54
+
55
+ # ... (the rest of your api.py file remains unchanged)
56
+ @api_bp.route('/login', methods=['POST'])
57
+ def login():
58
+ data = request.get_json()
59
+ email, password = data.get('email'), data.get('password')
60
+ user = mongo.db.users.find_one({'email': email})
61
+
62
+ if user and user.get('password') and bcrypt.check_password_hash(user['password'], password):
63
+ if not user.get('is_approved', False): return jsonify({"msg": "Account pending approval"}), 403
64
+
65
+ try:
66
+ send_login_notification_email(user)
67
+ except Exception as e:
68
+ current_app.logger.error(f"Failed to send login notification email to {email}: {e}")
69
+
70
+ access_token = create_access_token(identity=email)
71
+ return jsonify(access_token=access_token, email=user['email'], companyName=user['businessName'],contactPerson=user.get('contactPerson', '')) , 200
72
+
73
+ return jsonify({"msg": "Bad email or password"}), 401
74
+
75
+ @api_bp.route('/profile', methods=['GET'])
76
+ @jwt_required()
77
+ def get_user_profile():
78
+ user_email = get_jwt_identity()
79
+ user = mongo.db.users.find_one({'email': user_email})
80
+
81
+ if not user:
82
+ return jsonify({"msg": "User not found"}), 404
83
+
84
+ profile_data = {
85
+ 'deliveryAddress': user.get('businessAddress', ''),
86
+ 'mobileNumber': user.get('phoneNumber', '')
87
+ }
88
+
89
+ return jsonify(profile_data), 200
90
+
91
+ @api_bp.route('/products', methods=['GET'])
92
+ def get_products():
93
+ products = [{
94
+ 'id': str(p['_id']), 'name': p.get('name'), 'category': p.get('category'),
95
+ 'unit': p.get('unit'), 'image_url': p.get('image_url', ''), 'price': p.get('price', '')
96
+ } for p in mongo.db.products.find()]
97
+ return jsonify(products)
98
+
99
+
100
+ @api_bp.route('/cart', methods=['GET', 'POST'])
101
+ @jwt_required()
102
+ def handle_cart():
103
+ user_email = get_jwt_identity()
104
+
105
+ if request.method == 'GET':
106
+ cart = mongo.db.carts.find_one({'user_email': user_email})
107
+ if not cart:
108
+ return jsonify({'items': [], 'deliveryDate': None})
109
+
110
+ populated_items = []
111
+ if cart.get('items'):
112
+ product_ids = [ObjectId(item['productId']) for item in cart['items']]
113
+ if product_ids:
114
+ products = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': product_ids}})}
115
+ for item in cart['items']:
116
+ details = products.get(item['productId'])
117
+ if details:
118
+ populated_items.append({
119
+ 'product': {'id': str(details['_id']), 'name': details.get('name'), 'unit': details.get('unit'), 'image_url': details.get('image_url'), 'price': details.get('price')},
120
+ 'quantity': item['quantity'],
121
+ 'mode': item.get('mode', 'pieces')
122
+ })
123
+
124
+ return jsonify({
125
+ 'items': populated_items,
126
+ 'deliveryDate': cart.get('deliveryDate')
127
+ })
128
+
129
+ if request.method == 'POST':
130
+ data = request.get_json()
131
+
132
+ update_doc = {
133
+ 'user_email': user_email,
134
+ 'updated_at': datetime.utcnow()
135
+ }
136
+
137
+ if 'items' in data:
138
+ update_doc['items'] = data['items']
139
+
140
+ if 'deliveryDate' in data:
141
+ update_doc['deliveryDate'] = data['deliveryDate']
142
+
143
+ mongo.db.carts.update_one(
144
+ {'user_email': user_email},
145
+ {'$set': update_doc},
146
+ upsert=True
147
+ )
148
+ return jsonify({"msg": "Cart updated successfully"})
149
+
150
+ @api_bp.route('/orders', methods=['GET', 'POST'])
151
+ @jwt_required()
152
+ def handle_orders():
153
+ user_email = get_jwt_identity()
154
+
155
+ if request.method == 'POST':
156
+ cart = mongo.db.carts.find_one({'user_email': user_email})
157
+ if not cart or not cart.get('items'): return jsonify({"msg": "Your cart is empty"}), 400
158
+
159
+ data = request.get_json()
160
+ if not all([data.get('deliveryDate'), data.get('deliveryAddress'), data.get('mobileNumber')]): return jsonify({"msg": "Missing delivery information"}), 400
161
+
162
+ user = mongo.db.users.find_one({'email': user_email})
163
+ if not user:
164
+ return jsonify({"msg": "User not found"}), 404
165
+
166
+ order_doc = {
167
+ 'user_email': user_email, 'items': cart['items'], 'delivery_date': data['deliveryDate'],
168
+ 'delivery_address': data['deliveryAddress'], 'mobile_number': data['mobileNumber'],
169
+ 'additional_info': data.get('additionalInfo'), 'total_amount': data.get('totalAmount'),
170
+ 'status': 'pending', 'created_at': datetime.utcnow()
171
+ }
172
+ order_doc['serial_no'] = get_next_order_serial()
173
+ order_id = mongo.db.orders.insert_one(order_doc).inserted_id
174
+ order_doc['_id'] = order_id
175
+
176
+ order_details_for_xero = {
177
+ "order_id": str(order_id), "user_email": user_email, "items": cart['items'],
178
+ "delivery_address": data['deliveryAddress'], "mobile_number": data['mobileNumber'],"deliverydate":data["deliveryDate"]
179
+ }
180
+ trigger_po_creation(order_details_for_xero)
181
+
182
+ try:
183
+ product_ids = [ObjectId(item['productId']) for item in cart['items']]
184
+ products_map = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': product_ids}})}
185
+
186
+ order_doc['populated_items'] = [{
187
+ "name": products_map.get(item['productId'], {}).get('name', 'N/A'),
188
+ "quantity": item['quantity'],
189
+ "mode": item.get('mode', 'pieces')
190
+ } for item in cart['items']]
191
+
192
+ send_order_confirmation_email(order_doc, user)
193
+
194
+ except Exception as e:
195
+ current_app.logger.error(f"Failed to send confirmation email for order {order_id}: {e}")
196
+
197
+ mongo.db.carts.delete_one({'user_email': user_email})
198
+ return jsonify({"msg": "Order placed successfully! You will be redirected shortly to the Orders Page!", "orderId": str(order_id)}), 201
199
+
200
+ if request.method == 'GET':
201
+ user_orders = list(mongo.db.orders.find({'user_email': user_email}).sort('created_at', -1))
202
+ if not user_orders: return jsonify([])
203
+
204
+ all_product_ids = {ObjectId(item['productId']) for order in user_orders for item in order.get('items', [])}
205
+ products = {str(p['_id']): p for p in mongo.db.products.find({'_id': {'$in': list(all_product_ids)}})}
206
+
207
+ for order in user_orders:
208
+ order['items'] = [
209
+ {
210
+ 'quantity': item['quantity'],
211
+ 'mode': item.get('mode', 'pieces'),
212
+ 'product': {
213
+ 'id': str(p['_id']),
214
+ 'name': p.get('name'),
215
+ 'unit': p.get('unit'),
216
+ 'image_url': p.get('image_url')
217
+ }
218
+ }
219
+ for item in order.get('items', []) if (p := products.get(item['productId']))
220
+ ]
221
+ order['_id'] = str(order['_id'])
222
+ order['created_at'] = order['created_at'].isoformat()
223
+ order['delivery_date'] = order['delivery_date'] if isinstance(order['delivery_date'], str) else order['delivery_date'].isoformat()
224
+ return jsonify(user_orders)
225
+
226
+ @api_bp.route('/orders/<order_id>', methods=['GET'])
227
+ @jwt_required()
228
+ def get_order(order_id):
229
+ user_email = get_jwt_identity()
230
+ try:
231
+ order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
232
+ if not order:
233
+ return jsonify({"msg": "Order not found or access denied"}), 404
234
+
235
+ order['_id'] = str(order['_id'])
236
+ return jsonify(order), 200
237
+ except Exception as e:
238
+ return jsonify({"msg": f"Invalid Order ID format: {e}"}), 400
239
+
240
+ @api_bp.route('/orders/<order_id>', methods=['PUT'])
241
+ @jwt_required()
242
+ def update_order(order_id):
243
+ user_email = get_jwt_identity()
244
+
245
+ order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
246
+ if not order:
247
+ return jsonify({"msg": "Order not found or access denied"}), 404
248
+
249
+ if order.get('status') not in ['pending', 'confirmed']:
250
+ return jsonify({"msg": f"Order with status '{order.get('status')}' cannot be modified."}), 400
251
+
252
+ cart = mongo.db.carts.find_one({'user_email': user_email})
253
+ if not cart or not cart.get('items'):
254
+ return jsonify({"msg": "Cannot update with an empty cart. Please add items."}), 400
255
+
256
+ data = request.get_json()
257
+ update_doc = {
258
+ 'items': cart['items'],
259
+ 'delivery_date': data['deliveryDate'],
260
+ 'delivery_address': data['deliveryAddress'],
261
+ 'mobile_number': data['mobileNumber'],
262
+ 'additional_info': data.get('additionalInfo'),
263
+ 'total_amount': data.get('totalAmount'),
264
+ 'updated_at': datetime.utcnow()
265
+ }
266
+
267
+ mongo.db.orders.update_one({'_id': ObjectId(order_id)}, {'$set': update_doc})
268
+ mongo.db.carts.delete_one({'user_email': user_email})
269
+
270
+ return jsonify({"msg": "Order updated successfully!", "orderId": order_id}), 200
271
+
272
+ @api_bp.route('/orders/<order_id>/cancel', methods=['POST'])
273
+ @jwt_required()
274
+ def cancel_order(order_id):
275
+ user_email = get_jwt_identity()
276
+ order = mongo.db.orders.find_one({'_id': ObjectId(order_id), 'user_email': user_email})
277
+
278
+ if not order:
279
+ return jsonify({"msg": "Order not found or access denied"}), 404
280
+
281
+ if order.get('status') in ['delivered', 'cancelled']:
282
+ return jsonify({"msg": "This order can no longer be cancelled."}), 400
283
+
284
+ mongo.db.orders.update_one(
285
+ {'_id': ObjectId(order_id)},
286
+ {'$set': {'status': 'cancelled', 'updated_at': datetime.utcnow()}}
287
+ )
288
+
289
+ return jsonify({"msg": "Order has been cancelled."}), 200
290
+
291
+ @api_bp.route('/sendmail', methods=['GET'])
292
+ def send_cart_reminders():
293
+ try:
294
+ carts_with_items = list(mongo.db.carts.find({'items': {'$exists': True, '$ne': []}}))
295
+
296
+ if not carts_with_items:
297
+ return jsonify({"msg": "No users with pending items in cart."}), 200
298
+
299
+ user_emails = [cart['user_email'] for cart in carts_with_items]
300
+ all_product_ids = {
301
+ ObjectId(item['productId'])
302
+ for cart in carts_with_items
303
+ for item in cart.get('items', [])
304
+ }
305
+
306
+ users_cursor = mongo.db.users.find({'email': {'$in': user_emails}})
307
+ products_cursor = mongo.db.products.find({'_id': {'$in': list(all_product_ids)}})
308
+
309
+ users_map = {user['email']: user for user in users_cursor}
310
+ products_map = {str(prod['_id']): prod for prod in products_cursor}
311
+
312
+ emails_sent_count = 0
313
+
314
+ for cart in carts_with_items:
315
+ user = users_map.get(cart['user_email'])
316
+ if not user:
317
+ current_app.logger.warning(f"Cart found for non-existent user: {cart['user_email']}")
318
+ continue
319
+
320
+ populated_items = []
321
+ for item in cart.get('items', []):
322
+ product_details = products_map.get(item['productId'])
323
+ if product_details:
324
+ populated_items.append({
325
+ 'product': {
326
+ 'id': str(product_details['_id']),
327
+ 'name': product_details.get('name'),
328
+ },
329
+ 'quantity': item['quantity']
330
+ })
331
+
332
+ if populated_items:
333
+ try:
334
+ send_cart_reminder_email(user, populated_items)
335
+ emails_sent_count += 1
336
+ except Exception as e:
337
+ current_app.logger.error(f"Failed to send cart reminder to {user['email']}: {e}")
338
+
339
+ return jsonify({"msg": f"Cart reminder process finished. Emails sent to {emails_sent_count} users."}), 200
340
+
341
+ except Exception as e:
342
+ current_app.logger.error(f"Error in /sendmail endpoint: {e}")
343
+ return jsonify({"msg": "An internal error occurred while sending reminders."}), 500
344
+
345
+ @api_bp.route('/admin/users/approve/<user_id>', methods=['POST'])
346
+ @jwt_required()
347
+ def approve_user(user_id):
348
+ mongo.db.users.update_one({'_id': ObjectId(user_id)}, {'$set': {'is_approved': True}})
349
+ return jsonify({"msg": f"User {user_id} approved"})
350
+
351
+ # +++ START: NEW ENDPOINT FOR ITEM REQUESTS +++
352
+ @api_bp.route('/request-item', methods=['POST'])
353
+ @jwt_required()
354
+ def request_item():
355
+ """
356
+ Allows a logged-in user to request an item that is not in the catalog.
357
+ The request is saved to the database for admin review.
358
+ """
359
+ user_email = get_jwt_identity()
360
+ data = request.get_json()
361
+
362
+ if not data or not data.get('details'):
363
+ return jsonify({"msg": "Item details are required."}), 400
364
+
365
+ details = data.get('details').strip()
366
+ if not details:
367
+ return jsonify({"msg": "Item details cannot be empty."}), 400
368
+
369
+ try:
370
+ # Fetch user info for more context in the request
371
+ user = mongo.db.users.find_one({'email': user_email}, {'company_name': 1})
372
+ company_name = user.get('company_name', 'N/A') if user else 'N/A'
373
+
374
+ request_doc = {
375
+ 'user_email': user_email,
376
+ 'company_name': company_name,
377
+ 'details': details,
378
+ 'status': 'new', # Possible statuses: 'new', 'reviewed', 'sourced', 'rejected'
379
+ 'requested_at': datetime.utcnow()
380
+ }
381
+
382
+ # The collection 'item_requests' will be created if it doesn't exist
383
+ mongo.db.item_requests.insert_one(request_doc)
384
+
385
+ # Optional: Here you could add a call to an email utility to notify admins
386
+ # For example: send_item_request_notification(user_email, company_name, details)
387
+
388
+ return jsonify({"msg": "Your item request has been submitted. We will look into it!"}), 201
389
+
390
+ except Exception as e:
391
+ current_app.logger.error(f"Error processing item request for {user_email}: {e}")
392
  return jsonify({"msg": "An internal server error occurred."}), 500