add dine360_uber module for Uber Eats and Direct delivery integration
This commit is contained in:
parent
d0024c6b83
commit
a209e448a0
@ -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': [
|
||||
|
||||
@ -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', {})
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
96
addons/dine360_uber/views/website_templates.xml
Normal file
96
addons/dine360_uber/views/website_templates.xml
Normal 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 & 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 & 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 & 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>
|
||||
Loading…
x
Reference in New Issue
Block a user