Files
doormile_backend/scratch/seed_test_data.go
Suriyakumarvijayanayagam 5e5230e1f1 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WRaFH5hMRqmUQvVPQsyjZD
2026-09-16 12:41:02 +05:30

362 lines
16 KiB
Go

//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")
}