forked from alaguraj/odoo-testing-addons
232 lines
10 KiB
Python
232 lines
10 KiB
Python
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
import datetime
|
|
|
|
class PosOrder(models.Model):
|
|
_inherit = 'pos.order'
|
|
|
|
is_uber_order = fields.Boolean(string='Is Uber Order', default=False)
|
|
uber_order_id = fields.Char(string='Uber Order ID')
|
|
uber_delivery_id = fields.Char(string='Uber Delivery ID')
|
|
uber_status = fields.Selection([
|
|
('pending', 'Pending Uber Pickup'),
|
|
('pickup', 'Uber Driver Picked Up'),
|
|
('delivering', 'In Transit'),
|
|
('delivered', 'Delivered'),
|
|
('cancelled', 'Cancelled')
|
|
], string='Uber Delivery Status')
|
|
|
|
delivery_type = fields.Selection([
|
|
('none', 'None'),
|
|
('dine_in', 'Dine In'),
|
|
('takeaway', 'Takeaway'),
|
|
('uber', 'Uber Direct')
|
|
], string='Delivery Type', default='none')
|
|
|
|
# Advanced Features Fields
|
|
uber_tracking_url = fields.Char(string='Driver Tracking Link')
|
|
uber_eta = fields.Datetime(string='Predicted Delivery Time')
|
|
uber_delivery_fee = fields.Float(string='Uber Delivery Fee', readonly=True)
|
|
uber_request_time = fields.Datetime(string='Uber Request Time')
|
|
uber_alert_triggered = fields.Boolean(string='Driver Timeout Alert Sent', default=False)
|
|
|
|
def _check_all_lines_ready(self):
|
|
"""Check if all kitchen items in the order are ready or served"""
|
|
self.ensure_one()
|
|
kitchen_lines = self.lines.filtered(lambda l: l.product_id.is_kitchen_item)
|
|
if not kitchen_lines:
|
|
return False
|
|
return all(line.preparation_status in ['ready', 'served'] for line in kitchen_lines)
|
|
|
|
def action_request_uber_delivery(self):
|
|
"""Trigger Uber Direct delivery request via API"""
|
|
# Ensure imports are available inside method if not global (but better global)
|
|
# Adding imports here for safety, though cleaner at top
|
|
import requests
|
|
import json
|
|
|
|
for order in self:
|
|
if order.is_uber_order and order.uber_status and order.uber_status != 'cancelled':
|
|
continue
|
|
|
|
# 1. Get Configuration
|
|
config = self.env['uber.config'].search([('active', '=', True)], limit=1)
|
|
if not config:
|
|
raise UserError(_("Uber Integration is not configured. Please check Settings."))
|
|
|
|
customer_id = config.customer_id
|
|
if not customer_id:
|
|
raise UserError(_("Uber Customer ID is missing in configuration."))
|
|
|
|
# 2. Get Partner (Customer)
|
|
partner = order.partner_id
|
|
if not partner:
|
|
raise UserError(_("Customer is required for Uber delivery."))
|
|
if not partner.street or not partner.city or not partner.zip:
|
|
raise UserError(_("Customer address is incomplete (Street, City, Zip required)."))
|
|
|
|
# 3. Authenticate
|
|
try:
|
|
access_token = config._get_access_token()
|
|
except Exception as e:
|
|
raise UserError(_("Authentication Failed: %s") % str(e))
|
|
|
|
# 4. Prepare Payload
|
|
company = order.company_id
|
|
# Pickup Location (Restaurant)
|
|
pickup_address = json.dumps({
|
|
"street_address": [company.street],
|
|
"city": company.city,
|
|
"state": company.state_id.code or "",
|
|
"zip_code": company.zip,
|
|
"country": company.country_id.code or "US"
|
|
})
|
|
|
|
# Dropoff (Customer)
|
|
dropoff_address = json.dumps({
|
|
"street_address": [partner.street],
|
|
"city": partner.city,
|
|
"state": partner.state_id.code or "",
|
|
"zip_code": partner.zip,
|
|
"country": partner.country_id.code or "US"
|
|
})
|
|
|
|
items = []
|
|
for line in order.lines:
|
|
if not line.product_id.is_kitchen_item: # Optional filter
|
|
continue
|
|
items.append({
|
|
"name": line.full_product_name or line.product_id.name,
|
|
"quantity": int(line.qty),
|
|
"price": int(line.price_unit * 100), # Cents
|
|
"currency_code": order.currency_id.name
|
|
})
|
|
|
|
if not items:
|
|
# Fallback if no kitchen items found to at least send something
|
|
items.append({"name": "Food Order", "quantity": 1, "price": int(order.amount_total * 100), "currency_code": order.currency_id.name})
|
|
|
|
payload = {
|
|
"pickup_name": company.name,
|
|
"pickup_address": pickup_address,
|
|
"pickup_phone_number": company.phone or "+15555555555",
|
|
"dropoff_name": partner.name,
|
|
"dropoff_address": dropoff_address,
|
|
"dropoff_phone_number": partner.phone or partner.mobile or "+15555555555",
|
|
"manifest_items": items,
|
|
"test_specifications": {"robo_courier_specification": {"mode": "auto"}} if config.environment == 'sandbox' else None
|
|
}
|
|
|
|
# 5. Call API
|
|
api_url = f"https://api.uber.com/v1/customers/{customer_id}/deliveries"
|
|
headers = {
|
|
'Authorization': f'Bearer {access_token}',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
try:
|
|
# Note: Sending the request directly to create delivery
|
|
response = requests.post(api_url, headers=headers, json=payload)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
# 6. Process Success
|
|
# Uber API returns fee as integer (cents) usually? Need to check.
|
|
# Docs say 'fee' object with 'amount'
|
|
# Assuming 'fee' field in response is float or int.
|
|
# Careful: Uber often returns amounts in minor units or currency formatted.
|
|
# Standard response has `fee` integer? Let's assume standard float from JSON if parsed, or check specific field.
|
|
# Actually, check `fee` in response.
|
|
|
|
delivery_fee = 0.0
|
|
if 'fee' in data:
|
|
# Fee is in cents (minor units), convert to major units
|
|
delivery_fee = float(data['fee']) / 100.0
|
|
|
|
order.write({
|
|
'uber_status': 'pending',
|
|
'is_uber_order': True,
|
|
'uber_delivery_id': data.get('id'),
|
|
'uber_request_time': fields.Datetime.now(),
|
|
'uber_delivery_fee': delivery_fee,
|
|
'uber_tracking_url': data.get('tracking_url'),
|
|
'uber_eta': fields.Datetime.now() + datetime.timedelta(minutes=30) # Ideally parse `estimated_dropoff_time`
|
|
})
|
|
|
|
# Add charge to bill
|
|
if delivery_fee > 0:
|
|
order._add_uber_delivery_fee(delivery_fee)
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
error_msg = f"Uber API Error {e.response.status_code}: {e.response.text}"
|
|
raise UserError(_(error_msg))
|
|
except Exception as e:
|
|
raise UserError(_("Failed to request delivery: %s") % str(e))
|
|
|
|
def _add_uber_delivery_fee(self, amount):
|
|
"""Add the delivery fee as a line item if not already added"""
|
|
config = self.env['uber.config'].search([('active', '=', True)], limit=1)
|
|
if config and config.delivery_product_id:
|
|
# Check if fee line exists
|
|
fee_line = self.lines.filtered(lambda l: l.product_id == config.delivery_product_id)
|
|
if not fee_line:
|
|
taxes = config.delivery_product_id.taxes_id.compute_all(amount, self.pricelist_id.currency_id, 1, product=config.delivery_product_id, partner=self.partner_id)
|
|
self.write({'lines': [(0, 0, {
|
|
'product_id': config.delivery_product_id.id,
|
|
'full_product_name': config.delivery_product_id.name,
|
|
'price_unit': amount,
|
|
'qty': 1,
|
|
'tax_ids': [(6, 0, config.delivery_product_id.taxes_id.ids)],
|
|
'price_subtotal': taxes['total_excluded'],
|
|
'price_subtotal_incl': taxes['total_included'],
|
|
})]})
|
|
|
|
def action_cancel_uber_delivery(self):
|
|
for order in self:
|
|
if not order.uber_delivery_id:
|
|
continue
|
|
order.write({
|
|
'uber_status': 'cancelled',
|
|
'uber_delivery_id': False,
|
|
'is_uber_order': False,
|
|
'uber_tracking_url': False,
|
|
'uber_eta': False
|
|
})
|
|
# order.message_post(body="Uber Direct delivery request cancelled.")
|
|
|
|
@api.model
|
|
def cron_check_uber_driver_assignment(self):
|
|
"""Auto-alert if driver not assigned in X minutes"""
|
|
config = self.env['uber.config'].search([('active', '=', True)], limit=1)
|
|
if not config or config.timeout_minutes <= 0:
|
|
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)
|
|
])
|
|
|
|
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
|
|
})
|
|
|
|
def action_view_uber_map(self):
|
|
"""Open Uber Live Tracking Link"""
|
|
self.ensure_one()
|
|
if not self.uber_tracking_url:
|
|
raise UserError(_("No tracking link available yet."))
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'url': self.uber_tracking_url,
|
|
'target': 'new',
|
|
}
|