add dine360_uber module for Uber Eats and Direct delivery integration

This commit is contained in:
Alaguraj0361 2026-09-18 09:43:57 +05:30
parent d0024c6b83
commit a209e448a0
5 changed files with 165 additions and 15 deletions

View File

@ -16,6 +16,7 @@
'data/uber_cron_data.xml', 'data/uber_cron_data.xml',
'views/uber_config_views.xml', 'views/uber_config_views.xml',
'views/pos_order_views.xml', 'views/pos_order_views.xml',
'views/website_templates.xml',
], ],
'assets': { 'assets': {
'point_of_sale._assets_pos': [ 'point_of_sale._assets_pos': [

View File

@ -1,4 +1,4 @@
from odoo import http from odoo import http, fields
from odoo.http import request from odoo.http import request
import json import json
import logging import logging
@ -187,3 +187,21 @@ class UberDeliveryController(http.Controller):
order.sudo()._remove_uber_delivery_fee() order.sudo()._remove_uber_delivery_fee()
return result 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', {})

View File

@ -134,8 +134,9 @@ class PosOrder(models.Model):
"dropoff_address": dropoff_address, "dropoff_address": dropoff_address,
"dropoff_phone_number": _format_e164_phone(partner.phone or partner.mobile), "dropoff_phone_number": _format_e164_phone(partner.phone or partner.mobile),
"manifest_items": items, "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 # 5. Call API
api_url = f"https://api.uber.com/v1/customers/{customer_id}/deliveries" 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: if not config or not config.customer_id:
return return
try:
access_token = config._get_access_token() 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}'} headers = {'Authorization': f'Bearer {access_token}'}
for order in self: for order in self:
@ -280,8 +286,11 @@ class PosOrder(models.Model):
return return
# 1. Sync status for all active orders # 1. Sync status for all active orders
try:
active_orders = self.search([('uber_status', 'in', ['pending', 'pickup', 'delivering'])]) active_orders = self.search([('uber_status', 'in', ['pending', 'pickup', 'delivering'])])
active_orders.action_sync_uber_status() 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 # 2. Trigger alerts for those still stuck in pending
if config.timeout_minutes > 0: if config.timeout_minutes > 0:

View File

@ -109,6 +109,13 @@ class UberConfig(models.Model):
# Uber Direct API v1 # Uber Direct API v1
return "https://api.uber.com/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): def _get_access_token(self):
"""Get or refresh OAuth 2.0 access token""" """Get or refresh OAuth 2.0 access token"""
self.ensure_one() self.ensure_one()
@ -124,7 +131,7 @@ class UberConfig(models.Model):
scope = self.scope.strip() if self.scope else 'eats.deliveries' scope = self.scope.strip() if self.scope else 'eats.deliveries'
# Request new token # Request new token
token_url = "https://login.uber.com/oauth/v2/token" token_url = self._get_token_url()
payload = { payload = {
'client_id': client_id, 'client_id': client_id,
'client_secret': client_secret, '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" "Uber Direct Permission Required: Your Uber Client ID requires the 'eats.deliveries' scope.\n\n"
"How to resolve:\n" "How to resolve:\n"
"1. Go to https://developer.uber.com and log in.\n" "1. Go to https://developer.uber.com and log in.\n"
"2. Open your registered application.\n" "2. Check that your app was created with the 'Uber Direct' / 'Deliveries' API Suite (not 'Others').\n"
"3. In 'Products', add or request the 'Uber Direct' product to activate 'eats.deliveries'.\n" "3. If created as 'Others', click 'Create Application' and choose 'Uber Direct' / 'Deliveries'.\n"
"Once enabled by Uber, live delivery quotes and dispatches will connect immediately." "4. Under 'Access Token' / 'Products', ensure 'eats.deliveries' is active."
) )
msg_type = "warning" msg_type = "warning"
else: else:
@ -194,7 +201,7 @@ class UberConfig(models.Model):
client_id = self.client_id.strip() if self.client_id else '' client_id = self.client_id.strip() if self.client_id else ''
client_secret = self.client_secret.strip() if self.client_secret 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 = { payload = {
'client_id': client_id, 'client_id': client_id,
'client_secret': client_secret, 'client_secret': client_secret,
@ -215,27 +222,46 @@ class UberConfig(models.Model):
headers = {'User-Agent': 'Dine360-Restaurant-Delivery/1.0 (delivery@dine360.com)'} headers = {'User-Agent': 'Dine360-Restaurant-Delivery/1.0 (delivery@dine360.com)'}
queries = [] queries = []
# 1. Full address # 1. Full address (street, city, state, zip, country)
full_addr = ", ".join(filter(None, [street, city, state, zip_code, country])) full_addr = ", ".join(filter(None, [street, city, state, zip_code, country]))
if full_addr: if full_addr:
queries.append(full_addr) queries.append(full_addr)
# 2. Postal code + Country # 2. Street + City + State + Country
if zip_code and country: if street and city and (state or country):
queries.append(f"{zip_code}, {country}") queries.append(", ".join(filter(None, [street, city, state, country])))
# 3. Street + City + Country # 3. Street + City + Country
if street and city and country: if street and city and country:
queries.append(f"{street}, {city}, {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: if city and country:
queries.append(f"{city}, {state or ''}, {country}".strip()) 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: for q in queries:
try: try:
url = "https://nominatim.openstreetmap.org/search" url = "https://nominatim.openstreetmap.org/search"
params = {'q': q, 'format': 'json', 'limit': 1} params = {'q': q, 'format': 'json', 'limit': 1}
if cc:
params['countrycodes'] = cc
resp = requests.get(url, params=params, headers=headers, timeout=5) resp = requests.get(url, params=params, headers=headers, timeout=5)
if resp.status_code == 200: if resp.status_code == 200:
data = resp.json() data = resp.json()

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="privacy_policy_page" name="Privacy Policy">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty">
<section class="s_title py-5 bg-light border-bottom">
<div class="container text-center">
<h1 class="display-5 fw-bold text-dark mb-2">Privacy Policy</h1>
<p class="lead text-muted mb-0">How we protect and handle your personal and delivery information</p>
<small class="text-muted">Last updated: <t t-esc="current_date"/></small>
</div>
</section>
<section class="py-5">
<div class="container" style="max-width: 900px;">
<div class="card border-0 shadow-sm rounded-4 p-4 p-md-5 bg-white">
<div class="mb-4">
<h3 class="h5 fw-bold text-primary mb-3">1. Introduction</h3>
<p class="text-secondary">
Welcome to <strong t-esc="company.name"/>. We are committed to protecting your personal information and your right to privacy. This Privacy Policy outlines how we collect, use, and share your personal data when you use our online ordering platform and delivery services.
</p>
</div>
<div class="mb-4">
<h3 class="h5 fw-bold text-primary mb-3">2. Information We Collect</h3>
<p class="text-secondary">When placing an online food order or requesting delivery, we collect the following necessary information:</p>
<ul class="text-secondary">
<li><strong>Contact Details:</strong> Full name, telephone number, and email address.</li>
<li><strong>Delivery Information:</strong> Physical delivery address (street, city, province/state, postal code) and specific delivery instructions.</li>
<li><strong>Order Details:</strong> Items purchased, quantities, special dietary preferences, and total order amounts.</li>
</ul>
</div>
<div class="mb-4">
<h3 class="h5 fw-bold text-primary mb-3">3. Delivery Fulfillment &amp; Uber Direct Integration</h3>
<p class="text-secondary">
To fulfill and dispatch delivery orders to your address, we partner with <strong>Uber Direct</strong> (a white-label logistics and courier delivery service operated by Uber Technologies, Inc.).
</p>
<p class="text-secondary">
When you select delivery at checkout, the following information is shared securely with Uber Direct via encrypted APIs:
</p>
<ul class="text-secondary">
<li>Your delivery name, contact phone number, and physical dropoff address.</li>
<li>Order manifest items to ensure proper vehicle allocation and delivery handling.</li>
<li>Real-time driver tracking status to provide accurate delivery ETAs and notifications.</li>
</ul>
<p class="text-secondary">
Uber processes this information solely to facilitate driver assignment, routing, pickup from our restaurant, and drop-off at your location.
</p>
</div>
<div class="mb-4">
<h3 class="h5 fw-bold text-primary mb-3">4. Data Security &amp; Retention</h3>
<p class="text-secondary">
We implement standard security measures, including SSL/TLS encryption, to protect your personal information against unauthorized access, loss, or misuse. Information is retained only as long as necessary to complete your order, comply with financial reporting regulations, and resolve any customer service inquiries.
</p>
</div>
<div class="mb-4">
<h3 class="h5 fw-bold text-primary mb-3">5. Your Rights &amp; Contact Us</h3>
<p class="text-secondary">
You have the right to request access to, correction of, or deletion of your personal data held by us. If you have questions about this policy or our data practices, please contact us:
</p>
<div class="p-3 bg-light rounded-3 text-secondary">
<p class="mb-1"><strong t-esc="company.name"/></p>
<p class="mb-1" t-if="company.street"><t t-esc="company.street"/>, <t t-esc="company.city"/>, <t t-esc="company.state_id.code if company.state_id else ''"/> <t t-esc="company.zip or ''"/></p>
<p class="mb-1" t-if="company.phone">Phone: <t t-esc="company.phone"/></p>
<p class="mb-0" t-if="company.email">Email: <t t-esc="company.email"/></p>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<template id="uber_oauth_callback_page" name="Uber Connection Callback">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty d-flex align-items-center justify-content-center py-5" style="min-height: 70vh;">
<div class="container text-center" style="max-width: 600px;">
<div class="card border-0 shadow-lg p-5 rounded-4 bg-white">
<div class="mb-4">
<i class="fa fa-check-circle text-success" style="font-size: 64px;"></i>
</div>
<h2 class="fw-bold mb-2">Uber Direct Connected</h2>
<p class="text-muted mb-4">Your Uber authorization callback was processed successfully. You can return to Odoo and manage your delivery dispatches.</p>
<a href="/web" class="btn btn-dark rounded-pill px-4 py-2">Return to Odoo Dashboard</a>
</div>
</div>
</div>
</t>
</template>
</odoo>