101 lines
3.7 KiB
Go
101 lines
3.7 KiB
Go
package controllers
|
|
|
|
import (
|
|
"doormile/constants"
|
|
"doormile/internal/cxstage"
|
|
"doormile/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Per-order stages — the second half of §8.3.
|
|
//
|
|
// Stages 6-8 belong to each order rather than to the booking, and may differ
|
|
// between destinations of the same pickup: one parcel out for delivery in
|
|
// Chennai while another is still at a hub in Kerala. Each of the operational
|
|
// writes that moves a consignment records its customer stage against the
|
|
// destination that consignment belongs to, and the booking's own stage falls
|
|
// back to the least-advanced of them.
|
|
|
|
// cxStageForConsignmentStatus maps an operational consignment status onto the
|
|
// customer stage it means. Not every status has one: a parcel sitting on a
|
|
// tripsheet, or one flagged missing, is still "in transit" as far as the
|
|
// customer's seven milestones go, and inventing a stage for it would put a key
|
|
// on the wire the client silently falls back to `booked` for.
|
|
func cxStageForConsignmentStatus(status string) (string, bool) {
|
|
switch status {
|
|
case constants.ConsignmentInwardedAtHub,
|
|
constants.ConsignmentTripsheetLoaded,
|
|
constants.ConsignmentInTransit:
|
|
return constants.CxStageInTransit, true
|
|
case constants.ConsignmentOutForDelivery:
|
|
return constants.CxStageOutForDelivery, true
|
|
case constants.ConsignmentDelivered:
|
|
return constants.CxStageDelivered, true
|
|
default:
|
|
// Created and Collected_By_Miler both mean "the order exists and is in
|
|
// the rider's hands", which is order_created — already recorded at
|
|
// pickup-complete, so there is nothing new to say.
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
// recordCxConsignmentStage records the customer stage for one consignment,
|
|
// inside the caller's transaction, and returns the notification to fire once
|
|
// that transaction commits.
|
|
//
|
|
// The consignment is resolved to its destination through bookingdestinations,
|
|
// not through pickupbookings.consignmentid. That column names only the FIRST
|
|
// order of a multi-destination pickup, so a lookup through it finds nothing for
|
|
// destinations 2..N — which would mean no stage advance and no notification on
|
|
// every order after the first.
|
|
//
|
|
// A consignment that belongs to no customer booking at all — a console-created
|
|
// express shipment — is a no-op, not an error. Those have no customer app
|
|
// watching them.
|
|
func recordCxConsignmentStage(tx *gorm.DB, consignmentID int, status string, actorType string, actorID *int, source string) (afterCommit func(), err error) {
|
|
noop := func() {}
|
|
|
|
stage, ok := cxStageForConsignmentStatus(status)
|
|
if !ok {
|
|
return noop, nil
|
|
}
|
|
|
|
dest, booking, found := cxDestinationForConsignment(consignmentID)
|
|
if !found || booking == nil {
|
|
return noop, nil
|
|
}
|
|
if booking.Bookingsource != constants.BookingSourceCustomerApp {
|
|
return noop, nil
|
|
}
|
|
|
|
destinationID := cxDestinationIDFor(dest)
|
|
if err := cxstage.Record(tx, cxstage.Event{
|
|
BookingID: booking.Bookingid,
|
|
DestinationID: destinationID,
|
|
Stage: stage,
|
|
ActorType: actorType,
|
|
ActorID: actorID,
|
|
Source: source,
|
|
At: utils.DBNow(),
|
|
}); err != nil {
|
|
return noop, err
|
|
}
|
|
|
|
bookingID := booking.Bookingid
|
|
return func() { go cxstage.Notify(bookingID, destinationID, stage) }, nil
|
|
}
|
|
|
|
// cancelCxBookingFromOps stands a pickup down on behalf of a rider or an ops
|
|
// user, recording who did it and why.
|
|
//
|
|
// A customer whose pickup disappears with no explanation has no way to tell a
|
|
// cancellation from a bug, and support has no way to answer them — so the
|
|
// actor and the reason are part of the write, not an afterthought.
|
|
func cancelCxBookingFromOps(tx *gorm.DB, bookingID int, reason, actorType string, actorID *int, source string) error {
|
|
if reason == "" {
|
|
reason = "Cancelled by Doormile"
|
|
}
|
|
return cxstage.Cancel(tx, bookingID, reason, actorType, actorID, source)
|
|
}
|