208 lines
9.4 KiB
Python
208 lines
9.4 KiB
Python
from odoo import http, fields
|
|
from odoo.http import request
|
|
import json
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class UberWebhookController(http.Controller):
|
|
|
|
@http.route('/uber/webhook/delivery', type='http', auth='public', methods=['POST'], csrf=False)
|
|
def uber_delivery_webhook(self, **post):
|
|
"""Handle status updates from Uber Direct via standard HTTP webhook"""
|
|
try:
|
|
raw_data = request.httprequest.get_data(as_text=True)
|
|
data = json.loads(raw_data) if raw_data else {}
|
|
except Exception as e:
|
|
_logger.error("Uber Webhook invalid JSON: %s", str(e))
|
|
return request.make_json_response({'status': 'error', 'message': 'Invalid JSON'}, status=400)
|
|
|
|
_logger.info("Uber Webhook Received: %s", json.dumps(data, indent=2))
|
|
|
|
uber_delivery_id = (
|
|
data.get('delivery_id') or
|
|
data.get('id') or
|
|
(data.get('data') and data['data'].get('id')) or
|
|
(data.get('meta') and data['meta'].get('resource_id'))
|
|
)
|
|
status = (
|
|
data.get('status') or
|
|
(data.get('data') and data['data'].get('status')) or
|
|
data.get('event_type', '').split('.')[-1]
|
|
)
|
|
|
|
if uber_delivery_id:
|
|
order = request.env['pos.order'].sudo().search([('uber_delivery_id', '=', uber_delivery_id)], limit=1)
|
|
if order:
|
|
# Map Uber status to Odoo status
|
|
status_map = {
|
|
'pickup': 'pickup',
|
|
'pickup_ready': 'pickup',
|
|
'courier_assigned': 'pickup',
|
|
'pickup_completed': 'delivering',
|
|
'in_transit': 'delivering',
|
|
'dropoff_completed': 'delivered',
|
|
'delivered': 'delivered',
|
|
'cancelled': 'cancelled',
|
|
'canceled': 'cancelled'
|
|
}
|
|
new_status = status_map.get(status)
|
|
if new_status and new_status != order.uber_status:
|
|
vals = {'uber_status': new_status}
|
|
if new_status != 'pending':
|
|
vals['uber_alert_triggered'] = False
|
|
order.write(vals)
|
|
request.env['bus.bus'].sudo()._sendone('uber_status_updates', 'status_changed', {
|
|
'order_id': order.id,
|
|
'new_status': new_status
|
|
})
|
|
return request.make_json_response({'status': 'success'})
|
|
|
|
return request.make_json_response({'status': 'ignored'})
|
|
|
|
|
|
|
|
class UberDeliveryController(http.Controller):
|
|
|
|
@http.route('/shop/uber/quote', type='json', auth='public', website=True, csrf=False)
|
|
def uber_quote(self, address_data, **post):
|
|
"""Get Uber quote for a website address with cleaned address formatting and boundary validation"""
|
|
order = request.website.sale_get_order()
|
|
if not order:
|
|
return {'success': False, 'error': 'No active order'}
|
|
|
|
config = request.env['uber.config'].sudo().search([('active', '=', True)], limit=1)
|
|
if not config:
|
|
return {'success': False, 'error': 'Uber delivery is not configured'}
|
|
|
|
company = request.website.company_id
|
|
company_country = company.country_id
|
|
|
|
# User entered address fields
|
|
street = (address_data.get('street') or '').strip()
|
|
street2 = (address_data.get('street2') or '').strip()
|
|
full_street = f"{street}, {street2}" if street2 else street
|
|
city = (address_data.get('city') or '').strip()
|
|
zip_code = (address_data.get('zip') or '').strip()
|
|
country_name = (address_data.get('country') or '').strip()
|
|
country_id = address_data.get('country_id')
|
|
state_input = (address_data.get('state') or '').split('(')[0].strip()
|
|
|
|
# Resolve customer country
|
|
customer_country = None
|
|
if country_id:
|
|
try:
|
|
customer_country = request.env['res.country'].sudo().browse(int(country_id))
|
|
except Exception:
|
|
pass
|
|
if not customer_country and country_name:
|
|
customer_country = request.env['res.country'].sudo().search([
|
|
'|', ('name', '=ilike', country_name), ('code', '=ilike', country_name)
|
|
], limit=1)
|
|
|
|
# 1. Geographic Boundary Check: Customer Country vs Restaurant Operating Country
|
|
if customer_country and company_country and customer_country.id != company_country.id:
|
|
_logger.warning("Delivery rejected: Customer country '%s' does not match company country '%s'",
|
|
customer_country.name, company_country.name)
|
|
order.sudo()._remove_uber_delivery_fee()
|
|
return {
|
|
'success': False,
|
|
'error': f"Delivery is not available outside {company_country.name}. Please select Store Pickup or enter a Canadian address."
|
|
}
|
|
|
|
if country_name and company_country:
|
|
if country_name.lower() not in [company_country.name.lower(), company_country.code.lower()]:
|
|
_logger.warning("Delivery rejected: Country name '%s' does not match '%s'", country_name, company_country.name)
|
|
order.sudo()._remove_uber_delivery_fee()
|
|
return {
|
|
'success': False,
|
|
'error': f"Delivery is not available outside {company_country.name}. Please select Store Pickup or enter a Canadian address."
|
|
}
|
|
|
|
# Resolve State
|
|
state_code = company.state_id.code if company.state_id else "ON"
|
|
if company_country:
|
|
state_record = request.env['res.country.state'].sudo().search([
|
|
('country_id', '=', company_country.id),
|
|
'|', ('name', '=ilike', state_input), ('code', '=ilike', state_input)
|
|
], limit=1)
|
|
if state_record:
|
|
state_code = state_record.code
|
|
|
|
# Build STRUCTURED pickup address dynamically from Company in Settings
|
|
company_street = [s.strip() for s in [company.street, company.street2] if s and s.strip()]
|
|
pickup_address = {
|
|
"street_address": company_street if company_street else [company.name],
|
|
"city": (company.city or "").strip(),
|
|
"state": company.state_id.code if company.state_id else "",
|
|
"zip_code": (company.zip or "").strip(),
|
|
"country": company.country_id.code if company.country_id else "CA"
|
|
}
|
|
|
|
# Build STRUCTURED dropoff address (Object)
|
|
dropoff_address = {
|
|
"street_address": [full_street],
|
|
"city": city,
|
|
"state": state_code,
|
|
"zip_code": zip_code,
|
|
"country": company.country_id.code or "CA"
|
|
}
|
|
|
|
# Structured dict for distance calculator
|
|
dropoff_calc_data = {
|
|
'street': street,
|
|
'street2': street2,
|
|
'city': city,
|
|
'state': state_code,
|
|
'zip': zip_code,
|
|
'country': customer_country.name if customer_country else (country_name or (company_country.name if company_country else 'Canada'))
|
|
}
|
|
|
|
# For logging, create strings
|
|
p_str = f"{', '.join(pickup_address['street_address'])}, {pickup_address['city']} {pickup_address['state']} {pickup_address['zip_code']}"
|
|
d_str = f"{dropoff_address['street_address'][0]}, {dropoff_address['city']} {dropoff_address['state']} {dropoff_address['zip_code']}"
|
|
_logger.info("WEBSITE UBER QUOTE -\nPickup: [%s]\nDropoff: [%s, %s]", p_str, d_str, dropoff_calc_data['country'])
|
|
|
|
# POS ENCODING: The POS sends these as JSON-encoded STRINGS
|
|
result = config.get_uber_quote(
|
|
json.dumps(pickup_address),
|
|
json.dumps(dropoff_address),
|
|
dropoff_data=dropoff_calc_data,
|
|
company=company
|
|
)
|
|
|
|
if result.get('success'):
|
|
order.sudo()._add_uber_delivery_fee(result['fee_amount'])
|
|
return {
|
|
'success': True,
|
|
'fee': result['fee_amount'],
|
|
'distance_km': result.get('distance_km'),
|
|
'eta': result.get('estimated_arrival'),
|
|
'is_fallback': result.get('is_fallback', False),
|
|
'is_distance_based': result.get('is_distance_based', False),
|
|
'is_uber_direct': result.get('is_uber_direct', False),
|
|
}
|
|
else:
|
|
_logger.warning("Uber Quote Rejected/Failed: %s", result.get('error'))
|
|
order.sudo()._remove_uber_delivery_fee()
|
|
return result
|
|
|
|
|
|
class UberPolicyController(http.Controller):
|
|
|
|
@http.route(['/privacy', '/privacy-policy'], type='http', auth='public', website=True, sitemap=True)
|
|
def privacy_policy(self, **kwargs):
|
|
"""Render restaurant delivery privacy policy compliant with Uber Direct policies"""
|
|
company = request.website.company_id if hasattr(request, 'website') and request.website else request.env.company
|
|
today_str = fields.Date.today().strftime('%B %d, %Y')
|
|
return request.render('dine360_uber.privacy_policy_page', {
|
|
'company': company,
|
|
'current_date': today_str,
|
|
})
|
|
|
|
@http.route(['/uber/callback', '/uber/auth/callback'], type='http', auth='public', website=True, csrf=False)
|
|
def uber_callback(self, **kwargs):
|
|
"""Handle Uber OAuth callback redirect"""
|
|
return request.render('dine360_uber.uber_oauth_callback_page', {})
|
|
|