updates on the sweeper and eligibility things in the order

This commit is contained in:
2026-10-06 15:41:16 +05:30
parent 9b94be1f07
commit efad4d3d12
10 changed files with 803 additions and 35 deletions

View File

@@ -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 {