From 0eb5c7cd1141ad52567da152fd890bd0bb90c3f6 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Mon, 4 Dec 2023 12:20:07 +0000 Subject: [PATCH 1/2] Validating all regimes, with scenario tag validation --- regimes/co/corrections.go | 6 +++--- regimes/common/invoice_tags.go | 13 +++++++++++++ regimes/pt/tax_categories.go | 6 +++++- tax/regime.go | 13 ++++++++++++- tax/regimes_test.go | 16 ++++++++++++++++ tax/scenario.go | 15 +++++++++------ 6 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 tax/regimes_test.go diff --git a/regimes/co/corrections.go b/regimes/co/corrections.go index 7ad4124d..960cc10a 100644 --- a/regimes/co/corrections.go +++ b/regimes/co/corrections.go @@ -75,9 +75,9 @@ var correctionMethodList = []*tax.KeyDefinition{ }, { Key: CorrectionMethodKeyOther, - Desc: i18n.String{ - i18n.EN: "Otros.", - i18n.ES: "Other.", + Name: i18n.String{ + i18n.EN: "Other", + i18n.ES: "Otros", }, Map: cbc.CodeMap{ KeyDIAN: "5", diff --git a/regimes/common/invoice_tags.go b/regimes/common/invoice_tags.go index 2a41f023..66ec8535 100644 --- a/regimes/common/invoice_tags.go +++ b/regimes/common/invoice_tags.go @@ -60,6 +60,19 @@ var invoiceTags = []*tax.KeyDefinition{ i18n.DE: "Kundensätze", }, }, + + // Partial invoice document, implying that this is only a first part + // and a final invoice for the remaining amount will be made later. + // A few regimes use this tag to classify invoices, notably Italy. + { + Key: tax.TagPartial, + Name: i18n.String{ + i18n.EN: "Partial", + i18n.ES: "Parcial", + i18n.IT: "Parziale", + i18n.DE: "Teilweise", + }, + }, } // InvoiceTags returns a list of common invoice tag key diff --git a/regimes/pt/tax_categories.go b/regimes/pt/tax_categories.go index 59171fb7..6a0c3f3a 100644 --- a/regimes/pt/tax_categories.go +++ b/regimes/pt/tax_categories.go @@ -112,7 +112,11 @@ var taxCategories = []*tax.Category{ }, }, { - Key: tax.RateExempt, + Key: tax.RateExempt, + Name: i18n.String{ + i18n.EN: "Exempt", + i18n.PT: "Isento", + }, Exempt: true, Map: cbc.CodeMap{ KeyATTaxCode: TaxCodeExempt, diff --git a/tax/regime.go b/tax/regime.go index 2ce7d157..372b5fd9 100644 --- a/tax/regime.go +++ b/tax/regime.go @@ -296,7 +296,14 @@ func (r *Regime) CorrectionDefinitionFor(schema string) *CorrectionDefinition { // Validate enures the region definition is valid, including all // subsequent categories. func (r *Regime) Validate() error { - err := validation.ValidateStruct(r, + return r.ValidateWithContext(context.Background()) +} + +// ValidateWithContext enures the region definition is valid, including all +// subsequent categories, and passes through the context. +func (r *Regime) ValidateWithContext(ctx context.Context) error { + ctx = context.WithValue(ctx, KeyRegime, r) + err := validation.ValidateStructWithContext(ctx, r, validation.Field(&r.Country, validation.Required), validation.Field(&r.Name, validation.Required), validation.Field(&r.Description), @@ -542,6 +549,10 @@ func checkRateValuesOrder(list interface{}) error { // loop through and check order of Since value for i := range values { v := values[i] + if len(v.Zones) > 0 { + // TODO: check zone order also + continue + } if date != nil && date.IsValid() { if v.Since.IsValid() && !v.Since.Before(date.Date) { return errors.New("invalid date order") diff --git a/tax/regimes_test.go b/tax/regimes_test.go new file mode 100644 index 00000000..f59dfba3 --- /dev/null +++ b/tax/regimes_test.go @@ -0,0 +1,16 @@ +package tax_test + +import ( + "testing" + + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" +) + +func TestAllRegimes(t *testing.T) { + for _, r := range tax.AllRegimes() { + t.Run(r.Name.String(), func(t *testing.T) { + assert.NoError(t, r.Validate()) + }) + } +} diff --git a/tax/scenario.go b/tax/scenario.go index 37a385e0..03c37788 100644 --- a/tax/scenario.go +++ b/tax/scenario.go @@ -1,6 +1,8 @@ package tax import ( + "context" + "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/validation" @@ -47,9 +49,9 @@ type ScenarioSummary struct { Meta cbc.Meta } -// Validate checks the scenario set for errors. -func (ss *ScenarioSet) Validate() error { - err := validation.ValidateStruct(ss, +// ValidateWithContext checks the scenario set for errors. +func (ss *ScenarioSet) ValidateWithContext(ctx context.Context) error { + err := validation.ValidateStructWithContext(ctx, ss, validation.Field(&ss.Schema, validation.Required), validation.Field(&ss.List, validation.Required), ) @@ -116,10 +118,11 @@ func (s *Scenario) hasTags(docTags []cbc.Key) bool { } // Validate checks the scenario for errors. -func (s *Scenario) Validate() error { - err := validation.ValidateStruct(s, +func (s *Scenario) ValidateWithContext(ctx context.Context) error { + r := ctx.Value(KeyRegime).(*Regime) + err := validation.ValidateStructWithContext(ctx, s, validation.Field(&s.Types), - validation.Field(&s.Tags), + validation.Field(&s.Tags, validation.Each(InKeyDefs(r.Tags))), validation.Field(&s.Name), validation.Field(&s.Note), validation.Field(&s.Codes), From 5e31e7abace8e37e0f775a693c5ed31569ca7d27 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 5 Dec 2023 11:08:28 +0000 Subject: [PATCH 2/2] Adding validation for Rate extension definitions --- tax/regime.go | 17 ++++++++++------- tax/scenario.go | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/tax/regime.go b/tax/regime.go index 372b5fd9..c4309972 100644 --- a/tax/regime.go +++ b/tax/regime.go @@ -473,9 +473,9 @@ func ValidateStructWithRegime(ctx context.Context, obj interface{}, fields ...*v return ValidateInRegime(ctx, obj) } -// Validate ensures the Category's contents are correct. -func (c *Category) Validate() error { - err := validation.ValidateStruct(c, +// ValidateWithContext ensures the Category's contents are correct. +func (c *Category) ValidateWithContext(ctx context.Context) error { + err := validation.ValidateStructWithContext(ctx, c, validation.Field(&c.Code, validation.Required), validation.Field(&c.Name, validation.Required), validation.Field(&c.Title, validation.Required), @@ -507,17 +507,20 @@ func (s *Source) Validate() error { ) } -// Validate checks that our tax definition is valid. This is only really +// ValidateWithContext checks that our tax definition is valid. This is only really // meant to be used when testing new regional tax definitions. -func (r *Rate) Validate() error { - err := validation.ValidateStruct(r, +func (r *Rate) ValidateWithContext(ctx context.Context) error { + reg := ctx.Value(KeyRegime).(*Regime) + err := validation.ValidateStructWithContext(ctx, r, validation.Field(&r.Key, validation.Required), validation.Field(&r.Name, validation.Required), validation.Field(&r.Values, validation.When(r.Exempt, validation.Nil), validation.By(checkRateValuesOrder), ), - validation.Field(&r.Extensions), + validation.Field(&r.Extensions, + validation.Each(InKeyDefs(reg.Extensions)), + ), validation.Field(&r.Map), validation.Field(&r.Meta), ) diff --git a/tax/scenario.go b/tax/scenario.go index 03c37788..e09f665c 100644 --- a/tax/scenario.go +++ b/tax/scenario.go @@ -117,7 +117,8 @@ func (s *Scenario) hasTags(docTags []cbc.Key) bool { return false } -// Validate checks the scenario for errors. +// ValidateWithContext checks the scenario for errors, using the regime in the context +// to validate the list of tags. func (s *Scenario) ValidateWithContext(ctx context.Context) error { r := ctx.Value(KeyRegime).(*Regime) err := validation.ValidateStructWithContext(ctx, s,