File size: 16,962 Bytes
211d39c
621242c
 
d8f44d1
b5489cb
 
 
 
 
 
 
621242c
 
 
 
 
 
b5489cb
d8f44d1
621242c
b5489cb
 
 
 
 
 
 
 
d8f44d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b5489cb
d8f44d1
b5489cb
 
 
 
 
 
 
 
 
 
 
 
621242c
b5489cb
621242c
b5489cb
621242c
b5489cb
 
 
 
 
 
621242c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
621242c
b5489cb
 
 
621242c
b5489cb
621242c
b5489cb
 
211d39c
b5489cb
 
621242c
b5489cb
 
 
211d39c
 
 
 
b5489cb
 
621242c
b5489cb
621242c
b5489cb
 
621242c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
211d39c
b5489cb
 
 
621242c
b5489cb
211d39c
b5489cb
 
 
 
211d39c
b5489cb
 
621242c
b5489cb
211d39c
b5489cb
211d39c
0140872
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
211d39c
b5489cb
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
 
211d39c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621242c
0140872
 
 
b5489cb
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
0140872
 
b5489cb
 
211d39c
 
 
b5489cb
211d39c
b5489cb
 
 
 
 
 
211d39c
 
 
b5489cb
 
211d39c
b5489cb
 
 
211d39c
 
 
b5489cb
211d39c
b5489cb
 
 
 
 
 
0140872
b5489cb
 
 
 
 
0140872
 
 
 
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
 
 
b5489cb
 
 
211d39c
 
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
0140872
b5489cb
211d39c
b5489cb
211d39c
b5489cb
 
211d39c
 
b5489cb
 
 
 
 
211d39c
b5489cb
 
 
 
 
 
 
 
 
 
 
 
 
211d39c
b5489cb
 
 
 
 
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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
from flask import Blueprint, render_template, request, session, jsonify, redirect, url_for
from salesforce import get_salesforce_connection
import os
import re
import logging
from urllib.parse import quote
from datetime import datetime, timedelta

# Initialize logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

menu_blueprint = Blueprint('menu', __name__)

# Initialize Salesforce connection
sf = get_salesforce_connection()

# Constants
STATIC_DIR = os.path.join(os.path.dirname(__file__), '..', 'static')
PLACEHOLDER_VIDEO = 'placeholder.mp4'
PLACEHOLDER_IMAGE = 'placeholder.jpg'
PLACEHOLDER_VIDEO_PATH = os.path.join(STATIC_DIR, PLACEHOLDER_VIDEO)
PLACEHOLDER_IMAGE_PATH = os.path.join(STATIC_DIR, PLACEHOLDER_IMAGE)
SECTION_ORDER = ["Best Sellers", "Starters", "Biryanis", "Curries", "Breads", 
                "Customized Dish", "Appetizer", "Desserts", "Soft Drinks"]
MAX_BEST_SELLERS = 4
DEFAULT_VIDEO_URL = "https://yourdomain.my.salesforce.com/sfc/servlet.shepherd/version/download/"

# Custom Jinja2 filter
def slugify(text):
    """Convert text to a slug format (lowercase, replace spaces with hyphens)"""
    if not text:
        return ""
    text = text.lower()
    text = re.sub(r'[^\w\s-]', '', text)  # Remove non-alphanumeric chars
    text = re.sub(r'[\s-]+', '-', text)   # Replace spaces and multiple hyphens with single hyphen
    return text

# Register the filter with the blueprint
@menu_blueprint.app_template_filter('slugify')
def slugify_filter(s):
    return slugify(s)

def initialize_placeholders():
    """Create placeholder files if they don't exist"""
    try:
        if not os.path.exists(PLACEHOLDER_VIDEO_PATH):
            with open(PLACEHOLDER_VIDEO_PATH, 'wb') as f:
                f.write(b'')  # Empty file
            logger.info(f"Created placeholder video at {PLACEHOLDER_VIDEO_PATH}")
        
        if not os.path.exists(PLACEHOLDER_IMAGE_PATH):
            with open(PLACEHOLDER_IMAGE_PATH, 'wb') as f:
                f.write(b'')  # Empty file
            logger.info(f"Created placeholder image at {PLACEHOLDER_IMAGE_PATH}")
    except Exception as e:
        logger.error(f"Error creating placeholder files: {str(e)}")

