// Set GST rates on the POS catalogue products. // // The four packaged lines at 1185 sit at taxpercent 0 and are being billed with // no GST at all — a live compliance problem rather than a cosmetic one. Those // are written. // // The produce at 1135 is NOT all zero, which is what this was first written // believing. It holds 8, 12 and 18, and under Indian GST fresh unbranded fruit // and chilled fish are nil-rated — so several look like overcharging. Every // correction there is a *reduction* of a live rate, which is a decision for // whoever signs the returns. Reported as REVIEW and left untouched. // // go run ./scratch/gstrates plan // go run ./scratch/gstrates apply package main import ( "fmt" "log" "os" "github.com/joho/godotenv" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) // Indian GST on food, as it applies to these lines. // // Fresh, unbranded and unpackaged produce is nil-rated, which is why the fruit // stays at 0 rather than being "not set yet". Packaged branded snacks are 12%. // Breakfast cereal is 18%. // // Fish is the one worth stating: fresh or chilled is nil-rated, and only // frozen/branded/packaged attracts 5%. Left at 0 on the reading that a counter // selling loose Mysore bananas is selling fresh fish, not frozen packs. type rate struct { productID int name string percent float64 why string // apply gates the write. Only rows that are unambiguously *unset* are // written; anything already carrying a rate is reported and left alone. // // The fresh produce at 1135 is the reason for this flag. Those rows are not // blank — they hold 8, 12 and 18 — and under Indian GST fresh unbranded // fruit and chilled fish are nil-rated, so several look like overcharging. // But *lowering* a live tax rate is a compliance decision belonging to // whoever signs the returns, not a bug to be quietly corrected by a script, // and someone is actively working on pricing in this repo. Reported, not // touched. apply bool } var rates = []rate{ // 1135 — already rated. Listed so the plan shows what is there, and flagged // for a human because every one of these is a reduction. {6988, "Mysore Banana", 0, "fresh fruit — nil-rated, currently 8%", false}, {6989, "Jammu Apple", 0, "fresh fruit — nil-rated, currently 18%", false}, {6990, "Small orange", 0, "fresh fruit — nil-rated, currently 18%", false}, {6991, "Red Guava", 0, "fresh fruit — nil-rated, currently 18%", false}, {6992, "Pomegrante", 0, "fresh fruit — nil-rated, currently 12%", false}, {6993, "Salem Mango", 0, "fresh fruit — nil-rated", false}, {6994, "Pineapple", 0, "fresh fruit — nil-rated", false}, {6995, "Strawberries", 0, "fresh fruit — nil-rated, currently 18%", false}, {6996, "Maceral", 0, "fresh fish nil-rated; 5% only if frozen/packaged", false}, {6997, "Tuna", 0, "fresh fish nil-rated; 5% only if frozen/packaged", false}, {6998, "Hatsun curd", 0, "curd nil-rated; flavoured yoghurt would be 5%", false}, {7014, "Apple", 0, "fresh fruit — nil-rated", false}, // 1185 — genuinely unset, and being billed with no GST at all today. This // is the half that is unambiguous: every one is an increase from zero, so // nothing is being under-collected on the strength of a script's opinion. {7074, "Amla Dabur Oral Care Chewing Gum 10g", 18, "chewing gum, 18%", true}, {7075, "Cheetos Chips 100g", 12, "packaged extruded snack, 12%", true}, {7076, "Cheerios Breakfast Cereal 100g", 18, "packaged cereal, 18%", true}, {7077, "Hot Heads 30g", 12, "packaged snack, 12%", true}, } 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) } write := mode == "apply" fmt.Printf("%-6s %-38s %6s -> %6s %s\n", "id", "product", "now", "new", "why") fmt.Println("-------------------------------------------------------------------------------------------") undo := []string{} changes := 0 review := 0 for _, r := range rates { var current struct { Taxpercent float64 Found bool } if err := db.Raw(`SELECT COALESCE(taxpercent, 0) AS taxpercent, true AS found FROM products WHERE productid = ? LIMIT 1`, r.productID).Scan(¤t).Error; err != nil { log.Fatalf("reading %d: %v", r.productID, err) } if !current.Found { fmt.Printf("%-6d %-38s NO products ROW - skipped\n", r.productID, r.name) continue } if current.Taxpercent == r.percent { fmt.Printf("%-6d %-38s %6.0f unchanged %s\n", r.productID, r.name, current.Taxpercent, r.why) continue } if !r.apply { fmt.Printf("%-6d %-38s %6.0f REVIEW %-3.0f %s\n", r.productID, r.name, current.Taxpercent, r.percent, r.why) review++ continue } fmt.Printf("%-6d %-38s %6.0f -> %6.0f %s\n", r.productID, r.name, current.Taxpercent, r.percent, r.why) undo = append(undo, fmt.Sprintf( "UPDATE products SET taxpercent = %.0f WHERE productid = %d;", current.Taxpercent, r.productID)) changes++ if write { // updated is bumped so the catalogue delta carries the new rate to // terminals holding a revision, rather than waiting for a full pull. if err := db.Exec(`UPDATE products SET taxpercent = ?, updated = NOW() WHERE productid = ?`, r.percent, r.productID).Error; err != nil { log.Fatalf("writing %d: %v", r.productID, err) } } } fmt.Println("-------------------------------------------------------------------------------------------") if write { fmt.Printf("APPLIED %d rate(s).\n", changes) } else { fmt.Printf("%d rate(s) would change. Nothing written — run `apply` to commit.\n", changes) } if review > 0 { fmt.Printf("%d row(s) flagged REVIEW and deliberately not written — each is a\n"+ "reduction of a live tax rate and needs a decision, not a script.\n", review) } fmt.Println() if len(undo) > 0 { fmt.Println("-- undo:") for _, u := range undo { fmt.Println(u) } } }