updates on the order bulk fix

This commit is contained in:
2026-10-06 17:29:07 +05:30
parent efad4d3d12
commit 7ff9dad288
12 changed files with 1029 additions and 48 deletions

View File

@@ -68,9 +68,16 @@ func maxActiveBookings() int64 {
}
type milerCandidate struct {
profile models.MilerProfile
distanceKm float64
profile models.MilerProfile
distanceKm float64
// activeBookings is the rider's load "in hand": today's open orders that
// are not cancelled (openStopsToday). Balancing gives the next order to
// the rider with the fewest; it is also what the ceiling caps.
activeBookings int64
// sessionStops counts orders given to the rider since they started duty —
// the first tie-break, so a rider who just came online is not passed over
// for one who has been busy all day.
sessionStops int64
}
// AssignCRMMiler finds the best available nearby miler for an express booking
@@ -144,14 +151,17 @@ func tryAssign(bookingID int) (bool, error) {
return false, nil
}
candidate, agentDecisionID, found := selectMilerWithAI(&booking, nearby)
candidate, pool, agentDecisionID, found := selectMilerWithAI(&booking, nearby)
if !found {
return false, nil
}
if err := commitAssignment(&booking, candidate, agentDecisionID); err != nil {
if errors.Is(err, errBookingTaken) {
if _, err := commitAssignment(&booking, candidate, pool, agentDecisionID); err != nil {
switch {
case errors.Is(err, errBookingTaken):
return true, nil
case errors.Is(err, errNoRiderCapacity):
return false, nil // everyone filled up meanwhile; retry later
}
return false, fmt.Errorf("commit: %w", err)
}
@@ -200,7 +210,7 @@ func TryAssignOnce(bookingID int) (AutoAssignResult, error) {
return AutoAssignResult{Escalated: true, Reasoning: "no milers within search radius", SearchedRadiusKm: geoRadiusKm}, nil
}
candidate, agentDecisionID, found := selectMilerWithAI(&booking, nearby)
candidate, pool, agentDecisionID, found := selectMilerWithAI(&booking, nearby)
if !found {
return AutoAssignResult{
Escalated: true,
@@ -210,12 +220,25 @@ func TryAssignOnce(bookingID int) (AutoAssignResult, error) {
}, nil
}
if err := commitAssignment(&booking, candidate, agentDecisionID); err != nil {
if errors.Is(err, errBookingTaken) {
chosenID := candidate.profile.Userid
candidate, err = commitAssignment(&booking, candidate, pool, agentDecisionID)
if err != nil {
switch {
case errors.Is(err, errBookingTaken):
return AutoAssignResult{Assigned: true}, nil
case errors.Is(err, errNoRiderCapacity):
return AutoAssignResult{
Escalated: true,
Reasoning: "every nearby rider is at the order limit",
SearchedRadiusKm: geoRadiusKm,
CandidatesFound: len(nearby),
}, nil
}
return AutoAssignResult{}, fmt.Errorf("commit: %w", err)
}
if candidate.profile.Userid != chosenID {
agentDecisionID = nil // balancing overrode the AI's pick; its reasoning no longer applies
}
reasoning := ""
if agentDecisionID != nil {
@@ -252,11 +275,23 @@ func queryNearbyMilers(lat, lon float64) ([]redis.GeoLocation, error) {
// commitAssignment writes the BookingAssignment row, updates the booking and the
// miler's availability status in a single transaction, then publishes to NATS.
func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate, agentDecisionID *uint64) error {
milerUserID := candidate.profile.Userid
func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate, pool []*milerCandidate, agentDecisionID *uint64) (*milerCandidate, error) {
tx := db.DB.Begin()
// Re-check the choice with fresh loads under the decision lock, so a burst
// of orders spreads across riders instead of all landing on the one that
// looked least loaded when each attempt started. See finalizeChoice.
final, err := finalizeChoice(tx, candidate, pool)
if err != nil {
tx.Rollback()
return nil, err
}
if final != candidate {
agentDecisionID = nil
}
candidate = final
milerUserID := candidate.profile.Userid
// 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{}{
@@ -265,7 +300,7 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
"updatedat": time.Now(),
}); err != nil {
tx.Rollback()
return err
return nil, err
}
assignment := models.BookingAssignment{
@@ -274,17 +309,18 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
Assignmentstatus: constants.AssignmentAssigned,
Assignedat: time.Now(),
AgentDecisionID: agentDecisionID,
Remarks: autoAssignedRemark,
}
if err := tx.Create(&assignment).Error; err != nil {
tx.Rollback()
return fmt.Errorf("create BookingAssignment: %w", err)
return nil, fmt.Errorf("create BookingAssignment: %w", err)
}
if err := tx.Model(&models.MilerProfile{}).
Where("userid = ?", milerUserID).
Update("availabilitystatus", constants.MilerAssigned).Error; err != nil {
tx.Rollback()
return fmt.Errorf("update MilerProfile availability: %w", err)
return nil, fmt.Errorf("update MilerProfile availability: %w", err)
}
// The customer's "Miler assigned" milestone, in the same transaction as the
@@ -298,7 +334,7 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
Source: "internal/assignment.commitAssignment",
}); err != nil {
tx.Rollback()
return fmt.Errorf("record assigned stage: %w", err)
return nil, fmt.Errorf("record assigned stage: %w", err)
}
tx.Commit()
@@ -323,7 +359,7 @@ func commitAssignment(booking *models.PickupBooking, candidate *milerCandidate,
// best-effort and off this goroutine's critical path.
routing.SequenceMilerStopsAsync(milerUserID)
return nil
return candidate, nil
}
// publishAssignment sends the booking.assigned event to NATS JetStream.