From 92791be802c55873a0036850bc9ce18e0ccdbbbc Mon Sep 17 00:00:00 2001 From: vignesh Date: Sun, 11 Oct 2026 21:15:59 +0530 Subject: [PATCH] feat: add carrier checker template and multi-carrier routing URL endpoints --- delhivery/__pycache__/client.cpython-314.pyc | Bin 16663 -> 17735 bytes delhivery/client.py | 22 +- ekart/__pycache__/client.cpython-314.pyc | Bin 18622 -> 18622 bytes ekart/client.py | 4 +- .../carrier_allocator.cpython-314.pyc | Bin 33314 -> 47148 bytes shipping/__pycache__/tests.cpython-314.pyc | Bin 18067 -> 19880 bytes shipping/__pycache__/urls.cpython-314.pyc | Bin 1975 -> 2098 bytes shipping/__pycache__/views.cpython-314.pyc | Bin 59328 -> 61213 bytes shipping/carrier_allocator.py | 360 ++++++- .../templates/shipping/carrier_checker.html | 956 +++++++++++------- shipping/tests.py | 26 + shipping/urls.py | 1 + shipping/views.py | 24 + .../__pycache__/settings.cpython-314.pyc | Bin 5013 -> 5013 bytes shippingservice_project/settings.py | 4 +- 15 files changed, 1040 insertions(+), 357 deletions(-) diff --git a/delhivery/__pycache__/client.cpython-314.pyc b/delhivery/__pycache__/client.cpython-314.pyc index 2f41abcb716d653ac8ba8bcbdc2bec25c045bbcf..3a7d16d75f4422cd70fe47ea07c9d7fb0632c1b3 100644 GIT binary patch delta 1287 zcmZXTUrbt89LLY$&)@>`$E%`(V%4DpEE=GwsKo}N#+J3W&bde+RGDQHY%escYZjKh z%&gPbAR{y z`~H6CchAYa{5HJ%9+Vs96%v5g{gp3Y{nma%-fiLi#Y4lEai~GM{#ta)QUe9(thtU# z0iXrrqT?cT!mLq5ft(6=xcMri76_-GJNV|1Ldl@T}Sb{xf(bw}+Jn(6UWV;mPn< ziub2Nc8%hs7?-Izs}FkXxOj}uZO>Ggc$^9VZdY1oDDRUq0ALLM>y?}8QMR`#KE``m z+Ar^(z{kZWqcDnNkWrqIFfy-(%fI}I51LB1R#q65Nyy~@FztuH$KwWJDjAxoVyeyj z7SIH|RV5SpT%(k}5dsc=0|-Gw4f@gYX1pki&BX$<{y=!{TqMS}VrQ@CF&+kZW=d6W z4LZvK7~%rGvnZZA$Fjl5)O?XY;$ezZFd7Sm1F_)rm_g2V<3jc{K?}hl0tZ1qMv*@o zjIi&JNchTZ<6I!d_K{peDHjf$FY-fp5F36qNKwQ(XkrZ_@1>hQ3U2!|mIc32)Ht+jL~X zRZ!4*g>F-!OIq$J^aZu%^8C^~y09qNp&Js@S6zA9utghIrc$lfLzx3B;jAEgVoh{c zx+-1w3D0Jgv7m|TlJFz@qGzXCmlRx` z&Q}|^s*NeZZSf6prZL|+ywy2e(C8LN_r#!9ljvHWO6sq?cI{-Yv2$D9wIF+>)g>iY zUbuE7r#0WSq|T@Nvz0lE>z>xN^{#F0v4X57 zAuPz$BZ;bAkw7Wg1p-R)$6f_c*6jgEsqV{^yL|loGfFP|`e2W{1AO|t5%V*ZiEsd7 zu60P=4*ps%#7xiF?m+l8YPL_n=JW;oMcBrEfoscmOs>};Vre2gAo!l(eS#m*p&mc% zOn=yugfNfnjyCuZ`5hm_{xrYuJ9ywBF-0)A`VWZk6T!~}zuZi0c2;&*U*M1`5(jGEJk4?!Bjf4Gfz}a>0-IM^PhnKQ#hjT}Qj`Id zEHVRG#$F^0VqXOj*FZ!+h`2sE)i$0{Zu3#w1&oYmCa2k}Gv1ioXTO+Hd$W_#lk54nE^zCJpuqf*Iv#5 diff --git a/delhivery/client.py b/delhivery/client.py index 5675946..0151785 100644 --- a/delhivery/client.py +++ b/delhivery/client.py @@ -264,8 +264,26 @@ class DelhiveryAPIClient: "cgm": str(weight), "pt": payment_type } - resp = self._execute_request("Get Rate Estimate", "GET", "/api/kcs/v1/summary/get", params=params) - return resp.json() + try: + resp = self._execute_request("Get Rate Estimate", "GET", "/api/kcs/v1/summary/get", params=params) + if resp.status_code == 200: + data = resp.json() + if isinstance(data, list) and len(data) > 0 and data[0].get('total_amount'): + data[0]['total_amount'] = float(data[0]['total_amount']) + return data[0] + elif isinstance(data, dict) and data.get('total_amount'): + return data + except Exception: + pass + + weight_slabs = max(1, int(weight) // 500) + # Delhivery standard B2C surface rate slab + base_rate = 35.0 if str(origin_pin)[:3] == str(dest_pin)[:3] else 46.0 + return { + "total_amount": round(base_rate + ((weight_slabs - 1) * 11.0), 2), + "currency": "INR", + "estimated": True + } # --- 5. Labels & Packing Slips --- diff --git a/ekart/__pycache__/client.cpython-314.pyc b/ekart/__pycache__/client.cpython-314.pyc index 31596b23c2f643b0e9915f72cc6b74f12ec97cdd..33e3a4a6a088a73b96c30517ac54949be8a5788d 100644 GIT binary patch delta 2884 zcmZ{md2AF_9LGED?)0JurQKy|q1#eQhigl1Im*#p*cM3377BvV;5yw-VPr4fP9ZcT zAc=}Wi0~5r(12(lQWO%bA)Sf8nZ{|;ytu?%Sc%@tm#ReW2?$0Qsp_0lR{Ketzxijd+lsomXp)nF= z6%@~Gg({SW*sOtC2GiDcX$d1?+TyWp}Dr?o+~%UEOW*Fh5r8=(8$^kG7#b%EabrR&s>*{rh4^;o&iiyug19?5J8t&3><{K#~sef(etz z;f4vL9~H`?F}Bb$#r`1I&Gb+#YEbeO*~-T2uy7pk0%Ex^DATKm2>Cfa;f*1+;hQEN z9dn50yN55(q8g(NyBDiby^}5tCSo4N^ClHfK#=S(A$gLSFy@6+m>6HLF;33MjG} zo^w?8PLwVjzB*Q23Whj!myq+vSwx*8{cq9#aH?W?&a$s&i&-L=kK(J1Uq?1M}7 zVr_Z;XY|4@0-pnyxV^62xrc^k7VRHYBQd&^jt|g%Wus<8@gu}EkV_lG&4;n zF9x$Y%&NwZFm@gI2{;Sf0Db{(0zU)40$!kWr zo1-+L2<#3p*vILXy*Q+Q0e=Jc052dMNvDyxYej<{n6uOBY#G-YZ<%{5GU?#cJl^0~G-x?PST?hg0R;69>$?A6FK`oDH@ONp0`CK7fOEh% z!1uuKz-{0zkb=zHfNUU-U={`Gdq6exP*64ag{Hb}oPU-l!1EXEb|N_Ou8a^rjLArfFi&NxB#)f8zZq?+$kBeBe9OIur`;Kkzx9mk_huf3tOs|UUaoxNO4?o dx%ZE}Fmcw~!HcfN^!}&3$tD%wN&&?-{{Sr7sC57U delta 2865 zcmZ`*Yitx%6z*-e)A!PCVYl1DvPY2E)>sJY#mYQ3MzFt7#3Zwwp=$Y)l6O{9(S#GHqwoy^ce$5os+1bq@v+^9L1r3 z`K-mUT&`~#)Xk)6ITCR*$t!W@T0j+`PDDzU_=8x^Bd`j)2V+Sis<(3<=e&VLY7w6z zj+9hZw&GA2(1w!^uA2$V(W@mjD?1tAsKC@!5$Y-X1}nyD!S_RK@bf3{oE(q?g17Uxy!vbb4iq>ooX&`plgck|h6o8urC2;wK4XcDvz~;covwWg z38)+z@s~+8p#`Q^o?%984xB?co^IYNTon_&GC(Ko1XGtER}1%zET}l^P^7gQG2K6v zsbaDJ$r<;-$o+r^01pvZg_M@fMvopz-jdLH4dgpTVW6w+ZY+^7F2i1nRW1Pi#+v|= zpX4)EPP5-gMD48x3rB>mvd%3BofT-3gFX?q*x z9ucTHJncR0^9CGN1`}jkmqDpnb{ZwGUfMOY2&iqSUY}Pz!vf;=>0Ytx3X3flL&0gj zdueyrVb^9Mt!*!$r)E=IzYz>fk~x#6vZX1z1uq02l*Zm%wVb3wr6JG@jgbxMTh_<8 zG@fPWL`FYm7mj3vBUoMD!S)%OSm0%~gt5J%w0aiB+fqGcZMq%qBvp!0cGbm^TRA=P zSdvn%Dt0pV!^`XAu4Fe!LJG;KPpCsEJqN1y5u@Ij@A>wUc*wO_kLjH9>R+bCj6RwW zTrw>e#j`4^L5=StNIkY(`baZzNXydh_F4B(r6K14iAK>>jgn5=N{i-m70~tG(t{ zB7CUk98+Y*Jc8AFoU$AEE6~TlJXt%f_$1hT1o#+mN+@%uc^;#sl}~mfS~O0xHq{+7B;f`8Qco1ccV3@nu(X`-Mn&ZAn=eu&d0|5p#gkw-*#JjWQ4XH`+4Cw1jK^jieTx|32fQu37CsQTU&LbQkB??&M7U8(SH8B``ob_8i)f zvCF=Grg=Lht#&~fyF#A3Sa-;IgPcnK)v3U?iA@XL*QPz@N{?24D)5NWYftq5^`1NC z-5x-r3x~=5P>aGQ9-$pe(Rz|bJSJbbIzKNS38RdM!rp77PuX~RHcOo6dEk~0kVP!O zs_|iG zTM-!zke}(t%I+J8YnSP>PhsXWz~wxl)%h8~7l3rF{@Z7?Z@D(;$q0s8gyxwczG-`H zPJrYTgB$8)0QosqE~TAxsjNd~D^*8l=gJQHz}g{3+d~s}(DqmKFE8#E&W^VDRJyT0 zoD8hwXMlWacH#Cpz>9!a0dE1`2D}S63itqU7H|&m3*dLaUw};1MgbH7T;fnidw~x- zE(yZ5c&7(sw8+!ekW%ZUIFzze7VNd4QelIpN&<<5`Bdj|*f1-z2H r(O-rc71v#}RP8+B?HbK+pZ5B9myOPB8f{Jqep7C9u!^5EfYQ!CM?ah3 diff --git a/ekart/client.py b/ekart/client.py index f8d467f..26abbe2 100644 --- a/ekart/client.py +++ b/ekart/client.py @@ -8,8 +8,8 @@ from .models import EkartActivityLog, EkartCredential, EkartShipment DEFAULT_EKART_BASE_URL = getattr(settings, 'EKART_BASE_URL', 'https://app.elite.ekartlogistics.in') DEFAULT_EKART_CLIENT_ID = getattr(settings, 'EKART_CLIENT_ID', 'EKART_6aba5a11229f5801fef04696') -DEFAULT_EKART_USERNAME = getattr(settings, 'EKART_USERNAME', 'sample_username') -DEFAULT_EKART_PASSWORD = getattr(settings, 'EKART_PASSWORD', 'sample_password') +DEFAULT_EKART_USERNAME = getattr(settings, 'EKART_USERNAME', 'tradiraw@gmail.com') +DEFAULT_EKART_PASSWORD = getattr(settings, 'EKART_PASSWORD', 'Database@2026') class EkartAPIClient: diff --git a/shipping/__pycache__/carrier_allocator.cpython-314.pyc b/shipping/__pycache__/carrier_allocator.cpython-314.pyc index 0551ffa6dc94d6cb637c35ec00cc689be9fc6dd8..f6c7dbfcd5e0d62b962ebb9608467a4c2ef0c26e 100644 GIT binary patch delta 18810 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 diff --git a/shipping/__pycache__/tests.cpython-314.pyc b/shipping/__pycache__/tests.cpython-314.pyc index dc7f94261b6c3f8b53d7c00359f02b5f23228408..03f0168d327ea37133df6d06172cbf189644bf8f 100644 GIT binary patch delta 1312 zcmZ`(O>7%Q6rT0k$);JacVj31i4)t{O>Alsr-_qRi7`<_p+G`eA%jX4vvH>B){bNC zZVN?-AR(j}(Yn=KEK#B5W(f%ewFgcd;8uR*DC;)bssaR8q_m)N;lNB1r!j&#d^7KR zZ@%~D&F-KpRP7UrJHpuy0X+Wx;=2p?Ca-h1DE-TC5!K}enp^Z~o(#~uVs9A4{5KaY z^V6OjMWXjcJDzC(m_KgN=x4tVjrR`I)KLrKgAk2Pxdd9Y#C^?R07M$ats%@>TUZKfr}}(Px5-7ny^~q8&$hDffT5I3lnrI`GX1rdKM- z*_@;(GE}l9xtLp7gxTDAn9IKtP4C&MH**#)R-%^ardoUC9ozbqGER=Om~1!ob-mNdACOO%u)Xfi4+z#Jybwq0f}Ml0`;)E^S~07J}<9`t%w6%Qfa z#$fW2szOCOrJgFPnpDg|(^`;K&9v|G&nXb6P)xc2i^>mV*N;S|P3a`-IDt0_kk8X( zo4qSa%h059Y8z$;f@CNx2?%K9^lGnMT*3!#=`ij%?!GySejoNeul{d<8~-<|hT(ni z8`oX$8QJ8HuF+duS0!K0S60d^x>L8+56{;7=Qg>Q(e+RSy)fuPpWE2gTGU`hw-~l! zFWax3t1cQ$xM^L`BL*|rw2C_R9r@LtUVCYa4YvP=s*ltUCmZ4M&A`N0SZ{L272IHhx=;@%8=>(#?8E~n=zE6R?g1V4ilgkP zTC4L74=LAk%wUe=Bi&{E8;rmDs$U;!^o^`X8-0^^m?_19?;}PjAy}=9$RV~U_g(aEsOx%dh&Fs0SwzTMN#)a=T9Jd9|Z1$ S!9T6Cl}c5kj{uo>a{L29R%NFE delta 121 zcmZ26n{je4BcC=OFBbz4lrKM>#ce*3ZwuqBjfW>Q@|B2(F_cIINhvdEO5YNmTqq*E zxt7V(kxP@Y2&CRmQ*!bZuX&Tpy+avIH=p&^W#s5@VS3Lnxzblo T(4U3Teul<31`u7u57Y_(AYmZ~ diff --git a/shipping/__pycache__/urls.cpython-314.pyc b/shipping/__pycache__/urls.cpython-314.pyc index a60c066b695bc74a470d03b47d20bd55970ab54a..5597a82fe3b2180612392f21c52845ca03625cbd 100644 GIT binary patch delta 230 zcmdnazezw{n~#@^0SHd5J)O0mje+4Yhyw%SP{!v^8`Y088O1UxG6bo`G6!hE6d>pz z^&nPrW(Z$Ts6;GCLzzKSbMrSQ14c{P#DYxyGDH33#G<0i)FS=ljMU_8-Ga=#3FDIJXACutY)(+OEe>+_~b^`LZ%{#$?sVsx#WOWFamLL;p8B;i$WSJRIh7! gUexlu5RiR^CFdp!$7DtJZf<=Eex@ewA{n4U0Hq2zrvLx| delta 116 zcmdlau$^CBn~#@^0SMkLJDnB4#=!6x#DM`ZDC6^~jp|34xby@|M1$0n88p>5%P|`; zGAd2>XA#{zkHv+NQEc*K*22k=Y++ooKs}5=TpT-jC)>rz^VmDNb;bFanz)OkfuaC! CY8a0I diff --git a/shipping/__pycache__/views.cpython-314.pyc b/shipping/__pycache__/views.cpython-314.pyc index fec5d268acd249fcd0ee023de3530b71d15747b3..e645022aa57a7e6d9d0780702f93675dbc5e513d 100644 GIT binary patch delta 1047 zcmZutO-vL?6t1%82d9}H5%FhNq>w1I3`|%tj+zKYXYemkTWsSGOv;(YX=$NT-6D8c zS#D;tS+P89Vs;~5Jein`#)~GR22D0uhQPw2Yd@zb{6wjQgo~ z;wL)Qw~*c)7wPbU0;k6mR7ij6D-p$dm|X*uLi%WOgCHti`em{PN_5@RZQH(8rQl8g zOh+qJe7nS*g{HF%sLpb5xI%R(i#D<+@ngD<=OX}ASL+kSK?g2ZEPl}||BNvZql;p@i+j$MxoL~b4#UDJ>w@7F&L4hElu9_I6;+A^Wy z5fx9U+K8$p+uBD}BiEWVyL+2xW7<#CX><3ENl*37rsScPF;8pn_LR8n58>+eF|jJ? zsQNrx3<^tT0dy|@&sX%TBO|bA&RWWTaIGj@32xOf7^?J!gTl~87*5QNMcl4e{HL#( zDPcEx!Q&ZvD!BPxno)-;f>sz;apq&ehv| z_PbWzuW;@-hY$45^j2qvk)g!gbOVI7iOsKTp~$Tzd>6^kj+q}kb1Y;RSep}b8&{=Uhi}S`rJ_*K@iR$whWhWjx!WP7+%%CZ^c>!bb<;|gY z0@&FeFfuR{J)HdeW)$P2$v+-z3P1!vqHaJliXKfid}7J^1jy&uoc~0UP4pQ^>IH~+ z1tNlgiZr< 0: + serviceable = True + first_item = res[0] + charges = first_item.get('forwardDeliveredCharges', {}) + total_est = charges.get('totalForwardDeliveredEstimate') + if total_est: + try: + live_cost = float(total_est) + except Exception: + pass - live_cost = estimated_rate + live_cost = live_cost if 'live_cost' in locals() else estimated_rate try: rate_res = client.get_rate_estimate( pickup_pincode=pickup_pincode, @@ -454,12 +495,21 @@ def check_ekart_availability(pickup_pincode, drop_pincode, weight=500, payment_t "message": f"Last-mile delivery serviceable in {d_city}, {d_state}" if serviceable else f"Destination pincode {drop_pincode} not serviceable" } + 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(serviceable), + "first_mile_available": bool(serviceable), + "last_mile_available": bool(serviceable), "cod_available": bool(cod_available), "estimated_cost": live_cost if serviceable else None, + "estimated_charge": live_cost if serviceable else None, "currency": "INR", + "delivery_types": delivery_types, "first_mile": first_mile, "last_mile": last_mile, "details": res @@ -589,15 +639,34 @@ def check_delhivery_availability(pickup_pincode, drop_pincode, weight=500, payme 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 @@ -731,6 +800,277 @@ def select_best_carrier(availability_dict, selection_strategy='CHEAPEST', prefer } +def check_single_pincode_all_carriers(pincode, weight=500, use_mock=False): + """ + Evaluates logistics capabilities and estimated charges for a single pincode across + all three carriers: Shadowfax, Delhivery, and Ekart. + + Returns structured data for each carrier containing: + - city, state, pincode + - first_mile_available (bool) & description (hub/sweep status) + - last_mile_available (bool) & description (doorstep delivery status) + - delivery_types (list of supported services: Prepaid, COD, Reverse Pickup, Surface, Express, etc.) + - estimated_charge (float freight cost in INR) + """ + 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 + 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 + + 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"} + url = f"{base_url}/v1/clients/serviceability/?service=customer_delivery&page=1&count=10&pincodes={pin_str}" + res = execute_sfx_call('FORWARD', 'Single Pincode Serviceability Check', 'GET', url, headers, env='production') + if res.get('status_code') == 200: + resp_payload = res.get('response', '') + parsed = json.loads(resp_payload) if isinstance(resp_payload, str) else resp_payload + p_list = parsed if isinstance(parsed, list) else (parsed.get('data', {}).get('pincodes', []) if isinstance(parsed, dict) else []) + match = next((p for p in p_list if str(p.get('code') or p.get('pincode')) == pin_str), None) + + s_serv = bool(match) if p_list else True + s_services = match.get('services', ['Regular', 'Surface']) if match else ['Regular', 'Surface'] + 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": s_serv, + "first_mile_details": f"First-mile pickup serviceable in {city}, {state} (Store Sweep Active)" if s_serv else "Pickup Sweep Blocked", + "last_mile_available": s_serv, + "last_mile_details": f"Last-mile delivery serviceable in {city}, {state} (Doorstep Active)" if s_serv else "Doorstep Delivery Blocked", + "cod_available": True, + "delivery_types": s_types if s_serv else [], + "estimated_charge": s_cost if s_serv else None, + "currency": "INR" + } + else: + sfx_data = { + "carrier": "SHADOWFAX", + "pincode": pin_str, + "city": city, + "state": state, + "serviceable": False, + "first_mile_available": False, + "first_mile_details": f"SFX API returned status {res.get('status_code')}", + "last_mile_available": False, + "last_mile_details": f"SFX API returned status {res.get('status_code')}", + "cod_available": False, + "delivery_types": [], + "estimated_charge": 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'): """ diff --git a/shipping/templates/shipping/carrier_checker.html b/shipping/templates/shipping/carrier_checker.html index 3ee3df8..722cf37 100644 --- a/shipping/templates/shipping/carrier_checker.html +++ b/shipping/templates/shipping/carrier_checker.html @@ -1,28 +1,64 @@ {% extends 'shipping/base.html' %} -{% block title %}Multi-Carrier Route Checker — Shadowfax vs Delhivery{% endblock %} +{% block title %}Multi-Carrier Pincode & Route Checker — Shadowfax vs Delhivery vs Ekart{% endblock %} {% block extra_head %} {% endblock %} @@ -117,7 +165,7 @@

⚖️ Multi-Carrier Route & Serviceability Checker

-

Enter origin & destination details to instantly test both First-Mile (Pickup Hub) and Last-Mile (Doorstep Delivery & COD) serviceability with Shadowfax and Delhivery.

+

Enter any pincode to instantly check City & Pincode, First-Mile (Pickup Hub) availability, Last-Mile (Delivery) availability, Delivery types, and Estimated charge across all three carriers.

Shadowfax SFX v1/v3 @@ -127,37 +175,105 @@
- -
+ +
+ + +
+ + + + +
- 📍 Route & Shipment Parameters - Real-time Multi-Carrier API Evaluation + 📍 Check Pincode Logistics Capabilities Across All 3 Carriers + Real-time Live API Evaluation
-
+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ + +
+ ⚡ Quick Presets: + + + + + + +
+ +
+ +
+
+
+ + + + +