initialize_placeholders()

def get_valid_media_path(media_url=None, media_type='video'):
    """
    Get valid media path with placeholder fallback
    Args:
        media_url: URL or ID from Salesforce
        media_type: 'video' or 'image'
    Returns:
        Valid URL for the media
    """
    # Default placeholders
    default_path = f"/static/{PLACEHOLDER_VIDEO if media_type == 'video' else PLACEHOLDER_IMAGE}"
    
    if not media_url:
        return default_path
    
    # Handle Salesforce File ID (starts with '069')
    if media_url.startswith('069'):
        return f"{DEFAULT_VIDEO_URL}{media_url}"
    
    # Handle complete URLs
    if media_url.startswith(('http://', 'https://')):
        return media_url
    
    # Handle relative paths
    if media_url.startswith('/'):
        return media_url
    
    return default_path

def validate_user_session():
    """Validate and return user session data or redirect to login"""
    user_email = session.get('user_email')
    user_name = session.get('user_name')
    
    if not user_email:
        user_email = request.args.get('email')
        user_name = request.args.get('name')
        
        if user_email:
            session['user_email'] = user_email
            session['user_name'] = user_name
        else:
            return None, None, redirect(url_for("login"))
    
    first_letter = user_name[0].upper() if user_name else "A"
    return user_email, first_letter, None

def get_user_details(sf_connection, email):
    """Fetch user details from Salesforce"""
    try:
        query = f"""
            SELECT Id, Referral__c, Reward_Points__c 
            FROM Customer_Login__c 
            WHERE Email__c = '{email}'
        """
        result = sf_connection.query(query)
        
        if not result['records']:
            return None
        
        return {
            'referral_code': result['records'][0].get('Referral__c', 'N/A'),
            'reward_points': result['records'][0].get('Reward_Points__c', 0)
        }
    except Exception as e:
        logger.error(f"Error fetching user details: {str(e)}")
        return None

def get_cart_count(sf_connection, email):
    """Get cart item count for user"""
    try:
        query = f"SELECT COUNT() FROM Cart_Item__c WHERE Customer_Email__c = '{email}'"
        result = sf_connection.query(query)
        return result['totalSize']
    except Exception as e:
        logger.error(f"Error fetching cart count: {str(e)}")
        return 0

def fetch_menu_items(sf_connection, selected_category):
    """Fetch and process menu items from Salesforce"""
    try:
        # Query for standard menu items
        menu_query = """
            SELECT Id, Name, Price__c, Description__c, Image1__c, Image2__c, 
                   Veg_NonVeg__c, Section__c, Total_Ordered__c, Video1__c,
                   Is_Available__c, Spice_Level__c, Preparation_Time__c
            FROM Menu_Item__c
            WHERE Is_Available__c = true
        """
        menu_result = sf_connection.query(menu_query)
        food_items = menu_result.get('records', [])
        
        # Query for custom dishes (last 7 days)
        custom_dish_query = """
            SELECT Id, Name, Price__c, Description__c, Image1__c, Image2__c, 
                   Veg_NonVeg__c, Section__c, Total_Ordered__c, Is_Available__c
            FROM Custom_Dish__c
            WHERE CreatedDate >= LAST_N_DAYS:7 AND Is_Available__c = true
        """
        custom_dish_result = sf_connection.query(custom_dish_query)
        custom_dishes = custom_dish_result.get('records', [])
        
        # Process and merge items
        all_items = []
        
        for item in food_items + custom_dishes:
            processed_item = {
                'id': item.get('Id'),
                'name': item.get('Name', 'Unnamed Item'),
                'price': item.get('Price__c', 0),
                'description': item.get('Description__c', 'No description available'),
                'image1': get_valid_media_path(item.get('Image1__c'), 'image'),
                'image2': get_valid_media_path(item.get('Image2__c'), 'image'),
                'video': get_valid_media_path(item.get('Video1__c'), 'video'),
                'veg_non_veg': item.get('Veg_NonVeg__c', 'Unknown'),
                'section': item.get('Section__c', 'Others'),
                'total_ordered': item.get('Total_Ordered__c', 0),
                'is_available': item.get('Is_Available__c', True),
                'spice_level': item.get('Spice_Level__c', 'Medium'),
                'prep_time': item.get('Preparation_Time__c', 20)
            }
            
            # Apply category filter
            if selected_category == "Veg" and processed_item['veg_non_veg'] not in ["Veg", "both"]:
                continue
            if selected_category == "Non veg" and processed_item['veg_non_veg'] not in ["Non veg", "both"]:
                continue
                
            all_items.append(processed_item)
        
        return all_items
        
    except Exception as e:
        logger.error(f"Error fetching menu items: {str(e)}")
        return []

