-
Notifications
You must be signed in to change notification settings - Fork 2
On‐demand Delivery Services
- Overview
- Setup and Installation
- Configuration
- Using ODDS
- API Reference
- Webhook Configuration
- Provider-Specific Guides
- Troubleshooting
- Best Practices
The ODDS (On-demand Delivery Services) system provides a unified interface for integrating with multiple on-demand delivery service providers. It is part of the Transport module and supports:
- Lalamove ✅ (Fully implemented)
- Transportify (Ready for implementation)
- GrabExpress (Ready for implementation)
- PandaGo (Ready for implementation)
- NinjaVAN (Ready for implementation)
- Unified Interface: Same API for all providers
- Multi-Provider Support: Switch between providers easily
- Automatic Quotation Caching: Quotations are cached for reuse
- Real-time Status Updates: Webhook integration for status changes
- Cross-Module Support: Works with Transport, Warehousing, Air Freight, and Sea Freight modules
- Frappe/ERPNext installation
- Logistics app installed
- Transport module enabled
- API credentials from your chosen ODDS provider(s)
-
Ensure ODDS module is available
- The ODDS system is automatically included with the Transport module
- No additional installation required
-
Create ODDS Settings
- Navigate to: Transport > ODDS Settings
- Or search for "ODDS Settings" in the search bar
-
Install Doctypes (if not already installed)
bench --site [your-site] migrate
- Go to Transport > ODDS Settings
- Or search for "ODDS Settings" in the search bar
- Enable ODDS Integration: Check the "ODDS Integration Enabled" checkbox
-
Set Default Provider: Choose your default provider from the dropdown:
- lalamove
- transportify
- grabexpress
- pandago
- ninjavan
- Enable Lalamove: Check "Enabled" under Lalamove Settings
- API Key: Enter your Lalamove API key
- API Secret: Enter your Lalamove API secret (stored encrypted)
- Environment: Select "Sandbox" for testing or "Production" for live use
- Market Code: Enter your market code (e.g., HK_HKG, SG_SIN, TH_BKK)
- Webhook Secret: (Optional) Enter webhook secret for webhook validation
Configure other providers similarly when they are implemented:
- Transportify
- GrabExpress
- PandaGo
- NinjaVAN
Click Save to store your configuration.
- Open a supported document (e.g., Transport Order, Transport Job, Warehouse Job)
- Look for the ODDS section or button
- Click Get Quotation
- Select your provider (or use default)
- Review the quotation details:
- Price
- Currency
- Distance
- Service type
- Expiration time
- After getting a quotation, click Create Order
- Confirm the order details
- The order will be created with the selected provider
- Track the order status in real-time
import frappe
result = frappe.call(
"logistics.transport.odds.api.get_odds_quotation",
doctype="Transport Order",
docname="TO-00001",
provider="lalamove" # Optional, uses default if not provided
)
if result["success"]:
quotation = result["data"]
print(f"Price: {quotation['price']} {quotation['currency']}")
print(f"Quotation ID: {quotation['quotation_id']}")
else:
print(f"Error: {result['error']}")import frappe
result = frappe.call(
"logistics.transport.odds.api.create_odds_order",
doctype="Transport Order",
docname="TO-00001",
quotation_id=None, # Optional, will get new quotation if None
provider="lalamove" # Optional
)
if result["success"]:
order = result["data"]
print(f"Order ID: {order['order_id']}")
print(f"Status: {order['status']}")
else:
print(f"Error: {result['error']}")import frappe
result = frappe.call(
"logistics.transport.odds.api.get_odds_order_status",
order_id="123456789",
provider="lalamove"
)
if result["success"]:
order = result["data"]
print(f"Status: {order['status']}")
print(f"Driver: {order.get('driver', {}).get('name', 'N/A')}")import frappe
result = frappe.call(
"logistics.transport.odds.api.sync_odds_order_status",
order_id="123456789",
provider="lalamove"
)import frappe
result = frappe.call(
"logistics.transport.odds.api.cancel_odds_order",
order_id="123456789",
provider="lalamove"
)from logistics.transport.odds.service import ODDSService
# Initialize service with provider
service = ODDSService(provider_code="lalamove")
# Get quotation
quotation = service.get_quotation("Transport Order", "TO-00001")
print(f"Price: {quotation['price']}")
# Create order
order = service.create_order("Transport Order", "TO-00001")
print(f"Order ID: {order['order_id']}")
# Get order details
order_details = service.get_order_details(order['order_id'])
# Sync status
service.sync_order_status(order['order_id'])
# Cancel order
service.cancel_order(order['order_id'])All endpoints are whitelisted and can be called from client-side scripts or server-side code.
Get a delivery quotation for a document.
Parameters:
-
doctype(required): Source doctype (e.g., "Transport Order") -
docname(required): Source document name -
provider(optional): Provider code (defaults to settings default)
Returns:
{
"success": true,
"data": {
"quotation_id": "123456789",
"price": 50.00,
"currency": "HKD",
"expires_at": "2025-01-01T12:00:00Z",
"service_type": "VAN",
"distance": 5.2
}
}Create a delivery order.
Parameters:
-
doctype(required): Source doctype -
docname(required): Source document name -
quotation_id(optional): Quotation ID (will get new if not provided) -
provider(optional): Provider code
Returns:
{
"success": true,
"data": {
"order_id": "987654321",
"status": "ASSIGNING_DRIVER",
"price": 50.00,
"currency": "HKD"
},
"message": "ODDS order created successfully"
}Get current order status.
Parameters:
-
order_id(required): ODDS order ID -
provider(optional): Provider code
Returns:
{
"success": true,
"data": {
"order_id": "987654321",
"status": "ON_GOING",
"driver": {
"name": "John Doe",
"phone": "+85212345678"
}
}
}Manually sync order status from provider.
Parameters:
-
order_id(required): ODDS order ID -
provider(optional): Provider code
Cancel an order.
Parameters:
-
order_id(required): ODDS order ID -
provider(optional): Provider code
Get list of available providers.
Returns:
{
"success": true,
"data": [
{"code": "lalamove", "name": "Lalamove"},
{"code": "transportify", "name": "Transportify"}
]
}Webhooks allow real-time status updates from ODDS providers.
-
Get Webhook URL
- Your webhook URL:
https://your-domain.com/api/method/logistics.transport.odds.webhook.handle_webhook?provider=lalamove
- Your webhook URL:
-
Configure in Lalamove Dashboard
- Log in to Lalamove Developer Dashboard
- Navigate to Webhook Settings
- Enter the webhook URL
- Configure events to subscribe to:
- ORDER_STATUS_CHANGED
- DRIVER_ASSIGNED
- ORDER_AMOUNT_CHANGED
- ORDER_REPLACED
- ORDER_EDITED
-
Set Webhook Secret
- In ODDS Settings, enter the webhook secret
- This is used to validate incoming webhooks
-
Test Webhook
- Use Lalamove's webhook testing tool
- Verify that status updates are received
- ORDER_STATUS_CHANGED: Order status updated
- DRIVER_ASSIGNED: Driver assigned to order
- ORDER_AMOUNT_CHANGED: Order price changed
- ORDER_REPLACED: Order replaced with new order
- ORDER_EDITED: Order details edited
- Webhooks are validated using HMAC signatures
- Invalid signatures are rejected
- Webhook secret should be kept secure
- ✅ Quotation retrieval
- ✅ Order creation
- ✅ Order cancellation
- ✅ Order status tracking
- ✅ Driver information
- ✅ Multiple stops (up to 15)
- ✅ Scheduled deliveries
- ✅ Webhook integration
- MOTORCYCLE: For small, lightweight items
- VAN: For medium-sized items
- TRUCK: For large items or bulk deliveries
Common market codes:
-
HK_HKG: Hong Kong -
SG_SIN: Singapore -
TH_BKK: Thailand -
MY_KUL: Malaysia -
PH_MNL: Philippines
- Quotation requests: 300/minute
- Order creation: 30/minute
- Order status: 300/minute
- Quotation Validity: Quotations expire in 5 minutes
- Order Creation: Create orders immediately after getting quotation
- Status Updates: Use webhooks for real-time updates
- Error Handling: Always check for quotation expiration
Configuration guides for other providers will be added as they are implemented.
Problem: Provider code is not recognized.
Solution:
- Check that provider is registered in the system
- Verify provider code spelling (case-insensitive)
- Ensure provider module is properly imported
Problem: API credentials are incorrect.
Solution:
- Verify API key and secret in ODDS Settings
- Check that provider is enabled
- Ensure credentials match the selected environment (sandbox/production)
Problem: Quotation has expired (valid for 5 minutes).
Solution:
- Get a new quotation
- Create order immediately after getting quotation
- Use
auto_get_quotation=Truewhen creating orders
Problem: Address is missing latitude/longitude.
Solution:
- Geocode the address first
- Ensure address has valid coordinates
- Use the address geocoding feature in the system
Problem: Webhooks are not being received.
Solution:
- Verify webhook URL is correct and accessible
- Check webhook secret matches provider configuration
- Ensure webhook is enabled in provider dashboard
- Check server logs for webhook errors
Problem: Order status is not syncing.
Solution:
- Manually sync using
sync_odds_order_statusAPI - Check webhook configuration
- Verify order ID is correct
- Check provider API status
import frappe
# Enable debug mode
frappe.conf.developer_mode = 1
# Check logs
frappe.log_error("Debug message", "ODDS Debug")- Go to Transport > ODDS Order
- Search for your order ID
- Check the "Order Data" field for raw provider response
- Review "Error Message" field if present
- Go to Transport > ODDS Quotation
- Search for quotation ID
- Verify quotation is still valid
- Check expiration time
- Get quotations on-demand: Don't cache quotations for too long
- Handle expiration: Always check quotation validity before creating orders
- Use cached quotations: System automatically caches valid quotations
- Create immediately: Create orders right after getting quotation
- Handle errors: Implement retry logic for failed orders
- Validate data: Ensure all required fields are present before creating order
- Use webhooks: Prefer webhooks over polling for status updates
- Sync periodically: Set up periodic sync for critical orders
- Handle all statuses: Implement handlers for all possible order statuses
- Graceful degradation: Handle provider unavailability gracefully
- Retry logic: Implement retry for transient errors
- User feedback: Provide clear error messages to users
- Protect credentials: Never expose API keys/secrets
- Validate webhooks: Always validate webhook signatures
- Use HTTPS: Ensure webhook URLs use HTTPS
- Limit access: Restrict ODDS Settings access to authorized users
- Cache quotations: Reuse valid quotations when possible
- Batch operations: Group multiple operations when possible
- Async processing: Use background jobs for non-critical operations
- Track orders: Monitor order creation and completion rates
- Log errors: Log all errors for debugging
- Monitor webhooks: Track webhook delivery success rate
- Alert on failures: Set up alerts for critical failures
from logistics.transport.odds.service import ODDSService
# Initialize service
service = ODDSService(provider_code="lalamove")
# Step 1: Get quotation
quotation = service.get_quotation("Transport Order", "TO-00001")
print(f"Quotation: {quotation['price']} {quotation['currency']}")
# Step 2: Create order
order = service.create_order(
"Transport Order",
"TO-00001",
quotation_id=quotation['quotation_id']
)
print(f"Order created: {order['order_id']}")
# Step 3: Monitor status
status = service.get_order_details(order['order_id'])
print(f"Status: {status['status']}")
# Step 4: Sync status (if needed)
service.sync_order_status(order['order_id'])from logistics.transport.odds.service import ODDSService
from logistics.transport.odds.exceptions import ODDSQuotationExpiredException
service = ODDSService(provider_code="lalamove")
try:
order = service.create_order("Transport Order", "TO-00001")
except ODDSQuotationExpiredException:
# Quotation expired, get new one and retry
quotation = service.get_quotation("Transport Order", "TO-00001")
order = service.create_order(
"Transport Order",
"TO-00001",
quotation_id=quotation['quotation_id']
)from logistics.transport.odds.service import ODDSService
# Use Lalamove
lalamove_service = ODDSService(provider_code="lalamove")
lalamove_order = lalamove_service.create_order("Transport Order", "TO-00001")
# Use Transportify (when implemented)
transportify_service = ODDSService(provider_code="transportify")
transportify_order = transportify_service.create_order("Transport Order", "TO-00001")// Get quotation
frappe.call({
method: 'logistics.transport.odds.api.get_odds_quotation',
args: {
doctype: 'Transport Order',
docname: 'TO-00001',
provider: 'lalamove'
},
callback: function(r) {
if (r.message.success) {
console.log('Quotation:', r.message.data);
} else {
frappe.show_alert({
message: 'Error: ' + r.message.error,
indicator: 'red'
});
}
}
});
// Create order
frappe.call({
method: 'logistics.transport.odds.api.create_odds_order',
args: {
doctype: 'Transport Order',
docname: 'TO-00001',
provider: 'lalamove'
},
callback: function(r) {
if (r.message.success) {
frappe.show_alert({
message: 'Order created successfully!',
indicator: 'green'
});
}
}
});- ODDS README:
logistics/transport/odds/README.md - API Documentation: See API Reference section above
- Provider Documentation: Check provider-specific guides
- Check Troubleshooting: Review common issues above
- Review Logs: Check error logs in Frappe
- Provider Support: Contact provider support for API issues
- System Administrator: Contact your system administrator for system issues
- Frappe Documentation: https://frappeframework.com/docs
- Lalamove API Docs: https://developers.lalamove.com/
- Transport Module Documentation: See Transport module docs
- ✅ Generic ODDS system implementation
- ✅ Lalamove provider fully implemented
- ✅ Multi-provider support structure
- ✅ Unified API endpoints
- ✅ Webhook integration
- ✅ Quotation caching
- ✅ Order tracking
Last Updated: January 2025
Version: 1.0
Module: Transport
Getting Started
- Getting Started
- Recent Platform Updates
- CargoNext v1 — Release Notes
- CargoNext v1 — Astraea Press Release
- Document Management
- Milestone Tracking
- Customer Portal
Setup and Settings
- Logistics Settings
- Credit Management
- Default Details and Relationships
- Sea Freight Settings
- Air Freight Settings
- Transport Settings
- Warehouse Settings
- Customs Settings
Sea Freight
- Sea Freight Module
- Sea Booking
- Sea Shipment
- Sea Consolidation
- Master Bill
- Shipper
- Consignee
- Container Type
- Container Management
Air Freight
Transport
- Transport Module
- Transport Order
- Transport Job
- Transport Consolidation
- Transport Leg
- Transport Plan
- Run Sheet
- Proof of Delivery
- Transport Template
- Load Type
- Transport Order — Inter-module Field Copy
Customs
Warehousing
- Warehousing Module
- Inbound Order
- Release Order
- Transfer Order
- VAS Order
- Stocktake Order
- Warehouse Job
- Warehouse Contract
- Gate Pass
- Periodic Billing
- Storage Location
- Handling Unit Type
Pricing Center
- Sales Quote
- Sales Quote — Separate Billings and Internal Job
- Change Request
- Sales Quote – Calculation Method
Job Management
- Job Management Module
- Revenue Recognition Policy — Accounts, Dates, and Charges
- Proforma GL Entries
- WIP and Accrual Reversal on Invoicing
Sustainability
Intercompany
Special Projects
Pages
Features
Reports
Glossary