190 lines
8.5 KiB
Python

from odoo import http
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