def organize_menu_items(items, selected_category):
    """Organize menu items into sections"""
    ordered_menu = {section: [] for section in SECTION_ORDER}
    added_item_ids = set()
    
    # Process best sellers
    best_sellers = sorted(
        [item for item in items if item['total_ordered'] > 0],
        key=lambda x: x['total_ordered'], 
        reverse=True
    )[:MAX_BEST_SELLERS]
    
    if best_sellers:
        ordered_menu["Best Sellers"] = best_sellers
        added_item_ids.update(item['id'] for item in best_sellers)
    
    # Organize other sections
    for item in items:
        if item['id'] in added_item_ids:
            continue
            
        section = item['section']
        if section not in ordered_menu:
            section = "Others"  # Fallback section
            
        ordered_menu[section].append(item)
        added_item_ids.add(item['id'])
    
    # Remove empty sections
    return {section: items for section, items in ordered_menu.items() if items}

@menu_blueprint.route("/menu", methods=["GET"])
def menu():
    # Validate session
    user_email, first_letter, redirect_response = validate_user_session()
    if redirect_response:
        return redirect_response
    
    selected_category = request.args.get("category", "All")
    
    try:
        # Get user details
        user_details = get_user_details(sf, user_email)
        if not user_details:
            return redirect(url_for('login'))
        
        # Get cart count
        cart_item_count = get_cart_count(sf, user_email)
        
        # Fetch and organize menu items
        all_items = fetch_menu_items(sf, selected_category)
        ordered_menu = organize_menu_items(all_items, selected_category)
        
        # Prepare categories
        categories = ["All", "Veg", "Non veg"]
        
        return render_template(
            "menu.html",
            ordered_menu=ordered_menu,
            categories=categories,
            selected_category=selected_category,
            referral_code=user_details['referral_code'],
            reward_points=user_details['reward_points'],
            user_name=session.get('user_name'),
            first_letter=first_letter,
            cart_item_count=cart_item_count
        )
        
    except Exception as e:
        logger.error(f"Error in menu route: {str(e)}")
        # Fallback data
        return render_template(
            "menu.html",
            ordered_menu={},
            categories=["All", "Veg", "Non veg"],
            selected_category=selected_category,
            referral_code='N/A',
            reward_points=0,
            user_name=session.get('user_name'),
            first_letter=first_letter,
            cart_item_count=0,
            error_message="We're experiencing technical difficulties. Please try again later."
        )

@menu_blueprint.route('/api/addons', methods=['GET'])
def get_addons():
    try:
        item_name = request.args.get('item_name', '').strip()
        item_section = request.args.get('item_section', '').strip()
        
        if not item_name or not item_section:
            return jsonify({
                "success": False, 
                "error": "Item name and section are required."
            }), 400
        
        # Query customization options
        query = f"""
            SELECT Id, Name, Customization_Type__c, Options__c, 
                   Max_Selections__c, Extra_Charge__c, Extra_Charge_Amount__c,
                   Is_Active__c
            FROM Customization_Options__c
            WHERE Section__c = '{item_section}'
            AND Is_Active__c = true
            ORDER BY Display_Order__c NULLS LAST
        """
        result = sf.query(query)
        addons = result.get('records', [])
        
        if not addons:
            return jsonify({
                "success": False, 
                "error": "No customization options found."
            }), 404
        
        # Format response
        formatted_addons = []
        for addon in addons:
            options = addon.get("Options__c", "")
            options = [opt.strip() for opt in options.split(",")] if options else []
            
            formatted_addons.append({
                "id": addon.get("Id"),
                "name": addon.get("Name"),
                "type": addon.get("Customization_Type__c", "checkbox"),
                "options": options,
                "max_selections": addon.get("Max_Selections__c", 1),
                "extra_charge": addon.get("Extra_Charge__c", False),
                "extra_charge_amount": float(addon.get("Extra_Charge_Amount__c", 0))
            })
        
        return jsonify({
            "success": True, 
            "addons": formatted_addons
        })
        
    except Exception as e:
        logger.error(f"Error in get_addons: {str(e)}")
        return jsonify({
            "success": False, 
            "error": "An error occurred while fetching options."
        }), 500

