feat: add shipping views, delhivery client, carrier allocator, and url routing
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
This commit is contained in:
parent
2f0271ee1a
commit
3ab3807331
9 changed files with 456 additions and 8 deletions
Binary file not shown.
|
|
@ -304,13 +304,17 @@ class DelhiveryAPIClient:
|
|||
def create_pickup_request(self, pickup_time, pickup_date, pickup_location, expected_package_count=1):
|
||||
"""POST /fm/request/create/"""
|
||||
payload = {
|
||||
"pickup_time": pickup_time,
|
||||
"pickup_date": pickup_date,
|
||||
"pickup_location": pickup_location,
|
||||
"pickup_time": str(pickup_time),
|
||||
"pickup_date": str(pickup_date),
|
||||
"pickup_location": str(pickup_location),
|
||||
"expected_package_count": int(expected_package_count)
|
||||
}
|
||||
resp = self._execute_request("Create Pickup Request", "POST", "/fm/request/create/", payload=payload)
|
||||
return resp.json()
|
||||
try:
|
||||
return resp.json()
|
||||
except Exception:
|
||||
return {"status": resp.status_code == 200, "status_code": resp.status_code, "text": resp.text}
|
||||
|
||||
|
||||
# --- 7. Order Cancellation ---
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1386,3 +1386,192 @@ def dispatch_auto_allocated_order(order_payload, selection_strategy='CHEAPEST',
|
|||
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 'Seller Warehouse').strip()
|
||||
pkg_cnt = max(1, int(package_count or 1))
|
||||
|
||||
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
|
||||
)
|
||||
pickup_id = res.get('pickup_id') or res.get('id') or res.get('pr_id')
|
||||
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'}
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@
|
|||
<button type="button" class="mode-tab-btn" id="tabSingleMode" onclick="switchMode('single')">
|
||||
<span>📍</span> Single Pincode Logistics Capabilities
|
||||
</button>
|
||||
<button type="button" class="mode-tab-btn" id="tabPickupMode" onclick="switchMode('pickup')">
|
||||
<span>📅</span> Schedule Courier Pickup
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
|
|
@ -314,6 +317,83 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- FORM 3: SCHEDULE PICKUP MODE FORM -->
|
||||
<!-- ========================================== -->
|
||||
<div class="card" id="cardPickupMode" style="display: none; margin-bottom: 1.75rem;">
|
||||
<div class="card-title">
|
||||
<span>📅 Schedule Courier Pickup Across Carriers</span>
|
||||
<span style="font-size: 0.8rem; font-weight: normal; color: var(--text-muted);">Dynamic Options Enabled per Assigned Carrier</span>
|
||||
</div>
|
||||
|
||||
<form id="pickupScheduleForm" onsubmit="handlePickupSubmit(event)">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.2rem; margin-bottom: 1.2rem;">
|
||||
<!-- Carrier Selector -->
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">🚚 Assigned Carrier</label>
|
||||
<select id="pickup_carrier" class="form-select" onchange="onPickupCarrierChange()" style="font-size: 1rem; font-weight: 700;">
|
||||
<option value="DELHIVERY" selected>Delhivery Logistics</option>
|
||||
<option value="EKART">Ekart Logistics</option>
|
||||
<option value="SHADOWFAX">Shadowfax Logistics</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Pickup Date -->
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">
|
||||
<span>📅 Pickup Date</span>
|
||||
<span id="pickupDateNotice" style="color: var(--accent-cyan); font-weight: 600; text-transform: none; margin-left: 0.5rem;"></span>
|
||||
</label>
|
||||
<input type="date" id="pickup_date" class="form-control" style="font-size: 1rem; font-weight: 700;" required>
|
||||
</div>
|
||||
|
||||
<!-- Time Slot (Dynamically Enabled/Disabled) -->
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">
|
||||
<span>⏰ Arrival Slot</span>
|
||||
<span id="pickupSlotBadge" style="color: var(--accent-cyan); font-weight: 600; text-transform: none; margin-left: 0.5rem;"></span>
|
||||
</label>
|
||||
<select id="pickup_time" class="form-select" style="font-size: 1rem; font-weight: 600;">
|
||||
<option value="10:00:00">Morning (10:00 AM – 1:00 PM)</option>
|
||||
<option value="14:00:00" selected>Afternoon (2:00 PM – 5:00 PM)</option>
|
||||
<option value="17:00:00">Evening (5:00 PM – 7:00 PM)</option>
|
||||
</select>
|
||||
<small id="slotNotice" style="color: var(--text-muted); font-size: 0.78rem; display: block; margin-top: 0.35rem;"></small>
|
||||
</div>
|
||||
|
||||
<!-- Package Count -->
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">📦 Package Count</label>
|
||||
<input type="number" id="pickup_count" class="form-control" value="1" min="1" max="100" style="font-size: 1rem; font-weight: 700;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.2rem; margin-bottom: 1.2rem;">
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">🏭 Registered Warehouse / Seller Facility Name</label>
|
||||
<input type="text" id="pickup_location" class="form-control" placeholder="e.g. StyleHub Warehouse Phase 2" value="vignesh store">
|
||||
</div>
|
||||
<div class="form-group" style="margin: 0;">
|
||||
<label class="form-label">🏷️ Waybill / Tracking ID (Optional)</label>
|
||||
<input type="text" id="pickup_tracking" class="form-control" placeholder="e.g. 142839182390">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carrier Capability Live Banner -->
|
||||
<div id="carrierCapabilityBanner" style="padding: 0.85rem 1.2rem; border-radius: 0.75rem; background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.3); margin-bottom: 1.2rem; font-size: 0.88rem; color: #bae6fd;">
|
||||
<!-- Populated dynamically -->
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end;">
|
||||
<button type="submit" id="btnPickupSubmit" class="btn btn-primary" style="padding: 0.85rem 2.2rem; font-size: 1rem;">
|
||||
<span>📅</span>
|
||||
<span id="btnPickupText">Schedule Courier Pickup</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- UNIFIED RESULTS SECTION -->
|
||||
<!-- ========================================== -->
|
||||
|
|
@ -560,22 +640,133 @@
|
|||
activeMode = mode;
|
||||
const tabSingle = document.getElementById('tabSingleMode');
|
||||
const tabRoute = document.getElementById('tabRouteMode');
|
||||
const tabPickup = document.getElementById('tabPickupMode');
|
||||
const cardSingle = document.getElementById('cardSingleMode');
|
||||
const cardRoute = document.getElementById('cardRouteMode');
|
||||
const cardPickup = document.getElementById('cardPickupMode');
|
||||
const resultsWrapper = document.getElementById('resultsWrapper');
|
||||
|
||||
tabSingle.classList.remove('active');
|
||||
tabRoute.classList.remove('active');
|
||||
if (tabPickup) tabPickup.classList.remove('active');
|
||||
|
||||
cardSingle.style.display = 'none';
|
||||
cardRoute.style.display = 'none';
|
||||
if (cardPickup) cardPickup.style.display = 'none';
|
||||
|
||||
if (mode === 'single') {
|
||||
tabSingle.classList.add('active');
|
||||
tabRoute.classList.remove('active');
|
||||
cardSingle.style.display = 'block';
|
||||
cardRoute.style.display = 'none';
|
||||
} else if (mode === 'pickup') {
|
||||
if (tabPickup) tabPickup.classList.add('active');
|
||||
if (cardPickup) cardPickup.style.display = 'block';
|
||||
resultsWrapper.style.display = 'none';
|
||||
initPickupForm();
|
||||
} else {
|
||||
tabSingle.classList.remove('active');
|
||||
tabRoute.classList.add('active');
|
||||
cardSingle.style.display = 'none';
|
||||
cardRoute.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function initPickupForm() {
|
||||
const tomorrow = new Date(Date.now() + 86400000);
|
||||
const tomorrowStr = tomorrow.toISOString().split('T')[0];
|
||||
const dateInput = document.getElementById('pickup_date');
|
||||
if (!dateInput.value) {
|
||||
dateInput.value = tomorrowStr;
|
||||
dateInput.min = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
onPickupCarrierChange();
|
||||
}
|
||||
|
||||
function onPickupCarrierChange() {
|
||||
const carrier = document.getElementById('pickup_carrier').value;
|
||||
const timeSelect = document.getElementById('pickup_time');
|
||||
const slotNotice = document.getElementById('slotNotice');
|
||||
const slotBadge = document.getElementById('pickupSlotBadge');
|
||||
const dateNotice = document.getElementById('pickupDateNotice');
|
||||
const banner = document.getElementById('carrierCapabilityBanner');
|
||||
|
||||
if (carrier === 'DELHIVERY') {
|
||||
timeSelect.disabled = false;
|
||||
timeSelect.style.opacity = '1';
|
||||
slotBadge.textContent = '🟢 Custom Time Slot Enabled';
|
||||
slotBadge.style.color = '#34d399';
|
||||
dateNotice.textContent = '(Flexible Date)';
|
||||
slotNotice.textContent = 'Delhivery allocates a dedicated courier vehicle for your selected arrival slot.';
|
||||
banner.innerHTML = '<strong>✅ Delhivery Logistics:</strong> Full Date and Time Slot selection is active. Orders will be scheduled with Delhivery First-Mile API (<code>/fm/request/create/</code>).';
|
||||
} else if (carrier === 'EKART') {
|
||||
timeSelect.disabled = true;
|
||||
timeSelect.style.opacity = '0.55';
|
||||
slotBadge.textContent = '🔒 Standard Hub Sweep';
|
||||
slotBadge.style.color = '#fbbf24';
|
||||
dateNotice.textContent = '(Dispatch Date Enabled)';
|
||||
slotNotice.textContent = 'Ekart operates daily routine hub sweeps (Morning 10 AM – 1 PM / Evening 4 PM – 7 PM).';
|
||||
banner.innerHTML = '<strong>ℹ️ Ekart Logistics:</strong> Preferred dispatch date is supported (<code>/data/shipment/dispatch-date</code>). Pickup time is managed per standard Ekart daily sweep windows.';
|
||||
} else {
|
||||
timeSelect.disabled = true;
|
||||
timeSelect.style.opacity = '0.55';
|
||||
slotBadge.textContent = '🔒 Automated Cutoff';
|
||||
slotBadge.style.color = '#a78bfa';
|
||||
dateNotice.textContent = '(Automated SLA)';
|
||||
slotNotice.textContent = 'Shadowfax automatically dispatches sweep riders based on registered merchant SLA cutoff.';
|
||||
banner.innerHTML = '<strong>ℹ️ Shadowfax Logistics:</strong> Warehouse sweeps are scheduled automatically based on registered daily SLA cutoffs.';
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePickupSubmit(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('btnPickupSubmit');
|
||||
const btnText = document.getElementById('btnPickupText');
|
||||
btn.disabled = true;
|
||||
btnText.textContent = 'Scheduling Pickup with Carrier...';
|
||||
|
||||
const carrier = document.getElementById('pickup_carrier').value;
|
||||
const pickupDate = document.getElementById('pickup_date').value;
|
||||
const pickupTime = document.getElementById('pickup_time').value;
|
||||
const pickupCount = document.getElementById('pickup_count').value;
|
||||
const pickupLocation = document.getElementById('pickup_location').value;
|
||||
const pickupTracking = document.getElementById('pickup_tracking').value;
|
||||
|
||||
const payload = {
|
||||
carrier: carrier,
|
||||
pickup_date: pickupDate,
|
||||
pickup_time: pickupTime,
|
||||
pickup_location: pickupLocation,
|
||||
expected_package_count: parseInt(pickupCount, 10) || 1,
|
||||
tracking_ids: pickupTracking ? [pickupTracking] : []
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/v1/carrier/schedule-pickup/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
const resultsWrapper = document.getElementById('resultsWrapper');
|
||||
resultsWrapper.style.display = 'block';
|
||||
|
||||
document.getElementById('verdictBanner').style.display = 'flex';
|
||||
document.getElementById('verdictTitle').textContent = data.status === 'success' ? '✅ Pickup Scheduled Successfully' : '⚠️ Pickup Request Response';
|
||||
document.getElementById('verdictDesc').textContent = data.message || JSON.stringify(data);
|
||||
|
||||
// Update raw JSON
|
||||
document.getElementById('rawJsonResponse').textContent = JSON.stringify(data, null, 2);
|
||||
document.getElementById('rawJsonTitle').textContent = `🔍 Schedule Pickup Response (${carrier})`;
|
||||
|
||||
// Scroll to response
|
||||
resultsWrapper.scrollIntoView({ behavior: 'smooth' });
|
||||
} catch (err) {
|
||||
alert('Failed to schedule pickup: ' + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btnText.textContent = 'Schedule Courier Pickup';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateSingleLocationHint() {
|
||||
const pin = document.getElementById('single_pincode').value.trim();
|
||||
const hintEl = document.getElementById('singleLocationHint');
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ urlpatterns = [
|
|||
path('api/v1/carrier/check-pincode/', views.api_carrier_pincode_check, name='api_carrier_pincode_check'),
|
||||
path('api/v1/carrier/check-availability/', views.api_carrier_availability, name='api_carrier_availability'),
|
||||
path('api/v1/carrier/auto-dispatch/', views.api_carrier_auto_dispatch, name='api_carrier_auto_dispatch'),
|
||||
path('api/v1/carrier/schedule-pickup/', views.api_carrier_schedule_pickup, name='api_carrier_schedule_pickup'),
|
||||
|
||||
# Microservices REST API Endpoints under /api/v1/
|
||||
path('api/v1/health/', views.api_healthcheck, name='api_healthcheck'),
|
||||
|
|
|
|||
|
|
@ -1130,5 +1130,68 @@ def api_carrier_auto_dispatch(request):
|
|||
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
|
||||
|
||||
if request.method == 'GET':
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue