backend requirements onthe xustomer app
This commit is contained in:
@@ -254,6 +254,28 @@ func MilerGetMyBookings(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
// A customer-app pickup fans out into one consignment per destination, and
|
||||
// each of those is a SEPARATE delivery the rider has to make. Without this
|
||||
// the queue showed one row per booking keyed on pickupbookings.consignmentid
|
||||
// — which names only the first order — so on a three-destination pickup two
|
||||
// parcels would exist in the rider's bag with no stop, no deliver button and
|
||||
// no way to close them.
|
||||
//
|
||||
// Bookings with no destination rows (every console/express booking, and
|
||||
// everything written before the fan-out) are untouched: they still produce
|
||||
// exactly one row, built from the booking's own columns.
|
||||
destinationsByBooking := map[int][]models.BookingDestination{}
|
||||
if len(bookingIDs) > 0 {
|
||||
var destinations []models.BookingDestination
|
||||
db.DB.Where("bookingid IN ?", bookingIDs).Order("seq ASC").Find(&destinations)
|
||||
for _, d := range destinations {
|
||||
destinationsByBooking[d.Bookingid] = append(destinationsByBooking[d.Bookingid], d)
|
||||
if d.Consignmentid != nil {
|
||||
consignmentIDs = append(consignmentIDs, *d.Consignmentid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// step / road-optimized sequence lives on the active assignment row, written
|
||||
// by the express route optimizer. Step 0 = not sequenced (single-stop or
|
||||
// optimizer down), never a position — passed through verbatim.
|
||||
@@ -287,41 +309,6 @@ func MilerGetMyBookings(c *fiber.Ctx) error {
|
||||
var customer models.AppCustomer
|
||||
db.DB.Where("appcustomerid = ?", b.Appcustomerid).First(&customer)
|
||||
|
||||
// Cash-to-collect: prefer the consignment's COD once it exists, otherwise
|
||||
// fall back to a pending Cash payment on the booking. Prepaid/UPI stays 0.
|
||||
codAmount := 0.0
|
||||
paymentMode := ""
|
||||
if b.Consignmentid != nil {
|
||||
if cn, ok := codByConsignment[*b.Consignmentid]; ok {
|
||||
paymentMode = cn.Paymentmode
|
||||
codAmount = cn.Codamount - cn.Codcollected
|
||||
if codAmount < 0 {
|
||||
codAmount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if codAmount == 0 {
|
||||
for _, p := range b.Payments {
|
||||
if p.Paymentmode == constants.PaymentModeCash && p.Paymentstatus == constants.PaymentStatusPending {
|
||||
codAmount = p.Amount
|
||||
paymentMode = p.Paymentmode
|
||||
break
|
||||
}
|
||||
if paymentMode == "" {
|
||||
paymentMode = p.Paymentmode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// consignmentid/consignmentstatus so the app can call deliver, skip and
|
||||
// start-delivery straight from the list without a per-order lookup.
|
||||
var consignmentStatus string
|
||||
if b.Consignmentid != nil {
|
||||
if cn, ok := codByConsignment[*b.Consignmentid]; ok {
|
||||
consignmentStatus = cn.Status
|
||||
}
|
||||
}
|
||||
|
||||
// Where this parcel is collected FROM, and what kind of place that is, so
|
||||
// Home can title the stop correctly. Without pickup_source_type every
|
||||
// logistics pickup was grouped under the rider's own base name and a
|
||||
@@ -334,79 +321,145 @@ func MilerGetMyBookings(c *fiber.Ctx) error {
|
||||
sourceType, sourceID, sourceName, sourceAddress := pickupSource(&b,
|
||||
customer.Firstname+" "+customer.Lastname)
|
||||
|
||||
// next_action / next_hub: the leg this parcel is on, rebuilt from server
|
||||
// state on every poll. pickup-complete used to be the only place that ever
|
||||
// said it, so a restart mid-leg left the app with nothing authoritative to
|
||||
// read — consignment status alone cannot separate a hub-routed parcel from
|
||||
// a freshly-collected hyperlocal one, since both can sit on Created.
|
||||
nextAction := constants.NextActionPickup
|
||||
var nextHub fiber.Map
|
||||
if b.Status == constants.BookingCancelled {
|
||||
nextAction = constants.NextActionNone
|
||||
} else if b.Consignmentid != nil {
|
||||
if cn, ok := codByConsignment[*b.Consignmentid]; ok {
|
||||
nextAction = nextActionForConsignment(cn.Status)
|
||||
nextHub = nextHubForConsignment(&cn)
|
||||
}
|
||||
}
|
||||
stops := milerStopsForBooking(&b, destinationsByBooking[b.Bookingid])
|
||||
|
||||
row := fiber.Map{
|
||||
"bookingid": b.Bookingid,
|
||||
"bookingreference": b.Bookingno,
|
||||
"status": b.Status,
|
||||
"stoptype": milerStopType(b.Status),
|
||||
"consignmentid": b.Consignmentid,
|
||||
"consignmentstatus": consignmentStatus,
|
||||
"next_action": nextAction,
|
||||
"next_hub": nextHub,
|
||||
"pickup_source_type": sourceType,
|
||||
"sourceid": sourceID,
|
||||
"pickuplocationid": sourceID,
|
||||
"pickup_source_name": sourceName,
|
||||
"pickupaddress": sourceAddress,
|
||||
"pickuplatitude": b.Pickuplatitude,
|
||||
"pickuplongitude": b.Pickuplongitude,
|
||||
"deliveryaddress": b.Deliveryaddress,
|
||||
"deliverylatitude": b.Deliverylatitude,
|
||||
"deliverylongitude": b.Deliverylongitude,
|
||||
"customername": strings.TrimSpace(customer.Firstname + " " + customer.Lastname),
|
||||
"customerphone": customer.Phone,
|
||||
// Arrival fact: the rider app derives its "Arrived" rung from
|
||||
// pickup-scheduled + a non-null reachedat, so this survives an app
|
||||
// restart without a separate booking status. Null until the rider hits
|
||||
// the reached endpoint. arrivallatitude/longitude are the GPS captured
|
||||
// at that moment (null if the app sent none).
|
||||
"reachedat": b.Arrivedat,
|
||||
"arrivallatitude": b.Arrivallatitude,
|
||||
"arrivallongitude": b.Arrivallongitude,
|
||||
"parcels": b.Parcels,
|
||||
"serviceoptions": b.ServiceOptions,
|
||||
"codamount": codAmount,
|
||||
"paymentmode": paymentMode,
|
||||
"createdat": b.Createdat,
|
||||
// Route sequencing — 0/empty when the stop was never sequenced.
|
||||
// sequencedat is the authoritative-order signal: non-null means the
|
||||
// console/optimizer fixed this stop's position and the app must follow
|
||||
// step exactly; null means no route was assigned and the app is free to
|
||||
// fall back to its own nearest-first ordering.
|
||||
"step": 0,
|
||||
"cumulativekms": 0.0,
|
||||
"etaminutes": 0,
|
||||
"cumulativeeta": 0,
|
||||
"sequencedat": nil,
|
||||
for _, stop := range stops {
|
||||
// Cash-to-collect: prefer the consignment's COD once it exists, otherwise
|
||||
// fall back to a pending Cash payment on the booking. Prepaid/UPI stays 0.
|
||||
codAmount := 0.0
|
||||
paymentMode := ""
|
||||
var consignmentStatus string
|
||||
|
||||
// next_action / next_hub: the leg this parcel is on, rebuilt from server
|
||||
// state on every poll. pickup-complete used to be the only place that ever
|
||||
// said it, so a restart mid-leg left the app with nothing authoritative to
|
||||
// read — consignment status alone cannot separate a hub-routed parcel from
|
||||
// a freshly-collected hyperlocal one, since both can sit on Created.
|
||||
nextAction := constants.NextActionPickup
|
||||
var nextHub fiber.Map
|
||||
|
||||
if stop.consignmentID != nil {
|
||||
if cn, ok := codByConsignment[*stop.consignmentID]; ok {
|
||||
consignmentStatus = cn.Status
|
||||
paymentMode = cn.Paymentmode
|
||||
codAmount = cn.Codamount - cn.Codcollected
|
||||
if codAmount < 0 {
|
||||
codAmount = 0
|
||||
}
|
||||
nextAction = nextActionForConsignment(cn.Status)
|
||||
nextHub = nextHubForConsignment(&cn)
|
||||
} else {
|
||||
// The consignment row did not load. Fall back to what this
|
||||
// destination asked for rather than to zero — a rider shown
|
||||
// ₹0 collects nothing, and the customer's money is the one
|
||||
// thing that must not silently vanish from a stop.
|
||||
codAmount = stop.codAmount
|
||||
}
|
||||
} else if stop.codAmount > 0 {
|
||||
// Not yet collected: the collection is what the customer
|
||||
// requested for this door.
|
||||
codAmount = stop.codAmount
|
||||
}
|
||||
if b.Status == constants.BookingCancelled {
|
||||
nextAction = constants.NextActionNone
|
||||
}
|
||||
|
||||
// The pickup fee is charged once for the visit, so it is only offered
|
||||
// against the first stop — asking a rider to collect it again at the
|
||||
// second parcel would double-charge the customer.
|
||||
if codAmount == 0 && stop.seq == 0 {
|
||||
for _, p := range b.Payments {
|
||||
if p.Paymentmode == constants.PaymentModeCash && p.Paymentstatus == constants.PaymentStatusPending {
|
||||
codAmount = p.Amount
|
||||
paymentMode = p.Paymentmode
|
||||
break
|
||||
}
|
||||
if paymentMode == "" {
|
||||
paymentMode = p.Paymentmode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row := fiber.Map{
|
||||
"bookingid": b.Bookingid,
|
||||
"bookingreference": b.Bookingno,
|
||||
"status": b.Status,
|
||||
"stoptype": milerStopType(b.Status),
|
||||
// consignmentid/consignmentstatus so the app can call deliver, skip and
|
||||
// start-delivery straight from the list without a per-order lookup.
|
||||
"consignmentid": stop.consignmentID,
|
||||
"consignmentstatus": consignmentStatus,
|
||||
"trackingno": stop.trackingNo,
|
||||
// Which of the booking's destinations this stop is, and how many
|
||||
// there are in total, so the app can say "Stop 2 of 3" instead of
|
||||
// showing three identical-looking rows. Always 0 and 1 for a
|
||||
// single-destination or console booking.
|
||||
"destinationseq": stop.seq,
|
||||
"destinationcount": len(stops),
|
||||
"next_action": nextAction,
|
||||
"next_hub": nextHub,
|
||||
|
||||
"pickup_source_type": sourceType,
|
||||
"sourceid": sourceID,
|
||||
"pickuplocationid": sourceID,
|
||||
"pickup_source_name": sourceName,
|
||||
"pickupaddress": sourceAddress,
|
||||
"pickuplatitude": b.Pickuplatitude,
|
||||
"pickuplongitude": b.Pickuplongitude,
|
||||
|
||||
"deliveryaddress": stop.deliveryAddress,
|
||||
"deliverylatitude": stop.deliveryLat,
|
||||
"deliverylongitude": stop.deliveryLng,
|
||||
"recipientname": stop.recipientName,
|
||||
"recipientphone": stop.recipientPhone,
|
||||
|
||||
"customername": strings.TrimSpace(customer.Firstname + " " + customer.Lastname),
|
||||
"customerphone": customer.Phone,
|
||||
// Arrival fact: the rider app derives its "Arrived" rung from
|
||||
// pickup-scheduled + a non-null reachedat, so this survives an app
|
||||
// restart without a separate booking status. Null until the rider hits
|
||||
// the reached endpoint. arrivallatitude/longitude are the GPS captured
|
||||
// at that moment (null if the app sent none).
|
||||
"reachedat": b.Arrivedat,
|
||||
"arrivallatitude": b.Arrivallatitude,
|
||||
"arrivallongitude": b.Arrivallongitude,
|
||||
"parcels": stop.parcels,
|
||||
"serviceoptions": b.ServiceOptions,
|
||||
"codamount": codAmount,
|
||||
// collectionamt is the same number under the name the rider app
|
||||
// reads. Added rather than renamed: `codamount` is what this
|
||||
// endpoint has always emitted and the deployed build parses it,
|
||||
// so removing it would break every stop on every existing
|
||||
// device. Both are written from one variable, so they cannot
|
||||
// drift apart.
|
||||
"collectionamt": codAmount,
|
||||
"paymentmode": paymentMode,
|
||||
"createdat": b.Createdat,
|
||||
// Route sequencing — 0/empty when the stop was never sequenced.
|
||||
// sequencedat is the authoritative-order signal: non-null means the
|
||||
// console/optimizer fixed this stop's position and the app must follow
|
||||
// step exactly; null means no route was assigned and the app is free to
|
||||
// fall back to its own nearest-first ordering.
|
||||
"step": 0,
|
||||
"cumulativekms": 0.0,
|
||||
"etaminutes": 0,
|
||||
"cumulativeeta": 0,
|
||||
"sequencedat": nil,
|
||||
}
|
||||
if a, ok := seqByBooking[b.Bookingid]; ok {
|
||||
row["step"] = a.Step
|
||||
row["cumulativekms"] = a.Cumulativekms
|
||||
row["etaminutes"] = a.Etaminutes
|
||||
row["cumulativeeta"] = a.Cumulativeeta
|
||||
row["sequencedat"] = a.Sequencedat
|
||||
}
|
||||
response = append(response, row)
|
||||
}
|
||||
if a, ok := seqByBooking[b.Bookingid]; ok {
|
||||
row["step"] = a.Step
|
||||
row["cumulativekms"] = a.Cumulativekms
|
||||
row["etaminutes"] = a.Etaminutes
|
||||
row["cumulativeeta"] = a.Cumulativeeta
|
||||
row["sequencedat"] = a.Sequencedat
|
||||
}
|
||||
response = append(response, row)
|
||||
}
|
||||
|
||||
// Sequenced stops ascend by step; unsequenced (step 0) fall to the end while
|
||||
// keeping the newest-first order the app already relied on.
|
||||
// keeping the newest-first order the app already relied on. Stops from one
|
||||
// booking share its step, so they stay adjacent and in destination order.
|
||||
sort.SliceStable(response, func(i, j int) bool {
|
||||
si, sj := response[i]["step"].(int), response[j]["step"].(int)
|
||||
switch {
|
||||
@@ -424,6 +477,121 @@ func MilerGetMyBookings(c *fiber.Ctx) error {
|
||||
return utils.List(c, response, int64(len(response)))
|
||||
}
|
||||
|
||||
// milerStop is one thing the rider actually has to do with one parcel — a
|
||||
// pickup before collection, a delivery leg after it.
|
||||
type milerStop struct {
|
||||
seq int
|
||||
consignmentID *int
|
||||
trackingNo string
|
||||
deliveryAddress string
|
||||
deliveryLat float64
|
||||
deliveryLng float64
|
||||
recipientName string
|
||||
recipientPhone string
|
||||
// codAmount is the collection the CUSTOMER asked for at THIS door, read off
|
||||
// the destination row. The consignment's own figure wins at delivery time
|
||||
// because it also knows what has already been collected — but this is what
|
||||
// makes the per-stop split provable without a database, and it is the value
|
||||
// used if the consignment row could not be loaded. Money must never fall
|
||||
// back to another destination's number.
|
||||
codAmount float64
|
||||
parcels []models.BookingParcel
|
||||
}
|
||||
|
||||
// milerStopsForBooking decides how many stops a booking is worth to the rider.
|
||||
//
|
||||
// Before the parcels are collected it is always ONE stop: the rider makes a
|
||||
// single visit to the customer's door, whatever it is carrying away. After
|
||||
// collection a customer-app pickup becomes one stop per destination, because
|
||||
// each parcel now has its own journey, its own tracking number and its own
|
||||
// deliver/skip action.
|
||||
//
|
||||
// A booking with no destination rows — every console/express booking, and
|
||||
// everything written before the fan-out — is always one stop built from the
|
||||
// booking's own columns, exactly as before.
|
||||
func milerStopsForBooking(b *models.PickupBooking, destinations []models.BookingDestination) []milerStop {
|
||||
single := []milerStop{{
|
||||
seq: 0,
|
||||
consignmentID: b.Consignmentid,
|
||||
deliveryAddress: b.Deliveryaddress,
|
||||
deliveryLat: b.Deliverylatitude,
|
||||
deliveryLng: b.Deliverylongitude,
|
||||
parcels: b.Parcels,
|
||||
}}
|
||||
// A booking with exactly one destination row still carries its COD there.
|
||||
if len(destinations) == 1 {
|
||||
single[0].codAmount = destinations[0].Codamount
|
||||
}
|
||||
|
||||
if len(destinations) == 0 {
|
||||
return single
|
||||
}
|
||||
|
||||
// Not collected yet: one visit, one stop. The destinations are still only an
|
||||
// intention, and showing three rows for a pickup that has not happened would
|
||||
// have the rider drive to the same door three times.
|
||||
collected := false
|
||||
for _, d := range destinations {
|
||||
if d.Consignmentid != nil {
|
||||
collected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !collected {
|
||||
// Destination 0's address is already mirrored onto the booking, so the
|
||||
// single pre-pickup stop is correct as built above.
|
||||
return single
|
||||
}
|
||||
|
||||
parcelsByDestination := map[int][]models.BookingParcel{}
|
||||
for _, p := range b.Parcels {
|
||||
if p.Bookingdestinationid != nil {
|
||||
parcelsByDestination[*p.Bookingdestinationid] = append(parcelsByDestination[*p.Bookingdestinationid], p)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop order is `seq`, guaranteed here rather than inherited from whatever
|
||||
// ORDER BY the caller happened to use. The queue query does sort by seq
|
||||
// today, so this changes nothing — but the ordering IS the route the rider
|
||||
// drives, and leaving it as an unstated precondition means the next caller,
|
||||
// or an edited query, silently reorders someone's afternoon. Sorted on a
|
||||
// copy so the caller's slice is never mutated underneath it.
|
||||
ordered := make([]models.BookingDestination, len(destinations))
|
||||
copy(ordered, destinations)
|
||||
sort.SliceStable(ordered, func(i, j int) bool { return ordered[i].Seq < ordered[j].Seq })
|
||||
destinations = ordered
|
||||
|
||||
stops := make([]milerStop, 0, len(destinations))
|
||||
for _, d := range destinations {
|
||||
lat, lng := 0.0, 0.0
|
||||
if d.Pinlatitude != nil && d.Pinlongitude != nil {
|
||||
lat, lng = *d.Pinlatitude, *d.Pinlongitude
|
||||
}
|
||||
stops = append(stops, milerStop{
|
||||
seq: d.Seq,
|
||||
consignmentID: d.Consignmentid,
|
||||
trackingNo: d.Trackingno,
|
||||
deliveryAddress: joinNonEmpty(", ", d.Building, d.Street, d.Landmark, d.Districtname, d.Statename),
|
||||
deliveryLat: lat,
|
||||
deliveryLng: lng,
|
||||
recipientName: d.Recipientname,
|
||||
recipientPhone: d.Recipientphone,
|
||||
codAmount: d.Codamount,
|
||||
parcels: parcelsByDestination[d.Bookingdestinationid],
|
||||
})
|
||||
}
|
||||
|
||||
// Destination 0's coordinates are mirrored onto the booking and may have
|
||||
// been corrected there by the rider at the door, so prefer those when the
|
||||
// destination itself never got a pin.
|
||||
if stops[0].deliveryLat == 0 && stops[0].deliveryLng == 0 {
|
||||
stops[0].deliveryLat = b.Deliverylatitude
|
||||
stops[0].deliveryLng = b.Deliverylongitude
|
||||
}
|
||||
|
||||
return stops
|
||||
}
|
||||
|
||||
// milerConsignmentForRider loads a consignment and confirms it belongs to this
|
||||
// rider — either it is linked to a booking currently assigned to them, or they
|
||||
// are the one who collected it (Createdby). Returns a stable error code on
|
||||
@@ -437,6 +605,19 @@ func milerConsignmentForRider(milerUserID, consignmentID int) (*models.Consignme
|
||||
db.DB.Model(&models.PickupBooking{}).
|
||||
Where("consignmentid = ? AND assignedmileruserid = ?", consignmentID, milerUserID).
|
||||
Count(&count)
|
||||
|
||||
// pickupbookings.consignmentid names only the FIRST order of a
|
||||
// multi-destination pickup, so the count above misses orders 2..N entirely.
|
||||
// The destination table is what actually knows which booking a consignment
|
||||
// belongs to.
|
||||
if count == 0 {
|
||||
db.DB.Model(&models.BookingDestination{}).
|
||||
Joins("JOIN pickupbookings ON pickupbookings.bookingid = bookingdestinations.bookingid").
|
||||
Where("bookingdestinations.consignmentid = ? AND pickupbookings.assignedmileruserid = ?",
|
||||
consignmentID, milerUserID).
|
||||
Count(&count)
|
||||
}
|
||||
|
||||
if count == 0 && consignment.Createdby != milerUserID {
|
||||
return nil, constants.ErrConsignmentNotAssigned, fmt.Errorf("not this rider's consignment")
|
||||
}
|
||||
@@ -544,14 +725,30 @@ func MilerStartDelivery(c *fiber.Ctx) error {
|
||||
return utils.Internal(c, "failed to update miler availability")
|
||||
}
|
||||
|
||||
notifyOutForDelivery, err := recordCxConsignmentStage(tx, consignment.Consignmentid,
|
||||
constants.ConsignmentOutForDelivery, constants.CxActorMiler, &milerUserID,
|
||||
"POST /miler/consignments/{id}/start-delivery")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
utils.Error("MilerStartDelivery: could not record out_for_delivery",
|
||||
"consignment_id", consignment.Consignmentid, "error", err)
|
||||
return utils.Internal(c, "failed to start delivery")
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return utils.Internal(c, "failed to start delivery")
|
||||
}
|
||||
|
||||
notifyOutForDelivery()
|
||||
|
||||
// Tell the customer it's on the way, and hand the receiver their OTP (only the
|
||||
// receiver — the rider is told it at the door).
|
||||
var booking models.PickupBooking
|
||||
if db.DB.Where("consignmentid = ?", consignment.Consignmentid).First(&booking).Error == nil {
|
||||
//
|
||||
// Resolved through bookingdestinations: pickupbookings.consignmentid names
|
||||
// only the first order of a multi-destination pickup, so joining on it meant
|
||||
// no customer was ever told about orders 2..N going out for delivery.
|
||||
if _, bookingPtr, ok := cxDestinationForConsignment(consignment.Consignmentid); ok && bookingPtr != nil {
|
||||
booking := *bookingPtr
|
||||
var customer models.AppCustomer
|
||||
if db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error == nil && customer.Devicetoken != "" {
|
||||
body := "Your parcel is out for delivery."
|
||||
@@ -598,16 +795,32 @@ func MilerDeliverConsignment(c *fiber.Ctx) error {
|
||||
return utils.BadRequest(c, "deliveredtoname is required")
|
||||
}
|
||||
|
||||
var consignment models.Consignment
|
||||
if err := db.DB.First(&consignment, consignmentID).Error; err != nil {
|
||||
return utils.NotFound(c, "consignment not found")
|
||||
}
|
||||
|
||||
var booking models.PickupBooking
|
||||
if err := db.DB.Where("consignmentid = ? AND assignedmileruserid = ?", consignment.Consignmentid, milerUserID).
|
||||
First(&booking).Error; err != nil {
|
||||
// Ownership and the booking behind the parcel, via the one helper that
|
||||
// understands a multi-destination pickup. This used to be a direct
|
||||
// `WHERE consignmentid = ? AND assignedmileruserid = ?` on pickupbookings —
|
||||
// which names only the FIRST order of a pickup, so a rider delivering the
|
||||
// second parcel of a three-destination visit was told "assigned consignment
|
||||
// not found" and could not close the delivery at all.
|
||||
// Both failures answer 404 with the exact messages this endpoint has always
|
||||
// returned. milerConsignmentForRider distinguishes "no such consignment"
|
||||
// from "not yours" and start-delivery reports that as a 403, but the
|
||||
// deployed rider app was built against a 404 here and changing a live
|
||||
// endpoint's status code is not this work's business. Only the LOOKUP is
|
||||
// fixed; the contract is byte-identical.
|
||||
consignmentPtr, code, err := milerConsignmentForRider(milerUserID, consignmentID)
|
||||
if err != nil {
|
||||
if code == constants.ErrConsignmentNotFound {
|
||||
return utils.NotFound(c, "consignment not found")
|
||||
}
|
||||
return utils.NotFound(c, "assigned consignment not found")
|
||||
}
|
||||
consignment := *consignmentPtr
|
||||
|
||||
_, bookingPtr, ok := cxDestinationForConsignment(consignment.Consignmentid)
|
||||
if !ok || bookingPtr == nil {
|
||||
return utils.NotFound(c, "assigned consignment not found")
|
||||
}
|
||||
booking := *bookingPtr
|
||||
|
||||
if consignment.Status != constants.ConsignmentOutForDelivery {
|
||||
return utils.Fail(c, fiber.StatusBadRequest, constants.ErrInvalidState,
|
||||
@@ -702,10 +915,27 @@ func MilerDeliverConsignment(c *fiber.Ctx) error {
|
||||
return utils.Internal(c, "failed to close assignment")
|
||||
}
|
||||
|
||||
// The customer's Delivered milestone, against THIS order. The booking only
|
||||
// reads as completed once every destination has landed — cxstage rolls the
|
||||
// booking up from the least-advanced order, so a two-parcel pickup with one
|
||||
// still in transit stays "In transit" rather than telling the customer
|
||||
// everything arrived.
|
||||
notifyDelivered, err := recordCxConsignmentStage(tx, consignment.Consignmentid,
|
||||
constants.ConsignmentDelivered, constants.CxActorMiler, &milerUserID,
|
||||
"POST /miler/consignments/{id}/deliver")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
utils.Error("MilerDeliverConsignment: could not record delivered",
|
||||
"consignment_id", consignment.Consignmentid, "error", err)
|
||||
return utils.Internal(c, "failed to confirm delivery")
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return utils.Internal(c, "failed to confirm delivery")
|
||||
}
|
||||
|
||||
notifyDelivered()
|
||||
|
||||
if db.Js != nil {
|
||||
payload := map[string]interface{}{
|
||||
"bookingid": booking.Bookingid,
|
||||
|
||||
Reference in New Issue
Block a user