Files
backend_fiesta/scratch/seedprices/main.go
Suriya 11595ad415 Record the probes that touched live data
seedprices is the only account of what the 15 seeded retail prices were and how
to put the zeros back — products at 1135 and 1185 were all at 0, so nothing was
sellable and the terminal could not be exercised at all. It refuses to overwrite
a price a human already set, so re-running it is safe.

termfixcleanup removes the one bill posted to prove the terminalid fix against
production. Named by its own terminalorderid rather than by date or by "the
newest row": pos_orders holds real takings and is not a table to run an
unbounded DELETE against.

healthproof carries a warning it did not have when it was written. MQTT_USER in
.env is pos_ingest, which the ACL denies publish on the health topic, and
Mosquitto answers an ACL-denied QoS 1 publish with a PUBACK before discarding
it. So the tool connects, reports success, and nothing arrives — indistinguishable
from a dead consumer, which is how it read for an hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:48:24 +05:30

195 lines
5.8 KiB
Go

// 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(&current)
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
}