feat(logistics): add Smart Auto-Courier cascading router, OSM Nominatim + 64-District spatial gazetteer fallback, NDR phone tag, and JSON guard
This commit is contained in:
Vendored
+42
-18
@@ -40,28 +40,51 @@ export class CourierRegistry {
|
||||
getGoogleMaps() {
|
||||
return this.googleMaps;
|
||||
}
|
||||
async createParcel(req) {
|
||||
let courierName = "steadfast";
|
||||
if (req.courier && req.courier !== "auto") {
|
||||
courierName = req.courier;
|
||||
resolveSmartAutoCourierOrder(address, priority = "balanced") {
|
||||
const addr = String(address || "").toLowerCase();
|
||||
const isSubDhaka = /savar|ashulia|keraniganj|gajipur|gazipur|tongi|narayanganj|siddhirganj|fatullah|dohar|nawabganj|সাভার|আশুলিয়া|কেরানীগঞ্জ|গাজীপুর|টঙ্গী|নারায়ণগঞ্জ/.test(addr);
|
||||
const isDhakaMetro = !isSubDhaka && (/dhaka|ঢাকা|gulshan|banani|dhanmondi|uttara|mirpur|mohammadpur|motijheel|badda|rampura|bashundhara|tejgaon|khilgaon|malibagh|mogbazar|farmgate|shahbag|hatirpool|lalmatia|wari|jatrabari|গুলশান|বনানী|ধানমন্ডি|উত্তরা|মিরপুর|মোহাম্মদপুর|মতিঝিল|বাড্ডা|রামপুরা|বসুন্ধরা|তেজগাঁও|খিলগাঁও|মালিবাগ|মগবাজার|ফার্মগেট|শাহবাগ|হাতিরপুল|ওয়ারী|যাত্রাবাড়ী/.test(addr));
|
||||
let idealOrder;
|
||||
if (isDhakaMetro) {
|
||||
idealOrder = priority === "cheapest"
|
||||
? ["pathao", "redx", "paperfly", "steadfast"]
|
||||
: ["pathao", "redx", "steadfast", "paperfly"];
|
||||
}
|
||||
else if (isSubDhaka) {
|
||||
idealOrder = priority === "fastest"
|
||||
? ["pathao", "steadfast", "redx", "paperfly"]
|
||||
: ["paperfly", "steadfast", "pathao", "redx"];
|
||||
}
|
||||
else {
|
||||
// Smart routing: inside Dhaka metro with Pathao configured prefers Pathao, else Steadfast
|
||||
const addr = req.recipient_address.toLowerCase();
|
||||
if (addr.includes("dhaka") &&
|
||||
(addr.includes("gulshan") ||
|
||||
addr.includes("banani") ||
|
||||
addr.includes("dhanmondi") ||
|
||||
addr.includes("uttara") ||
|
||||
addr.includes("mirpur"))) {
|
||||
courierName = this.adapters.get("pathao")?.isConfigured() ? "pathao" : "steadfast";
|
||||
idealOrder = priority === "cheapest"
|
||||
? ["paperfly", "pathao", "steadfast", "redx"]
|
||||
: ["steadfast", "paperfly", "pathao", "redx"];
|
||||
}
|
||||
const configured = idealOrder.filter((c) => this.adapters.get(c)?.isConfigured());
|
||||
return configured.length > 0 ? configured : idealOrder;
|
||||
}
|
||||
async createParcel(req) {
|
||||
const rawChoice = String(req.courier || "auto").toLowerCase();
|
||||
const isAuto = rawChoice === "auto" || rawChoice.startsWith("auto_");
|
||||
const priority = rawChoice === "auto_fastest" ? "fastest" : rawChoice === "auto_cheapest" ? "cheapest" : "balanced";
|
||||
const candidates = isAuto
|
||||
? this.resolveSmartAutoCourierOrder(req.recipient_address, priority)
|
||||
: [rawChoice];
|
||||
let lastErr = null;
|
||||
for (const courierName of candidates) {
|
||||
try {
|
||||
const adapter = this.getAdapter(courierName);
|
||||
const res = await adapter.createParcel({ ...req, courier: courierName });
|
||||
if (res && res.success !== false && (res.tracking_code || res.consignment_id)) {
|
||||
return res;
|
||||
}
|
||||
lastErr = new Error(res?.message || res?.error || `${courierName} rejected parcel`);
|
||||
}
|
||||
else {
|
||||
courierName = "steadfast";
|
||||
catch (err) {
|
||||
lastErr = err;
|
||||
}
|
||||
}
|
||||
const adapter = this.getAdapter(courierName);
|
||||
return await adapter.createParcel(req);
|
||||
throw lastErr || new Error("All configured courier gateways failed to book parcel.");
|
||||
}
|
||||
async trackParcel(trackingCode, courierName) {
|
||||
if (courierName) {
|
||||
@@ -95,6 +118,7 @@ export class CourierRegistry {
|
||||
const cid = req.consignment_id;
|
||||
const name = req.customer_name || "গ্রাহক";
|
||||
const courier = req.courier;
|
||||
const phoneTag = req.customer_phone ? ` [Customer Phone: ${req.customer_phone}]` : "";
|
||||
let status = "CUSTOMER_REENGAGED";
|
||||
let actionTaken = "";
|
||||
let customerMsg = "";
|
||||
@@ -138,7 +162,7 @@ export class CourierRegistry {
|
||||
resolution_status: status,
|
||||
action_taken: actionTaken,
|
||||
customer_whatsapp_message: customerMsg,
|
||||
carrier_instruction: carrierInstr,
|
||||
carrier_instruction: carrierInstr + phoneTag,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user