- pricingid null: lookupDoormilePrice now returns matched rule ID; wired to BookingServiceOption.Pricingid - Zone rename: Interstate→Regional, OtherState→National throughout (code + DB migrated) - Zone from pincodes: CheckPrice now accepts pickup_pincode+delivery_pincode and auto-resolves zone - Delivery geocoding: pincodeToLatLon() maps 3-digit prefix to city coords when lat/lon are 0 - Device tokens: device_token field added to PinVerify DTOs; saved on both customer and miler login - Assignment retry: RejectMilerAssignment now re-triggers AssignCustomerMiler/AssignCRMMiler immediately - Provider empty B2C: defaults to Doormile when no pricing provider row matches - City gate 422→400: StatusUnprocessableEntity corrected to StatusBadRequest - Miler GPS 0,0: WS tracking falls back to MilerProfile DB coords when Redis key is expired Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"time"
|
|
|
|
"doormile/db"
|
|
"doormile/models"
|
|
"doormile/utils"
|
|
)
|
|
|
|
// pincodeToState maps an Indian pincode to a state key using its first 3 digits.
|
|
// Covers the states Doormile operates in; other pincodes fall back to their 2-digit prefix.
|
|
func pincodeToState(pincode string) string {
|
|
if len(pincode) < 3 {
|
|
return pincode
|
|
}
|
|
p, err := strconv.Atoi(pincode[:3])
|
|
if err != nil {
|
|
if len(pincode) >= 2 {
|
|
return pincode[:2]
|
|
}
|
|
return pincode
|
|
}
|
|
switch {
|
|
case p >= 500 && p <= 535:
|
|
return "AP_TS" // Andhra Pradesh / Telangana
|
|
case p >= 560 && p <= 591:
|
|
return "KA" // Karnataka
|
|
case p >= 600 && p <= 643:
|
|
return "TN" // Tamil Nadu
|
|
case p >= 670 && p <= 695:
|
|
return "KL" // Kerala
|
|
case p >= 380 && p <= 396:
|
|
return "GJ" // Gujarat
|
|
case p >= 400 && p <= 444:
|
|
return "MH" // Maharashtra
|
|
default:
|
|
if len(pincode) >= 2 {
|
|
return pincode[:2]
|
|
}
|
|
return pincode
|
|
}
|
|
}
|
|
|
|
// resolveZone determines the DoormilePricing zone from pickup and delivery pincodes.
|
|
// - Local — same 3-digit prefix (same city/sorting district)
|
|
// - Regional — different city, same state
|
|
// - National — different state
|
|
func resolveZone(pickupPincode, deliveryPincode string) string {
|
|
if len(pickupPincode) >= 3 && len(deliveryPincode) >= 3 && pickupPincode[:3] == deliveryPincode[:3] {
|
|
return "Local"
|
|
}
|
|
if pincodeToState(pickupPincode) == pincodeToState(deliveryPincode) {
|
|
return "Regional"
|
|
}
|
|
return "National"
|
|
}
|
|
|
|
// pincodeToLatLon returns approximate coordinates for a pincode using its
|
|
// 3-digit postal division. Returns ok=false for unrecognised pincodes.
|
|
func pincodeToLatLon(pincode string) (lat, lon float64, ok bool) {
|
|
if len(pincode) < 3 {
|
|
return
|
|
}
|
|
p, err := strconv.Atoi(pincode[:3])
|
|
if err != nil {
|
|
return
|
|
}
|
|
switch {
|
|
case p >= 500 && p <= 535:
|
|
return 17.385044, 78.486671, true // Hyderabad / Telangana
|
|
case p >= 560 && p <= 591:
|
|
return 12.971599, 77.594566, true // Bengaluru / Karnataka
|
|
case p >= 600 && p <= 643:
|
|
return 13.082680, 80.270718, true // Chennai / Tamil Nadu
|
|
case p >= 670 && p <= 695:
|
|
return 10.850516, 76.271080, true // Kerala
|
|
case p >= 380 && p <= 396:
|
|
return 23.022505, 72.571365, true // Ahmedabad / Gujarat
|
|
case p >= 400 && p <= 444:
|
|
return 19.075984, 72.877656, true // Mumbai / Maharashtra
|
|
}
|
|
return
|
|
}
|
|
|
|
// normalizePricingCategory returns a DoormilePricing-compatible category string.
|
|
// Falls back to "General" for unknown or empty values.
|
|
func normalizePricingCategory(category string) string {
|
|
switch category {
|
|
case "General", "Documents", "Electronics", "Clothing", "Fragile", "Medical", "Automotive", "Food":
|
|
return category
|
|
default:
|
|
return "General"
|
|
}
|
|
}
|
|
|
|
// mapServiceTypeToPricing maps the booking service type to a DoormilePricing service type.
|
|
// DoormilePricing has only Normal and Express.
|
|
func mapServiceTypeToPricing(serviceType string) string {
|
|
if serviceType == "Fast" || serviceType == "Superfast" {
|
|
return "Express"
|
|
}
|
|
return "Normal"
|
|
}
|
|
|
|
// lookupDoormilePrice fetches pricing for the given zone/serviceType/weight/category
|
|
// from Redis first, falling back to Postgres on a cache miss.
|
|
// Returns (midpoint price, pricingID, true) on match, or (0, nil, false) when no rule matches.
|
|
func lookupDoormilePrice(zone, pricingServiceType string, weight float64, category string) (float64, *int, bool) {
|
|
var allRules []models.DoormilePricing
|
|
|
|
if db.Rdb != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
cached, err := db.Rdb.Get(ctx, pricingCacheKey(zone, pricingServiceType)).Result()
|
|
cancel()
|
|
if err == nil {
|
|
if jerr := json.Unmarshal([]byte(cached), &allRules); jerr != nil {
|
|
utils.Warn("Corrupt pricing cache in booking, evicting", "zone", zone, "servicetype", pricingServiceType)
|
|
invalidatePricingCache(zone, pricingServiceType)
|
|
allRules = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if allRules == nil {
|
|
var err error
|
|
allRules, err = loadFromPostgres(zone, pricingServiceType)
|
|
if err != nil || len(allRules) == 0 {
|
|
return 0, nil, false
|
|
}
|
|
}
|
|
|
|
matched := applyFilters(allRules, weight, category)
|
|
if len(matched) == 0 && category != "General" {
|
|
matched = applyFilters(allRules, weight, "General")
|
|
}
|
|
if len(matched) == 0 {
|
|
return 0, nil, false
|
|
}
|
|
|
|
rule := matched[0]
|
|
id := rule.Doormile_pricing_id
|
|
return (rule.Minprice + rule.Maxprice) / 2, &id, true
|
|
}
|