Files
doormile_backend/controllers/cxConsignmentHooks.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)
}