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:
2026-07-08 20:31:25 +05:30
parent a30713c633
commit d7c0f35ffe
3 changed files with 153 additions and 59 deletions

View File

@@ -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
// --------------------