// Seed retail prices so the POS has something sellable. // // These are plausible Coimbatore figures, not authoritative ones. They exist so // the terminal can ring a real bill; the owner corrects them afterwards. // // go run ./scratch/seedprices plan # show every change and the undo SQL // go run ./scratch/seedprices apply # write them // go run ./scratch/seedprices verify # read back what the catalogue now serves package main import ( "fmt" "log" "os" "github.com/joho/godotenv" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) type priced struct { locationID int productID int name string unit string price float64 note string } // Prices are per the product's own unit — per kilogram where the unit is // kilogram, per pack where it is piece. Getting that backwards is the easiest // way to make a till look broken, so the unit is carried through and printed. var seed = []priced{ // 1135 — fresh produce {1135, 6988, "Mysore Banana", "kilogram", 60, ""}, {1135, 6989, "Jammu Apple", "piece", 30, "per fruit, not per kg"}, {1135, 6990, "Small orange", "kilogram", 90, ""}, {1135, 6991, "Red Guava", "kilogram", 80, ""}, {1135, 6992, "Pomegrante", "kilogram", 180, ""}, {1135, 6993, "Salem Mango", "kilogram", 90, "seasonal, swings 80-120"}, {1135, 6994, "Pineapple", "kilogram", 60, ""}, {1135, 6995, "Strawberries", "piece", 150, "priced as a punnet"}, {1135, 6996, "Maceral", "kilogram", 220, "READ AS MACKEREL - correct if wrong"}, {1135, 6997, "Tuna", "kilogram", 280, ""}, {1135, 6998, "Hatsun curd", "piece", 30, "500g pouch"}, {1135, 7014, "Apple", "kilogram", 200, ""}, // 1185 — packaged {1185, 7074, "Amla Dabur Oral Care Chewing Gum 10g", "piece", 10, ""}, {1185, 7075, "Cheetos Chips 100g", "piece", 40, ""}, {1185, 7076, "Cheerios Breakfast Cereal 100g", "piece", 120, ""}, // 7077 Hot Heads is already at 50 — someone set it deliberately, leave it. } func main() { mode := "plan" if len(os.Args) > 1 { mode = os.Args[1] } _ = godotenv.Load() dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME")) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { log.Fatal(err) } switch mode { case "plan": plan(db, false) case "apply": plan(db, true) case "verify": verify(db) default: log.Fatalf("unknown mode %q — use plan, apply or verify", mode) } } func plan(db *gorm.DB, write bool) { fmt.Printf("%-6s %-38s %-9s %8s -> %8s\n", "id", "product", "unit", "now", "new") fmt.Println("--------------------------------------------------------------------------------") undo := []string{} changes := 0 for _, p := range seed { var current struct { Price float64 Tenantid int Found bool } row := db.Raw(`SELECT COALESCE(price, 0) AS price, tenantid, true AS found FROM productlocations WHERE productid = ? AND locationid = ? LIMIT 1`, p.productID, p.locationID).Scan(¤t) if row.Error != nil { log.Fatalf("reading %d: %v", p.productID, row.Error) } if !current.Found { fmt.Printf("%-6d %-38s NO productlocations ROW - skipped\n", p.productID, p.name) continue } // Never overwrite a price a human already set. A seed value is a // placeholder; a real one is a decision, and losing it silently would // be worse than leaving a gap. if current.Price > 0 { fmt.Printf("%-6d %-38s %-9s %8.2f already priced, left alone\n", p.productID, p.name, p.unit, current.Price) continue } note := "" if p.note != "" { note = " <- " + p.note } fmt.Printf("%-6d %-38s %-9s %8.2f -> %8.2f%s\n", p.productID, p.name, p.unit, current.Price, p.price, note) undo = append(undo, fmt.Sprintf( "UPDATE productlocations SET price = %.2f WHERE productid = %d AND locationid = %d;", current.Price, p.productID, p.locationID)) changes++ if write { // updated is bumped so the catalogue delta carries the new price to // terminals that already hold a revision, rather than waiting for // someone to force a full pull. err := db.Exec(`UPDATE productlocations SET price = ?, updated = NOW() WHERE productid = ? AND locationid = ?`, p.price, p.productID, p.locationID).Error if err != nil { log.Fatalf("writing %d: %v", p.productID, err) } } } fmt.Println("--------------------------------------------------------------------------------") if write { fmt.Printf("APPLIED %d price(s).\n\n", changes) } else { fmt.Printf("%d price(s) would change. Nothing written — run `apply` to commit.\n\n", changes) } fmt.Println("-- undo, if you want the zeros back:") for _, u := range undo { fmt.Println(u) } } func verify(db *gorm.DB) { type row struct { Locationid int Productid int Productname string Price float64 Taxpercent float64 } var rows []row db.Raw(`SELECT b.locationid, a.productid, a.productname, COALESCE(b.price, 0) AS price, COALESCE(a.taxpercent, 0) AS taxpercent FROM products a INNER JOIN productlocations b ON a.productid = b.productid AND a.tenantid = b.tenantid WHERE b.locationid IN (1135, 1185) AND a.productid > 0 ORDER BY b.locationid, a.productid`).Scan(&rows) sellable := 0 for _, r := range rows { flag := "" if r.Price > 0 { sellable++ } else { flag = " <- still zero, not sellable" } fmt.Printf("loc %d %-6d %-38s %8.2f tax=%.0f%s\n", r.Locationid, r.Productid, r.Productname[:min(38, len(r.Productname))], r.Price, r.Taxpercent, flag) } fmt.Printf("\n%d of %d rows are sellable.\n", sellable, len(rows)) } func min(a, b int) int { if a < b { return a } return b }