From 5e5230e1f1880a61f542e611888cbd13f75ad39b Mon Sep 17 00:00:00 2001 From: Suriyakumarvijayanayagam Date: Wed, 16 Sep 2026 12:41:02 +0530 Subject: [PATCH] chore: add idempotent test-data seed (no-PIN milers, refs, multi-dest booking) scratch/seed_test_data.go (//go:build ignore) fills a DB with what the current customer/miler app builds need to test against: - reference serviceable states/districts + pricing - base/hub network + a tenant location - 3 TEST MILERS WITH NO PIN (8000000001/2/3) so the first-login set-PIN flow can be exercised end to end - a sample customer (9000000001) and a multi-destination customer-app booking (DM-SEED-CX-001: 2 destinations, 2 consignments in the rider's hands) to test the base-handover fix Idempotent and non-destructive: every block is create-if-absent by a natural key; it never truncates, never overwrites a curated row, and never resets a test miler's PIN once set. Already run against the live DB. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WRaFH5hMRqmUQvVPQsyjZD --- scratch/seed_test_data.go | 361 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 scratch/seed_test_data.go diff --git a/scratch/seed_test_data.go b/scratch/seed_test_data.go new file mode 100644 index 0000000..0ed5ad6 --- /dev/null +++ b/scratch/seed_test_data.go @@ -0,0 +1,361 @@ +//go:build ignore + +// seed_test_data.go — fills the live DB with the reference + test data the +// current flows need, so Dharaneesh's customer/miler app builds have something +// real to work against. +// +// It seeds: (1) reference states/districts + pricing, (2) the base/hub network +// and tenant locations, (3) TEST MILERS WITH NO PIN so the first-login set-PIN +// flow can be exercised, and (4) a sample customer plus a multi-destination +// customer-app booking with two consignments in the rider's hands (ready to +// test the base-handover fix). +// +// It is IDEMPOTENT and NON-DESTRUCTIVE: every block is create-if-absent by a +// natural key. It never TRUNCATEs, never overwrites a curated live row, and — +// critically — never resets a test miler's Password once set, so re-running it +// after a rider has chosen their PIN does not wipe it. +// +// Run: go run scratch/seed_test_data.go (needs .env pointing at the target DB) + +package main + +import ( + "fmt" + "log" + + "doormile/config" + "doormile/db" + "doormile/migrations" + "doormile/models" + "doormile/utils" + + "github.com/joho/godotenv" +) + +func main() { + _ = godotenv.Load() + cfg := config.Load() + db.Connect(cfg) + if db.DB == nil { + log.Fatal("DB connection is nil") + } + if err := migrations.Migrate(db.DB); err != nil { + log.Fatalf("migration failed: %v", err) + } + now := utils.DBNow() + + // ───────────────────────────────────────────────────────────────────────── + // 1. REFERENCE DATA — serviceable states + districts (customer app reads + // these), plus a little pricing. Create-if-absent so curated live rows + // are never overwritten. + // ───────────────────────────────────────────────────────────────────────── + states := []models.ServiceableState{ + {Statecode: "TN", Statename: "Tamil Nadu", Transittag: "Ultra-fast transit", Displayorder: 1, Status: "Active"}, + {Statecode: "KA", Statename: "Karnataka", Transittag: "Next-day transit", Displayorder: 2, Status: "Active"}, + {Statecode: "KL", Statename: "Kerala", Transittag: "2-day transit", Displayorder: 3, Status: "Active"}, + } + for _, s := range states { + var ex models.ServiceableState + if db.DB.Where("statecode = ?", s.Statecode).First(&ex).Error != nil { + if err := db.DB.Create(&s).Error; err != nil { + log.Fatalf("seed state %s: %v", s.Statecode, err) + } + } + } + fmt.Println("✔ reference states") + + // Ensure the two anchor bases exist first, so districts can point at them. + baseSeeds := []models.Hub{ + {Hubname: "Coimbatore Jupiter Hub", Hubtype: "sorting_center", Applocationid: 1, Contactno: "9876543210", Address: "Gandhipuram, Coimbatore", Latitude: 11.0168, Longitude: 76.9558, Pincode: "641012", Status: "Active"}, + {Hubname: "Coimbatore Neptune Hub", Hubtype: "delivery_hub", Applocationid: 1, Contactno: "9876543210", Address: "Peelamedu, Coimbatore", Latitude: 11.0301, Longitude: 77.0034, Pincode: "641004", Status: "Active"}, + {Hubname: "Chennai Comet Hub", Hubtype: "delivery_hub", Applocationid: 1, Contactno: "9876543210", Address: "Guindy, Chennai", Latitude: 13.0067, Longitude: 80.2206, Pincode: "600032", Status: "Active"}, + {Hubname: "Bangalore Earth Hub", Hubtype: "sorting_center", Applocationid: 3, Contactno: "9876543210", Address: "Koramangala, Bangalore", Latitude: 12.9352, Longitude: 77.6244, Pincode: "560034", Status: "Active"}, + } + for _, h := range baseSeeds { + var ex models.Hub + if db.DB.Where("hubname = ?", h.Hubname).First(&ex).Error != nil { + if err := db.DB.Create(&h).Error; err != nil { + log.Fatalf("seed hub %s: %v", h.Hubname, err) + } + } + } + fmt.Println("✔ base / hub network") + + hubID := func(name string) *int { + var h models.Hub + if db.DB.Where("hubname = ?", name).First(&h).Error != nil { + return nil + } + return &h.Hubid + } + cbeHub := hubID("Coimbatore Jupiter Hub") + chnHub := hubID("Chennai Comet Hub") + blrHub := hubID("Bangalore Earth Hub") + + districts := []models.ServiceableDistrict{ + {Districtcode: "CBE", Statecode: "TN", Districtname: "Coimbatore", Available: true, Hubid: cbeHub, Promise: "Same-day delivery", Centrelatitude: 11.0168, Centrelongitude: 76.9558, Pincodeprefix: "6410", Displayorder: 1}, + {Districtcode: "CHN", Statecode: "TN", Districtname: "Chennai", Available: true, Hubid: chnHub, Promise: "Next-day delivery", Centrelatitude: 13.0827, Centrelongitude: 80.2707, Pincodeprefix: "6000", Displayorder: 2}, + {Districtcode: "BLR", Statecode: "KA", Districtname: "Bengaluru Urban", Available: true, Hubid: blrHub, Promise: "Next-day delivery", Centrelatitude: 12.9716, Centrelongitude: 77.5946, Pincodeprefix: "5600", Displayorder: 3}, + {Districtcode: "EKM", Statecode: "KL", Districtname: "Ernakulam", Available: true, Hubid: nil, Promise: "2-day delivery", Centrelatitude: 9.9816, Centrelongitude: 76.2999, Pincodeprefix: "6820", Displayorder: 4}, + } + for _, d := range districts { + var ex models.ServiceableDistrict + if db.DB.Where("districtcode = ?", d.Districtcode).First(&ex).Error != nil { + if err := db.DB.Create(&d).Error; err != nil { + log.Fatalf("seed district %s: %v", d.Districtcode, err) + } + } + } + fmt.Println("✔ reference districts") + + // Pricing (create-if-absent by tenant+location+vehicle). + farFuture := now.AddDate(1, 0, 0) + pricing := []models.Pricing{ + {Tenantid: 1, Applocationid: 1, Vehicletype: "Bike", Baseprice: 40, Baseweight: 5, Priceperkg: 8, Basedistance: 5, Priceperkm: 8, Handlingcharges: 5, Effectivefrom: now, Effectiveto: farFuture, Currency: "INR", Priority: 1, Status: "Active"}, + {Tenantid: 1, Applocationid: 1, Vehicletype: "Auto", Baseprice: 60, Baseweight: 10, Priceperkg: 10, Basedistance: 5, Priceperkm: 12, Handlingcharges: 10, Effectivefrom: now, Effectiveto: farFuture, Currency: "INR", Priority: 1, Status: "Active"}, + } + for _, p := range pricing { + var ex models.Pricing + if db.DB.Where("tenantid = ? AND applocationid = ? AND vehicletype = ?", p.Tenantid, p.Applocationid, p.Vehicletype).First(&ex).Error != nil { + if err := db.DB.Create(&p).Error; err != nil { + log.Fatalf("seed pricing %s: %v", p.Vehicletype, err) + } + } + } + fmt.Println("✔ pricing") + + // ───────────────────────────────────────────────────────────────────────── + // 2. TENANT LOCATIONS — the client sites parcels are collected from. + // Tenant 1 must exist (create-if-absent), then its primary location. + // ───────────────────────────────────────────────────────────────────────── + var t1 models.Tenant + if db.DB.Where("tenantid = ?", 1).First(&t1).Error != nil { + t1 = models.Tenant{Tenantid: 1, Tenantname: "Doormile Coimbatore Logistics", Primaryemail: "cbe-ops@doormile.com", Primarycontact: "9876543210", Status: "Active"} + if err := db.DB.Create(&t1).Error; err != nil { + log.Fatalf("seed tenant 1: %v", err) + } + } + var tl models.TenantLocation + if db.DB.Where("tenantid = ? AND address = ?", 1, "124 Gandhipuram Main Rd").First(&tl).Error != nil { + tl = models.TenantLocation{Tenantid: 1, Address: "124 Gandhipuram Main Rd", City: "Coimbatore", State: "Tamil Nadu", Pincode: "641012", Latitude: 11.0168, Longitude: 76.9558, Isprimary: true, Status: "Active"} + if err := db.DB.Create(&tl).Error; err != nil { + log.Fatalf("seed tenant location: %v", err) + } + } + fmt.Println("✔ tenant locations") + + // ───────────────────────────────────────────────────────────────────────── + // 3. TEST MILERS — NO PIN. Created with an empty Password so LoginMiler + // reports pin_set:false and the app shows the set-PIN screen. On re-run, + // an already-created rider is LEFT ALONE (their chosen PIN is never wiped). + // ───────────────────────────────────────────────────────────────────────── + testMilers := []struct { + Authname, Email, Phone, Display string + Lat, Lng float64 + Pincode string + Hubid *int + }{ + {"Test Rider One", "test.rider1@doormile.in", "8000000001", "Test Rider One (Coimbatore)", 11.0175, 76.9565, "641012", cbeHub}, + {"Test Rider Two", "test.rider2@doormile.in", "8000000002", "Test Rider Two (Coimbatore)", 11.0301, 77.0034, "641004", cbeHub}, + {"Test Rider Three", "test.rider3@doormile.in", "8000000003", "Test Rider Three (Coimbatore)", 11.0205, 76.9760, "641018", cbeHub}, + } + var firstTestRiderID int + for i, m := range testMilers { + var user models.AppUser + if db.DB.Where("contactno = ? AND configid = ?", m.Phone, 1001).First(&user).Error != nil { + user = models.AppUser{ + Authname: m.Authname, + Email: m.Email, + Contactno: m.Phone, + Password: "", // no PIN — rider self-sets on first login + Roleid: 5, + Configid: 1001, + Tenantid: 1, + Applocationid: 1, + Status: "Active", + } + if m.Hubid != nil { + user.Hubid = m.Hubid + } + if err := db.DB.Create(&user).Error; err != nil { + log.Fatalf("seed test miler %s: %v", m.Phone, err) + } + fmt.Printf(" created %s (%s) — no PIN, ready for set-pin\n", m.Display, m.Phone) + } else { + state := "PIN already set" + if user.Password == "" { + state = "no PIN — ready for set-pin" + } + fmt.Printf(" exists %s (%s) — %s (left untouched)\n", m.Display, m.Phone, state) + } + + var profile models.MilerProfile + if db.DB.Where("userid = ?", user.Userid).First(&profile).Error != nil { + profile = models.MilerProfile{ + Userid: user.Userid, + Displayname: m.Display, + Phone: m.Phone, + Availabilitystatus: "Offline", + Rating: 5.0, + Currentlatitude: m.Lat, + Currentlongitude: m.Lng, + Currentpincode: m.Pincode, + Defaultvehicletype: "Bike", + Applocationid: 1, + } + if m.Hubid != nil { + profile.Hubid = m.Hubid + } + if err := db.DB.Create(&profile).Error; err != nil { + log.Fatalf("seed test miler profile %s: %v", m.Phone, err) + } + } + if i == 0 { + firstTestRiderID = user.Userid + } + } + fmt.Println("✔ test milers (no PIN)") + + // ───────────────────────────────────────────────────────────────────────── + // 4. SAMPLE CUSTOMER + MULTI-DESTINATION BOOKING. + // One pickup in Coimbatore → two destinations (Chennai, Bengaluru), two + // consignments in status Created (in the rider's hands), assigned to the + // first test rider — the exact shape to test the base-handover fix: + // inwarding parcel #1 must NOT free the rider while #2 is still carried. + // ───────────────────────────────────────────────────────────────────────── + var customer models.AppCustomer + if db.DB.Where("phone = ?", "9000000001").First(&customer).Error != nil { + customer = models.AppCustomer{ + Firstname: "Test", + Lastname: "Customer", + Phone: "9000000001", + Email: "test.customer@doormile.in", + Defaultlatitude: 11.0168, + Defaultlongitude: 76.9558, + Defaultpincode: "641012", + Status: "Active", + Configid: 1001, + } + if err := db.DB.Create(&customer).Error; err != nil { + log.Fatalf("seed customer: %v", err) + } + } + fmt.Println("✔ sample customer") + + const seedBookingNo = "DM-SEED-CX-001" + var existingBooking models.PickupBooking + if db.DB.Where("bookingno = ?", seedBookingNo).First(&existingBooking).Error == nil { + fmt.Printf("✔ sample multi-destination booking already present (%s) — skipped\n", seedBookingNo) + } else { + tenant1 := 1 + booking := models.PickupBooking{ + Bookingno: seedBookingNo, + Tenantid: &tenant1, + Appcustomerid: customer.Appcustomerid, + Tenantlocationid: &tl.Tenantlocationid, + Pickupsourcetype: "merchant", + Pickupaddress: "124 Gandhipuram Main Rd, Coimbatore 641012", + Pickuptitle: "124 Gandhipuram Main Rd", + Pickupsub: "Gandhipuram, Coimbatore 641012", + Pickuppincode: "641012", + Pickuplatitude: 11.0168, + Pickuplongitude: 76.9558, + Deliveryaddress: "Guindy, Chennai 600032", + Deliverypincode: "600032", + Deliverylatitude: 13.0067, + Deliverylongitude: 80.2206, + Deliverycity: "Chennai", + Nearesthubid: cbeHub, + Bookingsource: "Customer_App", + Status: "Converted_To_Consignment", + Customerstage: "assigned", + Customerstatus: "active", + Estimateminrupees: 220, + Estimatemaxrupees: 280, + Routekm: 505, + Assignedmileruserid: &firstTestRiderID, + Createdat: now, + Updatedat: now, + } + if err := db.DB.Create(&booking).Error; err != nil { + log.Fatalf("seed booking: %v", err) + } + + srv := models.BookingServiceOption{Bookingid: booking.Bookingid, Servicetype: "Normal", Estimatedprice: 250, Createdat: now} + if err := db.DB.Create(&srv).Error; err != nil { + log.Fatalf("seed service option: %v", err) + } + + // One consignment + one destination per leg. Consignments are Created — + // collected, in the rider's hands, on the way to a base. + legs := []struct { + Seq int + StateCode, StateName string + DistrictCode, DistrictName string + DelLat, DelLng float64 + DelPincode string + Packages int + }{ + {0, "TN", "Tamil Nadu", "CHN", "Chennai", 13.0827, 80.2707, "600032", 2}, + {1, "KA", "Karnataka", "BLR", "Bengaluru Urban", 12.9716, 77.5946, "560001", 1}, + } + for _, leg := range legs { + trackingno := fmt.Sprintf("%s-%d", seedBookingNo, leg.Seq+1) + cons := models.Consignment{ + Trackingno: trackingno, + Tenantid: 1, + Tenantlocationid: &tl.Tenantlocationid, + Pickuppincode: "641012", + Deliverypincode: leg.DelPincode, + Pickuplatitude: 11.0168, + Pickuplongitude: 76.9558, + Deliverylatitude: leg.DelLat, + Deliverylongitude: leg.DelLng, + Deadweight: 2.0, + Chargeableweight: 2.0, + Paymentmode: "Prepaid", + Billingstatus: "Unbilled", + Status: "Created", + Createdat: now, + Updatedat: now, + } + if err := db.DB.Create(&cons).Error; err != nil { + log.Fatalf("seed consignment %s: %v", trackingno, err) + } + dest := models.BookingDestination{ + Bookingid: booking.Bookingid, + Seq: leg.Seq, + Statecode: leg.StateCode, + Statename: leg.StateName, + Districtcode: leg.DistrictCode, + Districtname: leg.DistrictName, + Packagecount: leg.Packages, + Recipientname: "Test Recipient", + Recipientphone: "9000000009", + Consignmentid: &cons.Consignmentid, + Trackingno: trackingno, + Stage: "assigned", + } + if err := db.DB.Create(&dest).Error; err != nil { + log.Fatalf("seed destination %d: %v", leg.Seq, err) + } + } + + assignment := models.BookingAssignment{ + Bookingid: booking.Bookingid, + Mileruserid: firstTestRiderID, + Assignmentstatus: "Accepted", + Assignedat: now, + Acceptedat: &now, + } + if err := db.DB.Create(&assignment).Error; err != nil { + log.Fatalf("seed assignment: %v", err) + } + fmt.Printf("✔ sample multi-destination booking %s (2 destinations, 2 consignments, assigned to test rider %d)\n", seedBookingNo, firstTestRiderID) + } + + fmt.Println("\n✅ seed complete.") + fmt.Println(" Test rider phones (no PIN, set on first login): 8000000001 / 8000000002 / 8000000003") + fmt.Println(" Test customer phone: 9000000001") + fmt.Println(" Sample multi-destination booking: DM-SEED-CX-001") +}