updates on the milerappcontroller and routes allother thing

This commit is contained in:
2026-10-07 12:07:57 +05:30
parent 0326624ca3
commit 6048145377
4 changed files with 130 additions and 6 deletions

View File

@@ -37,7 +37,7 @@ func MilerStartDuty(c *fiber.Ctx) error {
var existing models.MilerDutyLog
if err := db.DB.Where("userid = ? AND onduty = ? AND logoutat IS NULL AND loginat >= ?", milerUserID, true, midnight).
First(&existing).Error; err == nil {
return utils.BadRequest(c, "already on duty, end current duty first")
return resumeMilerDuty(c, milerUserID, existing, req.Lat, req.Lon)
}
now := time.Now()
@@ -79,6 +79,37 @@ func MilerStartDuty(c *fiber.Ctx) error {
})
}
// resumeMilerDuty answers a start-duty for a rider whose duty from earlier
// today is still open — after a reinstall, a crash, or signing in again. It
// used to answer 400 "already on duty" and change nothing: the app treated
// that as "on duty" and showed the rider online, while their profile stayed
// Offline, so the console listed them Offline and auto-assignment skipped
// them. Now the open duty is resumed: an Offline rider becomes Available
// (a rider mid-delivery or on a break keeps that status) and is made findable
// again from where they are.
func resumeMilerDuty(c *fiber.Ctx, milerUserID int, duty models.MilerDutyLog, lat, lon float64) error {
now := time.Now()
db.DB.Model(&models.MilerProfile{}).
Where("userid = ? AND availabilitystatus = ?", milerUserID, constants.MilerOffline).
Updates(map[string]interface{}{"availabilitystatus": constants.MilerAvailable, "updatedat": now})
if lat != 0 && lon != 0 {
db.DB.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Updates(map[string]interface{}{
"currentlatitude": lat,
"currentlongitude": lon,
"lastlocationupdatedat": now,
})
indexMilerLocation(milerUserID, lat, lon)
go assignment.SweepNear(lat, lon)
}
db.DB.Model(&models.AppUser{}).Where("userid = ?", milerUserID).Update("onduty", 1)
return utils.OK(c, fiber.Map{
"dutylogid": duty.Dutylogid,
"loginat": duty.Loginat,
"resumed": true,
})
}
func MilerEndDuty(c *fiber.Ctx) error {
milerUserID := c.Locals("userid").(int)

View File

@@ -450,12 +450,14 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
adminAuth.Delete("/carrier-pricing/:id", middlewares.DoormileStaffOnly, controllers.DeleteCarrierPricing)
// Pricing
// A client login reads its own rate card (GetPricing is tenant-scoped); every
// change, and the quote (which picks a rule across ALL tenants), is staff-only.
adminAuth.Get("/pricing", controllers.GetPricing)
adminAuth.Post("/pricing", controllers.CreatePricing)
adminAuth.Put("/pricing/:id", controllers.UpdatePricing)
adminAuth.Delete("/pricing/:id", controllers.DeletePricing)
adminAuth.Post("/pricing/simulate", controllers.GetPricingQuoteSimulate)
adminAuth.Post("/pricing/quote", controllers.GetPricingQuoteSimulate)
adminAuth.Post("/pricing", middlewares.DoormileStaffOnly, controllers.CreatePricing)
adminAuth.Put("/pricing/:id", middlewares.DoormileStaffOnly, controllers.UpdatePricing)
adminAuth.Delete("/pricing/:id", middlewares.DoormileStaffOnly, controllers.DeletePricing)
adminAuth.Post("/pricing/simulate", middlewares.DoormileStaffOnly, controllers.GetPricingQuoteSimulate)
adminAuth.Post("/pricing/quote", middlewares.DoormileStaffOnly, controllers.GetPricingQuoteSimulate)
// Doormile pricing bands
adminAuth.Get("/doormile-pricing", controllers.GetDoormilePricing)

View File

@@ -18,6 +18,11 @@ import (
// token on the server, not just be hidden in the UI.
var staffOnlyFleetRoutes = []struct{ method, path string }{
{http.MethodPost, "/api/v1/admin/tenants"},
{http.MethodPost, "/api/v1/admin/pricing"},
{http.MethodPut, "/api/v1/admin/pricing/1"},
{http.MethodDelete, "/api/v1/admin/pricing/1"},
{http.MethodPost, "/api/v1/admin/pricing/simulate"},
{http.MethodPost, "/api/v1/admin/pricing/quote"},
{http.MethodGet, "/api/v1/admin/users"},
{http.MethodPost, "/api/v1/admin/users"},
{http.MethodPut, "/api/v1/admin/users/1"},

View File

@@ -0,0 +1,86 @@
package routes_test
import (
"net/http"
"os"
"strings"
"testing"
"time"
"doormile/constants"
"doormile/db"
"doormile/internal/testpg"
"doormile/models"
"doormile/utils"
)
// A rider whose duty from earlier today is still open (reinstall, crash,
// signing in again) and who starts duty again. The server used to answer 400
// "already on duty" and leave the profile Offline, while the app showed the
// rider online: the console listed them Offline and auto-assignment skipped
// them. Postgres-gated; the DSN must be a THROWAWAY database.
func TestStartDutyResumesAnOpenDutyAndMarksTheRiderAvailable(t *testing.T) {
dsn := os.Getenv("REGISTRY_TEST_DSN")
if dsn == "" {
t.Skip("REGISTRY_TEST_DSN not set; skipping Postgres duty test")
}
gdb := testpg.Open(t, dsn, "miler_duty_routes_test")
all := []any{&models.AppUser{}, &models.MilerProfile{}, &models.MilerDutyLog{}}
if err := gdb.Migrator().DropTable(all...); err != nil {
t.Fatal(err)
}
if err := gdb.AutoMigrate(all...); err != nil {
t.Fatal(err)
}
prev := db.DB
db.DB = gdb
t.Cleanup(func() { db.DB = prev })
app := onboardingApp()
rider := func(name, phone, status string) (models.AppUser, string) {
u := models.AppUser{Authname: name, Email: name + "@riders.test", Contactno: phone, Password: "x", Roleid: 5, Status: "Active", Configid: 1001}
if err := gdb.Create(&u).Error; err != nil {
t.Fatal(err)
}
if err := gdb.Create(&models.MilerProfile{Userid: u.Userid, Displayname: name, Phone: phone, Availabilitystatus: status}).Error; err != nil {
t.Fatal(err)
}
// The duty left open from earlier today.
if err := gdb.Create(&models.MilerDutyLog{Userid: u.Userid, Loginat: time.Now(), Onduty: true}).Error; err != nil {
t.Fatal(err)
}
tok, err := utils.GenerateToken(u.Userid, u.Email, 5, 0, 1001, jwtSecret)
if err != nil {
t.Fatal(err)
}
return u, tok
}
statusOf := func(uid int) string {
var p models.MilerProfile
gdb.Where("userid = ?", uid).First(&p)
return p.Availabilitystatus
}
offline, tok := rider("nagalakshmi", "9000000101", constants.MilerOffline)
code, body := do(t, app, http.MethodPost, "/api/v1/miler/duty/start", tok, `{"lat":0,"lon":0}`)
if code != 200 || !strings.Contains(body, `"resumed":true`) {
t.Fatalf("start duty with an open duty = %d %s, want 200 resumed", code, body)
}
if got := statusOf(offline.Userid); got != constants.MilerAvailable {
t.Fatalf("rider status after resuming = %q, want Available", got)
}
var open int64
gdb.Model(&models.MilerDutyLog{}).Where("userid = ? AND logoutat IS NULL", offline.Userid).Count(&open)
if open != 1 {
t.Fatalf("open duty logs = %d, want the one resumed, not a second", open)
}
// A rider mid-delivery keeps that status; resuming only lifts Offline.
busy, tok := rider("busy", "9000000102", constants.MilerOnDelivery)
if code, body := do(t, app, http.MethodPost, "/api/v1/miler/duty/start", tok, `{"lat":0,"lon":0}`); code != 200 {
t.Fatalf("start duty = %d %s", code, body)
}
if got := statusOf(busy.Userid); got != constants.MilerOnDelivery {
t.Fatalf("on-delivery rider became %q, want On_Delivery kept", got)
}
}