package main import ( "encoding/json" "strconv" ) func GetTaxAmount(total int64, tax int) int64 { taxD := 10000 / float64(tax) return int64(float64(total) / float64((1 + taxD))) } type Price struct { IncVat int64 `json:"incVat"` VatRates map[float32]int64 `json:"vat,omitempty"` } func NewPrice() *Price { return &Price{ IncVat: 0, VatRates: make(map[float32]int64), } } func NewPriceFromIncVat(incVat int64, taxRate float32) *Price { tax := GetTaxAmount(incVat, int(taxRate*100)) return &Price{ IncVat: incVat, VatRates: map[float32]int64{ taxRate: tax, }, } } func (p *Price) ValueExVat() int64 { exVat := p.IncVat for _, amount := range p.VatRates { exVat -= amount } return exVat } func (p *Price) TotalVat() int64 { total := int64(0) for _, amount := range p.VatRates { total += amount } return total } func MultiplyPrice(p Price, qty int64) *Price { ret := &Price{ IncVat: p.IncVat * qty, VatRates: make(map[float32]int64), } for rate, amount := range p.VatRates { ret.VatRates[rate] = amount * qty } return ret } func (p *Price) Multiply(qty int64) { p.IncVat *= qty for rate, amount := range p.VatRates { p.VatRates[rate] = amount * qty } } func (p Price) MarshalJSON() ([]byte, error) { // Build a stable wire format without calling Price.MarshalJSON recursively exVat := p.ValueExVat() var vat map[string]int64 if len(p.VatRates) > 0 { vat = make(map[string]int64, len(p.VatRates)) for rate, amount := range p.VatRates { // Rely on default formatting that trims trailing zeros for whole numbers // Using %g could output scientific notation for large numbers; float32 rates here are small. key := trimFloat(rate) vat[key] = amount } } type wire struct { ExVat int64 `json:"exVat"` IncVat int64 `json:"incVat"` Vat map[string]int64 `json:"vat,omitempty"` } return json.Marshal(wire{ExVat: exVat, IncVat: p.IncVat, Vat: vat}) } // trimFloat converts a float32 tax rate like 25 or 12.5 into a compact string without // unnecessary decimals ("25", "12.5"). func trimFloat(f float32) string { // Convert via FormatFloat then trim trailing zeros and dot. s := strconv.FormatFloat(float64(f), 'f', -1, 32) return s } func (p *Price) Add(price Price) { p.IncVat += price.IncVat for rate, amount := range price.VatRates { p.VatRates[rate] += amount } } func (p *Price) Subtract(price Price) { p.IncVat -= price.IncVat for rate, amount := range price.VatRates { p.VatRates[rate] -= amount } } func SumPrices(prices ...Price) *Price { if len(prices) == 0 { return NewPrice() } aggregated := NewPrice() for _, price := range prices { aggregated.IncVat += price.IncVat for rate, amount := range price.VatRates { aggregated.VatRates[rate] += amount } } if len(aggregated.VatRates) == 0 { aggregated.VatRates = nil } return aggregated }