Files
doormile_backend/controllers/customerController.go

263 lines
8.2 KiB
Go

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,
}
}