Files
backend_fiesta/scratch/gstrates/main.go
Suriya d0c3cb751e Document the sale-date contract, and rate the packaged goods
parsePosSaleDate needed no change — RFC3339Nano already accepts the offset the
terminal now sends, and Format("2006-01-02") on a zoned time still yields the
till's own trading day rather than UTC's. But the ordering of those layouts is
load-bearing and nothing said so, and the two bare layouts are a legacy that
should be recognisable as one: they exist for terminals built before the offset,
whose bills record an instant wrong by the offset with nothing in the payload to
recover it from. Two tests pin both halves, including the case that motivated
this — 00:30 IST, where UTC has not yet rolled into the same day.

The GST script writes only the four packaged lines at 1185, which sat at 0 and
were being billed with no tax at all.

It deliberately does not touch the produce at 1135. That was written believing
every rate was 0 — read from a field name that does not exist in the response,
so the check silently returned nothing. The rows in fact hold 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 belongs to whoever signs the returns rather than to a script. They
are reported as REVIEW and left as found.

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

173 lines
6.2 KiB
Go

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