updates on the sweeper and eligibility things in the order
This commit is contained in:
@@ -3,6 +3,7 @@ package assignment
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"doormile/utils"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,6 +35,24 @@ const (
|
||||
defaultMaxActive = 3
|
||||
)
|
||||
|
||||
// defaultMaxGPSAgeMinutes: a rider whose app has not reported a position for
|
||||
// longer than this is not offered orders. Override with
|
||||
// ASSIGNMENT_MAX_GPS_AGE_MINUTES; 0 turns the check off.
|
||||
const defaultMaxGPSAgeMinutes = 15
|
||||
|
||||
// maxGPSAgeMinutes is read per call, like maxActiveBookings. A typo falls back
|
||||
// to the default rather than switching the check off.
|
||||
func maxGPSAgeMinutes() int {
|
||||
if v := strings.TrimSpace(os.Getenv("ASSIGNMENT_MAX_GPS_AGE_MINUTES")); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n >= 0 {
|
||||
return n
|
||||
}
|
||||
utils.Warn("ASSIGNMENT_MAX_GPS_AGE_MINUTES is not a non-negative integer, using the default",
|
||||
"value", v, "default", defaultMaxGPSAgeMinutes)
|
||||
}
|
||||
return defaultMaxGPSAgeMinutes
|
||||
}
|
||||
|
||||
// maxActiveBookings reads the per-miler concurrent-stop cap, read per call so
|
||||
// it can be changed without a redeploy. A non-numeric or non-positive value
|
||||
// falls back to the default rather than uncapping the fleet by typo.
|
||||
@@ -70,6 +90,28 @@ func AssignCRMMiler(bookingID int) {
|
||||
enqueue(bookingID, kindExpress)
|
||||
}
|
||||
|
||||
// errBookingTaken: the booking got a rider (or was cancelled) between this
|
||||
// attempt reading it and committing. Not a failure — the work is done.
|
||||
var errBookingTaken = errors.New("booking already assigned or cancelled")
|
||||
|
||||
// claimBooking sets the booking's rider only if it still has none and is not
|
||||
// cancelled, in the caller's transaction. Two attempts can run for one booking
|
||||
// at once — the retry queue, the pending-order sweeper, a hub "auto-assign"
|
||||
// tap — and without this both would commit and the booking would end up with
|
||||
// two riders.
|
||||
func claimBooking(tx *gorm.DB, bookingID int, updates map[string]interface{}) error {
|
||||
res := tx.Model(&models.PickupBooking{}).
|
||||
Where("bookingid = ? AND assignedmileruserid IS NULL AND status <> ?", bookingID, constants.BookingCancelled).
|
||||
Updates(updates)
|
||||
if res.Error != nil {
|
||||
return fmt.Errorf("update PickupBooking: %w", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errBookingTaken
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tryAssign performs a single attempt: queries Redis GEO, scores candidates, commits.
|
||||
// Returns (true, nil) on success or when the booking no longer needs assignment.
|
||||
// Returns (false, nil) when no eligible miler was found (retry warranted).
|
||||
@@ -108,6 +150,9 @@ func tryAssign(bookingID int) (bool, error) {
|
||||
}
|
||||
|
||||
if err := commitAssignment(&booking, candidate, agentDecisionID); err != nil {
|
||||
if errors.Is(err, errBookingTaken) {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
@@ -166,6 +211,9 @@ func TryAssignOnce(bookingID int) (AutoAssignResult, error) {
|
||||
}
|
||||
|
||||
if err := commitAssignment(&booking, candidate, agentDecisionID); err != nil {
|
||||
if errors.Is(err, errBookingTaken) {
|
||||
return AutoAssignResult{Assigned: true}, nil
|
||||
}
|
||||
return AutoAssignResult{}, fmt.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
@@ -209,6 +257,17 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
|
||||
|
||||
tx := db.DB.Begin()
|
||||
|
||||
// Claim the booking first, and only if it is still unassigned and not
|
||||
// cancelled — see claimBooking.
|
||||
if err := claimBooking(tx, booking.Bookingid, map[string]interface{}{
|
||||
"assignedmileruserid": milerUserID,
|
||||
"status": constants.BookingMilerAssigned,
|
||||
"updatedat": time.Now(),
|
||||
}); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
assignment := models.BookingAssignment{
|
||||
Bookingid: booking.Bookingid,
|
||||
Mileruserid: milerUserID,
|
||||
@@ -221,16 +280,6 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
|
||||
return fmt.Errorf("create BookingAssignment: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err := tx.Model(booking).Updates(map[string]interface{}{
|
||||
"assignedmileruserid": milerUserID,
|
||||
"status": constants.BookingMilerAssigned,
|
||||
"updatedat": now,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("update PickupBooking: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Model(&models.MilerProfile{}).
|
||||
Where("userid = ?", milerUserID).
|
||||
Update("availabilitystatus", constants.MilerAssigned).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user