feat: date-range endpoints for hub inbound, bookings, and batches
Adds GET /hub/inbound, GET /hub/bookings (both new), and extends the existing GET /hub/batches with optional from/to (YYYY-MM-DD, inclusive) query params, backing the Hub Console's date-range picker on the Pickup Requests, Receive Parcels, and Dispatch & Transfer pages. Reuses the same range-parsing helper (renamed from parseHubDashboardRange to parseHubDateRange) added for GET /hub/dashboard, defaulting to today when omitted. The existing live endpoints (/inbound/today, /bookings/unassigned) are untouched. GET /hub/bookings also surfaces each booking's assignment status, mapped to a small vocabulary (pending/assigned/picked_up/delivered/cancelled) via the new hubBookingDisplayStatus, plus milername when assigned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,11 +100,11 @@ func todayMidnight() time.Time {
|
||||
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// parseHubDashboardRange parses optional from/to (YYYY-MM-DD) query params
|
||||
// for GetHubDashboardStats, defaulting to "today so far" when both are
|
||||
// omitted. Mirrors GetHubReport's range semantics: to is inclusive of the
|
||||
// whole day.
|
||||
func parseHubDashboardRange(c *fiber.Ctx) (time.Time, time.Time, error) {
|
||||
// parseHubDateRange parses optional from/to (YYYY-MM-DD) query params shared
|
||||
// by the hub console's date-range-scoped endpoints (dashboard, inbound,
|
||||
// bookings), defaulting to "today so far" when both are omitted. Mirrors
|
||||
// GetHubReport's range semantics: to is inclusive of the whole day.
|
||||
func parseHubDateRange(c *fiber.Ctx) (time.Time, time.Time, error) {
|
||||
fromStr := c.Query("from")
|
||||
toStr := c.Query("to")
|
||||
|
||||
@@ -215,7 +215,7 @@ func GetHubDashboardStats(c *fiber.Ctx) error {
|
||||
hubID := c.Locals("hubid").(int)
|
||||
prefix := hubPincodePrefix(hubID)
|
||||
|
||||
from, to, err := parseHubDashboardRange(c)
|
||||
from, to, err := parseHubDateRange(c)
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, err.Error())
|
||||
}
|
||||
@@ -305,6 +305,115 @@ func GetHubUnassignedBookings(c *fiber.Ctx) error {
|
||||
return utils.List(c, response, int64(len(response)))
|
||||
}
|
||||
|
||||
// hubBookingDisplayStatus normalizes a booking's raw status into the small
|
||||
// vocabulary the hub console's history views key off: "pending" (needs a
|
||||
// miler), "cancelled", or a handled state ("assigned", "picked_up",
|
||||
// "delivered").
|
||||
func hubBookingDisplayStatus(status string, hasMiler bool) string {
|
||||
if status == constants.BookingCancelled {
|
||||
return "cancelled"
|
||||
}
|
||||
if !hasMiler {
|
||||
return "pending"
|
||||
}
|
||||
switch status {
|
||||
case constants.BookingPickedUp:
|
||||
return "picked_up"
|
||||
case constants.BookingConvertedConsignment:
|
||||
return "delivered"
|
||||
default:
|
||||
return "assigned"
|
||||
}
|
||||
}
|
||||
|
||||
// GetHubBookingsRange returns all pickup requests created at this hub in
|
||||
// [from, to] (both YYYY-MM-DD, inclusive), each with its current assignment
|
||||
// status and miler. Historical superset of GetHubUnassignedBookings, which
|
||||
// stays as the live "needs a miler now" view.
|
||||
func GetHubBookingsRange(c *fiber.Ctx) error {
|
||||
hubID := c.Locals("hubid").(int)
|
||||
prefix := hubPincodePrefix(hubID)
|
||||
|
||||
from, to, err := parseHubDateRange(c)
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, err.Error())
|
||||
}
|
||||
|
||||
query := db.DB.Preload("Parcels").Where("createdat BETWEEN ? AND ?", from, to)
|
||||
if prefix != "" {
|
||||
query = query.Where("pickuppincode LIKE ?", prefix+"%")
|
||||
}
|
||||
|
||||
var bookings []models.PickupBooking
|
||||
if err := query.Order("createdat DESC").Find(&bookings).Error; err != nil {
|
||||
return utils.Internal(c, "failed to fetch bookings")
|
||||
}
|
||||
|
||||
response := make([]fiber.Map, 0, len(bookings))
|
||||
for _, b := range bookings {
|
||||
var customer models.AppCustomer
|
||||
db.DB.Where("appcustomerid = ?", b.Appcustomerid).First(&customer)
|
||||
|
||||
hasMiler := b.Assignedmileruserid != nil
|
||||
var milerName interface{}
|
||||
if hasMiler {
|
||||
var mp models.MilerProfile
|
||||
if db.DB.Where("userid = ?", *b.Assignedmileruserid).First(&mp).Error == nil {
|
||||
milerName = mp.Displayname
|
||||
}
|
||||
}
|
||||
|
||||
response = append(response, fiber.Map{
|
||||
"bookingid": b.Bookingid,
|
||||
"bookingno": b.Bookingno,
|
||||
"customer_name": strings.TrimSpace(customer.Firstname + " " + customer.Lastname),
|
||||
"pickup_address": b.Pickupaddress,
|
||||
"pickup_pincode": b.Pickuppincode,
|
||||
"delivery_address": b.Deliveryaddress,
|
||||
"parcels": b.Parcels,
|
||||
"status": hubBookingDisplayStatus(b.Status, hasMiler),
|
||||
"milername": milerName,
|
||||
"created_at": b.Createdat,
|
||||
})
|
||||
}
|
||||
|
||||
return utils.List(c, response, int64(len(response)))
|
||||
}
|
||||
|
||||
// renderInboundConsignment builds the hub console's inbound-parcel row,
|
||||
// shared by GetHubInboundToday and GetHubInboundRange.
|
||||
func renderInboundConsignment(cs models.Consignment) fiber.Map {
|
||||
originName := fmt.Sprintf("Direct pickup (%s)", cs.Pickuppincode)
|
||||
if cs.Originhubid != nil {
|
||||
var oh models.Hub
|
||||
if db.DB.Where("hubid = ?", *cs.Originhubid).First(&oh).Error == nil {
|
||||
originName = oh.Hubname
|
||||
}
|
||||
}
|
||||
|
||||
return fiber.Map{
|
||||
"consignmentid": cs.Consignmentid,
|
||||
"trackingno": cs.Trackingno,
|
||||
// sendername is empty: consignments carry no FK back to any
|
||||
// customer/booking record (Senderid/Receiverid are never
|
||||
// populated anywhere in this codebase).
|
||||
"sendername": "",
|
||||
"originname": originName,
|
||||
// destinationname: consignments store no free-text delivery
|
||||
// address, only deliverypincode — used as the best available label.
|
||||
"destinationname": cs.Deliverypincode,
|
||||
"deliverypincode": cs.Deliverypincode,
|
||||
"weight": fmt.Sprintf("%.1f kg", cs.Chargeableweight),
|
||||
"chargeableweight": cs.Chargeableweight,
|
||||
"condition": cs.Condition,
|
||||
"shelf": cs.Shelf,
|
||||
// temperature: no column exists anywhere yet for this.
|
||||
"temperature": "N/A",
|
||||
"status": cs.Status,
|
||||
"updatedat": cs.Updatedat,
|
||||
}
|
||||
}
|
||||
|
||||
func GetHubInboundToday(c *fiber.Ctx) error {
|
||||
hubID := c.Locals("hubid").(int)
|
||||
midnight := todayMidnight()
|
||||
@@ -317,33 +426,33 @@ func GetHubInboundToday(c *fiber.Ctx) error {
|
||||
|
||||
response := make([]fiber.Map, 0, len(consignments))
|
||||
for _, cs := range consignments {
|
||||
originName := fmt.Sprintf("Direct pickup (%s)", cs.Pickuppincode)
|
||||
if cs.Originhubid != nil {
|
||||
var oh models.Hub
|
||||
if db.DB.Where("hubid = ?", *cs.Originhubid).First(&oh).Error == nil {
|
||||
originName = oh.Hubname
|
||||
}
|
||||
}
|
||||
response = append(response, renderInboundConsignment(cs))
|
||||
}
|
||||
|
||||
response = append(response, fiber.Map{
|
||||
"consignmentid": cs.Consignmentid,
|
||||
"trackingno": cs.Trackingno,
|
||||
// sendername is empty: consignments carry no FK back to any
|
||||
// customer/booking record (Senderid/Receiverid are never
|
||||
// populated anywhere in this codebase).
|
||||
"sendername": "",
|
||||
"originname": originName,
|
||||
// destinationname: consignments store no free-text delivery
|
||||
// address, only deliverypincode — used as the best available label.
|
||||
"destinationname": cs.Deliverypincode,
|
||||
"weight": fmt.Sprintf("%.1f kg", cs.Chargeableweight),
|
||||
"condition": cs.Condition,
|
||||
"shelf": cs.Shelf,
|
||||
// temperature: no column exists anywhere yet for this.
|
||||
"temperature": "N/A",
|
||||
"status": cs.Status,
|
||||
"updatedat": cs.Updatedat,
|
||||
})
|
||||
return utils.List(c, response, int64(len(response)))
|
||||
}
|
||||
|
||||
// GetHubInboundRange returns parcels received/inbounded at this hub whose
|
||||
// inbound timestamp falls in [from, to] (both YYYY-MM-DD, inclusive),
|
||||
// defaulting to today when omitted. Historical counterpart to
|
||||
// GetHubInboundToday, which stays as the live "since midnight" view.
|
||||
func GetHubInboundRange(c *fiber.Ctx) error {
|
||||
hubID := c.Locals("hubid").(int)
|
||||
|
||||
from, to, err := parseHubDateRange(c)
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, err.Error())
|
||||
}
|
||||
|
||||
var consignments []models.Consignment
|
||||
if err := db.DB.Where("currenthubid = ? AND status = ? AND updatedat BETWEEN ? AND ?", hubID, constants.ConsignmentInwardedAtHub, from, to).
|
||||
Order("updatedat DESC").Find(&consignments).Error; err != nil {
|
||||
return utils.Internal(c, "failed to fetch inbound consignments")
|
||||
}
|
||||
|
||||
response := make([]fiber.Map, 0, len(consignments))
|
||||
for _, cs := range consignments {
|
||||
response = append(response, renderInboundConsignment(cs))
|
||||
}
|
||||
|
||||
return utils.List(c, response, int64(len(response)))
|
||||
@@ -442,12 +551,23 @@ func CreateInboundScan(c *fiber.Ctx) error {
|
||||
// HUB BATCHES (DISPATCH) — backed by the existing Tripsheet model
|
||||
// --------------------
|
||||
|
||||
// GetHubBatches lists this hub's outgoing batches. With no from/to it
|
||||
// returns every batch (unchanged default); passing from/to (YYYY-MM-DD,
|
||||
// inclusive) scopes it to batches created in that range.
|
||||
func GetHubBatches(c *fiber.Ctx) error {
|
||||
hubID := c.Locals("hubid").(int)
|
||||
|
||||
query := db.DB.Where("sourcehubid = ? AND deletedat IS NULL", hubID)
|
||||
if c.Query("from") != "" || c.Query("to") != "" {
|
||||
from, to, err := parseHubDateRange(c)
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, err.Error())
|
||||
}
|
||||
query = query.Where("createdat BETWEEN ? AND ?", from, to)
|
||||
}
|
||||
|
||||
var tripsheets []models.Tripsheet
|
||||
if err := db.DB.Where("sourcehubid = ? AND deletedat IS NULL", hubID).
|
||||
Order("createdat DESC").Find(&tripsheets).Error; err != nil {
|
||||
if err := query.Order("createdat DESC").Find(&tripsheets).Error; err != nil {
|
||||
return utils.Internal(c, "failed to fetch batches")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user