shippingservice/shipping/views.py
vignesh b8ba39c3f8
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 39s
feat: implement shipping views, carrier allocation, models, and pickup scheduling architecture
2026-10-13 19:23:16 +05:30

1247 lines
59 KiB
Python

import json
import time
import hmac
import hashlib
from django.conf import settings
from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Q
from .models import ActivityLog, RiderAssignment
from .sfx_client import execute_sfx_call, get_base_url, get_default_token
from ekart.client import EkartAPIClient
def generate_mock_serviceability_response(pincodes_str, service='express'):
pin_list = [p.strip() for p in str(pincodes_str).split(',') if p.strip()]
if not pin_list:
pin_list = ['560077', '110009']
results = []
for pin in pin_list:
city_name = "Chennai" if pin.startswith('6') else ("Bengaluru" if pin.startswith('5') else ("Delhi" if pin.startswith('1') else ("Mumbai" if pin.startswith('4') else "Metro Area")))
if service == 'reverse':
results.append({
"pincode": pin,
"city": city_name,
"reverse_pickup_available": True
})
else:
results.append({
"pincode": pin,
"city": city_name,
"serviceable": True,
"cod_available": True
})
return {
"status": "success",
"data": {
"pincodes": results
}
}
def index_redirect(request):
return redirect('forward_view')
def forward_view(request):
if request.method == 'POST':
action = request.POST.get('action_type')
env = 'production'
token = request.POST.get('api_token', '').strip()
if not token:
token = get_default_token()
base_url = get_base_url()
headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
# Handle Mock/Simulation Mode toggle if real upstream fails or is blocked
is_mock = request.POST.get('use_mock', 'false') == 'true'
if action == 'serviceability':
pincodes = request.POST.get('pincodes', '560077,110009')
service = request.POST.get('service', 'express')
sfx_service = 'customer_delivery' if service in ['express', 'surface', 'customer_delivery'] else service
url = f"{base_url}/v1/clients/serviceability/?service={sfx_service}&page=1&count=100&pincodes={pincodes}"
if is_mock:
res_data = generate_mock_serviceability_response(pincodes, service)
log_res = record_mock_call('FORWARD', 'Serviceability Check', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Serviceability Check', 'GET', url, headers, env=env)
return JsonResponse(res)
elif action == 'generate_awb':
count = int(request.POST.get('count', 1))
client_code = request.POST.get('client_code', 'DEMO_CLIENT')
url = f"{base_url}/v3/clients/generate_marketplace_awb/"
payload = {"client_code": client_code, "count": count}
if is_mock:
res_data = {"status": "success", "data": {"awb_numbers": [f"SFX{int(time.time())}99"]}}
log_res = record_mock_call('FORWARD', 'Generate AWB', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Generate AWB', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'create_marketplace_order':
raw_payload = request.POST.get('payload', '{}')
try:
payload = json.loads(raw_payload)
except Exception as e:
return JsonResponse({'status': 'ERROR', 'response': f'Invalid JSON payload: {e}'}, status=400)
url = f"{base_url}/v3/clients/orders/"
if is_mock:
awb = payload.get('order_details', {}).get('awb_number') or f"SFX{int(time.time())}01"
order_id = payload.get('order_details', {}).get('client_order_id') or "ORD-MKT-101"
res_data = {"status": "success", "message": "Order created successfully", "data": {"client_order_id": order_id, "awb_number": awb, "order_status": "new"}}
log_res = record_mock_call('FORWARD', 'Create Marketplace Order', 'POST', url, headers, payload, 201, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Create Marketplace Order', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'create_warehouse_order':
raw_payload = request.POST.get('payload', '{}')
try:
payload = json.loads(raw_payload)
except Exception as e:
return JsonResponse({'status': 'ERROR', 'response': f'Invalid JSON payload: {e}'}, status=400)
url = f"{base_url}/v3/clients/orders/"
if is_mock:
awb = payload.get('order_details', {}).get('awb_number') or f"SFX{int(time.time())}02"
order_id = payload.get('order_details', {}).get('client_order_id') or "ORD-WH-505"
res_data = {"status": "success", "message": "Warehouse order created", "data": {"client_order_id": order_id, "awb_number": awb, "order_status": "new"}}
log_res = record_mock_call('FORWARD', 'Create Warehouse Order', 'POST', url, headers, payload, 201, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Create Warehouse Order', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'track_single':
awb = request.POST.get('awb_number', 'SFX9928374102')
url = f"{base_url}/v4/clients/orders/{awb}/track/"
if is_mock:
res_data = {
"status": "success",
"data": {
"awb_number": awb,
"current_status": "out_for_delivery",
"rider_details": {"name": "Rajesh Kumar", "phone": "+919876543210"},
"tracking_history": [
{"status": "new", "timestamp": "2026-09-25T08:00:00Z", "location": "Origin Hub"},
{"status": "out_for_delivery", "timestamp": "2026-09-25T14:00:00Z", "location": "Destination Hub"}
]
}
}
log_res = record_mock_call('FORWARD', 'Track Single Order V4', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Track Single Order V4', 'GET', url, headers, env=env)
return JsonResponse(res)
elif action == 'track_bulk':
awbs = request.POST.get('awb_numbers', 'SFX9928374102,SFX8817263541').split(',')
url = f"{base_url}/v4/clients/bulk_track/"
payload = {"awb_numbers": [a.strip() for a in awbs if a.strip()]}
if is_mock:
res_data = {
"status": "success",
"data": [
{"awb_number": a, "current_status": "in_transit"} for a in payload["awb_numbers"]
]
}
log_res = record_mock_call('FORWARD', 'Track Bulk Orders V4', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Track Bulk Orders V4', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'cancel_order':
client_order_id = request.POST.get('client_order_id', 'ORD-MKT-101')
awb = request.POST.get('awb_number', 'SFX9928374102')
reason = request.POST.get('reason', 'Customer requested cancellation')
url = f"{base_url}/v3/clients/orders/cancel/"
req_id = awb or client_order_id
payload = {"request_id": req_id, "cancel_remarks": reason}
if is_mock:
res_data = {"status": "success", "message": "Order cancelled successfully", "data": {"awb_number": awb, "status": "cancelled"}}
log_res = record_mock_call('FORWARD', 'Cancel Order', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Cancel Order', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'escalation':
awb = request.POST.get('awb_number', 'SFX9928374102')
category = request.POST.get('category', 'EXPEDITE_PICKUP')
description = request.POST.get('description', 'Pickup store closing early')
url = f"{base_url}/v1/clients/support/issue/"
payload = {"awb_number": awb, "category": category, "description": description}
if is_mock:
res_data = {"status": "success", "message": "Ticket raised", "data": {"ticket_id": "TKT-99120", "category": category}}
log_res = record_mock_call('FORWARD', 'Raise Escalation Ticket', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Raise Escalation Ticket', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'get_pod':
awb = request.POST.get('awb_number', 'SFX9928374102')
url = f"{base_url}/v1/clients/pod_details/"
payload = {"awb_number": awb}
if is_mock:
res_data = {"status": "success", "data": {"awb_number": awb, "signature_url": f"https://cdn.shadowfax.in/pod/{awb}.png", "otp_verified": True}}
log_res = record_mock_call('FORWARD', 'Get POD Details', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Get POD Details', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'generate_qr':
awb = request.POST.get('awb_number', 'SFX9928374102')
cod_amount = float(request.POST.get('cod_amount', 1499.0))
url = f"{base_url}/v2/clients/generate_qr_code/"
payload = {"awb_number": awb, "cod_amount": cod_amount}
if is_mock:
res_data = {"status": "success", "data": {"awb_number": awb, "cod_amount": cod_amount, "qr_code_string": f"upi://pay?pa=shadowfax@icici&am={cod_amount}"}}
log_res = record_mock_call('FORWARD', 'Generate Dynamic QR Code V2', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('FORWARD', 'Generate Dynamic QR Code V2', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
# GET Request: render template
recent_logs = ActivityLog.objects.filter(service_type='FORWARD')[:10]
return render(request, 'shipping/forward.html', {'recent_logs': recent_logs})
def reverse_view(request):
if request.method == 'POST':
action = request.POST.get('action_type')
env = 'production'
token = request.POST.get('api_token', '').strip()
if not token:
token = get_default_token()
base_url = get_base_url()
headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
is_mock = request.POST.get('use_mock', 'false') == 'true'
if action == 'reverse_serviceability':
pincodes = request.POST.get('pincodes', '560077,110009')
sfx_service = 'customer_pickup'
url = f"{base_url}/v1/clients/serviceability/?service={sfx_service}&page=1&count=100&pincodes={pincodes}"
if is_mock:
res_data = generate_mock_serviceability_response(pincodes, 'reverse')
log_res = record_mock_call('REVERSE', 'Reverse Serviceability Check', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Reverse Serviceability Check', 'GET', url, headers, env=env)
return JsonResponse(res)
elif action == 'generate_reverse_awb':
count = int(request.POST.get('count', 1))
client_code = request.POST.get('client_code', 'DEMO_CLIENT')
url = f"{base_url}/v3/clients/orders/generate_awb/"
payload = {"client_code": client_code, "count": count}
if is_mock:
res_data = {"status": "success", "data": {"awb_numbers": [f"SFX-REV-{int(time.time())}99"]}}
log_res = record_mock_call('REVERSE', 'Generate Reverse AWB', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Generate Reverse AWB', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'create_reverse_request':
raw_payload = request.POST.get('payload', '{}')
try:
payload = json.loads(raw_payload)
except Exception as e:
return JsonResponse({'status': 'ERROR', 'response': f'Invalid JSON payload: {e}'}, status=400)
url = f"{base_url}/v3/clients/requests"
if is_mock:
req_id = payload.get('client_request_id') or "REV-ORD-8801"
awb = payload.get('awb_number') or f"SFX-REV-{int(time.time())}01"
res_data = {"status": "success", "message": "Reverse pickup request created", "data": {"client_request_id": req_id, "awb_number": awb, "status": "new"}}
log_res = record_mock_call('REVERSE', 'Create Reverse Pickup Request', 'POST', url, headers, payload, 201, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Create Reverse Pickup Request', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'track_reverse_single':
req_id = request.POST.get('client_request_id', 'REV-ORD-8801')
url = f"{base_url}/v4/clients/requests/{req_id}"
if is_mock:
res_data = {
"status": "success",
"data": {
"client_request_id": req_id,
"current_status": "picked_up",
"qc_status": "PASSED",
"rider_details": {"name": "Ramesh Kumar", "phone": "+919876543210"},
"tracking_history": [
{"status": "new", "timestamp": "2026-09-25T10:00:00Z"},
{"status": "picked_up", "timestamp": "2026-09-25T12:00:00Z", "remarks": "Item passed QC"}
]
}
}
log_res = record_mock_call('REVERSE', 'Track Reverse Pickup V4', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Track Reverse Pickup V4', 'GET', url, headers, env=env)
return JsonResponse(res)
elif action == 'track_reverse_bulk':
req_ids = request.POST.get('client_request_ids', 'REV-ORD-8801,REV-ORD-8802').split(',')
url = f"{base_url}/v4/clients/requests/bulk_query"
payload = {"client_request_ids": [r.strip() for r in req_ids if r.strip()]}
if is_mock:
res_data = {"status": "success", "data": [{"client_request_id": r, "current_status": "picked_up"} for r in payload["client_request_ids"]]}
log_res = record_mock_call('REVERSE', 'Bulk Track Reverse Pickups V4', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Bulk Track Reverse Pickups V4', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'update_qc':
req_id = request.POST.get('client_request_id', 'REV-ORD-8801')
qc_check = request.POST.get('qc_check', 'true') == 'true'
serial_number = request.POST.get('serial_number', 'SN-9988112233')
url = f"{base_url}/v2/clients/requests/update_qc/"
payload = {"client_request_id": req_id, "qc_check": qc_check, "serial_number": serial_number}
if is_mock:
res_data = {"status": "success", "message": "QC Flag updated", "data": payload}
log_res = record_mock_call('REVERSE', 'Update QC Flag', 'PUT', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Update QC Flag', 'PUT', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'cancel_reverse':
req_id = request.POST.get('client_request_id', 'REV-ORD-8801')
reason = request.POST.get('reason', 'Customer withdrew return request')
url = f"{base_url}/v2/clients/requests/mark_cancel"
payload = {"client_request_id": req_id, "reason": reason}
if is_mock:
res_data = {"status": "success", "message": "Reverse request cancelled", "data": {"client_request_id": req_id, "status": "cancelled"}}
log_res = record_mock_call('REVERSE', 'Cancel Reverse Request', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Cancel Reverse Request', 'POST', url, headers, payload, env=env)
return JsonResponse(res)
elif action == 'get_reverse_pod':
req_id = request.POST.get('client_request_id', 'REV-ORD-8801')
url = f"{base_url}/v1/clients/pod_details/?client_request_id={req_id}"
if is_mock:
res_data = {"status": "success", "data": {"client_request_id": req_id, "signature_url": f"https://cdn.shadowfax.in/pop/{req_id}.png", "otp_verified": True}}
log_res = record_mock_call('REVERSE', 'Get Proof of Pickup (POP)', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
res = execute_sfx_call('REVERSE', 'Get Proof of Pickup (POP)', 'GET', url, headers, env=env)
return JsonResponse(res)
recent_logs = ActivityLog.objects.filter(service_type='REVERSE')[:10]
return render(request, 'shipping/reverse.html', {'recent_logs': recent_logs})
def logs_view(request):
query = request.GET.get('q', '')
service_filter = request.GET.get('service', '')
status_filter = request.GET.get('status', '')
logs = ActivityLog.objects.all()
if service_filter:
logs = logs.filter(service_type=service_filter)
if status_filter:
logs = logs.filter(status=status_filter)
if query:
logs = logs.filter(
Q(action_name__icontains=query) |
Q(endpoint_url__icontains=query) |
Q(request_body__icontains=query) |
Q(response_body__icontains=query)
)
return render(request, 'shipping/logs.html', {
'logs': logs[:100],
'query': query,
'service_filter': service_filter,
'status_filter': status_filter
})
def log_detail_api(request, log_id):
log_item = get_object_or_404(ActivityLog, id=log_id)
return JsonResponse({
'id': log_item.id,
'service_type': log_item.service_type,
'action_name': log_item.action_name,
'endpoint_url': log_item.endpoint_url,
'http_method': log_item.http_method,
'request_headers': log_item.request_headers,
'request_body': log_item.request_body,
'response_status_code': log_item.response_status_code,
'response_body': log_item.response_body,
'execution_time_ms': log_item.execution_time_ms,
'status': log_item.status,
'timestamp': log_item.timestamp.isoformat()
})
def clear_logs_view(request):
if request.method == 'POST':
ActivityLog.objects.all().delete()
return redirect('logs_view')
def print_label_view(request):
data = request.POST if request.method == 'POST' else request.GET
raw_pm = str(data.get('payment_mode') or '').upper()
try:
cod_amt_val = float(data.get('cod_amount', 0.0) or 0.0)
except (ValueError, TypeError):
cod_amt_val = 0.0
if raw_pm == 'PREPAID' or cod_amt_val == 0.0:
payment_mode_final = 'PREPAID'
else:
payment_mode_final = raw_pm or 'COD'
context = {
'awb_number': data.get('awb_number') or data.get('awb') or 'SFX9928374102',
'client_order_id': data.get('client_order_id') or data.get('order_id') or 'ORD-MKT-2026-101',
'order_type': data.get('order_type', 'Marketplace Forward'),
'payment_mode': payment_mode_final,
'cod_amount': str(cod_amt_val) if cod_amt_val > 0 else '0.00',
'actual_weight': data.get('actual_weight', '750g'),
'date': data.get('date', time.strftime('%Y-%m-%d')),
# FROM Address (Seller / Hub / Return Origin)
'from_name': data.get('from_name', 'StyleHub Retail Store'),
'from_phone': data.get('from_phone', '+91 9123456789'),
'from_address1': data.get('from_address1', 'Plot 12, Industrial Area Phase 2'),
'from_address2': data.get('from_address2', 'Near Metro Station'),
'from_city': data.get('from_city', 'Bengaluru'),
'from_state': data.get('from_state', 'Karnataka'),
'from_pincode': data.get('from_pincode', '560077'),
# TO Address (Customer Destination)
'to_name': data.get('to_name', 'Ananya Sharma'),
'to_phone': data.get('to_phone', '+91 9876543210'),
'to_address1': data.get('to_address1', 'Flat 402, Sunshine Apartments'),
'to_address2': data.get('to_address2', 'Indiranagar 10th Main Road'),
'to_city': data.get('to_city', 'Bengaluru'),
'to_state': data.get('to_state', 'Karnataka'),
'to_pincode': data.get('to_pincode', '560038'),
# Items Manifest
'item_name': data.get('item_name', 'Wireless Noise Cancelling Earbuds'),
'sku_code': data.get('sku_code', 'SKU-EAR-001'),
'quantity': data.get('quantity', '1'),
'product_value': data.get('product_value', '1499.00'),
# Dynamic UPI QR Code String
'qr_code_string': data.get('qr_code_string', 'upi://pay?pa=shadowfax@icici&pn=Shadowfax&am=1499.00&tr=SFX9928374102&tn=COD%20Payment'),
'auto_print': data.get('auto_print') in ['1', 'true', 'True'] or data.get('print') == '1',
}
return render(request, 'shipping/print_label.html', context)
@csrf_exempt
def webhook_listener_api(request):
if request.method == 'POST':
raw_body = request.body
try:
body_unicode = raw_body.decode('utf-8')
try:
body_data = json.loads(body_unicode)
body_formatted = json.dumps(body_data, indent=2)
except Exception:
body_formatted = body_unicode
except Exception as e:
body_formatted = f"Raw body error: {e}"
headers_dict = {k: v for k, v in request.META.items() if k.startswith('HTTP_') or k in ['CONTENT_TYPE', 'CONTENT_LENGTH']}
headers_formatted = json.dumps(headers_dict, indent=2)
# Signature validation if secret is configured
webhook_secret = getattr(settings, 'SFX_WEBHOOK_SECRET', '')
provided_sig = request.headers.get('X-Shadowfax-Signature', '')
sig_valid = True
if webhook_secret and provided_sig:
computed_sig = hmac.new(webhook_secret.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(computed_sig, provided_sig):
sig_valid = False
status_str = 'SUCCESS' if sig_valid else 'WARNING'
action_title = 'Incoming Webhook Callback' if sig_valid else 'Incoming Webhook Callback (Invalid Signature)'
log_entry = ActivityLog.objects.create(
service_type='WEBHOOK',
action_name=action_title,
endpoint_url=request.build_absolute_uri(),
http_method='POST',
request_headers=headers_formatted,
request_body=body_formatted,
response_status_code=200 if sig_valid else 401,
response_body=json.dumps({"status": "received" if sig_valid else "rejected", "sig_valid": sig_valid, "timestamp": time.time()}),
execution_time_ms=1.5,
status=status_str
)
if not sig_valid:
return JsonResponse({"status": "error", "message": "Invalid webhook signature", "log_id": log_entry.id}, status=401)
# Extract and save Rider / Executive Assignment Details
saved_rider_id = None
try:
b_data = json.loads(body_formatted) if isinstance(body_formatted, str) and body_formatted.startswith('{') else {}
if isinstance(b_data, dict):
awb = b_data.get('awb_number') or b_data.get('awb') or (b_data.get('order_details') or {}).get('awb_number')
client_ord = b_data.get('client_order_id') or b_data.get('order_id') or (b_data.get('order_details') or {}).get('client_order_id') or ''
r_name = b_data.get('rider_name') or (b_data.get('rider_details') or {}).get('name') or b_data.get('name')
r_phone = b_data.get('rider_phone') or b_data.get('rider_contact') or (b_data.get('rider_details') or {}).get('phone')
st = b_data.get('status') or b_data.get('current_status') or 'out_for_delivery'
lat = b_data.get('lat') or b_data.get('latitude')
lng = b_data.get('lng') or b_data.get('longitude')
if awb and (r_name or r_phone):
lat_val = float(lat) if lat is not None and str(lat).replace('.','',1).isdigit() else None
lng_val = float(lng) if lng is not None and str(lng).replace('.','',1).isdigit() else None
rider_obj, _ = RiderAssignment.objects.update_or_create(
awb_number=str(awb).strip(),
defaults={
'client_order_id': str(client_ord).strip(),
'rider_name': str(r_name or 'Assigned Carrier Executive').strip(),
'rider_phone': str(r_phone or 'N/A').strip(),
'status': str(st).strip(),
'latitude': lat_val,
'longitude': lng_val,
'raw_webhook_payload': body_formatted
}
)
saved_rider_id = rider_obj.id
except Exception as parse_err:
pass
return JsonResponse({"status": "success", "message": "Webhook logged & rider details saved", "log_id": log_entry.id, "rider_assignment_id": saved_rider_id})
return JsonResponse({"error": "POST method required"}, status=405)
def webhooks_ui_view(request):
if request.method == 'POST':
# Simulate webhook trigger
event_type = request.POST.get('event_type', 'out_for_delivery')
order_id = request.POST.get('order_id', 'ORD-MKT-2026-101')
awb = request.POST.get('awb_number', 'SFX9928374102')
service_type = request.POST.get('service_type', 'FORWARD')
sim_payload = {
"client_order_id": order_id,
"awb_number": awb,
"status": event_type,
"sub_status": event_type,
"remarks": f"Simulated {service_type} webhook event: {event_type}",
"reason_code": "SIM_OK",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ")
}
ActivityLog.objects.create(
service_type='WEBHOOK',
action_name=f'Simulated Callback ({service_type}: {event_type})',
endpoint_url='/api/webhook/simulator',
http_method='POST',
request_headers=json.dumps({"Content-Type": "application/json", "X-Simulated": "True"}, indent=2),
request_body=json.dumps(sim_payload, indent=2),
response_status_code=200,
response_body=json.dumps({"status": "simulated_success"}),
execution_time_ms=0.8,
status='SUCCESS'
)
return redirect('webhooks_ui_view')
webhook_logs = ActivityLog.objects.filter(service_type='WEBHOOK')[:30]
return render(request, 'shipping/webhooks.html', {'webhook_logs': webhook_logs})
def record_mock_call(service_type, action_name, method, url, headers, payload, status_code, response_data):
req_body_str = json.dumps(payload, indent=2) if payload else ""
req_headers_str = json.dumps(headers, indent=2) if headers else ""
res_body_str = json.dumps(response_data, indent=2)
log_entry = ActivityLog.objects.create(
service_type=service_type,
action_name=f"{action_name} (Simulation)",
endpoint_url=url,
http_method=method,
request_headers=req_headers_str,
request_body=req_body_str,
response_status_code=status_code,
response_body=res_body_str,
execution_time_ms=3.2,
status='SUCCESS'
)
return {
"log_id": log_entry.id,
"status_code": status_code,
"execution_time_ms": 3.2,
"status": "SUCCESS",
"response": res_body_str
}
# ==============================================================================
# REST API Endpoints for External Microservices (e.g. seller_central_backend)
# ==============================================================================
@csrf_exempt
def api_healthcheck(request):
return JsonResponse({
"status": "healthy",
"service": "Shadowfax Shipping Microservice",
"timestamp": time.time()
})
@csrf_exempt
def api_forward_router(request, action_name):
if request.method not in ['POST', 'GET']:
return JsonResponse({'error': 'POST or GET method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception:
data = {}
env = data.get('env') or request.GET.get('env') or 'production'
use_mock_param = request.GET.get('use_mock')
if use_mock_param is not None:
is_mock = use_mock_param.lower() in ('true', '1')
else:
is_mock = data.get('use_mock', False)
token = (data.get('token') or request.headers.get('Authorization', '').replace('Token ', '')).strip()
if not token:
token = get_default_token()
base_url = get_base_url()
headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
if action_name == 'serviceability':
pincodes = data.get('pincodes', '560077,110009')
service = data.get('service', 'express')
sfx_service = 'customer_delivery' if service in ['express', 'surface', 'customer_delivery'] else service
url = f"{base_url}/v1/clients/serviceability/?service={sfx_service}&page=1&count=100&pincodes={pincodes}"
if is_mock:
res_data = generate_mock_serviceability_response(pincodes, service)
log_res = record_mock_call('FORWARD', 'API: Serviceability Check', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Serviceability Check', 'GET', url, headers, env=env))
elif action_name in ['generate-awb', 'generate_awb']:
count = int(data.get('count', 1))
client_code = data.get('client_code', 'DEMO_CLIENT')
url = f"{base_url}/v3/clients/generate_marketplace_awb/"
payload = {"client_code": client_code, "count": count}
if is_mock:
res_data = {"status": "success", "data": {"awb_numbers": [f"SFX{int(time.time())}99"]}}
log_res = record_mock_call('FORWARD', 'API: Generate AWB', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Generate AWB', 'POST', url, headers, payload, env=env))
elif action_name in ['create-order', 'create_order']:
payload = data.get('payload', data)
url = f"{base_url}/v3/clients/orders/"
if is_mock:
awb = payload.get('order_details', {}).get('awb_number') or f"SFX{int(time.time())}01"
order_id = payload.get('order_details', {}).get('client_order_id') or "ORD-MKT-101"
res_data = {"status": "success", "message": "Order created", "data": {"client_order_id": order_id, "awb_number": awb, "order_status": "new"}}
log_res = record_mock_call('FORWARD', 'API: Create Order', 'POST', url, headers, payload, 201, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Create Order', 'POST', url, headers, payload, env=env))
elif action_name == 'track':
awb = request.GET.get('awb') or data.get('awb_number', 'SFX9928374102')
url = f"{base_url}/v4/clients/orders/{awb}/track/"
if is_mock:
res_data = {"status": "success", "data": {"awb_number": awb, "current_status": "out_for_delivery"}}
log_res = record_mock_call('FORWARD', 'API: Track Order', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Track Order', 'GET', url, headers, env=env))
elif action_name in ['bulk-track', 'bulk_track']:
raw_awbs = data.get('awb_numbers', ['SFX9928374102', 'SFX8817263541'])
if isinstance(raw_awbs, str):
raw_awbs = [a.strip() for a in raw_awbs.split(',') if a.strip()]
url = f"{base_url}/v4/clients/bulk_track/"
payload = {"awb_numbers": raw_awbs}
if is_mock:
res_data = {"status": "success", "data": [{"awb_number": a, "current_status": "in_transit"} for a in raw_awbs]}
log_res = record_mock_call('FORWARD', 'API: Bulk Track Orders V4', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Bulk Track Orders V4', 'POST', url, headers, payload, env=env))
elif action_name == 'cancel':
raw_payload = data.get('payload', data)
req_id = raw_payload.get('request_id') or raw_payload.get('awb_number') or raw_payload.get('awb') or raw_payload.get('client_order_id')
remarks = raw_payload.get('cancel_remarks') or raw_payload.get('reason') or 'Seller cancelled order'
awb_str = str(raw_payload.get('awb_number') or req_id or '').strip()
# Handle Delhivery shipment cancellation
if awb_str.isdigit() or awb_str.startswith(('698', 'DELHI', '14', '12')):
try:
from delhivery.client import DelhiveryAPIClient
d_client = DelhiveryAPIClient()
d_res = d_client.cancel_shipment(awb_str)
return JsonResponse({"status": "success", "message": "Delhivery shipment cancelled", "carrier": "DELHIVERY", "data": d_res})
except Exception as d_err:
pass
payload = {"request_id": str(req_id).strip(), "cancel_remarks": str(remarks).strip()}
url = f"{base_url}/v3/clients/orders/cancel/"
if is_mock:
res_data = {"status": "success", "message": "Order cancelled", "responseCode": 200, "responseMsg": "Pickup request is cancelled successfully"}
log_res = record_mock_call('FORWARD', 'API: Cancel Order', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Cancel Order', 'POST', url, headers, payload, env=env))
elif action_name == 'escalation':
payload = data.get('payload', data)
url = f"{base_url}/v1/clients/support/issue/"
if is_mock:
res_data = {"status": "success", "message": "Ticket raised", "data": {"ticket_id": "TKT-99120", "category": payload.get('category', 'EXPEDITE_PICKUP')}}
log_res = record_mock_call('FORWARD', 'API: Support Escalation', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Support Escalation', 'POST', url, headers, payload, env=env))
elif action_name in ['get-pod', 'get_pod', 'pod']:
awb = data.get('awb_number') or request.GET.get('awb', 'SFX9928374102')
url = f"{base_url}/v1/clients/pod_details/"
payload = {"awb_number": awb}
if is_mock:
res_data = {"status": "success", "data": {"awb_number": awb, "signature_url": f"https://cdn.shadowfax.in/pod/{awb}.png", "otp_verified": True}}
log_res = record_mock_call('FORWARD', 'API: Get POD', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Get POD', 'POST', url, headers, payload, env=env))
elif action_name in ['generate-qr', 'generate_qr']:
awb = data.get('awb_number', 'SFX9928374102')
cod_amount = float(data.get('cod_amount', 1499.0))
url = f"{base_url}/v2/clients/generate_qr_code/"
payload = {"awb_number": awb, "cod_amount": cod_amount}
if is_mock:
res_data = {"status": "success", "data": {"awb_number": awb, "cod_amount": cod_amount, "qr_code_string": f"upi://pay?pa=shadowfax@icici&am={cod_amount}"}}
log_res = record_mock_call('FORWARD', 'API: Generate Dynamic QR', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('FORWARD', 'API: Generate Dynamic QR', 'POST', url, headers, payload, env=env))
elif action_name in ['rider-details', 'rider_details', 'executive-details']:
awb = data.get('awb_number') or request.GET.get('awb') or request.GET.get('awb_number')
if not awb:
return JsonResponse({'status': 'error', 'message': 'awb query param or awb_number is required'}, status=400)
rider = RiderAssignment.objects.filter(awb_number=str(awb).strip()).first()
if rider:
return JsonResponse({
'status': 'success',
'data': {
'awb_number': rider.awb_number,
'client_order_id': rider.client_order_id,
'rider_name': rider.rider_name,
'rider_phone': rider.rider_phone,
'status': rider.status,
'latitude': rider.latitude,
'longitude': rider.longitude,
'updated_at': rider.updated_at.isoformat()
}
})
else:
return JsonResponse({
'status': 'success',
'message': 'No executive assigned yet for this AWB',
'data': {
'awb_number': awb,
'rider_name': None,
'rider_phone': None,
'status': 'pending_assignment'
}
})
return JsonResponse({'error': f'Action {action_name} not supported'}, status=400)
@csrf_exempt
def api_reverse_router(request, action_name):
if request.method not in ['POST', 'GET', 'PUT']:
return JsonResponse({'error': 'POST, GET or PUT method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception:
data = {}
env = data.get('env') or request.GET.get('env') or 'production'
use_mock_param = request.GET.get('use_mock')
if use_mock_param is not None:
is_mock = use_mock_param.lower() in ('true', '1')
else:
is_mock = data.get('use_mock', False)
token = (data.get('token') or request.headers.get('Authorization', '').replace('Token ', '')).strip()
if not token:
token = get_default_token()
base_url = get_base_url()
headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
if action_name == 'serviceability':
pincodes = data.get('pincodes', '560077,110009')
sfx_service = 'customer_pickup'
url = f"{base_url}/v1/clients/serviceability/?service={sfx_service}&page=1&count=100&pincodes={pincodes}"
if is_mock:
res_data = generate_mock_serviceability_response(pincodes, 'reverse')
log_res = record_mock_call('REVERSE', 'API: Reverse Serviceability Check', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Reverse Serviceability Check', 'GET', url, headers, env=env))
elif action_name in ['generate-awb', 'generate_awb']:
count = int(data.get('count', 1))
client_code = data.get('client_code', 'DEMO_CLIENT')
url = f"{base_url}/v3/clients/orders/generate_awb/"
payload = {"client_code": client_code, "count": count}
if is_mock:
res_data = {"status": "success", "data": {"awb_numbers": [f"SFX-REV-{int(time.time())}99"]}}
log_res = record_mock_call('REVERSE', 'API: Generate Reverse AWB', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Generate Reverse AWB', 'POST', url, headers, payload, env=env))
elif action_name in ['create-request', 'create_request']:
payload = data.get('payload', data)
url = f"{base_url}/v3/clients/requests"
if is_mock:
req_id = payload.get('client_request_id') or "REV-ORD-8801"
awb = payload.get('awb_number') or f"SFX-REV-{int(time.time())}01"
res_data = {"status": "success", "message": "Reverse pickup request created", "data": {"client_request_id": req_id, "awb_number": awb, "status": "new"}}
log_res = record_mock_call('REVERSE', 'API: Create Reverse Request', 'POST', url, headers, payload, 201, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Create Reverse Request', 'POST', url, headers, payload, env=env))
elif action_name == 'track':
req_id = request.GET.get('client_request_id') or data.get('client_request_id', 'REV-ORD-8801')
url = f"{base_url}/v4/clients/requests/{req_id}"
if is_mock:
res_data = {"status": "success", "data": {"client_request_id": req_id, "current_status": "picked_up", "qc_status": "PASSED"}}
log_res = record_mock_call('REVERSE', 'API: Track Reverse Request', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Track Reverse Request', 'GET', url, headers, env=env))
elif action_name in ['bulk-track', 'bulk_track']:
raw_ids = data.get('client_request_ids', ['REV-ORD-8801', 'REV-ORD-8802'])
if isinstance(raw_ids, str):
raw_ids = [r.strip() for r in raw_ids.split(',') if r.strip()]
url = f"{base_url}/v4/clients/requests/bulk_query"
payload = {"client_request_ids": raw_ids}
if is_mock:
res_data = {"status": "success", "data": [{"client_request_id": r, "current_status": "picked_up"} for r in raw_ids]}
log_res = record_mock_call('REVERSE', 'API: Bulk Track Reverse V4', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Bulk Track Reverse V4', 'POST', url, headers, payload, env=env))
elif action_name in ['update-qc', 'update_qc']:
payload = data.get('payload', data)
url = f"{base_url}/v2/clients/requests/update_qc/"
if is_mock:
res_data = {"status": "success", "message": "QC Flag updated", "data": payload}
log_res = record_mock_call('REVERSE', 'API: Update QC Flag', 'PUT', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Update QC Flag', 'PUT', url, headers, payload, env=env))
elif action_name == 'cancel':
payload = data.get('payload', data)
url = f"{base_url}/v2/clients/requests/mark_cancel"
if is_mock:
res_data = {"status": "success", "message": "Reverse request cancelled"}
log_res = record_mock_call('REVERSE', 'API: Cancel Reverse Request', 'POST', url, headers, payload, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Cancel Reverse Request', 'POST', url, headers, payload, env=env))
elif action_name in ['get-pod', 'get_pod', 'pod']:
req_id = data.get('client_request_id') or request.GET.get('client_request_id', 'REV-ORD-8801')
url = f"{base_url}/v1/clients/pod_details/?client_request_id={req_id}"
if is_mock:
res_data = {"status": "success", "data": {"client_request_id": req_id, "signature_url": f"https://cdn.shadowfax.in/pop/{req_id}.png", "otp_verified": True}}
log_res = record_mock_call('REVERSE', 'API: Get Proof of Pickup', 'GET', url, headers, None, 200, res_data)
return JsonResponse(log_res)
return JsonResponse(execute_sfx_call('REVERSE', 'API: Get Proof of Pickup', 'GET', url, headers, env=env))
return JsonResponse({'error': f'Action {action_name} not supported'}, status=400)
@csrf_exempt
def api_logs_export(request):
query = request.GET.get('q', '')
service_filter = request.GET.get('service', '')
logs = ActivityLog.objects.all()
if service_filter:
logs = logs.filter(service_type=service_filter)
if query:
logs = logs.filter(Q(action_name__icontains=query) | Q(request_body__icontains=query) | Q(response_body__icontains=query))
logs_data = [
{
"id": l.id,
"service_type": l.service_type,
"action_name": l.action_name,
"endpoint_url": l.endpoint_url,
"http_method": l.http_method,
"response_status_code": l.response_status_code,
"execution_time_ms": l.execution_time_ms,
"status": l.status,
"timestamp": l.timestamp.isoformat()
} for l in logs[:100]
]
return JsonResponse({"count": len(logs_data), "logs": logs_data})
@csrf_exempt
def api_ekart_router(request, action_name):
if request.method not in ['POST', 'GET', 'PUT', 'DELETE']:
return JsonResponse({'error': 'POST, GET, PUT or DELETE method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception:
data = {}
client = EkartAPIClient()
try:
if action_name in ['create-shipment', 'create_shipment']:
res = client.create_shipment(data.get('payload', data))
return JsonResponse(res)
elif action_name in ['cancel-shipment', 'cancel_shipment']:
tracking_id = data.get('tracking_id') or request.GET.get('tracking_id')
res = client.cancel_shipment(tracking_id)
return JsonResponse(res)
elif action_name == 'track':
tracking_id = data.get('tracking_id') or request.GET.get('tracking_id')
res = client.track_shipment(tracking_id)
return JsonResponse(res)
elif action_name == 'serviceability':
pincode = request.GET.get('pincode') or data.get('pincode')
if pincode:
res = client.check_serviceability_v2(pincode)
else:
res = client.check_serviceability_v3(
pickup_pincode=data.get('pickup_pincode', '560077'),
drop_pincode=data.get('drop_pincode', '110009'),
length=data.get('length', 10),
width=data.get('width', 10),
height=data.get('height', 10),
weight=data.get('weight', 500),
payment_type=data.get('payment_type', 'Prepaid'),
invoice_amount=data.get('invoice_amount', 1000)
)
return JsonResponse(res, safe=False)
elif action_name == 'estimate':
res = client.get_rate_estimate(
pickup_pincode=data.get('pickup_pincode', '560077'),
drop_pincode=data.get('drop_pincode', '110009'),
weight=data.get('weight', 500),
length=data.get('length', 10),
width=data.get('width', 10),
height=data.get('height', 10),
service_type=data.get('service_type', 'SURFACE'),
invoice_amount=data.get('invoice_amount', 1000.0),
cod_amount=data.get('cod_amount', 0.0)
)
return JsonResponse(res)
elif action_name == 'manifest':
tracking_ids = data.get('tracking_ids', [])
res = client.generate_manifest(tracking_ids)
return JsonResponse(res)
elif action_name == 'label':
tracking_ids = data.get('tracking_ids', [])
res = client.download_label(tracking_ids, json_only=True)
return JsonResponse(res)
return JsonResponse({'error': f'Ekart action {action_name} not supported'}, status=400)
except Exception as e:
return JsonResponse({'status': False, 'error': str(e)}, status=500)
def carrier_checker_view(request):
"""
Interactive web UI to test and compare first-mile and last-mile serviceability
across Shadowfax, Delhivery, and Ekart.
"""
default_sfx_token = get_default_token('production')
try:
from delhivery.client import DEFAULT_DELHIVERY_API_TOKEN
default_delhivery_token = DEFAULT_DELHIVERY_API_TOKEN
except Exception:
default_delhivery_token = 'e70dfd63665335ee6e0a1094f6d34e17a126f5a6'
context = {
'default_sfx_token': default_sfx_token,
'default_delhivery_token': default_delhivery_token,
}
return render(request, 'shipping/carrier_checker.html', context)
@csrf_exempt
def api_carrier_pincode_check(request):
"""
REST API endpoint to check city, first-mile availability, last-mile availability,
delivery types, and estimated charge for a single pincode across all three carriers (Shadowfax, Delhivery, Ekart).
GET or POST /api/v1/carrier/check-pincode/
"""
if request.method not in ['POST', 'GET']:
return JsonResponse({'error': 'POST or GET method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception:
data = {}
pincode = data.get('pincode') or request.GET.get('pincode') or data.get('pin') or request.GET.get('pin')
weight = data.get('weight') or request.GET.get('weight') or data.get('package_weight') or request.GET.get('package_weight')
use_mock = data.get('use_mock') is True or str(data.get('use_mock', '')).lower() == 'true' or request.GET.get('use_mock') == 'true'
if not pincode:
return JsonResponse({'status': 'error', 'message': 'Pincode is required.'}, status=400)
if not weight or float(weight) <= 0:
return JsonResponse({'status': 'error', 'message': 'Package weight is required. Please provide package weight in grams.'}, status=400)
from .carrier_allocator import check_single_pincode_all_carriers
res = check_single_pincode_all_carriers(pincode=pincode, weight=float(weight), use_mock=use_mock)
return JsonResponse(res, status=200 if res.get('status') == 'success' else 400)
@csrf_exempt
def api_carrier_availability(request):
"""
Unified REST API endpoint to check serviceability across all registered carriers (Shadowfax, Ekart, Delhivery).
POST or GET /api/v1/carrier/check-availability/
"""
if request.method not in ['POST', 'GET']:
return JsonResponse({'error': 'POST or GET method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception:
data = {}
pickup_pincode = data.get('pickup_pincode') or data.get('origin_pincode') or request.GET.get('pickup_pincode') or request.GET.get('origin_pincode') or request.GET.get('pickup') or '560077'
drop_pincode = data.get('drop_pincode') or data.get('destination_pincode') or request.GET.get('drop_pincode') or request.GET.get('destination_pincode') or request.GET.get('drop') or '110009'
payment_type = data.get('payment_type') or data.get('payment_mode') or request.GET.get('payment_type') or 'Prepaid'
weight = data.get('weight') or data.get('weight_grams') or request.GET.get('weight') or data.get('package_weight') or request.GET.get('package_weight') or request.GET.get('weight_grams')
env = data.get('env') or request.GET.get('env') or 'production'
sfx_token = data.get('sfx_token') or request.GET.get('sfx_token')
delhivery_token = data.get('delhivery_token') or request.GET.get('delhivery_token')
use_mock = data.get('use_mock') is True or str(data.get('use_mock', '')).lower() == 'true' or request.GET.get('use_mock') == 'true'
if not weight or float(weight) <= 0:
return JsonResponse({'status': 'error', 'message': 'Package weight is required. Please provide package weight in grams.'}, status=400)
from .carrier_allocator import check_all_carriers_availability
res = check_all_carriers_availability(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
payment_type=payment_type,
weight=float(weight),
env=env,
sfx_token=sfx_token,
delhivery_token=delhivery_token,
use_mock=use_mock
)
return JsonResponse(res, status=200)
@csrf_exempt
def api_carrier_auto_dispatch(request):
"""
Unified REST API endpoint to automatically check live carrier availability and dispatch order to the optimal carrier.
POST /api/v1/carrier/auto-dispatch/
"""
if request.method != 'POST':
return JsonResponse({'error': 'POST method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'Invalid JSON payload: {e}'}, status=400)
selection_strategy = data.get('selection_strategy', 'CHEAPEST')
preferred_carrier = data.get('preferred_carrier', 'SHADOWFAX')
allow_fallback = data.get('allow_fallback', True)
env = data.get('env', 'production')
payload = data.get('payload', data)
from .carrier_allocator import dispatch_auto_allocated_order
res_data, status_code = dispatch_auto_allocated_order(
order_payload=payload,
selection_strategy=selection_strategy,
preferred_carrier=preferred_carrier,
allow_fallback=allow_fallback,
env=env
)
return JsonResponse(res_data, status=status_code)
@csrf_exempt
def api_carrier_schedule_pickup(request):
"""
Unified REST API endpoint to query pickup capabilities and schedule courier pickups across carriers.
GET /api/v1/carrier/schedule-pickup/?carrier=<DELHIVERY|EKART|SHADOWFAX>
Returns supported scheduling options (date/time slot flags, available slots).
POST /api/v1/carrier/schedule-pickup/
Schedules pickup based on carrier capabilities.
Payload:
{
"carrier": "DELHIVERY" | "EKART" | "SHADOWFAX",
"pickup_date": "2026-10-15",
"pickup_time": "14:00:00" | "afternoon",
"pickup_location": "Store / Warehouse Name",
"expected_package_count": 1,
"tracking_ids": ["1234567890123"],
"client_order_id": "ORD-1234",
"notes": "Call before arrival"
}
"""
from .carrier_allocator import get_carrier_pickup_capabilities, schedule_carrier_pickup, check_pickup_schedule_for_tracking
if request.method == 'GET':
tracking_id = (
request.GET.get('tracking_id')
or request.GET.get('awb')
or request.GET.get('tracking')
or request.GET.get('waybill')
)
if tracking_id:
res_data, status_code = check_pickup_schedule_for_tracking(tracking_id)
return JsonResponse(res_data, status=status_code)
carrier = request.GET.get('carrier', 'ALL')
caps = get_carrier_pickup_capabilities(carrier)
return JsonResponse({'status': 'success', 'data': caps}, status=200)
if request.method != 'POST':
return JsonResponse({'error': 'POST or GET method required'}, status=405)
try:
data = json.loads(request.body.decode('utf-8')) if request.body else {}
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'Invalid JSON payload: {e}'}, status=400)
carrier = data.get('carrier') or request.GET.get('carrier') or 'DELHIVERY'
pickup_date = data.get('pickup_date') or data.get('date')
pickup_time = data.get('pickup_time') or data.get('time') or data.get('slot')
pickup_location = data.get('pickup_location') or data.get('location') or data.get('warehouse')
package_count = data.get('expected_package_count') or data.get('package_count') or data.get('count') or 1
tracking_ids = data.get('tracking_ids') or data.get('tracking_id') or data.get('awb') or data.get('waybill')
client_order_id = data.get('client_order_id') or data.get('order_id')
notes = data.get('notes') or data.get('instructions')
env = data.get('env', 'production')
if not pickup_date:
return JsonResponse({'status': 'error', 'message': 'pickup_date is required (format YYYY-MM-DD)'}, status=400)
res_data, status_code = schedule_carrier_pickup(
carrier=carrier,
pickup_date=pickup_date,
pickup_time=pickup_time,
pickup_location=pickup_location,
package_count=package_count,
tracking_ids=tracking_ids,
client_order_id=client_order_id,
notes=notes,
env=env
)
return JsonResponse(res_data, status=status_code)
@csrf_exempt
def api_carrier_check_pickup_schedule(request):
"""
REST API endpoint to check courier pickup schedule with date and time for a given tracking / AWB.
GET /api/v1/carrier/pickup-schedule/?tracking_id=<awb>
POST /api/v1/carrier/pickup-schedule/ with JSON {"tracking_id": "<awb>"}
"""
if request.method not in ['GET', 'POST']:
return JsonResponse({'error': 'GET or POST method required'}, status=405)
data = {}
if request.method == 'POST' and request.body:
try:
data = json.loads(request.body.decode('utf-8'))
except Exception:
data = {}
tracking_id = (
data.get('tracking_id')
or data.get('awb')
or data.get('tracking')
or data.get('waybill')
or request.GET.get('tracking_id')
or request.GET.get('awb')
or request.GET.get('tracking')
or request.GET.get('waybill')
)
if not tracking_id:
return JsonResponse({
'status': 'error',
'message': 'tracking_id or awb parameter is required (e.g. ?tracking_id=69873010000232)'
}, status=400)
from .carrier_allocator import check_pickup_schedule_for_tracking
res_data, status_code = check_pickup_schedule_for_tracking(tracking_id)
return JsonResponse(res_data, status=status_code)