|
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 |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
menu_blueprint = Blueprint('menu', __name__) |
|
|
|
|
|
sf = get_salesforce_connection() |
|
|
|
|
|
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/" |
|
|
|
|
|
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) |
|
text = re.sub(r'[\s-]+', '-', text) |
|
return text |
|
|
|
|
|
@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'') |
|
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'') |
|
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_path = f"/static/{PLACEHOLDER_VIDEO if media_type == 'video' else PLACEHOLDER_IMAGE}" |
|
|
|
if not media_url: |
|
return default_path |
|
|
|
|
|
if media_url.startswith('069'): |
|
return f"{DEFAULT_VIDEO_URL}{media_url}" |
|
|
|
|
|
if media_url.startswith(('http://', 'https://')): |
|
return media_url |
|
|
|
|
|
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: |
|
|
|
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', []) |
|
|
|
|
|
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', []) |
|
|
|
|
|
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) |
|
} |
|
|
|
|
|
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() |
|
|
|
|
|
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) |
|
|
|
|
|
for item in items: |
|
if item['id'] in added_item_ids: |
|
continue |
|
|
|
section = item['section'] |
|
if section not in ordered_menu: |
|
section = "Others" |
|
|
|
ordered_menu[section].append(item) |
|
added_item_ids.add(item['id']) |
|
|
|
|
|
return {section: items for section, items in ordered_menu.items() if items} |
|
|
|
@menu_blueprint.route("/menu", methods=["GET"]) |
|
def menu(): |
|
|
|
user_email, first_letter, redirect_response = validate_user_session() |
|
if redirect_response: |
|
return redirect_response |
|
|
|
selected_category = request.args.get("category", "All") |
|
|
|
try: |
|
|
|
user_details = get_user_details(sf, user_email) |
|
if not user_details: |
|
return redirect(url_for('login')) |
|
|
|
|
|
cart_item_count = get_cart_count(sf, user_email) |
|
|
|
|
|
all_items = fetch_menu_items(sf, selected_category) |
|
ordered_menu = organize_menu_items(all_items, selected_category) |
|
|
|
|
|
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)}") |
|
|
|
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 = 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 |
|
|
|
|
|
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: |
|
|
|
if 'user_email' not in session: |
|
return jsonify({ |
|
"success": False, |
|
"error": "Authentication required." |
|
}), 401 |
|
|
|
|
|
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 |
|
|
|
|
|
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'] |
|
} |
|
|
|
|
|
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 |
|
|
|
|
|
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: |
|
|
|
existing_item = existing_items[0] |
|
new_quantity = existing_item['Quantity__c'] + item_data['quantity'] |
|
|
|
|
|
existing_addons = existing_item.get('Add_Ons__c', "None") |
|
merged_addons = existing_addons if existing_addons == "None" else f"{existing_addons}; {addons_string}" |
|
|
|
|
|
existing_instructions = existing_item.get('Instructions__c', "") |
|
merged_instructions = existing_instructions if not item_data['instructions'] else \ |
|
f"{existing_instructions} | {item_data['instructions']}".strip(" | ") |
|
|
|
|
|
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: |
|
|
|
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 |