fix: admin console endpoints
- Remove scratch/check_users.go - GetAdminBookings: LIMIT/OFFSET enforced, returns pageno/pagesize/pages - GetAdminCustomers: new B2C appcustomers list with booking count - AdminCancelBooking: cancel + miler release + FCM + NATS event
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"doormile/config"
|
||||
@@ -442,6 +443,79 @@ func GetTenantCustomers(c *fiber.Ctx) error {
|
||||
return utils.List(c, customers, int64(len(customers)))
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// B2C APP CUSTOMERS
|
||||
// --------------------
|
||||
|
||||
func GetAdminCustomers(c *fiber.Ctx) error {
|
||||
pageno := max(1, c.QueryInt("pageno", 1))
|
||||
pagesize := min(100, max(1, c.QueryInt("pagesize", 20)))
|
||||
offset := (pageno - 1) * pagesize
|
||||
keyword := c.Query("keyword")
|
||||
|
||||
query := db.DB.Model(&models.AppCustomer{})
|
||||
if keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
query = query.Where("firstname ILIKE ? OR lastname ILIKE ? OR phone ILIKE ?", like, like, like)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return utils.Internal(c, "failed to count customers")
|
||||
}
|
||||
|
||||
var customers []models.AppCustomer
|
||||
if err := query.Offset(offset).Limit(pagesize).Find(&customers).Error; err != nil {
|
||||
return utils.Internal(c, "failed to fetch customers")
|
||||
}
|
||||
|
||||
ids := make([]int, 0, len(customers))
|
||||
for _, cust := range customers {
|
||||
ids = append(ids, cust.Appcustomerid)
|
||||
}
|
||||
|
||||
bookingCounts := make(map[int]int64, len(ids))
|
||||
if len(ids) > 0 {
|
||||
type countRow struct {
|
||||
Appcustomerid int
|
||||
Cnt int64
|
||||
}
|
||||
var rows []countRow
|
||||
if err := db.DB.Model(&models.PickupBooking{}).
|
||||
Select("appcustomerid, count(*) as cnt").
|
||||
Where("appcustomerid IN ?", ids).
|
||||
Group("appcustomerid").
|
||||
Scan(&rows).Error; err == nil {
|
||||
for _, r := range rows {
|
||||
bookingCounts[r.Appcustomerid] = r.Cnt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]fiber.Map, 0, len(customers))
|
||||
for _, cust := range customers {
|
||||
data = append(data, fiber.Map{
|
||||
"appcustomerid": cust.Appcustomerid,
|
||||
"name": strings.TrimSpace(cust.Firstname + " " + cust.Lastname),
|
||||
"phone": cust.Phone,
|
||||
"email": cust.Email,
|
||||
"createdat": cust.Createdat,
|
||||
"totalbookings": bookingCounts[cust.Appcustomerid],
|
||||
})
|
||||
}
|
||||
|
||||
pages := int(math.Ceil(float64(total) / float64(pagesize)))
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"success": true,
|
||||
"data": data,
|
||||
"total": total,
|
||||
"pageno": pageno,
|
||||
"pagesize": pagesize,
|
||||
"pages": pages,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateTenantCustomer(c *fiber.Ctx) error {
|
||||
req := new(dto.TenantCustomerCreateRequest)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
@@ -891,11 +965,31 @@ func AssignMilerVehicle(c *fiber.Ctx) error {
|
||||
// --------------------
|
||||
|
||||
func GetAdminBookings(c *fiber.Ctx) error {
|
||||
pageno := max(1, c.QueryInt("pageno", 1))
|
||||
pagesize := min(100, max(1, c.QueryInt("pagesize", 20)))
|
||||
offset := (pageno - 1) * pagesize
|
||||
|
||||
var total int64
|
||||
if err := db.DB.Model(&models.PickupBooking{}).Count(&total).Error; err != nil {
|
||||
return utils.Internal(c, "failed to count bookings")
|
||||
}
|
||||
|
||||
var bookings []models.PickupBooking
|
||||
if err := db.DB.Preload("Parcels").Preload("ServiceOptions").Find(&bookings).Error; err != nil {
|
||||
if err := db.DB.Preload("Parcels").Preload("ServiceOptions").
|
||||
Offset(offset).Limit(pagesize).Find(&bookings).Error; err != nil {
|
||||
return utils.Internal(c, "failed to fetch bookings")
|
||||
}
|
||||
return utils.List(c, bookings, int64(len(bookings)))
|
||||
|
||||
pages := int(math.Ceil(float64(total) / float64(pagesize)))
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"success": true,
|
||||
"data": bookings,
|
||||
"total": total,
|
||||
"pageno": pageno,
|
||||
"pagesize": pagesize,
|
||||
"pages": pages,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateCRMBooking(c *fiber.Ctx) error {
|
||||
@@ -1207,6 +1301,59 @@ func AdminUpdateBookingStatus(c *fiber.Ctx) error {
|
||||
return utils.OK(c, booking)
|
||||
}
|
||||
|
||||
// AdminCancelBooking cancels a booking on behalf of operations, frees up the
|
||||
// assigned miler (if any), and notifies downstream systems (NATS + customer push).
|
||||
// A booking that has already been converted to a consignment (shipped) or is
|
||||
// already cancelled cannot be cancelled again.
|
||||
func AdminCancelBooking(c *fiber.Ctx) error {
|
||||
id, err := strconv.Atoi(c.Params("id"))
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, "invalid booking ID")
|
||||
}
|
||||
|
||||
var booking models.PickupBooking
|
||||
if err := db.DB.First(&booking, id).Error; err != nil {
|
||||
return utils.NotFound(c, "booking not found")
|
||||
}
|
||||
|
||||
if booking.Status == constants.BookingConvertedConsignment || booking.Status == constants.BookingCancelled {
|
||||
return utils.BadRequest(c, "cannot cancel a delivered or already cancelled booking")
|
||||
}
|
||||
|
||||
booking.Status = constants.BookingCancelled
|
||||
booking.Updatedat = time.Now()
|
||||
if err := db.DB.Save(&booking).Error; err != nil {
|
||||
return utils.Internal(c, "failed to cancel booking")
|
||||
}
|
||||
|
||||
if booking.Assignedmileruserid != nil {
|
||||
db.DB.Model(&models.MilerProfile{}).
|
||||
Where("userid = ?", *booking.Assignedmileruserid).
|
||||
Update("availabilitystatus", constants.MilerAvailable)
|
||||
}
|
||||
|
||||
if db.Js != nil {
|
||||
payload := map[string]interface{}{
|
||||
"bookingid": booking.Bookingid,
|
||||
"reason": "admin_cancelled",
|
||||
}
|
||||
if data, err := json.Marshal(payload); err == nil {
|
||||
if _, err := db.Js.Publish("booking.cancelled", data); err != nil {
|
||||
utils.Warn("AdminCancelBooking: NATS publish failed", "booking_id", booking.Bookingid, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var customer models.AppCustomer
|
||||
if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
|
||||
if err := notify.SendToDevice(customer.Devicetoken, "Booking Cancelled", "Your booking has been cancelled by operations", nil); err != nil {
|
||||
utils.Warn("AdminCancelBooking: failed to notify customer", "booking_id", booking.Bookingid, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return utils.Message(c, "booking cancelled")
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// CONSIGNMENTS
|
||||
// --------------------
|
||||
|
||||
Reference in New Issue
Block a user