diff --git a/addons/dine360_uber/__manifest__.py b/addons/dine360_uber/__manifest__.py index 6674227..af0717e 100644 --- a/addons/dine360_uber/__manifest__.py +++ b/addons/dine360_uber/__manifest__.py @@ -16,6 +16,7 @@ 'data/uber_cron_data.xml', 'views/uber_config_views.xml', 'views/pos_order_views.xml', + 'views/website_templates.xml', ], 'assets': { 'point_of_sale._assets_pos': [ diff --git a/addons/dine360_uber/controllers/main.py b/addons/dine360_uber/controllers/main.py index 4301647..d94f06c 100644 --- a/addons/dine360_uber/controllers/main.py +++ b/addons/dine360_uber/controllers/main.py @@ -1,4 +1,4 @@ -from odoo import http +from odoo import http, fields from odoo.http import request import json import logging @@ -187,3 +187,21 @@ class UberDeliveryController(http.Controller): 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', {}) + diff --git a/addons/dine360_uber/models/pos_order.py b/addons/dine360_uber/models/pos_order.py index 3b44b86..7c90ea3 100644 --- a/addons/dine360_uber/models/pos_order.py +++ b/addons/dine360_uber/models/pos_order.py @@ -134,8 +134,9 @@ class PosOrder(models.Model): "dropoff_address": dropoff_address, "dropoff_phone_number": _format_e164_phone(partner.phone or partner.mobile), "manifest_items": items, - "test_specifications": {"robo_courier_specification": {"mode": "auto"}} if config.environment == 'sandbox' else None } + if config.environment == 'sandbox': + payload["test_specifications"] = {"robo_courier_specification": {"mode": "auto"}} # 5. Call API api_url = f"https://api.uber.com/v1/customers/{customer_id}/deliveries" @@ -232,7 +233,12 @@ class PosOrder(models.Model): if not config or not config.customer_id: return - access_token = config._get_access_token() + try: + access_token = config._get_access_token() + except Exception as e: + _logger.warning("Uber status sync could not get access token: %s", str(e)) + return + headers = {'Authorization': f'Bearer {access_token}'} for order in self: @@ -280,8 +286,11 @@ class PosOrder(models.Model): return # 1. Sync status for all active orders - active_orders = self.search([('uber_status', 'in', ['pending', 'pickup', 'delivering'])]) - active_orders.action_sync_uber_status() + try: + active_orders = self.search([('uber_status', 'in', ['pending', 'pickup', 'delivering'])]) + active_orders.action_sync_uber_status() + except Exception as e: + _logger.error("Error syncing Uber statuses in cron: %s", str(e)) # 2. Trigger alerts for those still stuck in pending if config.timeout_minutes > 0: diff --git a/addons/dine360_uber/models/uber_config.py b/addons/dine360_uber/models/uber_config.py index bec41d5..326f023 100644 --- a/addons/dine360_uber/models/uber_config.py +++ b/addons/dine360_uber/models/uber_config.py @@ -109,6 +109,13 @@ class UberConfig(models.Model): # Uber Direct API v1 return "https://api.uber.com/v1" + def _get_token_url(self): + """Return OAuth token URL based on environment""" + self.ensure_one() + if self.environment == 'sandbox': + return "https://sandbox-login.uber.com/oauth/v2/token" + return "https://login.uber.com/oauth/v2/token" + def _get_access_token(self): """Get or refresh OAuth 2.0 access token""" self.ensure_one() @@ -124,7 +131,7 @@ class UberConfig(models.Model): scope = self.scope.strip() if self.scope else 'eats.deliveries' # Request new token - token_url = "https://login.uber.com/oauth/v2/token" + token_url = self._get_token_url() payload = { 'client_id': client_id, 'client_secret': client_secret, @@ -178,9 +185,9 @@ class UberConfig(models.Model): "Uber Direct Permission Required: Your Uber Client ID requires the 'eats.deliveries' scope.\n\n" "How to resolve:\n" "1. Go to https://developer.uber.com and log in.\n" - "2. Open your registered application.\n" - "3. In 'Products', add or request the 'Uber Direct' product to activate 'eats.deliveries'.\n" - "Once enabled by Uber, live delivery quotes and dispatches will connect immediately." + "2. Check that your app was created with the 'Uber Direct' / 'Deliveries' API Suite (not 'Others').\n" + "3. If created as 'Others', click 'Create Application' and choose 'Uber Direct' / 'Deliveries'.\n" + "4. Under 'Access Token' / 'Products', ensure 'eats.deliveries' is active." ) msg_type = "warning" else: @@ -194,7 +201,7 @@ class UberConfig(models.Model): client_id = self.client_id.strip() if self.client_id else '' client_secret = self.client_secret.strip() if self.client_secret else '' - token_url = "https://login.uber.com/oauth/v2/token" + token_url = self._get_token_url() payload = { 'client_id': client_id, 'client_secret': client_secret, @@ -215,27 +222,46 @@ class UberConfig(models.Model): headers = {'User-Agent': 'Dine360-Restaurant-Delivery/1.0 (delivery@dine360.com)'} queries = [] - # 1. Full address + # 1. Full address (street, city, state, zip, country) full_addr = ", ".join(filter(None, [street, city, state, zip_code, country])) if full_addr: queries.append(full_addr) - # 2. Postal code + Country - if zip_code and country: - queries.append(f"{zip_code}, {country}") + # 2. Street + City + State + Country + if street and city and (state or country): + queries.append(", ".join(filter(None, [street, city, state, country]))) # 3. Street + City + Country if street and city and country: queries.append(f"{street}, {city}, {country}") - # 4. City + State + Country + # 4. Postal code + City + Country + if zip_code and city and country: + queries.append(f"{zip_code}, {city}, {country}") + + # 5. Postal code + Country + if zip_code and country: + queries.append(f"{zip_code}, {country}") + + # 6. City + State + Country if city and country: queries.append(f"{city}, {state or ''}, {country}".strip()) + # Resolve country ISO code to restrict search boundaries + country_code_map = { + 'canada': 'ca', 'ca': 'ca', + 'united states': 'us', 'usa': 'us', 'us': 'us', + 'united kingdom': 'gb', 'uk': 'gb', 'gb': 'gb', + 'india': 'in', 'in': 'in' + } + cc = country_code_map.get((country or '').strip().lower()) + for q in queries: try: url = "https://nominatim.openstreetmap.org/search" params = {'q': q, 'format': 'json', 'limit': 1} + if cc: + params['countrycodes'] = cc resp = requests.get(url, params=params, headers=headers, timeout=5) if resp.status_code == 200: data = resp.json() diff --git a/addons/dine360_uber/views/website_templates.xml b/addons/dine360_uber/views/website_templates.xml new file mode 100644 index 0000000..561cc97 --- /dev/null +++ b/addons/dine360_uber/views/website_templates.xml @@ -0,0 +1,96 @@ + + + + + +