@menu_blueprint.route('/cart/add', methods=['POST'])
def add_to_cart():
    try:
        # Validate session
        if 'user_email' not in session:
            return jsonify({
                "success": False, 
                "error": "Authentication required."
            }), 401
        
        # Validate input
        data = request.get_json()
        if not data:
            return jsonify({
                "success": False, 
                "error": "Invalid JSON data."
            }), 400
        
        required_fields = ['itemName', 'itemPrice', 'itemImage', 'section']
        for field in required_fields:
            if field not in data or not data[field]:
                return jsonify({
                    "success": False, 
                    "error": f"Missing required field: {field}"
                }), 400
        
        # Prepare data
        item_data = {
            'name': data['itemName'].strip(),
            'price': float(data['itemPrice']),
            'image': data['itemImage'].strip(),
            'section': data['section'].strip(),
            'category': data.get('category', ''),
            'addons': data.get('addons', []),
            'instructions': data.get('instructions', '').strip(),
            'quantity': int(data.get('quantity', 1)),
            'customer_email': session['user_email']
        }
        
        # Calculate addons
        addons_price = sum(float(addon.get('price', 0)) for addon in item_data['addons'])
        addons_string = "; ".join(
            f"{addon.get('name', '')} (${addon.get('price', 0):.2f})" 
            for addon in item_data['addons']
        ) if item_data['addons'] else "None"
        
        total_price = (item_data['price'] * item_data['quantity']) + addons_price
        
        # Check if item already exists in cart
        query = f"""
            SELECT Id, Quantity__c, Add_Ons__c, Add_Ons_Price__c, Instructions__c 
            FROM Cart_Item__c
            WHERE Customer_Email__c = '{item_data['customer_email']}'
            AND Name = '{item_data['name']}'
            LIMIT 1
        """
        result = sf.query(query)
        existing_items = result.get('records', [])
        
        if existing_items:
            # Update existing item
            existing_item = existing_items[0]
            new_quantity = existing_item['Quantity__c'] + item_data['quantity']
            
            # Merge addons
            existing_addons = existing_item.get('Add_Ons__c', "None")
            merged_addons = existing_addons if existing_addons == "None" else f"{existing_addons}; {addons_string}"
            
            # Merge instructions
            existing_instructions = existing_item.get('Instructions__c', "")
            merged_instructions = existing_instructions if not item_data['instructions'] else \
                f"{existing_instructions} | {item_data['instructions']}".strip(" | ")
            
            # Update in Salesforce
            sf.Cart_Item__c.update(existing_item['Id'], {
                "Quantity__c": new_quantity,
                "Add_Ons__c": merged_addons,
                "Add_Ons_Price__c": addons_price + existing_item.get('Add_Ons_Price__c', 0),
                "Instructions__c": merged_instructions,
                "Price__c": total_price + existing_item.get('Price__c', 0)
            })
        else:
            # Create new cart item
            sf.Cart_Item__c.create({
                "Name": item_data['name'],
                "Price__c": total_price,
                "Base_Price__c": item_data['price'],
                "Quantity__c": item_data['quantity'],
                "Add_Ons_Price__c": addons_price,
                "Add_Ons__c": addons_string,
                "Image1__c": item_data['image'],
                "Customer_Email__c": item_data['customer_email'],
                "Instructions__c": item_data['instructions'],
                "Category__c": item_data['category'],
                "Section__c": item_data['section']
            })
        
        return jsonify({
            "success": True,
            "message": "Item added to cart successfully."
        })
        
    except ValueError as e:
        logger.error(f"Value error in add_to_cart: {str(e)}")
        return jsonify({
            "success": False, 
            "error": "Invalid price or quantity format."
        }), 400
        
    except Exception as e:
        logger.error(f"Error in add_to_cart: {str(e)}")
        return jsonify({
            "success": False, 
            "error": "An error occurred while adding to cart."
        }), 500