add Uber integration for POS/Sales orders and customize checkout address UI with delivery/pickup selection
This commit is contained in:
parent
3c6e33cc37
commit
4c80bdf027
@ -385,9 +385,10 @@
|
||||
|
||||
const addressText = selectedCard.querySelector('address')?.innerText || "";
|
||||
const parts = addressText.split('\n').map(p => p.trim());
|
||||
// Crude parsing
|
||||
const street = parts[1] || "";
|
||||
const cityZip = parts[2] || "";
|
||||
const zipMatch = addressText.match(/[A-Z][0-9][A-Z]\s?[0-9][A-Z][0-9]/);
|
||||
const zip = zipMatch ? zipMatch[0] : "";
|
||||
const city = parts[2] ? parts[2].split(' ')[0] : "";
|
||||
|
||||
msgBox.className = 'alert alert-info my-3';
|
||||
msgBox.innerHTML = "Verifying Uber coverage for this address...";
|
||||
@ -396,14 +397,15 @@
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ params: { address_data: {
|
||||
street: street,
|
||||
zip: cityZip.split(' ').pop(),
|
||||
city: cityZip.split(' ')[0],
|
||||
zip: zip,
|
||||
city: city,
|
||||
} } })
|
||||
}).then(r => r.json()).then(data => {
|
||||
if (data.result && data.result.success) {
|
||||
msgBox.className = 'alert alert-success my-3';
|
||||
msgBox.innerHTML = `<strong>✓ Uber Delivery Available!</strong> Fee: $${data.result.fee}`;
|
||||
document.querySelector('button[type="submit"]')?.removeAttribute('disabled');
|
||||
setTimeout(() => { window.location.reload(); }, 1200);
|
||||
} else {
|
||||
msgBox.className = 'alert alert-danger my-3';
|
||||
msgBox.innerHTML = `<strong>✕ Uber Direct: Invalid Operation</strong><br/>${data.result?.error || "Outside delivery radius."}`;
|
||||
@ -413,6 +415,12 @@
|
||||
}
|
||||
|
||||
document.querySelectorAll('input[name="partner_id"]').forEach(i => i.addEventListener('change', verifySelectedAddress));
|
||||
|
||||
// NEW: Ultimate trigger for every touch on an address box
|
||||
$(document).on('click', '#address_selection .card, #address_selection label', function() {
|
||||
setTimeout(verifySelectedAddress, 50); // Small delay to let radio button update
|
||||
});
|
||||
|
||||
verifySelectedAddress();
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@
|
||||
'dine360_uber/static/src/js/uber_pos.js',
|
||||
'dine360_uber/static/src/xml/uber_pos.xml',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'dine360_uber/static/src/js/uber_backend.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class PosOrder(models.Model):
|
||||
_inherit = 'pos.order'
|
||||
@ -158,6 +161,8 @@ class PosOrder(models.Model):
|
||||
order._add_uber_delivery_fee(delivery_fee)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
# Log the raw response so we can see which parameter is invalid
|
||||
_logger.error("Uber Direct Raw Error Response (%s): %s", e.response.status_code, e.response.text)
|
||||
# Try to parse the error message if it's JSON
|
||||
try:
|
||||
err_data = e.response.json()
|
||||
@ -207,30 +212,79 @@ class PosOrder(models.Model):
|
||||
})
|
||||
# order.message_post(body="Uber Direct delivery request cancelled.")
|
||||
|
||||
def action_sync_uber_status(self):
|
||||
"""Fetch latest status from Uber API and update POS order"""
|
||||
import requests
|
||||
config = self.env['uber.config'].search([('active', '=', True)], limit=1)
|
||||
if not config or not config.customer_id:
|
||||
return
|
||||
|
||||
access_token = config._get_access_token()
|
||||
headers = {'Authorization': f'Bearer {access_token}'}
|
||||
|
||||
for order in self:
|
||||
if not order.uber_delivery_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
api_url = f"https://api.uber.com/v1/customers/{config.customer_id}/deliveries/{order.uber_delivery_id}"
|
||||
response = requests.get(api_url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
_logger.info("Uber Status Raw Data for %s: %s", order.name, data)
|
||||
status_map = {
|
||||
'pending': 'pending',
|
||||
'pickup': 'pickup',
|
||||
'pickup_complete': 'delivering',
|
||||
'delivery_complete': 'delivered',
|
||||
'delivered': 'delivered',
|
||||
'cancelled': 'cancelled'
|
||||
}
|
||||
new_status = status_map.get(data.get('status'), order.uber_status)
|
||||
|
||||
vals = {'uber_status': new_status}
|
||||
# If status progressed beyond pending, HIDE the alert
|
||||
if new_status != 'pending':
|
||||
vals['uber_alert_triggered'] = False
|
||||
|
||||
if new_status != order.uber_status:
|
||||
# Send signal to UI to refresh the order form
|
||||
self.env['bus.bus']._sendone('uber_status_updates', 'status_changed', {
|
||||
'order_id': order.id,
|
||||
'new_status': new_status
|
||||
})
|
||||
|
||||
order.write(vals)
|
||||
_logger.info("Uber Status Synced for %s: %s", order.name, new_status)
|
||||
except Exception as e:
|
||||
_logger.error("Failed to sync Uber status for %s: %s", order.name, str(e))
|
||||
|
||||
@api.model
|
||||
def cron_check_uber_driver_assignment(self):
|
||||
"""Auto-alert if driver not assigned in X minutes"""
|
||||
"""Auto-alert and status update cron"""
|
||||
config = self.env['uber.config'].search([('active', '=', True)], limit=1)
|
||||
if not config or config.timeout_minutes <= 0:
|
||||
if not config:
|
||||
return
|
||||
|
||||
timeout_threshold = fields.Datetime.now() - datetime.timedelta(minutes=config.timeout_minutes)
|
||||
pending_orders = self.search([
|
||||
('uber_status', '=', 'pending'),
|
||||
('uber_request_time', '<=', timeout_threshold),
|
||||
('uber_alert_triggered', '=', False)
|
||||
])
|
||||
# 1. Sync status for all active orders
|
||||
active_orders = self.search([('uber_status', 'in', ['pending', 'pickup', 'delivering'])])
|
||||
active_orders.action_sync_uber_status()
|
||||
|
||||
for order in pending_orders:
|
||||
# Send notification to POS Users/Managers
|
||||
order.uber_alert_triggered = True
|
||||
# order.message_post(body="🚨 ALERT: No Uber driver assigned for over %s minutes! Please check Uber dashboard." % config.timeout_minutes)
|
||||
|
||||
# Broadcaster for UI Alert
|
||||
self.env['bus.bus']._sendone('pos_alerts', 'uber_timeout', {
|
||||
'order_name': order.name,
|
||||
'minutes': config.timeout_minutes
|
||||
})
|
||||
# 2. Trigger alerts for those still stuck in pending
|
||||
if config.timeout_minutes > 0:
|
||||
timeout_threshold = fields.Datetime.now() - datetime.timedelta(minutes=config.timeout_minutes)
|
||||
pending_orders = self.search([
|
||||
('uber_status', '=', 'pending'),
|
||||
('uber_request_time', '<=', timeout_threshold),
|
||||
('uber_alert_triggered', '=', False)
|
||||
])
|
||||
|
||||
for order in pending_orders:
|
||||
order.uber_alert_triggered = True
|
||||
self.env['bus.bus']._sendone('pos_alerts', 'uber_timeout', {
|
||||
'order_name': order.name,
|
||||
'minutes': config.timeout_minutes
|
||||
})
|
||||
|
||||
def action_view_uber_map(self):
|
||||
"""Open Uber Live Tracking Link"""
|
||||
|
||||
@ -23,15 +23,25 @@ class SaleOrder(models.Model):
|
||||
Carrier = self.env['delivery.carrier'].sudo()
|
||||
config = self.env['uber.config'].sudo().search([('active', '=', True)], limit=1)
|
||||
|
||||
carrier = Carrier.search([('name', 'ilike', 'Uber')], limit=1)
|
||||
if not carrier and config and config.delivery_product_id:
|
||||
_logger.info("Uber: Creating new Uber Delivery carrier")
|
||||
# Search for any carrier linked to the Uber product or with Uber in the name
|
||||
carrier = Carrier.search(['|', ('name', 'ilike', 'Uber'), ('product_id', '=', config.delivery_product_id.id if config.delivery_product_id else 0)], limit=1)
|
||||
|
||||
if not carrier and config:
|
||||
# Fallback product if one isn't set in config
|
||||
product = config.delivery_product_id
|
||||
if not product:
|
||||
product = self.env['product.product'].sudo().search([('name', 'ilike', 'Delivery')], limit=1)
|
||||
if not product:
|
||||
product = self.env['product.product'].sudo().search([], limit=1) # Last resort
|
||||
|
||||
_logger.info("Uber: Creating new Uber Delivery carrier using product %s", product.name)
|
||||
carrier = Carrier.create({
|
||||
'name': 'Uber Delivery',
|
||||
'name': 'Uber Direct Delivery',
|
||||
'delivery_type': 'fixed',
|
||||
'product_id': config.delivery_product_id.id,
|
||||
'product_id': product.id,
|
||||
'website_published': True,
|
||||
'fixed_price': 0.0,
|
||||
'active': True,
|
||||
})
|
||||
|
||||
if carrier:
|
||||
|
||||
27
addons/dine360_uber/static/src/js/uber_backend.js
Normal file
27
addons/dine360_uber/static/src/js/uber_backend.js
Normal file
@ -0,0 +1,27 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const UberStatusService = {
|
||||
dependencies: ["bus_service", "action"],
|
||||
start(env, { bus_service, action }) {
|
||||
// Odoo 17 Bus Service uses addChannel and subscribe
|
||||
bus_service.addChannel("uber_status_updates");
|
||||
bus_service.subscribe("notification", (notifications) => {
|
||||
for (const { type, payload } of notifications) {
|
||||
if (type === "uber_status_updates") {
|
||||
const currentController = action.currentController;
|
||||
if (currentController &&
|
||||
currentController.props.resModel === "pos.order" &&
|
||||
currentController.props.resId === payload.order_id) {
|
||||
|
||||
console.log("Uber Status Update Received. Refreshing Form...");
|
||||
action.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("uber_status_service", UberStatusService);
|
||||
@ -18,14 +18,28 @@
|
||||
<button name="action_view_uber_map"
|
||||
string="📍 Track Driver"
|
||||
type="object"
|
||||
invisible="not uber_tracking_url"
|
||||
invisible="not uber_tracking_url or uber_status in ['delivered', 'cancelled']"
|
||||
class="btn-primary"/>
|
||||
<button name="action_sync_uber_status"
|
||||
string="🔄 Sync Uber Status"
|
||||
type="object"
|
||||
invisible="is_uber_order == False or uber_status in ['delivered', 'cancelled']"
|
||||
class="btn-secondary"/>
|
||||
<button name="action_cancel_uber_delivery"
|
||||
string="Cancel Uber Delivery"
|
||||
type="object"
|
||||
invisible="is_uber_order == False or uber_status in ['delivered', 'cancelled']"
|
||||
class="btn-danger"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="after">
|
||||
<div class="alert alert-success m-0 p-2 text-center border-bottom-0 rounded-0" role="alert" invisible="uber_status != 'delivered'" style="font-weight: bold; background-color: #d1e7dd; color: #0f5132;">
|
||||
<i class="fa fa-check-circle me-2"/> THE UBER DRIVER HAS SUCCESSFULLY DELIVERED THIS ORDER
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<widget name="web_ribbon" title="Delivered" bg_color="text-bg-success" invisible="uber_status != 'delivered'"/>
|
||||
<widget name="web_ribbon" title="Cancelled" bg_color="text-bg-danger" invisible="uber_status != 'cancelled'"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='pos_reference']" position="after">
|
||||
<field name="is_uber_order" invisible="1"/>
|
||||
<field name="uber_status" readonly="1" invisible="is_uber_order == False" decoration-info="uber_status == 'pending'" decoration-warning="uber_status == 'pickup'" decoration-success="uber_status == 'delivered'"/>
|
||||
@ -33,7 +47,7 @@
|
||||
<field name="uber_delivery_fee" widget="monetary" invisible="not uber_delivery_fee"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="after">
|
||||
<div class="alert alert-danger mb-0" role="alert" invisible="not uber_alert_triggered">
|
||||
<div class="alert alert-danger mb-0" role="alert" invisible="not uber_alert_triggered or uber_status != 'pending'">
|
||||
🚨 <strong>Attention!</strong> Driver not assigned for over 15 minutes. Please contact Uber support.
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user