package controllers import ( "math" "strconv" "doormile/db" "doormile/dto" "doormile/models" "doormile/utils" "github.com/gofiber/fiber/v2" ) // Customer profile and saved addresses. // // The rest of the customer surface — auth, catalogue, estimate, bookings, // tracking, places, devices — lives in the cx*Controller.go files and answers // in the customer envelope (utils.CxOK / utils.CxFail). The PIN login, // single-destination booking create/list/detail/cancel and the /customer/track // read that used to live here were replaced by that surface, not moved: a // customer books a pickup with 1..N destinations now, and there is no shape in // which the old single-address request is still a valid booking. func calculateDistance(lat1, lon1, lat2, lon2 float64) float64 { const R = 6371.0 dLat := (lat2 - lat1) * math.Pi / 180.0 dLon := (lon2 - lon1) * math.Pi / 180.0 a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Cos(lat1*math.Pi/180.0)*math.Cos(lat2*math.Pi/180.0)* math.Sin(dLon/2)*math.Sin(dLon/2) c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) return R * c } func calculateVolumetricWeight(length, width, height float64) float64 { return (length * width * height) / 5000.0 } func GetCustomerProfile(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) var customer models.AppCustomer if err := db.DB.First(&customer, customerID).Error; err != nil { return utils.CxNotFound(c, "We could not find your profile") } return utils.CxOK(c, renderCustomer(&customer)) } func UpdateCustomerProfile(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) var customer models.AppCustomer if err := db.DB.First(&customer, customerID).Error; err != nil { return utils.CxNotFound(c, "We could not find your profile") } // Pointer fields: an omitted key leaves the stored value alone, an explicit // value overwrites it. The previous version cleared email and lastname on // every call that did not resend them, which quietly wiped a customer's // email the first time they edited their name. var req struct { Name *string `json:"name"` Email *string `json:"email"` Defaultlatitude *float64 `json:"defaultLatitude"` Defaultlongitude *float64 `json:"defaultLongitude"` Defaultpincode *string `json:"defaultPincode"` } if err := c.BodyParser(&req); err != nil { return utils.CxBadRequest(c, "We could not read that request") } if req.Name != nil { first, last := splitName(*req.Name) if first == "" { return utils.CxFail(c, fiber.StatusBadRequest, utils.CxErrInvalidName, "Enter your full name") } customer.Firstname, customer.Lastname = first, last } if req.Email != nil { customer.Email = *req.Email } if req.Defaultlatitude != nil { customer.Defaultlatitude = *req.Defaultlatitude } if req.Defaultlongitude != nil { customer.Defaultlongitude = *req.Defaultlongitude } if req.Defaultpincode != nil { customer.Defaultpincode = *req.Defaultpincode } if err := db.DB.Save(&customer).Error; err != nil { utils.Error("UpdateCustomerProfile: save failed", "customer_id", customerID, "error", err) return utils.CxInternal(c) } return utils.CxOK(c, renderCustomer(&customer)) } func GetCustomerLocations(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) var locations []models.AppCustomerLocation if err := db.DB.Where("appcustomerid = ? AND status = ?", customerID, "Active"). Order("isdefault DESC, appcustomerlocationid DESC").Find(&locations).Error; err != nil { utils.Error("GetCustomerLocations: query failed", "customer_id", customerID, "error", err) return utils.CxInternal(c) } out := make([]fiber.Map, 0, len(locations)) for i := range locations { out = append(out, renderSavedAddress(&locations[i])) } return utils.CxList(c, out, len(out), nil) } func CreateCustomerLocation(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) var count int64 db.DB.Model(&models.AppCustomerLocation{}). Where("appcustomerid = ? AND status = ?", customerID, "Active").Count(&count) if count >= 10 { return utils.CxBadRequest(c, "You can save up to 10 addresses") } req := new(dto.LocationCreateRequest) if err := c.BodyParser(req); err != nil { return utils.CxBadRequest(c, "We could not read that request") } if req.Address == "" || req.Pincode == "" || req.Latitude == 0 || req.Longitude == 0 { return utils.CxBadRequest(c, "An address needs a street, a pincode and a map location") } if req.Isdefault { db.DB.Model(&models.AppCustomerLocation{}). Where("appcustomerid = ?", customerID).Update("isdefault", false) } location := models.AppCustomerLocation{ Appcustomerid: customerID, Label: req.Label, Receivername: req.Receivername, Receiverphone: req.Receiverphone, Address: req.Address, Landmark: req.Landmark, City: req.City, State: req.State, Pincode: req.Pincode, Latitude: req.Latitude, Longitude: req.Longitude, Isdefault: req.Isdefault, Status: "Active", } if err := db.DB.Create(&location).Error; err != nil { utils.Error("CreateCustomerLocation: insert failed", "customer_id", customerID, "error", err) return utils.CxInternal(c) } return utils.CxCreated(c, renderSavedAddress(&location)) } func UpdateCustomerLocation(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) locationID, err := strconv.Atoi(c.Params("id")) if err != nil { return utils.CxBadRequest(c, "That address could not be found") } var location models.AppCustomerLocation if err := db.DB.Where("appcustomerlocationid = ? AND appcustomerid = ?", locationID, customerID). First(&location).Error; err != nil { return utils.CxNotFound(c, "That address could not be found") } req := new(dto.LocationCreateRequest) if err := c.BodyParser(req); err != nil { return utils.CxBadRequest(c, "We could not read that request") } if req.Label != "" { location.Label = req.Label } location.Receivername = req.Receivername location.Receiverphone = req.Receiverphone if req.Address != "" { location.Address = req.Address } location.Landmark = req.Landmark location.City = req.City location.State = req.State if req.Pincode != "" { location.Pincode = req.Pincode } if req.Latitude != 0 { location.Latitude = req.Latitude } if req.Longitude != 0 { location.Longitude = req.Longitude } location.Isdefault = req.Isdefault if req.Isdefault { db.DB.Model(&models.AppCustomerLocation{}). Where("appcustomerid = ?", customerID).Update("isdefault", false) } if err := db.DB.Save(&location).Error; err != nil { utils.Error("UpdateCustomerLocation: save failed", "location_id", locationID, "error", err) return utils.CxInternal(c) } return utils.CxOK(c, renderSavedAddress(&location)) } func DeleteCustomerLocation(c *fiber.Ctx) error { customerID := c.Locals("userid").(int) locationID, err := strconv.Atoi(c.Params("id")) if err != nil { return utils.CxBadRequest(c, "That address could not be found") } var location models.AppCustomerLocation if err := db.DB.Where("appcustomerlocationid = ? AND appcustomerid = ?", locationID, customerID). First(&location).Error; err != nil { return utils.CxNotFound(c, "That address could not be found") } location.Status = "InActive" if err := db.DB.Save(&location).Error; err != nil { utils.Error("DeleteCustomerLocation: save failed", "location_id", locationID, "error", err) return utils.CxInternal(c) } return utils.CxOK(c, fiber.Map{"id": strconv.Itoa(location.Appcustomerlocationid), "deleted": true}) } // renderSavedAddress is the one shape a saved address is returned in, matching // the two-line title/sub the pickup search and the booking pickup block use — // so an address picked from Saved and one picked from search are the same // object to the client. func renderSavedAddress(l *models.AppCustomerLocation) fiber.Map { title := l.Label if title == "" { title = l.Address } return fiber.Map{ "id": strconv.Itoa(l.Appcustomerlocationid), "label": l.Label, "title": title, "sub": joinNonEmpty(", ", l.Address, l.Landmark, l.City, l.Pincode), "recipientName": l.Receivername, "recipientPhone": l.Receiverphone, "lat": l.Latitude, "lng": l.Longitude, "isDefault": l.Isdefault, } }