Skip to content

Commit

Permalink
Merge pull request #228 from invopop/tax-included-category-fix
Browse files Browse the repository at this point in the history
Maintain precision in category with taxes included
  • Loading branch information
samlown authored Jan 8, 2024
2 parents e9e2fc6 + 984eb7d commit e491b8c
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.51
version: v1.55
2 changes: 1 addition & 1 deletion bill/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func (inv *Invoice) calculate(r *tax.Regime, tID *tax.Identity) error {
// Remove any included taxes from the total.
ct := t.Taxes.Category(pit)
if ct != nil {
ti := ct.Amount
ti := ct.PreciseAmount()
t.TaxIncluded = &ti
t.Total = t.Total.Subtract(ti)
}
Expand Down
54 changes: 54 additions & 0 deletions bill/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,60 @@ func TestRemoveIncludedTax5(t *testing.T) {
assert.Equal(t, i.Totals.Payable.String(), i2.Totals.Payable.String())
}

func TestRemoveIncludedTax6WithDiscount(t *testing.T) {
lines := []*bill.Line{
{
Quantity: num.MakeAmount(1, 0),
Item: &org.Item{
Name: "Test Item",
Price: num.MakeAmount(9338, 2),
},
Discounts: []*bill.LineDiscount{
{
Percent: num.NewPercentage(40009, 5),
},
},
Taxes: tax.Set{
{
Category: "VAT",
Percent: num.NewPercentage(23, 2),
},
},
},
}
i := baseInvoice(t, lines...)
require.NoError(t, i.Calculate())

assert.Equal(t, "56.02", i.Totals.Sum.String())
assert.Equal(t, i.Totals.Sum.String(), i.Totals.Payable.String())

/*
data, _ := json.Marshal(i.Lines)
t.Logf("LINES: %v", string(data))
data, _ = json.Marshal(i.Totals)
t.Logf("TOTALS: %v", string(data))
*/

i2, err := i.RemoveIncludedTaxes()
require.NoError(t, err)

assert.Empty(t, i2.Tax.PricesInclude)
l0 := i2.Lines[0]
assert.Equal(t, "75.9187", l0.Item.Price.String())

/*
data, _ = json.Marshal(i2.Lines)
t.Logf("Lines: %v", string(data))
data, _ = json.Marshal(i2.Totals)
t.Logf("TOTALS: %v", string(data))
*/

assert.Equal(t, "45.54", i2.Totals.Sum.String())
assert.Equal(t, i.Totals.Total.String(), i2.Totals.Total.String())
assert.Equal(t, i.Totals.Tax.String(), i2.Totals.Tax.String())
assert.Equal(t, i.Totals.Payable.String(), i2.Totals.Payable.String())
}

func TestRemoveIncludedTaxQuantity(t *testing.T) {
i := &bill.Invoice{
Code: "123TEST",
Expand Down
4 changes: 2 additions & 2 deletions num/amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ func AmountFromString(val string) (Amount, error) {
return a, nil
}

// AmountFromHumanString removes an excess decimal places, commas, or
// AmountFromHumanString removes any excess decimal places, commas, or
// other symbols so that we end up with a simple string that can be parsed.
func AmountFromHumanString(val string) (Amount, error) {
func AmountFromHumanString(_ string) (Amount, error) {
return Amount{}, errors.New("not yet implemented")
}

Expand Down
8 changes: 4 additions & 4 deletions regimes/es/tax_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ var (

// Known combinations of codes
var (
taxCodeNationalRegexp = regexp.MustCompile(`^(?P<number>[0-9]{8})(?P<check>[` + taxCodeCheckLetters + `])$`)
taxCodeForeignRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeForeignTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[` + taxCodeCheckLetters + `])$`)
taxCodeOtherRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeOtherTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[0-9` + taxCodeOrgCheckLetters + `])$`)
taxCodeOrgRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeOrgTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[0-9` + taxCodeOrgCheckLetters + `])$`)
taxCodeNationalRegexp = regexp.MustCompile(`^(?P<number>[0-9]{8})(?P<check>[` + taxCodeCheckLetters + `])$`) //nolint:goconst
taxCodeForeignRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeForeignTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[` + taxCodeCheckLetters + `])$`) //nolint:goconst
taxCodeOtherRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeOtherTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[0-9` + taxCodeOrgCheckLetters + `])$`) //nolint:goconst
taxCodeOrgRegexp = regexp.MustCompile(`^(?P<type>[` + taxCodeOrgTypeLetters + `])(?P<number>[0-9]{7})(?P<check>[0-9` + taxCodeOrgCheckLetters + `])$`) //nolint:goconst
)

// validateTaxIdentity looks at the provided identity's code,
Expand Down
4 changes: 2 additions & 2 deletions regimes/nl/invoice_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ func (v *invoiceValidator) supplier(value interface{}) error {
)
}

func (v *invoiceValidator) customer(value interface{}) error {
return nil
func (v *invoiceValidator) customer(_ interface{}) error {
return nil // nothing to do yet
}
8 changes: 5 additions & 3 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
)

const (
// VERSION for the current version of the schema
VERSION = "draft-0"
// Version of the current version of the schema
Version = "draft-0"
// BaseURL is the base URL for all GOBL schemas
BaseURL = "https://gobl.org/"
// GOBL stores the base schema ID for GOBL, including current schema version.
GOBL ID = "https://gobl.org/" + VERSION
GOBL ID = BaseURL + Version
)

const (
Expand Down
6 changes: 3 additions & 3 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestID(t *testing.T) {
id := schema.GOBL.Add("test/bar")
base := "https://gobl.org/" + schema.VERSION
base := schema.BaseURL + schema.Version

assert.EqualValues(t, base+"/test/bar", id)

Expand All @@ -27,7 +27,7 @@ func TestID(t *testing.T) {
}

func TestExtract(t *testing.T) {
base := `https://gobl.org/` + schema.VERSION + ``
base := schema.BaseURL + schema.Version + ``
data := []byte(`{"$schema":"` + base + `/test/bar","random":"message"}`)

id, err := schema.Extract(data)
Expand All @@ -45,7 +45,7 @@ func TestExtract(t *testing.T) {
}

func TestInsert(t *testing.T) {
id := schema.ID(`https://gobl.org/` + schema.VERSION + `/test/bar`)
id := schema.ID(schema.BaseURL + schema.Version + `/test/bar`)
data := []byte(`{"random":"message"}`)
var err error
data, err = schema.Insert(id, data)
Expand Down
18 changes: 16 additions & 2 deletions tax/totals.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type CategoryTotal struct {
Rates []*RateTotal `json:"rates" jsonschema:"title=Rates"`
Amount num.Amount `json:"amount" jsonschema:"title=Amount"`
Surcharge *num.Amount `json:"surcharge,omitempty" jsonschema:"title=Surcharge"`

amount num.Amount // internal amount with greater accuracy
}

// RateTotal contains a sum of all the tax rates in the document with
Expand Down Expand Up @@ -51,8 +53,19 @@ type Total struct {
sum num.Amount
}

// PreciseSum is used internally to provide a more precise sum that maintains
// the accuracy provided by the original line totals.
// PreciseAmount contains the intermediary amount generated from the calculator
// with the original precision. This is useful when a Category Total needs
// to be used for further calculations, such as when an invoice includes taxes.
func (ct *CategoryTotal) PreciseAmount() num.Amount {
if !ct.amount.IsZero() {
return ct.amount
}
return ct.Amount
}

// PreciseSum contains an intermediary sum generated from the calculator
// with the original precision. If no calculations were made on the totals,
// such as when loading, the original sum will be provided instead.
func (t *Total) PreciseSum() num.Amount {
if !t.sum.IsZero() {
return t.sum
Expand All @@ -66,6 +79,7 @@ func newCategoryTotal(c *Combo, zero num.Amount) *CategoryTotal {
ct.Code = c.Category
ct.Rates = make([]*RateTotal, 0)
ct.Amount = zero
ct.amount = zero
ct.Retained = c.category.Retained
return ct
}
Expand Down
1 change: 1 addition & 0 deletions tax/totals_calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func (tc *TotalCalculator) round(t *Total) {
rt.Surcharge.Amount = rt.Surcharge.Amount.Rescale(zero.Exp())
}
}
ct.amount = ct.Amount
ct.Amount = ct.Amount.Rescale(zero.Exp())
if ct.Surcharge != nil {
*ct.Surcharge = ct.Surcharge.Rescale(zero.Exp())
Expand Down

0 comments on commit e491b8c

Please sign in to comment.