shippingservice/shipping/carrier_allocator.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

1932 lines
83 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import time
import requests
from django.conf import settings
from .sfx_client import get_base_url, get_default_token, execute_sfx_call
from .models import ActivityLog
from ekart.client import EkartAPIClient
from delhivery.client import DelhiveryAPIClient
from .delhivery_serviceability import (
get_delhivery_pincode_info,
is_delhivery_serviceable,
check_delhivery_route_serviceability,
resolve_delhivery_location,
STATE_CODE_TO_NAME
)
PINCODE_PREFIX_DIRECTORY = {
# Tamil Nadu & Puducherry
'600': ('Chennai', 'Tamil Nadu'),
'601': ('Chennai', 'Tamil Nadu'),
'602': ('Thiruvallur', 'Tamil Nadu'),
'603': ('Chengalpattu', 'Tamil Nadu'),
'604': ('Tindivanam', 'Tamil Nadu'),
'605': ('Puducherry', 'Puducherry'),
'606': ('Villupuram', 'Tamil Nadu'),
'607': ('Cuddalore', 'Tamil Nadu'),
'608': ('Chidambaram', 'Tamil Nadu'),
'609': ('Mayiladuthurai', 'Tamil Nadu'),
'610': ('Thiruvarur', 'Tamil Nadu'),
'611': ('Nagapattinam', 'Tamil Nadu'),
'612': ('Kumbakonam', 'Tamil Nadu'),
'613': ('Thanjavur', 'Tamil Nadu'),
'614': ('Pattukkottai', 'Tamil Nadu'),
'620': ('Tiruchirappalli', 'Tamil Nadu'),
'621': ('Lalgudi', 'Tamil Nadu'),
'622': ('Pudukkottai', 'Tamil Nadu'),
'623': ('Ramanathapuram', 'Tamil Nadu'),
'624': ('Dindigul', 'Tamil Nadu'),
'625': ('Madurai', 'Tamil Nadu'),
'626': ('Virudhunagar', 'Tamil Nadu'),
'627': ('Tirunelveli', 'Tamil Nadu'),
'628': ('Thoothukudi', 'Tamil Nadu'),
'629': ('Nagercoil', 'Tamil Nadu'),
'630': ('Karaikudi', 'Tamil Nadu'),
'631': ('Arakkonam', 'Tamil Nadu'),
'632': ('Vellore', 'Tamil Nadu'),
'635': ('Hosur', 'Tamil Nadu'),
'636': ('Salem', 'Tamil Nadu'),
'637': ('Namakkal', 'Tamil Nadu'),
'638': ('Erode', 'Tamil Nadu'),
'639': ('Karur', 'Tamil Nadu'),
'641': ('Coimbatore', 'Tamil Nadu'),
'642': ('Pollachi', 'Tamil Nadu'),
'643': ('Udhagamandalam', 'Tamil Nadu'),
# Karnataka
'560': ('Bengaluru', 'Karnataka'),
'561': ('Bengaluru Rural', 'Karnataka'),
'562': ('Bengaluru Rural', 'Karnataka'),
'563': ('Kolar', 'Karnataka'),
'570': ('Mysuru', 'Karnataka'),
'571': ('Mysuru Rural', 'Karnataka'),
'572': ('Tumakuru', 'Karnataka'),
'573': ('Hassan', 'Karnataka'),
'574': ('Mangaluru Rural', 'Karnataka'),
'575': ('Mangaluru', 'Karnataka'),
'576': ('Udupi', 'Karnataka'),
'577': ('Shivamogga', 'Karnataka'),
'580': ('Hubballi', 'Karnataka'),
'581': ('Sirsi', 'Karnataka'),
'582': ('Gadag', 'Karnataka'),
'583': ('Ballari', 'Karnataka'),
'584': ('Raichur', 'Karnataka'),
'585': ('Kalaburagi', 'Karnataka'),
'586': ('Vijayapura', 'Karnataka'),
'587': ('Bagalkote', 'Karnataka'),
'590': ('Belagavi', 'Karnataka'),
# Delhi & NCR
'110': ('Delhi', 'Delhi'),
'121': ('Faridabad', 'Haryana'),
'122': ('Gurugram', 'Haryana'),
'201': ('Noida', 'Uttar Pradesh'),
# Maharashtra
'400': ('Mumbai', 'Maharashtra'),
'401': ('Thane', 'Maharashtra'),
'410': ('Lonavala', 'Maharashtra'),
'411': ('Pune', 'Maharashtra'),
'412': ('Pune Rural', 'Maharashtra'),
'414': ('Ahmednagar', 'Maharashtra'),
'415': ('Satara', 'Maharashtra'),
'416': ('Kolhapur', 'Maharashtra'),
'421': ('Kalyan', 'Maharashtra'),
'422': ('Nashik', 'Maharashtra'),
'431': ('Chhatrapati Sambhajinagar', 'Maharashtra'),
'440': ('Nagpur', 'Maharashtra'),
# Telangana & AP
'500': ('Hyderabad', 'Telangana'),
'501': ('Hyderabad Rural', 'Telangana'),
'502': ('Medak', 'Telangana'),
'503': ('Nizamabad', 'Telangana'),
'505': ('Karimnagar', 'Telangana'),
'506': ('Warangal', 'Telangana'),
'515': ('Anantapur', 'Andhra Pradesh'),
'516': ('Kadapa', 'Andhra Pradesh'),
'517': ('Tirupati', 'Andhra Pradesh'),
'518': ('Kurnool', 'Andhra Pradesh'),
'520': ('Vijayawada', 'Andhra Pradesh'),
'521': ('Machilipatnam', 'Andhra Pradesh'),
'522': ('Guntur', 'Andhra Pradesh'),
'530': ('Visakhapatnam', 'Andhra Pradesh'),
'533': ('Kakinada', 'Andhra Pradesh'),
'534': ('Eluru', 'Andhra Pradesh'),
# Kerala
'670': ('Kannur', 'Kerala'),
'671': ('Kasaragod', 'Kerala'),
'673': ('Kozhikode', 'Kerala'),
'676': ('Malappuram', 'Kerala'),
'678': ('Palakkad', 'Kerala'),
'680': ('Thrissur', 'Kerala'),
'682': ('Kochi', 'Kerala'),
'683': ('Aluva', 'Kerala'),
'686': ('Kottayam', 'Kerala'),
'688': ('Alappuzha', 'Kerala'),
'689': ('Thiruvalla', 'Kerala'),
'690': ('Kayamkulam', 'Kerala'),
'691': ('Kollam', 'Kerala'),
'695': ('Thiruvananthapuram', 'Kerala'),
# West Bengal
'700': ('Kolkata', 'West Bengal'),
'711': ('Howrah', 'West Bengal'),
'712': ('Hooghly', 'West Bengal'),
'713': ('Durgapur', 'West Bengal'),
'734': ('Siliguri', 'West Bengal'),
# Gujarat
'380': ('Ahmedabad', 'Gujarat'),
'382': ('Gandhinagar', 'Gujarat'),
'390': ('Vadodara', 'Gujarat'),
'395': ('Surat', 'Gujarat'),
'360': ('Rajkot', 'Gujarat'),
# Rajasthan
'302': ('Jaipur', 'Rajasthan'),
'342': ('Jodhpur', 'Rajasthan'),
'313': ('Udaipur', 'Rajasthan'),
# UP
'226': ('Lucknow', 'Uttar Pradesh'),
'208': ('Kanpur', 'Uttar Pradesh'),
'221': ('Varanasi', 'Uttar Pradesh'),
'211': ('Prayagraj', 'Uttar Pradesh'),
'282': ('Agra', 'Uttar Pradesh'),
# MP & Bihar
'452': ('Indore', 'Madhya Pradesh'),
'462': ('Bhopal', 'Madhya Pradesh'),
'800': ('Patna', 'Bihar')
}
def resolve_city_state_by_pincode(pincode, default_city=None, default_state=None):
"""
Selects the destination city and state based directly on the customer delivery pincode.
Uses official Delhivery 18,699 serviceable pincode database as the primary authority.
"""
pin_str = str(pincode or "").strip()
# 1. Official Delhivery Serviceability Matrix (18,699 Pincodes)
delhivery_loc = resolve_delhivery_location(pin_str)
if delhivery_loc:
return delhivery_loc
# 2. Regional 3-digit prefix directory
prefix3 = pin_str[:3]
if prefix3 in PINCODE_PREFIX_DIRECTORY:
return PINCODE_PREFIX_DIRECTORY[prefix3]
# 3. Live Delhivery API query if 6 digits
if len(pin_str) == 6 and pin_str.isdigit():
try:
client = DelhiveryAPIClient()
res = client.check_serviceability(pin_str)
codes = res.get('delivery_codes', [])
if codes:
p_data = codes[0].get('postal_code', {})
d_city = p_data.get('city')
d_st_code = p_data.get('state_code')
d_state = STATE_CODE_TO_NAME.get(d_st_code, d_st_code)
if d_city and d_state:
return d_city, d_state
except Exception:
pass
if default_city and default_state:
return default_city, default_state
if pin_str.startswith('6'):
return ('Chennai', 'Tamil Nadu')
elif pin_str.startswith('5'):
return ('Bengaluru', 'Karnataka')
elif pin_str.startswith('1'):
return ('Delhi', 'Delhi')
elif pin_str.startswith('4'):
return ('Mumbai', 'Maharashtra')
elif pin_str.startswith('7'):
return ('Kolkata', 'West Bengal')
return ('Bengaluru', 'Karnataka')
def check_shadowfax_availability(pickup_pincode, drop_pincode, service='customer_delivery', weight=None, env='production', token=None, use_mock=False):
"""
Checks Shadowfax pincode serviceability and logistics rate estimation directly from live Shadowfax API.
Provides detailed first-mile (pickup hub) and last-mile (doorstep delivery) status.
Package weight (in grams) is a mandatory input.
"""
if weight is None or float(weight) <= 0:
raise ValueError("Package weight (in grams) is required for Shadowfax rate evaluation.")
weight_slabs = max(1, int(weight) // 500)
estimated_rate = round(45.0 + (weight_slabs * 15.0), 2)
p_city, p_state = resolve_city_state_by_pincode(pickup_pincode)
d_city, d_state = resolve_city_state_by_pincode(drop_pincode)
if use_mock:
return {
"carrier": "SHADOWFAX",
"serviceable": True,
"cod_available": True,
"estimated_cost": estimated_rate,
"currency": "INR",
"first_mile": {
"serviceable": True,
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"hub_active": True,
"pickup_type": "Seller Store / Warehouse Sweep",
"message": f"First-mile pickup serviceable in {p_city}, {p_state} (Simulation)"
},
"last_mile": {
"serviceable": True,
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"cod_available": True,
"delivery_type": "Doorstep Delivery (Express)",
"message": f"Last-mile delivery serviceable in {d_city}, {d_state} (Simulation)"
},
"details": {"mock": True, "pincodes": [str(pickup_pincode), str(drop_pincode)]}
}
base_url = get_base_url(env)
auth_token = str(token).strip() if token else get_default_token(env)
headers = {"Authorization": f"Token {auth_token}", "Content-Type": "application/json"}
try:
# 1. First-mile pickup check (seller/origin location)
url_pkp = f"{base_url}/v1/clients/serviceability/?service=seller_pickup&page=1&count=100&pincodes={pickup_pincode}"
res_pkp = execute_sfx_call('FORWARD', 'SFX First-Mile Pickup Check', 'GET', url_pkp, headers, env=env)
pickup_info = None
fm_ok = False
if res_pkp.get('status_code') == 200:
resp_pkp_payload = res_pkp.get('response', '')
parsed_pkp = json.loads(resp_pkp_payload) if isinstance(resp_pkp_payload, str) else resp_pkp_payload
p_list = parsed_pkp if isinstance(parsed_pkp, list) else (parsed_pkp.get('data', {}).get('pincodes', []) if isinstance(parsed_pkp, dict) else [])
pickup_info = next((p for p in p_list if str(p.get('code') or p.get('pincode')) == str(pickup_pincode)), None)
fm_ok = bool(pickup_info and len(pickup_info.get('services', [])) > 0)
# 2. Last-mile delivery check (customer/destination location)
url_dlv = f"{base_url}/v1/clients/serviceability/?service=customer_delivery&page=1&count=100&pincodes={drop_pincode}"
res_dlv = execute_sfx_call('FORWARD', 'SFX Last-Mile Delivery Check', 'GET', url_dlv, headers, env=env)
drop_info = None
lm_ok = False
if res_dlv.get('status_code') == 200:
resp_dlv_payload = res_dlv.get('response', '')
parsed_dlv = json.loads(resp_dlv_payload) if isinstance(resp_dlv_payload, str) else resp_dlv_payload
d_list = parsed_dlv if isinstance(parsed_dlv, list) else (parsed_dlv.get('data', {}).get('pincodes', []) if isinstance(parsed_dlv, dict) else [])
drop_info = next((p for p in d_list if str(p.get('code') or p.get('pincode')) == str(drop_pincode)), None)
lm_ok = bool(drop_info and len(drop_info.get('services', [])) > 0)
cod_ok = bool(drop_info.get('cod_available', True)) if drop_info else True
fm_city = (pickup_info.get('city') if pickup_info else None) or p_city
fm_state = (pickup_info.get('state') if pickup_info else None) or p_state
lm_city = (drop_info.get('city') if drop_info else None) or d_city
lm_state = (drop_info.get('state') if drop_info else None) or d_state
overall_serviceable = bool(fm_ok and lm_ok)
# Available Shadowfax delivery types
sfx_services = []
if drop_info and isinstance(drop_info.get('services'), list):
sfx_services = drop_info.get('services')
elif pickup_info and isinstance(pickup_info.get('services'), list):
sfx_services = pickup_info.get('services')
else:
sfx_services = ['Regular', 'Surface']
delivery_types = ['Prepaid', 'COD (Cash on Delivery)']
for s in sfx_services:
delivery_types.append(f"{s} Cargo/Delivery")
delivery_types.append('Reverse Pickup')
first_mile = {
"serviceable": bool(fm_ok),
"pincode": str(pickup_pincode),
"city": fm_city,
"state": fm_state,
"hub_active": bool(fm_ok),
"pickup_type": "Seller Store / Warehouse Sweep",
"message": f"First-mile pickup serviceable in {fm_city}, {fm_state}" if fm_ok else f"First-mile pickup NOT serviceable by Shadowfax in {fm_city}, {fm_state} ({pickup_pincode})"
}
last_mile = {
"serviceable": bool(lm_ok),
"pincode": str(drop_pincode),
"city": lm_city,
"state": lm_state,
"cod_available": bool(cod_ok),
"delivery_type": "Doorstep Delivery (Express)",
"message": f"Last-mile delivery serviceable in {lm_city}, {lm_state}" if lm_ok else f"Last-mile delivery NOT serviceable by Shadowfax in {lm_city}, {lm_state} ({drop_pincode})"
}
return {
"carrier": "SHADOWFAX",
"serviceable": overall_serviceable,
"first_mile_available": bool(fm_ok),
"last_mile_available": bool(lm_ok),
"cod_available": bool(cod_ok),
"estimated_cost": estimated_rate if overall_serviceable else None,
"estimated_charge": estimated_rate if overall_serviceable else None,
"currency": "INR",
"delivery_types": delivery_types if overall_serviceable else [],
"first_mile": first_mile,
"last_mile": last_mile,
"details": drop_info or pickup_info or {}
}
except Exception as e:
first_mile = {
"serviceable": False,
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"hub_active": False,
"pickup_type": "Seller Store / Warehouse Sweep",
"message": str(e)
}
last_mile = {
"serviceable": False,
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"cod_available": False,
"delivery_type": "Doorstep Delivery (Express)",
"message": str(e)
}
return {
"carrier": "SHADOWFAX",
"serviceable": False,
"cod_available": False,
"estimated_cost": None,
"first_mile": first_mile,
"last_mile": last_mile,
"error": str(e)
}
def check_ekart_availability(pickup_pincode, drop_pincode, weight=None, payment_type='Prepaid', invoice_amount=1000, use_mock=False):
"""
Checks Ekart pincode serviceability and logistics rate estimation directly from live Ekart API.
Provides detailed first-mile and last-mile breakdown.
Package weight (in grams) is a mandatory input.
"""
if weight is None or float(weight) <= 0:
raise ValueError("Package weight (in grams) is required for Ekart rate evaluation.")
weight_slabs = max(1, int(weight) // 500)
estimated_rate = round(38.0 + (weight_slabs * 12.0), 2)
p_city, p_state = resolve_city_state_by_pincode(pickup_pincode)
d_city, d_state = resolve_city_state_by_pincode(drop_pincode)
if use_mock:
return {
"carrier": "EKART",
"serviceable": True,
"cod_available": True,
"estimated_cost": estimated_rate,
"currency": "INR",
"first_mile": {
"serviceable": True,
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"hub_active": True,
"pickup_type": "Ekart Hub Sweep",
"message": f"First-mile pickup serviceable in {p_city}, {p_state} (Simulation)"
},
"last_mile": {
"serviceable": True,
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"cod_available": True,
"delivery_type": "Doorstep Surface/Air Delivery",
"message": f"Last-mile delivery serviceable in {d_city}, {d_state} (Simulation)"
},
"details": {"mock": True}
}
client = EkartAPIClient()
try:
# Step 1: Live First-Mile Pickup Check (Origin)
p_res = client.check_serviceability_v2(pickup_pincode)
p_details = p_res.get('details', {}) if isinstance(p_res, dict) else {}
fm_ok = bool(isinstance(p_res, dict) and p_res.get('status') and p_details.get('forward_pickup'))
if p_details.get('city'):
p_city = p_details.get('city')
if p_details.get('state'):
p_state = p_details.get('state')
# Step 2: Live Last-Mile Delivery Check (Destination)
d_res = client.check_serviceability_v2(drop_pincode)
d_details = d_res.get('details', {}) if isinstance(d_res, dict) else {}
is_cod = (str(payment_type).upper() == 'COD')
cod_available = bool(d_details.get('cod', False))
lm_drop_ok = bool(isinstance(d_res, dict) and d_res.get('status') and d_details.get('forward_drop'))
lm_ok = bool(lm_drop_ok and (cod_available if is_cod else True))
if d_details.get('city'):
d_city = d_details.get('city')
if d_details.get('state'):
d_state = d_details.get('state')
route_serviceable = bool(fm_ok and lm_ok)
# Step 3: Live End-to-End Route Serviceability & Pricing API (v3)
live_cost = None
v3_res = None
if route_serviceable:
v3_res = client.check_serviceability_v3(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
length=10,
width=10,
height=10,
weight=weight,
payment_type="COD" if str(payment_type).strip().upper() == "COD" else "Prepaid",
invoice_amount=invoice_amount
)
if isinstance(v3_res, list) and len(v3_res) > 0:
charges = v3_res[0].get('forwardDeliveredCharges', {})
total_est = charges.get('totalForwardDeliveredEstimate')
if total_est:
try:
live_cost = float(total_est)
except Exception:
pass
elif isinstance(v3_res, dict) and v3_res.get('totalForwardDeliveredEstimate'):
live_cost = float(v3_res['totalForwardDeliveredEstimate'])
if live_cost is None:
live_cost = estimated_rate
first_mile = {
"serviceable": bool(fm_ok),
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"hub_active": bool(fm_ok),
"pickup_type": "Ekart Hub Sweep (Live API)",
"message": f"First-mile pickup serviceable in {p_city}, {p_state}" if fm_ok else f"First-mile pickup unserviceable for pincode {pickup_pincode}"
}
last_mile = {
"serviceable": bool(lm_ok),
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"cod_available": bool(cod_available),
"delivery_type": "Doorstep Surface/Air Delivery (Live API)",
"message": f"Last-mile delivery serviceable in {d_city}, {d_state}" if lm_ok else f"Last-mile delivery unserviceable for pincode {drop_pincode}"
}
delivery_types = ['Prepaid', 'Forward Drop (Doorstep Delivery)', 'Forward Pickup (Hub Sweep)']
if cod_available:
delivery_types.append('COD (Cash on Delivery)')
delivery_types.extend(['Reverse Pickup (RVP)', 'Reverse Drop', 'Surface / Air Delivery'])
return {
"carrier": "EKART",
"serviceable": bool(route_serviceable),
"first_mile_available": bool(fm_ok),
"last_mile_available": bool(lm_ok),
"cod_available": bool(cod_available),
"estimated_cost": live_cost if route_serviceable else None,
"estimated_charge": live_cost if route_serviceable else None,
"currency": "INR",
"delivery_types": delivery_types if route_serviceable else [],
"first_mile": first_mile,
"last_mile": last_mile,
"details": {
"origin_v2": p_res,
"dest_v2": d_res,
"route_v3": v3_res
}
}
except Exception as e:
first_mile = {
"serviceable": False,
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"hub_active": False,
"pickup_type": "Ekart Hub Sweep",
"message": str(e)
}
last_mile = {
"serviceable": False,
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"cod_available": False,
"delivery_type": "Doorstep Surface/Air Delivery",
"message": str(e)
}
return {
"carrier": "EKART",
"serviceable": False,
"cod_available": False,
"estimated_cost": None,
"first_mile": first_mile,
"last_mile": last_mile,
"error": str(e)
}
def check_delhivery_availability(pickup_pincode, drop_pincode, weight=None, payment_type='Prepaid', api_token=None, use_mock=False):
"""
Checks Delhivery pincode serviceability and logistics rate estimation directly
against official Delhivery serviceability matrix (18,699 pincodes) and live Delhivery API.
Provides detailed first-mile (pickup hub) and last-mile (doorstep delivery) status.
Package weight (in grams) is a mandatory input.
"""
if weight is None or float(weight) <= 0:
raise ValueError("Package weight (in grams) is required for Delhivery rate evaluation.")
weight_slabs = max(1, int(weight) // 500)
estimated_rate = round(35.0 + (weight_slabs * 11.0), 2)
is_cod = (str(payment_type).upper() == 'COD')
# Step 1: Query first-mile and last-mile serviceability
if not use_mock:
client = DelhiveryAPIClient(api_token=api_token)
# 1. Live First-Mile Pickup Check (Origin)
try:
p_res = client.check_serviceability(pickup_pincode)
p_codes = p_res.get('delivery_codes', []) if isinstance(p_res, dict) else []
if p_codes:
p_postal = p_codes[0].get('postal_code', {})
p_ok = (p_postal.get('pickup') == 'Y')
p_msg = "First-mile pickup serviceable (B2C Express/Surface Hub Active)" if p_ok else "First-mile pickup blocked for this pincode"
p_city = p_postal.get('city') or p_postal.get('district') or resolve_city_state_by_pincode(pickup_pincode)[0]
p_st_code = p_postal.get('state_code', '')
p_state = STATE_CODE_TO_NAME.get(p_st_code, p_st_code) or resolve_city_state_by_pincode(pickup_pincode)[1]
pickup_info = {
"pincode": str(pickup_pincode),
"city": p_city,
"district": p_postal.get('district'),
"state_code": p_st_code,
"state": p_state,
"prepaid": (p_postal.get('pre_paid') == 'Y'),
"cod": (p_postal.get('cod') == 'Y'),
"reverse_pickup": (p_postal.get('pickup') == 'Y'),
"cash": (p_postal.get('cash') == 'Y'),
"repl": (p_postal.get('repl') == 'Y'),
"serviceable": True
}
else:
p_ok = False
p_msg = f"Pickup pincode {pickup_pincode} not found in Delhivery network"
p_city, p_state = resolve_city_state_by_pincode(pickup_pincode)
p_st_code = ''
pickup_info = None
except Exception as e:
p_ok = False
p_msg = f"Delhivery pickup check error: {str(e)}"
p_city, p_state = resolve_city_state_by_pincode(pickup_pincode)
p_st_code = ''
pickup_info = None
# 2. Live Last-Mile Delivery Check (Destination)
try:
d_res = client.check_serviceability(drop_pincode)
d_codes = d_res.get('delivery_codes', []) if isinstance(d_res, dict) else []
if d_codes:
d_postal = d_codes[0].get('postal_code', {})
cod_avail = (d_postal.get('cod') == 'Y')
d_ok = cod_avail if is_cod else (d_postal.get('pre_paid') == 'Y' or d_postal.get('cash') == 'Y')
d_msg = "Last-mile delivery serviceable" if d_ok else ("COD delivery blocked" if is_cod else "Destination delivery unserviceable")
d_city = d_postal.get('city') or d_postal.get('district') or resolve_city_state_by_pincode(drop_pincode)[0]
d_st_code = d_postal.get('state_code', '')
d_state = STATE_CODE_TO_NAME.get(d_st_code, d_st_code) or resolve_city_state_by_pincode(drop_pincode)[1]
drop_info = {
"pincode": str(drop_pincode),
"city": d_city,
"district": d_postal.get('district'),
"state_code": d_st_code,
"state": d_state,
"prepaid": (d_postal.get('pre_paid') == 'Y'),
"cod": cod_avail,
"reverse_pickup": (d_postal.get('pickup') == 'Y'),
"cash": (d_postal.get('cash') == 'Y'),
"repl": (d_postal.get('repl') == 'Y'),
"serviceable": True
}
else:
d_ok = False
d_msg = f"Drop pincode {drop_pincode} not found in Delhivery network"
d_city, d_state = resolve_city_state_by_pincode(drop_pincode)
d_st_code = ''
drop_info = None
cod_avail = False
except Exception as e:
d_ok = False
d_msg = f"Delhivery drop check error: {str(e)}"
d_city, d_state = resolve_city_state_by_pincode(drop_pincode)
d_st_code = ''
drop_info = None
cod_avail = False
else:
# Simulation / mock mode
p_ok, p_msg, pickup_info = is_delhivery_serviceable(pickup_pincode, is_cod=False)
d_ok, d_msg, drop_info = is_delhivery_serviceable(drop_pincode, is_cod=is_cod)
p_city = (pickup_info.get('city') if pickup_info else None) or resolve_city_state_by_pincode(pickup_pincode)[0]
p_state = (pickup_info.get('state') if pickup_info else None) or resolve_city_state_by_pincode(pickup_pincode)[1]
p_st_code = pickup_info.get('state_code', '') if pickup_info else ''
d_city = (drop_info.get('city') if drop_info else None) or resolve_city_state_by_pincode(drop_pincode)[0]
d_state = (drop_info.get('state') if drop_info else None) or resolve_city_state_by_pincode(drop_pincode)[1]
d_st_code = drop_info.get('state_code', '') if drop_info else ''
cod_avail = bool(drop_info.get('cod', True)) if drop_info else False
first_mile = {
"serviceable": bool(p_ok),
"pincode": str(pickup_pincode),
"city": p_city,
"state": p_state,
"state_code": p_st_code,
"reverse_pickup": bool(pickup_info.get('reverse_pickup', True)) if pickup_info else False,
"pickup_type": "B2C Surface / Express Hub Sweep (Live API)" if not use_mock else "B2C Surface / Express Hub Sweep",
"message": f"First-mile pickup serviceable in {p_city}, {p_state}" if p_ok else f"Pickup: {p_msg}"
}
last_mile = {
"serviceable": bool(d_ok),
"pincode": str(drop_pincode),
"city": d_city,
"state": d_state,
"state_code": d_st_code,
"cod_available": cod_avail,
"cash_support": bool(drop_info.get('cash', True)) if drop_info else False,
"delivery_type": "Doorstep Express / Surface Delivery (Live API)" if not use_mock else "Doorstep Express / Surface Delivery",
"message": f"Last-mile delivery serviceable in {d_city}, {d_state}" if d_ok else f"Destination: {d_msg}"
}
route_serviceable = bool(p_ok and d_ok)
if not route_serviceable:
err_parts = []
if not p_ok:
err_parts.append(f"Pickup: {p_msg}")
if not d_ok:
err_parts.append(f"Destination: {d_msg}")
return {
"carrier": "DELHIVERY",
"serviceable": False,
"cod_available": cod_avail,
"estimated_cost": None,
"error": " | ".join(err_parts),
"city": d_city,
"state": d_state,
"state_code": d_st_code,
"first_mile": first_mile,
"last_mile": last_mile,
"details": {
"route_message": " | ".join(err_parts),
"drop_info": drop_info,
"pickup_info": pickup_info
}
}
# Step 2: Route is verified serviceable in Delhivery network
live_cost = estimated_rate
api_details = {
"drop_info": drop_info,
"pickup_info": pickup_info
}
if not use_mock:
try:
rate_res = client.get_rate_estimate(
origin_pin=pickup_pincode,
dest_pin=drop_pincode,
weight=weight,
mode="S",
payment_type=payment_type
)
if isinstance(rate_res, dict) and rate_res.get('total_amount'):
live_cost = float(rate_res['total_amount'])
api_details['rate_estimate'] = rate_res
except Exception as e:
api_details['rate_api_warning'] = str(e)
delivery_types = []
if drop_info and drop_info.get('prepaid'):
delivery_types.append('Prepaid')
else:
delivery_types.append('Prepaid')
if drop_info and drop_info.get('cod'):
delivery_types.append('COD (Cash on Delivery)')
if drop_info and drop_info.get('cash'):
delivery_types.append('Cash Support')
if drop_info and drop_info.get('reverse_pickup'):
delivery_types.append('Reverse Pickup (RVP)')
if drop_info and drop_info.get('repl'):
delivery_types.append('Replacement (REPL)')
delivery_types.extend(['Surface Cargo', 'Express Delivery'])
return {
"carrier": "DELHIVERY",
"serviceable": True,
"first_mile_available": bool(p_ok),
"last_mile_available": bool(d_ok),
"cod_available": cod_avail,
"estimated_cost": live_cost,
"estimated_charge": live_cost,
"currency": "INR",
"city": d_city,
"state": d_state,
"state_code": d_st_code,
"delivery_types": delivery_types,
"first_mile": first_mile,
"last_mile": last_mile,
"details": api_details
}
def check_all_carriers_availability(pickup_pincode, drop_pincode, payment_type='Prepaid', weight=None, env='production', sfx_token=None, delhivery_token=None, use_mock=False):
"""
Evaluates serviceability, first-mile, and last-mile delivery across all registered carriers (Shadowfax, Delhivery, Ekart).
Package weight (in grams) is a mandatory input.
"""
if weight is None or float(weight) <= 0:
return {
"status": "error",
"message": "Package weight is required. Please provide parcel weight in grams."
}
weight = float(weight)
sfx_res = check_shadowfax_availability(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
weight=weight,
env=env,
token=sfx_token,
use_mock=use_mock
)
delhivery_res = check_delhivery_availability(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
weight=weight,
payment_type=payment_type,
api_token=delhivery_token,
use_mock=use_mock
)
ekart_res = check_ekart_availability(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
weight=weight,
payment_type=payment_type,
use_mock=use_mock
)
availability_dict = {
"status": "success",
"pickup_pincode": str(pickup_pincode),
"drop_pincode": str(drop_pincode),
"payment_type": payment_type,
"weight": weight,
"carriers": {
"SHADOWFAX": sfx_res,
"DELHIVERY": delhivery_res,
"EKART": ekart_res
}
}
best_carrier = select_best_carrier(
availability_dict,
selection_strategy='CHEAPEST',
preferred_carrier='SHADOWFAX',
is_cod=(str(payment_type).upper() == 'COD')
)
availability_dict["best_carrier"] = best_carrier
return availability_dict
CARRIER_PRIORITY = {
'SHADOWFAX': 0,
'DELHIVERY': 1,
'EKART': 2
}
def select_best_carrier(availability_dict, selection_strategy='CHEAPEST', preferred_carrier='SHADOWFAX', allow_fallback=True, is_cod=False):
"""
Selects the optimal carrier based on live API availability:
- CHEAPEST: Picks the serviceable carrier with lowest estimated logistics cost.
If costs tie across carriers, priority hierarchy is applied:
1. Shadowfax (Priority #1)
2. Delhivery (Priority #2)
3. Ekart (Priority #3)
- PREFERRED: Tries preferred_carrier first; falls back to cheapest serviceable carrier using tie-breaker priority.
"""
carriers = availability_dict.get('carriers', {})
strategy = (selection_strategy or 'CHEAPEST').upper()
pref = (preferred_carrier or 'SHADOWFAX').upper()
valid_candidates = []
for c_name, c_info in carriers.items():
if not c_info:
continue
is_serv = c_info.get('serviceable', False)
cod_ok = True if not is_cod else c_info.get('cod_available', False)
cost = c_info.get('estimated_cost')
if is_serv and cod_ok:
valid_candidates.append({
"carrier": c_name,
"cost": cost if cost is not None else float('inf'),
"info": c_info
})
if not valid_candidates:
return {
"selected": False,
"carrier": None,
"cost": None,
"reason": "No serviceable carrier found for route and payment mode"
}
if strategy == 'PREFERRED':
pref_match = next((cand for cand in valid_candidates if cand['carrier'] == pref), None)
if pref_match:
return {
"selected": True,
"carrier": pref_match['carrier'],
"cost": pref_match['cost'],
"reason": f"Selected {pref_match['carrier']} as preferred carrier (Cost: ₹{pref_match['cost']})"
}
elif not allow_fallback:
return {
"selected": False,
"carrier": None,
"cost": None,
"reason": f"Preferred carrier {pref} is unserviceable and allow_fallback=False"
}
# CHEAPEST strategy with tie-breaker priority: Shadowfax (0) > Delhivery (1) > Ekart (2)
valid_candidates.sort(key=lambda x: (x['cost'], CARRIER_PRIORITY.get(x['carrier'].upper(), 99)))
best = valid_candidates[0]
return {
"selected": True,
"carrier": best['carrier'],
"cost": best['cost'],
"reason": f"Selected {best['carrier']} as lowest cost carrier (Cost: ₹{best['cost']})"
}
def check_single_pincode_all_carriers(pincode, weight=None, use_mock=False):
"""
Evaluates logistics capabilities and estimated charges for a single pincode across
all three carriers: Shadowfax, Delhivery, and Ekart.
Package weight (in grams) is a mandatory input.
"""
if weight is None or float(weight) <= 0:
return {
"status": "error",
"message": "Package weight is required. Please provide parcel weight in grams."
}
weight = float(weight)
pin_str = str(pincode or "").strip()
city, state = resolve_city_state_by_pincode(pin_str)
weight_slabs = max(1, int(weight) // 500)
# 1. Ekart Logistics Check
ekart_data = {}
if use_mock:
ekart_data = {
"carrier": "EKART",
"pincode": pin_str,
"city": city,
"state": state,
"serviceable": True,
"first_mile_available": True,
"first_mile_details": f"First-mile pickup serviceable in {city}, {state} (Hub Sweep Active)",
"last_mile_available": True,
"last_mile_details": f"Last-mile delivery serviceable in {city}, {state} (Doorstep Active)",
"cod_available": True,
"delivery_types": ["Prepaid", "COD (Max ₹49,999)", "Forward Drop (Doorstep)", "Forward Pickup (Hub Sweep)", "Reverse Pickup (RVP)", "Reverse Drop", "Surface / Air Delivery"],
"estimated_charge": round(38.0 + (weight_slabs * 12.0), 2),
"currency": "INR"
}
else:
try:
client = EkartAPIClient()
v2_res = client.check_serviceability_v2(pin_str)
e_serv = bool(v2_res.get('status'))
e_det = v2_res.get('details', {}) if e_serv else {}
e_city = e_det.get('city') or city
e_state = e_det.get('state') or state
e_fm = bool(e_det.get('forward_pickup', e_serv))
e_lm = bool(e_det.get('forward_drop', e_serv))
e_cod = bool(e_det.get('cod', e_serv))
e_max_cod = e_det.get('max_cod_amount', 49999)
e_types = ['Prepaid']
if e_cod:
e_types.append(f"COD (Max Rs. {e_max_cod:,})" if isinstance(e_max_cod, (int, float)) else "COD (Cash on Delivery)")
if e_det.get('forward_drop', e_serv):
e_types.append('Forward Drop (Doorstep Delivery)')
if e_det.get('forward_pickup', e_serv):
e_types.append('Forward Pickup (Hub Sweep)')
if e_det.get('reverse_pickup', e_serv):
e_types.append('Reverse Pickup (RVP)')
if e_det.get('reverse_drop', e_serv):
e_types.append('Reverse Drop')
e_types.append('Surface / Air Delivery')
# Ekart Rate calculation
e_cost = round(33.04 + ((weight_slabs - 1) * 12.0), 2)
try:
v3_res = client.check_serviceability_v3(pin_str, pin_str, 10, 10, 10, weight, 'Prepaid', 1000)
if isinstance(v3_res, list) and len(v3_res) > 0:
chg = v3_res[0].get('forwardDeliveredCharges', {}).get('totalForwardDeliveredEstimate')
if chg:
e_cost = float(chg)
except Exception:
pass
ekart_data = {
"carrier": "EKART",
"pincode": pin_str,
"city": e_city,
"state": e_state,
"serviceable": e_serv,
"first_mile_available": e_fm,
"first_mile_details": f"First-mile pickup serviceable in {e_city}, {e_state} (Hub Sweep Active)" if e_fm else "Pickup Hub Sweep Blocked",
"last_mile_available": e_lm,
"last_mile_details": f"Last-mile delivery serviceable in {e_city}, {e_state} (Doorstep Active)" if e_lm else "Doorstep Delivery Blocked",
"cod_available": e_cod,
"delivery_types": e_types if e_serv else [],
"estimated_charge": e_cost if e_serv else None,
"currency": "INR"
}
except Exception as e:
ekart_data = {
"carrier": "EKART",
"pincode": pin_str,
"city": city,
"state": state,
"serviceable": False,
"first_mile_available": False,
"first_mile_details": str(e),
"last_mile_available": False,
"last_mile_details": str(e),
"cod_available": False,
"delivery_types": [],
"estimated_charge": None,
"currency": "INR",
"error": str(e)
}
# 2. Delhivery Logistics Check
if use_mock:
d_info = get_delhivery_pincode_info(pin_str)
d_serv = bool(d_info and d_info.get('serviceable'))
d_city = (d_info.get('city') if d_info else None) or city
d_state = (d_info.get('state') if d_info else None) or state
d_fm = bool(d_serv and (d_info.get('reverse_pickup', True) if d_info else True))
d_lm = bool(d_serv)
d_cod = bool(d_info.get('cod', True) if d_info else False)
d_types = []
if d_info and d_info.get('prepaid'):
d_types.append('Prepaid')
if d_cod:
d_types.append('COD (Cash on Delivery)')
if d_info and d_info.get('cash'):
d_types.append('Cash Support')
if d_info and d_info.get('reverse_pickup'):
d_types.append('Reverse Pickup (RVP)')
if d_info and d_info.get('repl'):
d_types.append('Replacement (REPL)')
if d_serv:
d_types.extend(['Surface Cargo', 'Express Delivery'])
d_cost = round(35.0 + ((weight_slabs - 1) * 11.0), 2) if d_serv else None
else:
try:
d_client = DelhiveryAPIClient()
d_res = d_client.check_serviceability(pin_str)
d_codes = d_res.get('delivery_codes', []) if isinstance(d_res, dict) else []
if d_codes:
p_code = d_codes[0].get('postal_code', {})
d_serv = True
d_city = p_code.get('city') or p_code.get('district') or city
d_state = STATE_CODE_TO_NAME.get(p_code.get('state_code'), p_code.get('state_code', '')) or state
d_fm = (p_code.get('pickup') == 'Y')
d_lm = (p_code.get('pre_paid') == 'Y' or p_code.get('cash') == 'Y')
d_cod = (p_code.get('cod') == 'Y')
d_types = []
if p_code.get('pre_paid') == 'Y':
d_types.append('Prepaid')
if d_cod:
d_types.append('COD (Cash on Delivery)')
if p_code.get('cash') == 'Y':
d_types.append('Cash Support')
if p_code.get('pickup') == 'Y':
d_types.append('Reverse Pickup (RVP)')
if p_code.get('repl') == 'Y':
d_types.append('Replacement (REPL)')
d_types.extend(['Surface Cargo', 'Express Delivery'])
# Live rate from Delhivery
d_cost = None
try:
r_res = d_client.get_rate_estimate(origin_pin=pin_str, dest_pin='600001', weight=weight or 1000)
if isinstance(r_res, dict) and r_res.get('total_amount'):
d_cost = float(r_res['total_amount'])
except Exception:
d_cost = round(35.0 + ((weight_slabs - 1) * 11.0), 2)
else:
d_serv = False
d_city = city
d_state = state
d_fm = False
d_lm = False
d_cod = False
d_types = []
d_cost = None
except Exception as e:
d_serv = False
d_city = city
d_state = state
d_fm = False
d_lm = False
d_cod = False
d_types = []
d_cost = None
delhivery_data = {
"carrier": "DELHIVERY",
"pincode": pin_str,
"city": d_city,
"state": d_state,
"serviceable": d_serv,
"first_mile_available": d_fm,
"first_mile_details": f"First-mile pickup serviceable in {d_city}, {d_state} (B2C Express Hub Active)" if d_fm else "Pickup Hub Blocked",
"last_mile_available": d_lm,
"last_mile_details": f"Last-mile delivery serviceable in {d_city}, {d_state} (Doorstep Active)" if d_lm else "Destination Pincode Unserviceable",
"cod_available": d_cod,
"delivery_types": d_types,
"estimated_charge": d_cost,
"currency": "INR"
}
# 3. Shadowfax Logistics Check
s_cost = round(45.0 + ((weight_slabs - 1) * 15.0), 2)
sfx_data = {}
if use_mock:
sfx_data = {
"carrier": "SHADOWFAX",
"pincode": pin_str,
"city": city,
"state": state,
"serviceable": True,
"first_mile_available": True,
"first_mile_details": f"First-mile pickup serviceable in {city}, {state} (Store Sweep Active)",
"last_mile_available": True,
"last_mile_details": f"Last-mile delivery serviceable in {city}, {state} (Doorstep Active)",
"cod_available": True,
"delivery_types": ["Prepaid", "COD (Cash on Delivery)", "Regular Cargo/Delivery", "Surface Cargo/Delivery", "Reverse Pickup"],
"estimated_charge": s_cost,
"currency": "INR"
}
else:
try:
base_url = get_base_url('production')
token = get_default_token('production')
headers = {"Authorization": f"Token {token}", "Content-Type": "application/json"}
# 1. Query first-mile pickup capabilities from seller/origin
url_pkp = f"{base_url}/v1/clients/serviceability/?service=seller_pickup&page=1&count=10&pincodes={pin_str}"
res_pkp = execute_sfx_call('FORWARD', 'Single Pincode Pickup Check', 'GET', url_pkp, headers, env='production')
fm_ok = False
if res_pkp.get('status_code') == 200:
resp_pkp_payload = res_pkp.get('response', '')
parsed_pkp = json.loads(resp_pkp_payload) if isinstance(resp_pkp_payload, str) else resp_pkp_payload
pkp_list = parsed_pkp if isinstance(parsed_pkp, list) else (parsed_pkp.get('data', {}).get('pincodes', []) if isinstance(parsed_pkp, dict) else [])
match_pkp = next((p for p in pkp_list if str(p.get('code') or p.get('pincode')) == pin_str), None)
fm_ok = bool(match_pkp and len(match_pkp.get('services', [])) > 0)
# 2. Query last-mile delivery capabilities to customer/destination
url_dlv = f"{base_url}/v1/clients/serviceability/?service=customer_delivery&page=1&count=10&pincodes={pin_str}"
res_dlv = execute_sfx_call('FORWARD', 'Single Pincode Delivery Check', 'GET', url_dlv, headers, env='production')
lm_ok = False
s_services = []
if res_dlv.get('status_code') == 200:
resp_dlv_payload = res_dlv.get('response', '')
parsed_dlv = json.loads(resp_dlv_payload) if isinstance(resp_dlv_payload, str) else resp_dlv_payload
dlv_list = parsed_dlv if isinstance(parsed_dlv, list) else (parsed_dlv.get('data', {}).get('pincodes', []) if isinstance(parsed_dlv, dict) else [])
match_dlv = next((p for p in dlv_list if str(p.get('code') or p.get('pincode')) == pin_str), None)
lm_ok = bool(match_dlv and len(match_dlv.get('services', [])) > 0)
if match_dlv:
s_services = match_dlv.get('services', ['Regular', 'Surface'])
s_serv = bool(fm_ok and lm_ok)
s_types = ['Prepaid', 'COD (Cash on Delivery)']
for s in s_services:
s_types.append(f"{s} Cargo/Delivery")
s_types.append('Reverse Pickup')
sfx_data = {
"carrier": "SHADOWFAX",
"pincode": pin_str,
"city": city,
"state": state,
"serviceable": s_serv,
"first_mile_available": fm_ok,
"first_mile_details": f"First-mile pickup serviceable in {city}, {state} (Store Sweep Active)" if fm_ok else f"First-mile pickup NOT serviceable by Shadowfax in {city}, {state} ({pin_str})",
"last_mile_available": lm_ok,
"last_mile_details": f"Last-mile delivery serviceable in {city}, {state} (Doorstep Active)" if lm_ok else f"Last-mile delivery NOT serviceable by Shadowfax in {city}, {state} ({pin_str})",
"cod_available": True,
"delivery_types": s_types if lm_ok else [],
"estimated_charge": s_cost if s_serv else None,
"currency": "INR"
}
except Exception as e:
sfx_data = {
"carrier": "SHADOWFAX",
"pincode": pin_str,
"city": city,
"state": state,
"serviceable": False,
"first_mile_available": False,
"first_mile_details": str(e),
"last_mile_available": False,
"last_mile_details": str(e),
"cod_available": False,
"delivery_types": [],
"estimated_charge": None,
"currency": "INR",
"error": str(e)
}
carriers_map = {
"SHADOWFAX": sfx_data,
"DELHIVERY": delhivery_data,
"EKART": ekart_data
}
# Best carrier recommendation
serviceable_carriers = [
c for c in carriers_map.values()
if c.get('serviceable') and c.get('estimated_charge') is not None
]
if serviceable_carriers:
serviceable_carriers.sort(key=lambda x: (x['estimated_charge'], CARRIER_PRIORITY.get(x['carrier'].upper(), 99)))
best = serviceable_carriers[0]
best_carrier = {
"selected": True,
"carrier": best['carrier'],
"charge": best['estimated_charge'],
"reason": f"Optimal carrier for pincode {pin_str} with lowest estimated charge of Rs. {best['estimated_charge']}"
}
else:
best_carrier = {
"selected": False,
"carrier": None,
"charge": None,
"reason": f"Pincode {pin_str} is not serviceable by any registered carrier."
}
return {
"status": "success",
"pincode": pin_str,
"city": city,
"state": state,
"weight": weight,
"carriers": carriers_map,
"best_carrier": best_carrier
}
def dispatch_auto_allocated_order(order_payload, selection_strategy='CHEAPEST', preferred_carrier='SHADOWFAX', allow_fallback=True, env='production'):
"""
Full cost-optimized auto-dispatch pipeline querying live production APIs for Shadowfax, Ekart, and Delhivery.
"""
order_details = order_payload.get('order_details', {})
customer_details = order_payload.get('customer_details', {})
pickup_details = order_payload.get('pickup_details', {})
pickup_pincode = pickup_details.get('pincode') or order_payload.get('pickup_pincode', '560077')
drop_pincode = customer_details.get('pincode') or order_payload.get('drop_pincode', '110009')
payment_mode = order_details.get('payment_mode', order_payload.get('payment_mode', 'Prepaid'))
weight = order_details.get('actual_weight') or order_payload.get('weight') or order_payload.get('package_weight')
if not weight or float(weight) <= 0:
return {
"status": "error",
"message": "Package weight is required. Please provide actual_weight or package_weight in grams."
}, 400
weight = float(weight)
is_cod = payment_mode.upper() == 'COD'
# Step 1: Check live availability & rates across all 3 carriers
avail = check_all_carriers_availability(
pickup_pincode=pickup_pincode,
drop_pincode=drop_pincode,
payment_type=payment_mode,
weight=weight,
env=env
)
# Step 2: Select best carrier
selection = select_best_carrier(
availability_dict=avail,
selection_strategy=selection_strategy,
preferred_carrier=preferred_carrier,
allow_fallback=allow_fallback,
is_cod=is_cod
)
if not selection.get('selected'):
return {
"status": "error",
"message": selection.get('reason'),
"availability": avail
}, 422
chosen_carrier = selection.get('carrier')
# Step 3: Dispatch to chosen carrier live API
if chosen_carrier == 'SHADOWFAX':
base_url = get_base_url(env)
token = get_default_token(env)
headers = {"Authorization": f"Token {token}", "Content-Type": "application/json"}
url = f"{base_url}/v3/clients/orders/"
res = execute_sfx_call('FORWARD', 'API: Auto-Dispatch Shadowfax', 'POST', url, headers, order_payload, env=env)
res['assigned_carrier'] = 'SHADOWFAX'
res['estimated_cost'] = selection.get('cost')
res['selection_reason'] = selection.get('reason')
return res, res.get('status_code', 200)
elif chosen_carrier == 'EKART':
client_order_id = order_details.get('client_order_id') or order_payload.get('order_number') or f"ORD-EKART-{int(time.time())}"
ekart_payload = {
"order_number": client_order_id,
"payment_mode": payment_mode,
"service": "SURFACE",
"consignee_name": customer_details.get('name', ''),
"consignee_alternate_phone": str(customer_details.get('phone', '')),
"pickup_location": {
"name": pickup_details.get('name', ''),
"pin": str(pickup_pincode)
},
"drop_location": {
"pin": str(drop_pincode),
"address_line1": customer_details.get('address_line_1', '')
}
}
client = EkartAPIClient()
try:
res = client.create_shipment(ekart_payload)
if res.get('status') and (res.get('tracking_id') or res.get('awb')):
res['assigned_carrier'] = 'EKART'
res['estimated_cost'] = selection.get('cost')
res['selection_reason'] = selection.get('reason')
res['awb_number'] = res.get('tracking_id') or res.get('awb')
return res, 200
# If Ekart booking failed (e.g. unregistered facility), fallback to Delhivery
if allow_fallback and avail.get('carriers', {}).get('DELHIVERY', {}).get('serviceable'):
chosen_carrier = 'DELHIVERY'
else:
res['assigned_carrier'] = 'EKART'
return res, 400
except Exception as e:
if allow_fallback and avail.get('carriers', {}).get('DELHIVERY', {}).get('serviceable'):
chosen_carrier = 'DELHIVERY'
else:
return {"status": False, "error": str(e), "assigned_carrier": "EKART"}, 500
if chosen_carrier == 'DELHIVERY':
client_order_id = order_details.get('client_order_id') or order_payload.get('order_number') or f"ORD-DELHIVERY-{int(time.time())}"
# Select destination city and state based directly on customer drop_pincode
dest_city, dest_state = resolve_city_state_by_pincode(
drop_pincode,
default_city=customer_details.get('city'),
default_state=customer_details.get('state')
)
# Resolve seller pickup city and state based directly on pickup_pincode
pickup_city, pickup_state = resolve_city_state_by_pincode(
pickup_pincode,
default_city=pickup_details.get('city'),
default_state=pickup_details.get('state')
)
seller_facility_name = pickup_details.get('name') or 'vignesh store'
delhivery_cmu_payload = {
"shipments": [
{
"name": customer_details.get('name', ''),
"add": customer_details.get('address_line_1', ''),
"pin": str(drop_pincode),
"city": dest_city,
"state": dest_state,
"country": "India",
"phone": str(customer_details.get('phone', '')),
"order": client_order_id,
"payment_mode": payment_mode,
"cod_amount": str(order_details.get('cod_amount', order_details.get('product_value', 0))) if is_cod else "0",
"total_amount": str(order_details.get('product_value', 0)),
"quantity": "1",
"weight": str(weight),
"shipping_mode": "Surface"
}
],
"pickup_location": {
"name": seller_facility_name,
"add": pickup_details.get('address_line_1', ''),
"pin": str(pickup_pincode),
"city": pickup_city,
"state": pickup_state,
"country": "India",
"phone": str(pickup_details.get('phone', ''))
}
}
client = DelhiveryAPIClient()
try:
res = client.create_shipment(delhivery_cmu_payload)
pkg_list = res.get('packages', []) if isinstance(res, dict) else []
has_waybill = bool(pkg_list and pkg_list[0].get('waybill'))
if res.get('success') and has_waybill:
res['assigned_carrier'] = 'DELHIVERY'
res['estimated_cost'] = selection.get('cost')
res['selection_reason'] = selection.get('reason')
res['awb_number'] = pkg_list[0]['waybill']
res['waybill'] = pkg_list[0]['waybill']
res['awb'] = pkg_list[0]['waybill']
return res, 200
# If Delhivery booking was unconfirmed, failover to Shadowfax only if Shadowfax is actually serviceable
is_sfx_serv = avail.get('carriers', {}).get('SHADOWFAX', {}).get('serviceable')
if allow_fallback and is_sfx_serv:
base_url = get_base_url(env)
token = get_default_token(env)
headers = {"Authorization": f"Token {token}", "Content-Type": "application/json"}
url = f"{base_url}/v3/clients/orders/"
sfx_res = execute_sfx_call('FORWARD', 'Auto-Dispatch Failover to Shadowfax', 'POST', url, headers, order_payload, env=env)
sfx_res['assigned_carrier'] = 'SHADOWFAX'
sfx_res['selection_reason'] = f"Delhivery booking unconfirmed ({res.get('rmk', 'No waybill')}). Successfully failed over to Shadowfax."
return sfx_res, sfx_res.get('status_code', 200)
res['assigned_carrier'] = 'DELHIVERY'
return res, 400
except Exception as e:
is_sfx_serv = avail.get('carriers', {}).get('SHADOWFAX', {}).get('serviceable')
if allow_fallback and is_sfx_serv:
base_url = get_base_url(env)
token = get_default_token(env)
headers = {"Authorization": f"Token {token}", "Content-Type": "application/json"}
url = f"{base_url}/v3/clients/orders/"
sfx_res = execute_sfx_call('FORWARD', 'Auto-Dispatch Failover to Shadowfax', 'POST', url, headers, order_payload, env=env)
sfx_res['assigned_carrier'] = 'SHADOWFAX'
sfx_res['selection_reason'] = f"Delhivery exception ({str(e)}). Successfully failed over to Shadowfax."
return sfx_res, sfx_res.get('status_code', 200)
return {"status": False, "error": str(e), "assigned_carrier": "DELHIVERY"}, 500
return {"status": "error", "message": "Unknown carrier error"}, 500
def get_carrier_pickup_capabilities(carrier=''):
"""
Return pickup scheduling capabilities for each carrier:
- DELHIVERY: Both Date and Time Slot are supported.
- EKART: Date is supported (dispatchDate), Time is fixed to standard hub sweeps.
- SHADOWFAX: Both Date and Time are automatically managed based on daily cutoff SLA.
"""
carrier_upper = str(carrier or '').strip().upper()
if 'DELHI' in carrier_upper:
key = 'DELHIVERY'
elif 'EKART' in carrier_upper:
key = 'EKART'
elif 'SHADOW' in carrier_upper or 'SFX' in carrier_upper:
key = 'SHADOWFAX'
else:
key = 'ALL'
capabilities = {
'DELHIVERY': {
'carrier': 'DELHIVERY',
'carrier_display': 'Delhivery Logistics',
'date_selection': True,
'time_selection': True,
'time_slots': [
{'id': 'morning', 'label': 'Morning (10:00 AM – 1:00 PM)', 'value': '10:00:00'},
{'id': 'afternoon', 'label': 'Afternoon (2:00 PM – 5:00 PM)', 'value': '14:00:00'},
{'id': 'evening', 'label': 'Evening (5:00 PM – 7:00 PM)', 'value': '17:00:00'}
],
'description': 'Delhivery supports custom pickup date and flexible time slot booking via dedicated first-mile courier scheduling.'
},
'EKART': {
'carrier': 'EKART',
'carrier_display': 'Ekart Logistics',
'date_selection': True,
'time_selection': False,
'time_slots': [],
'time_slot_label': 'Standard Daily Hub Sweep (Morning & Evening)',
'description': 'Ekart supports setting a preferred dispatch date. Pickup time is governed by daily routine courier hub sweeps.'
},
'SHADOWFAX': {
'carrier': 'SHADOWFAX',
'carrier_display': 'Shadowfax Logistics',
'date_selection': False,
'time_selection': False,
'time_slots': [],
'time_slot_label': 'Automated Cut-Off Dispatch Sweep',
'description': 'Shadowfax automatically assigns courier sweep riders for the registered seller location based on daily dispatch cut-off SLA.'
}
}
if key in capabilities:
return capabilities[key]
return capabilities
def schedule_carrier_pickup(carrier, pickup_date, pickup_time=None, pickup_location=None, package_count=1, tracking_ids=None, client_order_id=None, notes=None, env='production'):
"""
Unified multi-carrier pickup scheduling dispatcher.
Carrier specific behaviors:
- DELHIVERY: Calls POST /fm/request/create/ with date, time, location, package count.
- EKART: Calls POST /data/shipment/dispatch-date with tracking IDs and dispatchDate.
- SHADOWFAX: Confirms and records automated warehouse courier sweep.
"""
carrier_upper = str(carrier or '').strip().upper()
if 'DELHI' in carrier_upper:
carrier_key = 'DELHIVERY'
elif 'EKART' in carrier_upper:
carrier_key = 'EKART'
elif 'SHADOW' in carrier_upper or 'SFX' in carrier_upper:
carrier_key = 'SHADOWFAX'
else:
# Fallback identification based on tracking ID pattern
trk = str((tracking_ids[0] if isinstance(tracking_ids, list) and tracking_ids else tracking_ids) or '')
if trk.isdigit() or trk.startswith(('DELHI', '698', '14', '12')):
carrier_key = 'DELHIVERY'
elif trk.startswith(('EKART', 'ELIT', '500')):
carrier_key = 'EKART'
else:
carrier_key = 'SHADOWFAX'
clean_date = str(pickup_date or '').strip()
if not clean_date:
clean_date = time.strftime('%Y-%m-%d')
# Normalize tracking IDs
if tracking_ids and not isinstance(tracking_ids, list):
tracking_ids = [str(tracking_ids).strip()]
elif not tracking_ids:
tracking_ids = []
# --- 1. DELHIVERY ---
if carrier_key == 'DELHIVERY':
# Map time keywords or format HH:MM:SS
slot_map = {
'morning': '10:00:00',
'afternoon': '14:00:00',
'evening': '17:00:00'
}
clean_time = str(pickup_time or '14:00:00').strip().lower()
if clean_time in slot_map:
clean_time = slot_map[clean_time]
elif ':' not in clean_time:
clean_time = '14:00:00'
elif len(clean_time) == 5:
clean_time = f"{clean_time}:00"
location_name = str(pickup_location or '').strip()
pkg_cnt = max(1, int(package_count or 1))
# If location name is not provided, is generic, or is just a city name, resolve from shipment or tracking
if not location_name or location_name.lower() in ('seller warehouse', 'seller store', 'artisan workshop hub', 'coimbatore', 'chennai', 'bangalore', 'bengaluru'):
resolved_loc = None
# Check local DelhiveryShipment first
if tracking_ids:
first_trk = str(tracking_ids[0]).strip()
try:
from delhivery.models import DelhiveryShipment
ds = DelhiveryShipment.objects.filter(waybill=first_trk).first()
if ds and ds.pickup_location_name:
resolved_loc = ds.pickup_location_name
except Exception:
pass
# If not found locally, query live Delhivery package details
if not resolved_loc:
try:
import requests
from delhivery.models import DelhiveryCredential
cred = DelhiveryCredential.objects.filter(is_active=True).first()
tok = cred.api_token if cred and cred.api_token else 'e70dfd63665335ee6e0a1094f6d34e17a126f5a6'
t_resp = requests.get(
f"https://track.delhivery.com/api/v1/packages/json/?waybill={first_trk}",
headers={"Authorization": f"Token {tok}"},
timeout=5
)
if t_resp.status_code == 200:
s_data = t_resp.json().get('ShipmentData', [])
if s_data and s_data[0].get('Shipment', {}).get('PickupLocation'):
resolved_loc = s_data[0]['Shipment']['PickupLocation']
except Exception:
pass
if resolved_loc:
location_name = resolved_loc
elif not location_name:
location_name = 'SAM'
client = DelhiveryAPIClient()
try:
res = client.create_pickup_request(
pickup_time=clean_time,
pickup_date=clean_date,
pickup_location=location_name,
expected_package_count=pkg_cnt
)
# Check for error from Delhivery
has_error = isinstance(res, dict) and (res.get('status') == 'error' or (res.get('success') is False and not res.get('pr_exist')) or (res.get('error') and not res.get('pr_exist')) or res.get('prepaid'))
if has_error:
err_msg = res.get('message') or res.get('error') or (f"Delhivery error: {res.get('prepaid')}" if res.get('prepaid') else "Delhivery pickup request failed")
return {
'status': 'error',
'carrier': 'DELHIVERY',
'message': err_msg,
'details': res
}, 400
pickup_id = res.get('pickup_id') or res.get('id') or res.get('pr_id')
record_pickup_schedule(
tracking_ids=tracking_ids,
carrier='DELHIVERY',
pickup_date=clean_date,
pickup_time=clean_time,
pickup_id=pickup_id,
pickup_location=location_name,
client_order_id=client_order_id,
expected_package_count=pkg_cnt,
notes=notes,
raw_response=res
)
return {
'status': 'success',
'carrier': 'DELHIVERY',
'carrier_display': 'Delhivery Logistics',
'capabilities': {'date_selection': True, 'time_selection': True},
'pickup_date': clean_date,
'pickup_time': clean_time,
'pickup_location': location_name,
'expected_package_count': pkg_cnt,
'pickup_id': pickup_id,
'tracking_ids': tracking_ids,
'client_order_id': client_order_id,
'message': f"Delhivery courier pickup scheduled successfully for {clean_date} at {clean_time}",
'raw_response': res
}, 200
except Exception as e:
return {
'status': 'error',
'carrier': 'DELHIVERY',
'message': f"Failed to schedule Delhivery pickup: {str(e)}",
'details': str(e)
}, 500
# --- 2. EKART ---
elif carrier_key == 'EKART':
client = EkartAPIClient()
try:
res = client.set_dispatch_date(
tracking_ids=tracking_ids,
dispatch_date=clean_date
) if tracking_ids else {'status': True, 'message': 'Preferred dispatch date recorded'}
record_pickup_schedule(
tracking_ids=tracking_ids,
carrier='EKART',
pickup_date=clean_date,
pickup_time='Standard Hub Sweep',
pickup_location=pickup_location,
client_order_id=client_order_id,
notes=notes,
raw_response=res
)
return {
'status': 'success',
'carrier': 'EKART',
'carrier_display': 'Ekart Logistics',
'capabilities': {'date_selection': True, 'time_selection': False},
'pickup_date': clean_date,
'pickup_time': 'Standard Hub Sweep',
'time_slot_note': 'Ekart routine warehouse sweep (Morning & Evening cutoffs)',
'tracking_ids': tracking_ids,
'client_order_id': client_order_id,
'message': f"Ekart preferred dispatch date set to {clean_date}. Sweep scheduled during daily Ekart run.",
'raw_response': res
}, 200
except Exception as e:
return {
'status': 'error',
'carrier': 'EKART',
'message': f"Failed to set Ekart dispatch date: {str(e)}",
'details': str(e)
}, 500
# --- 3. SHADOWFAX ---
else:
record_pickup_schedule(
tracking_ids=tracking_ids,
carrier='SHADOWFAX',
pickup_date=clean_date,
pickup_time='Automated Cut-Off Dispatch Sweep',
pickup_location=pickup_location,
client_order_id=client_order_id,
notes=notes,
raw_response={'status': 'success', 'scheduled': True}
)
return {
'status': 'success',
'carrier': 'SHADOWFAX',
'carrier_display': 'Shadowfax Logistics',
'capabilities': {'date_selection': False, 'time_selection': False},
'pickup_date': clean_date,
'pickup_time': 'Automated Cut-Off Dispatch Sweep',
'time_slot_note': 'Shadowfax courier rider allocated automatically per registered seller cutoff SLA',
'tracking_ids': tracking_ids,
'client_order_id': client_order_id,
'message': f"Shadowfax automated pickup sweep confirmed for warehouse on {clean_date}.",
'raw_response': {'status': 'success', 'scheduled': True}
}, 200
def record_pickup_schedule(tracking_ids, carrier, pickup_date, pickup_time, pickup_id=None, pickup_location=None, client_order_id=None, expected_package_count=1, notes='', raw_response=None, status='SCHEDULED'):
"""
Saves or updates PickupSchedule records for tracking ID(s) upon scheduling.
"""
try:
from .models import PickupSchedule
carrier_upper = str(carrier or 'DELHIVERY').strip().upper()
carrier_disp = 'Delhivery Logistics' if 'DELHI' in carrier_upper else ('Ekart Logistics' if 'EKART' in carrier_upper else 'Shadowfax Logistics')
slot_label = ''
t_str = str(pickup_time or '').strip()
if t_str.startswith('10'):
slot_label = 'Morning Slot (10:00 AM – 1:00 PM)'
elif t_str.startswith('14'):
slot_label = 'Afternoon Slot (2:00 PM – 5:00 PM / 14:00 – 18:00)'
elif t_str.startswith('17'):
slot_label = 'Evening Slot (5:00 PM – 7:00 PM)'
else:
slot_label = t_str or 'Standard Sweep'
t_list = tracking_ids if isinstance(tracking_ids, list) and tracking_ids else ([str(tracking_ids).strip()] if tracking_ids else [])
if not t_list and client_order_id:
t_list = [f"ORD-{client_order_id}"]
if not t_list:
t_list = [f"LOC-{pickup_location or 'HUB'}-{pickup_date}"]
created_objs = []
for trk in t_list:
if not trk:
continue
obj, _ = PickupSchedule.objects.update_or_create(
tracking_id=str(trk).strip(),
defaults={
'client_order_id': str(client_order_id or '').strip(),
'carrier': carrier_upper,
'carrier_display': carrier_disp,
'pickup_id': str(pickup_id or '').strip(),
'pickup_date': str(pickup_date),
'pickup_time': str(pickup_time),
'pickup_slot': slot_label,
'pickup_location': str(pickup_location or ''),
'status': status,
'expected_package_count': int(expected_package_count or 1),
'notes': str(notes or ''),
'raw_response': raw_response if isinstance(raw_response, dict) else {'data': str(raw_response)}
}
)
created_objs.append(obj)
return created_objs
except Exception as e:
logger.warning(f"Failed to record pickup schedule: {e}")
return []
def check_pickup_schedule_for_tracking(tracking_id):
"""
Checks courier pickup schedule (date, time, arrival window, pickup request ID, location, carrier, status)
for a given tracking / AWB number.
Queries:
1. Local PickupSchedule database model in shippingservice.
2. Live Carrier Tracking API (Delhivery, Ekart, Shadowfax).
3. Cross-database Order records (customerprofile_beta database).
"""
trk = str(tracking_id or '').strip()
if not trk or trk in ('Pending', 'N/A'):
return {
'status': 'error',
'message': 'Valid tracking ID / AWB number is required.'
}, 400
from .models import PickupSchedule
local_obj = PickupSchedule.objects.filter(tracking_id=trk).order_by('-updated_at').first()
if not local_obj:
local_obj = PickupSchedule.objects.filter(client_order_id=trk).order_by('-updated_at').first()
# Identify carrier
if local_obj and local_obj.carrier:
carrier_key = local_obj.carrier
elif trk.isdigit() or trk.startswith(('DELHI', '698', '14', '12', '179', '139')):
carrier_key = 'DELHIVERY'
elif trk.startswith(('EKART', 'ELIT', '500')):
carrier_key = 'EKART'
else:
carrier_key = 'SHADOWFAX'
carrier_display = 'Delhivery Logistics' if carrier_key == 'DELHIVERY' else ('Ekart Logistics' if carrier_key == 'EKART' else 'Shadowfax Logistics')
pickup_id = local_obj.pickup_id if (local_obj and local_obj.pickup_id) else None
pickup_date = local_obj.pickup_date if (local_obj and local_obj.pickup_date) else None
pickup_time = local_obj.pickup_time if (local_obj and local_obj.pickup_time) else None
pickup_slot = local_obj.pickup_slot if (local_obj and local_obj.pickup_slot) else None
pickup_location = local_obj.pickup_location if (local_obj and local_obj.pickup_location) else None
order_id = local_obj.client_order_id if (local_obj and local_obj.client_order_id) else None
shipment_status = 'Manifested'
carrier_live_data = {}
consignee_info = {}
destination_info = {}
scan_history = []
# 1. Query live carrier tracking API if carrier is DELHIVERY
if carrier_key == 'DELHIVERY':
try:
import requests
from delhivery.models import DelhiveryCredential
cred = DelhiveryCredential.objects.filter(is_active=True).first()
tok = cred.api_token if cred and cred.api_token else 'e70dfd63665335ee6e0a1094f6d34e17a126f5a6'
t_resp = requests.get(
f"https://track.delhivery.com/api/v1/packages/json/?waybill={trk}",
headers={"Authorization": f"Token {tok}"},
timeout=6
)
if t_resp.status_code == 200:
s_data = t_resp.json().get('ShipmentData', [])
if s_data and isinstance(s_data, list):
sh = s_data[0].get('Shipment', {})
carrier_live_data = sh
shipment_status = sh.get('Status', {}).get('Status') or 'Manifested'
if not pickup_location and sh.get('PickupLocation'):
pickup_location = sh.get('PickupLocation')
if not order_id and sh.get('ReferenceNo'):
order_id = sh.get('ReferenceNo')
consignee = sh.get('Consignee') or {}
consignee_info = {
'name': consignee.get('Name'),
'city': consignee.get('City'),
'state': consignee.get('State'),
'pincode': consignee.get('PinCode')
}
destination_info = {
'city': sh.get('Destination') or consignee.get('City'),
'pincode': consignee.get('PinCode')
}
for sc in sh.get('Scans', []):
sd = sc.get('ScanDetail', {})
scan_history.append({
'scan': sd.get('Scan'),
'instructions': sd.get('Instructions'),
'location': sd.get('ScannedLocation'),
'timestamp': sd.get('ScanDateTime')
})
except Exception as dlh_err:
logger.warning(f"Error querying Delhivery live tracking for {trk}: {dlh_err}")
# 2. Query cross-database Order records (customerprofile_beta) to retrieve confirmed pickup ID & ETA
if not pickup_id or not pickup_date:
try:
import psycopg2
conn = psycopg2.connect(
dbname='customerprofile_beta',
user='vignesh',
password='vignesh_secret_pass_2026',
host='16.113.106.152',
port='5432'
)
cur = conn.cursor()
cur.execute(
"SELECT id, order_id, status, carrier, tracking, pickup_id, eta, pickup_payload FROM api_order WHERE tracking=%s OR order_id=%s",
(trk, order_id or trk)
)
row = cur.fetchone()
if row:
db_id, db_order_id, db_status, db_carrier, db_tracking, db_pickup_id, db_eta, db_payload = row
if not order_id and db_order_id:
order_id = db_order_id
if db_pickup_id:
pickup_id = str(db_pickup_id)
if db_eta and 'Pickup:' in str(db_eta):
import re
m_date = re.search(r'Pickup:\s*([\d-]+)', str(db_eta))
m_time = re.search(r'\(([\d:]+)\)', str(db_eta))
if m_date and not pickup_date:
pickup_date = m_date.group(1)
if m_time and not pickup_time:
pickup_time = m_time.group(1)
if isinstance(db_payload, dict):
if not pickup_id and db_payload.get('pickup_id'):
pickup_id = str(db_payload.get('pickup_id'))
if not pickup_date and db_payload.get('pickup_date'):
pickup_date = db_payload.get('pickup_date')
if not pickup_time and db_payload.get('pickup_time'):
pickup_time = db_payload.get('pickup_time')
conn.close()
except Exception as db_err:
logger.warning(f"Cross-database order lookup error: {db_err}")
# 3. Check if warehouse location has an active confirmed Pickup Request
if not pickup_id and pickup_location:
loc_match = PickupSchedule.objects.filter(pickup_location=pickup_location).exclude(pickup_id='').order_by('-updated_at').first()
if loc_match:
pickup_id = loc_match.pickup_id
if not pickup_date:
pickup_date = loc_match.pickup_date
if not pickup_time:
pickup_time = loc_match.pickup_time
# Default fallbacks if date or time are not yet resolved
if not pickup_date:
pickup_date = time.strftime('%Y-%m-%d')
if not pickup_time:
pickup_time = '14:00:00' if carrier_key == 'DELHIVERY' else ('Standard Hub Sweep' if carrier_key == 'EKART' else 'Automated Cut-Off Sweep')
# Format slot description
t_clean = str(pickup_time).strip()
if t_clean.startswith('10'):
pickup_slot = 'Morning Slot (10:00 AM – 1:00 PM)'
elif t_clean.startswith('14'):
pickup_slot = 'Afternoon Slot (2:00 PM – 5:00 PM / 14:00 – 18:00)'
elif t_clean.startswith('17'):
pickup_slot = 'Evening Slot (5:00 PM – 7:00 PM)'
elif not pickup_slot:
pickup_slot = t_clean
# Determine lifecycle status
is_picked_up = str(shipment_status).lower() in ['in transit', 'picked up', 'out for delivery', 'delivered'] or carrier_live_data.get('PickedupDate')
if is_picked_up:
status_code = 'IN_TRANSIT'
status_display = f"Package Picked Up & In Transit with {carrier_display}"
elif pickup_id:
status_code = 'CONFIRMED'
status_display = f"Pickup Confirmed with {carrier_display} (PR #{pickup_id}) for {pickup_date} ({pickup_slot})"
else:
status_code = 'SCHEDULED'
status_display = f"Pickup Scheduled with {carrier_display} for {pickup_date} ({pickup_slot})"
# Cache/update into PickupSchedule table
try:
PickupSchedule.objects.update_or_create(
tracking_id=trk,
defaults={
'client_order_id': str(order_id or ''),
'carrier': carrier_key,
'carrier_display': carrier_display,
'pickup_id': str(pickup_id or ''),
'pickup_date': str(pickup_date),
'pickup_time': str(pickup_time),
'pickup_slot': str(pickup_slot),
'pickup_location': str(pickup_location or 'SAM'),
'status': status_code,
'notes': f"Queried via check_pickup_schedule ({time.strftime('%Y-%m-%d %H:%M:%S')})"
}
)
except Exception as save_err:
logger.warning(f"Error caching PickupSchedule: {save_err}")
return {
'status': 'success',
'tracking_id': trk,
'order_id': order_id,
'carrier': carrier_key,
'carrier_display': carrier_display,
'pickup_scheduled': True,
'pickup_id': pickup_id,
'pickup_date': pickup_date,
'pickup_time': pickup_time,
'pickup_slot': pickup_slot,
'pickup_location': pickup_location or 'SAM',
'shipment_status': shipment_status,
'pickup_status': status_code,
'status_display': status_display,
'destination': destination_info,
'consignee': consignee_info,
'scans': scan_history,
'details': {
'expected_package_count': local_obj.expected_package_count if local_obj else 1,
'manifest_date': carrier_live_data.get('PickUpDate'),
'picked_up_date': carrier_live_data.get('PickedupDate')
}
}, 200