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
362 lines
16 KiB
Go
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")
|
|
}
|