package controllers import ( "encoding/json" "fmt" "time" "doormile/constants" "doormile/db" "doormile/internal/notify" "doormile/models" "doormile/utils" ) // AssignMilerToBooking is the single source of truth for manually assigning a // miler to a pickup booking — shared by the admin console (AdminAssignMiler) // and the hub console (HubAssignMiler) so both go through identical DB // updates, NATS publish, and FCM notify instead of duplicating the logic. func AssignMilerToBooking(bookingID, milerUserID int, assignedByUserID *int) (*models.PickupBooking, error) { tx := db.DB.Begin() var booking models.PickupBooking if err := tx.First(&booking, bookingID).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("booking not found") } booking.Status = constants.BookingMilerAssigned booking.Assignedmileruserid = &milerUserID booking.Updatedat = time.Now() if err := tx.Save(&booking).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("failed to update booking: %w", err) } assignment := models.BookingAssignment{ Bookingid: booking.Bookingid, Mileruserid: milerUserID, Assignedbyuserid: assignedByUserID, Assignmentstatus: constants.AssignmentAssigned, } if err := tx.Create(&assignment).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("failed to create assignment: %w", err) } if err := tx.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID). Update("availabilitystatus", constants.MilerAssigned).Error; err != nil { tx.Rollback() return nil, fmt.Errorf("failed to update miler availability: %w", err) } if err := tx.Commit().Error; err != nil { return nil, fmt.Errorf("failed to commit miler assignment: %w", err) } if db.Js != nil { payload := map[string]interface{}{ "booking_id": booking.Bookingid, "booking_no": booking.Bookingno, "status": booking.Status, "miler_id": milerUserID, "updated_at": time.Now().UnixMilli(), } if data, err := json.Marshal(payload); err == nil { if _, err := db.Js.Publish("api.v1.bookings.update", data); err != nil { utils.Warn("Failed to publish booking.update to NATS", "booking_id", booking.Bookingid, "error", err) } } } var miler models.MilerProfile if db.DB.Where("userid = ?", milerUserID).First(&miler).Error == nil && miler.Devicetoken != "" { if err := notify.SendToDevice( miler.Devicetoken, "New Pickup Assigned", "New booking assigned — tap to view details", map[string]string{"booking_id": fmt.Sprintf("%d", booking.Bookingid)}, ); err != nil { utils.Warn("FCM: failed to notify miler on manual assignment", "miler_id", milerUserID, "booking_id", booking.Bookingid, "error", err) } } return &booking, nil }