From 95d95afcfb3e4d7e30bd74236def35e4bbcddf54 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Mon, 20 Nov 2023 21:25:49 +0000 Subject: [PATCH 1/8] Defining common invoice tags, simplified invoice validation moved up --- bill/invoice.go | 48 +++++++++++++++-------- regimes/co/co.go | 3 +- regimes/co/invoices.go | 23 ----------- regimes/common/invoices.go | 69 +++++++++++++++++++++++++++++++++ regimes/es/invoices.go | 7 +--- regimes/es/scenarios.go | 36 +---------------- regimes/fr/fr.go | 3 +- regimes/fr/scenarios.go | 12 ------ regimes/gb/gb.go | 1 + regimes/gb/invoice_validator.go | 10 ++--- regimes/it/invoice_validator.go | 14 +++++-- regimes/it/scenarios.go | 18 +-------- regimes/mx/invoice_validator.go | 1 - regimes/mx/mx.go | 1 + regimes/nl/invoice_validator.go | 15 +++---- regimes/nl/nl.go | 1 + regimes/pt/invoice_validator.go | 6 --- regimes/pt/scenarios.go | 11 +----- regimes/us/invoice_validator.go | 1 - regimes/us/us.go | 1 + 20 files changed, 136 insertions(+), 145 deletions(-) create mode 100644 regimes/common/invoices.go diff --git a/bill/invoice.go b/bill/invoice.go index 9f2595f8..fff9fdfa 100644 --- a/bill/invoice.go +++ b/bill/invoice.go @@ -114,39 +114,53 @@ func (inv *Invoice) ValidateWithContext(ctx context.Context) error { ctx = r.WithContext(ctx) err := validation.ValidateStructWithContext(ctx, inv, validation.Field(&inv.UUID), - validation.Field(&inv.Type, validation.Required, isValidInvoiceType), + validation.Field(&inv.Type, + validation.Required, + isValidInvoiceType, + ), validation.Field(&inv.Series), validation.Field(&inv.Code, - validation.When(!internal.IsDraft(ctx), validation.Required), + validation.When( + !internal.IsDraft(ctx), + validation.Required, + ), + ), + validation.Field(&inv.IssueDate, + cal.DateNotZero(), ), - validation.Field(&inv.IssueDate, cal.DateNotZero()), validation.Field(&inv.OperationDate), validation.Field(&inv.ValueDate), - validation.Field(&inv.Currency, validation.Required), + validation.Field(&inv.Currency, + validation.Required, + ), validation.Field(&inv.ExchangeRates), - validation.Field(&inv.Preceding), - validation.Field(&inv.Tax), - - validation.Field(&inv.Supplier, validation.Required), - validation.Field(&inv.Customer), - - validation.Field(&inv.Lines, validation.Required), + validation.Field(&inv.Supplier, + validation.Required, + ), + validation.Field(&inv.Customer, + // Customer is not required for simplified invoices. + validation.When( + !inv.Tax.ContainsTag(common.TagSimplified), + validation.Required, + ), + ), + validation.Field(&inv.Lines, + validation.Required, + ), validation.Field(&inv.Discounts), validation.Field(&inv.Charges), validation.Field(&inv.Outlays), - validation.Field(&inv.Ordering), validation.Field(&inv.Payment), validation.Field(&inv.Delivery), - - validation.Field(&inv.Totals, validation.Required), - + validation.Field(&inv.Totals, + validation.Required, + ), validation.Field(&inv.Notes), + validation.Field(&inv.Complements), validation.Field(&inv.Meta), - - validation.Field(&inv.Complements, validation.Each()), ) if err == nil { err = r.ValidateObject(inv) diff --git a/regimes/co/co.go b/regimes/co/co.go index 979251ad..5f31871e 100644 --- a/regimes/co/co.go +++ b/regimes/co/co.go @@ -8,6 +8,7 @@ import ( "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/org" "github.com/invopop/gobl/pkg/here" + "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -44,7 +45,7 @@ func New() *tax.Regime { `), }, TimeZone: "America/Bogota", - Tags: invoiceTags, + Tags: common.InvoiceTags(), Validator: Validate, Calculator: Calculate, IdentityTypeKeys: taxIdentityTypeDefs, // see tax_identity.go diff --git a/regimes/co/invoices.go b/regimes/co/invoices.go index 5d7ab4a0..3622ff29 100644 --- a/regimes/co/invoices.go +++ b/regimes/co/invoices.go @@ -3,30 +3,12 @@ package co import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/currency" - "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" "github.com/invopop/validation" ) -var invoiceTags = []*tax.KeyDefinition{ - // Simplified invoices when a client ID is not available. For conversion to local formats, - // the correct client ID data will need to be provided automatically. - { - Key: common.TagSimplified, - Name: i18n.String{ - i18n.EN: "Simplified Invoice", - i18n.ES: "Factura Simplificada", - }, - Desc: i18n.String{ - i18n.EN: "Used for B2C transactions when the client details are not available, check with local authorities for limits.", - i18n.ES: "Usado para transacciones B2C cuando los detalles del cliente no están disponibles, consulte con las autoridades locales para los límites.", - }, - }, -} - type invoiceValidator struct { inv *bill.Invoice } @@ -48,15 +30,10 @@ func (v *invoiceValidator) validate() error { ), ), validation.Field(&inv.Supplier, - validation.Required, validation.By(v.validParty), validation.By(v.validSupplier), ), validation.Field(&inv.Customer, - validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), - validation.Required, - ), validation.By(v.validParty), ), validation.Field(&inv.Preceding, diff --git a/regimes/common/invoices.go b/regimes/common/invoices.go new file mode 100644 index 00000000..ff8376e4 --- /dev/null +++ b/regimes/common/invoices.go @@ -0,0 +1,69 @@ +package common + +import ( + "github.com/invopop/gobl/i18n" + "github.com/invopop/gobl/tax" +) + +var invoiceTags = []*tax.KeyDefinition{ + // Simplified invoices are issued when the complete fiscal details of + // a customer are not available. + { + Key: TagSimplified, + Name: i18n.String{ + i18n.EN: "Simplified Invoice", + i18n.ES: "Factura Simplificada", + i18n.IT: "Fattura Semplificata", + }, + Desc: i18n.String{ + i18n.EN: "Used for B2C transactions when the client details are not available, check with local authorities for limits.", + i18n.ES: "Usado para transacciones B2C cuando los detalles del cliente no están disponibles, consulte con las autoridades locales para los límites.", + i18n.IT: "Utilizzato per le transazioni B2C quando i dettagli del cliente non sono disponibili, controllare con le autorità locali per i limiti.", + }, + }, + + // Reverse Charge mechanism is used when the supplier is not + // required to charge VAT on the invoice and the customer is + // responsible for paying the VAT to the tax authorities. + { + Key: TagReverseCharge, + Name: i18n.String{ + i18n.EN: "Reverse Charge", + i18n.ES: "Inversión del Sujeto Pasivo", + i18n.IT: "Inversione del soggetto passivo", + }, + }, + + // Self-billed invoices are issued by the customer instead of the + // supplier. This is usually done when the customer is a large + // company and the supplier is a small company. + { + Key: TagSelfBilled, + Name: i18n.String{ + i18n.EN: "Self-billed", + i18n.ES: "Facturación por el destinatario", + i18n.IT: "Autofattura", + }, + }, + + // Customer rates (mainly for digital goods inside EU) + { + Key: TagCustomerRates, + Name: i18n.String{ + i18n.EN: "Customer rates", + i18n.ES: "Tarifas aplicables al destinatario", + }, + }, +} + +// InvoiceTags returns a list of common invoice tag key +// definitions. +func InvoiceTags() []*tax.KeyDefinition { + return invoiceTags +} + +// InvoiceTagsWith appends the list of provided key definitions +// to the base list of tags and returns a new array. +func InvoiceTagsWith(tags []*tax.KeyDefinition) []*tax.KeyDefinition { + return append(InvoiceTags(), tags...) +} diff --git a/regimes/es/invoices.go b/regimes/es/invoices.go index 4faf957f..94ab9cf5 100644 --- a/regimes/es/invoices.go +++ b/regimes/es/invoices.go @@ -5,7 +5,6 @@ import ( "github.com/invopop/gobl/currency" "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" "github.com/invopop/validation" ) @@ -40,11 +39,7 @@ func (v *invoiceValidator) validate() error { validation.By(v.supplier), ), validation.Field(&inv.Customer, - validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), - validation.Required, - validation.By(v.commercialCustomer), - ), + validation.By(v.commercialCustomer), ), ) } diff --git a/regimes/es/scenarios.go b/regimes/es/scenarios.go index 2dc1a30a..b25d1312 100644 --- a/regimes/es/scenarios.go +++ b/regimes/es/scenarios.go @@ -21,39 +21,7 @@ const ( TagCashBasis cbc.Key = "cash-basis" ) -var invoiceTags = []*tax.KeyDefinition{ - // Simplified Invoice - { - Key: common.TagSimplified, - Name: i18n.String{ - i18n.EN: "Simplified Invoice", - i18n.ES: "Factura Simplificada", - }, - }, - // Customer rates (mainly for digital goods inside EU) - { - Key: common.TagCustomerRates, - Name: i18n.String{ - i18n.EN: "Customer rates", - i18n.ES: "Tarifas aplicables al destinatario", - }, - }, - // Reverse Charge Mechanism - { - Key: common.TagReverseCharge, - Name: i18n.String{ - i18n.EN: "Reverse Charge", - i18n.ES: "Inversión del sujeto pasivo", - }, - }, - // Customer issued invoices - { - Key: common.TagSelfBilled, - Name: i18n.String{ - i18n.EN: "Customer issued invoice", - i18n.ES: "Facturación por el destinatario", - }, - }, +var invoiceTags = common.InvoiceTagsWith([]*tax.KeyDefinition{ // Copy of the original document { Key: TagCopy, @@ -119,7 +87,7 @@ var invoiceTags = []*tax.KeyDefinition{ i18n.ES: "Régimen especial del criterio de caja", }, }, -} +}) var invoiceScenarios = &tax.ScenarioSet{ Schema: bill.ShortSchemaInvoice, diff --git a/regimes/fr/fr.go b/regimes/fr/fr.go index 40397b64..79d5c756 100644 --- a/regimes/fr/fr.go +++ b/regimes/fr/fr.go @@ -7,6 +7,7 @@ import ( "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/pkg/here" + "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -39,7 +40,7 @@ func New() *tax.Regime { `), }, TimeZone: "Europe/Paris", - Tags: invoiceTags, + Tags: common.InvoiceTags(), Scenarios: []*tax.ScenarioSet{ invoiceScenarios, }, diff --git a/regimes/fr/scenarios.go b/regimes/fr/scenarios.go index 53d9b67c..cf83b282 100644 --- a/regimes/fr/scenarios.go +++ b/regimes/fr/scenarios.go @@ -3,22 +3,10 @@ package fr import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) -var invoiceTags = []*tax.KeyDefinition{ - // Reverse Charge Mechanism - { - Key: common.TagReverseCharge, - Name: i18n.String{ - i18n.EN: "Reverse Charge", - i18n.ES: "Inversión del sujeto pasivo", - }, - }, -} - var invoiceScenarios = &tax.ScenarioSet{ Schema: bill.ShortSchemaInvoice, List: []*tax.Scenario{ diff --git a/regimes/gb/gb.go b/regimes/gb/gb.go index b9ed943e..f69138e6 100644 --- a/regimes/gb/gb.go +++ b/regimes/gb/gb.go @@ -30,6 +30,7 @@ func New() *tax.Regime { TimeZone: "Europe/London", Validator: Validate, Calculator: Calculate, + Tags: common.InvoiceTags(), Categories: taxCategories, } } diff --git a/regimes/gb/invoice_validator.go b/regimes/gb/invoice_validator.go index 7cab7918..eedf64e0 100644 --- a/regimes/gb/invoice_validator.go +++ b/regimes/gb/invoice_validator.go @@ -3,7 +3,6 @@ package gb import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/currency" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/validation" ) @@ -21,11 +20,8 @@ func validateInvoice(inv *bill.Invoice) error { func (v *invoiceValidator) validate() error { inv := v.inv return validation.ValidateStruct(inv, - validation.Field(&inv.Currency, validation.In(currency.GBP)), - validation.Field(&inv.Supplier, validation.Required), - validation.Field(&inv.Customer, validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), - validation.Required, - )), + validation.Field(&inv.Currency, + validation.In(currency.GBP), + ), ) } diff --git a/regimes/it/invoice_validator.go b/regimes/it/invoice_validator.go index ba83f6d8..32869410 100644 --- a/regimes/it/invoice_validator.go +++ b/regimes/it/invoice_validator.go @@ -29,9 +29,15 @@ func (v *invoiceValidator) validate() error { inv := v.inv return validation.ValidateStruct(inv, - validation.Field(&inv.Currency, validation.In(currency.EUR)), - validation.Field(&inv.Supplier, validation.By(v.supplier)), - validation.Field(&inv.Customer, validation.By(v.customer)), + validation.Field(&inv.Currency, + validation.In(currency.EUR), + ), + validation.Field(&inv.Supplier, + validation.By(v.supplier), + ), + validation.Field(&inv.Customer, + validation.By(v.customer), + ), ) } @@ -59,7 +65,7 @@ func (v *invoiceValidator) supplier(value interface{}) error { func (v *invoiceValidator) customer(value interface{}) error { customer, _ := value.(*org.Party) if customer == nil { - return errors.New("missing customer details") + return nil } // Customers must have a tax ID (PartitaIVA) if they are Italian legal entities diff --git a/regimes/it/scenarios.go b/regimes/it/scenarios.go index 1c397219..81ed626d 100644 --- a/regimes/it/scenarios.go +++ b/regimes/it/scenarios.go @@ -28,7 +28,7 @@ const ( // This is only a partial list of all the potential tags that // could be available for use in Italy. Given the complexity // involved, we've focussed here on the most useful. -var invoiceTags = []*tax.KeyDefinition{ +var invoiceTags = common.InvoiceTagsWith([]*tax.KeyDefinition{ // *** Document Type Tags *** { Key: TagFreelance, @@ -37,20 +37,6 @@ var invoiceTags = []*tax.KeyDefinition{ i18n.IT: "Parcella", }, }, - { - Key: common.TagReverseCharge, - Name: i18n.String{ - i18n.EN: "Reverse Charge", - i18n.IT: "Inversione del soggetto passivo", - }, - }, - { - Key: common.TagSelfBilled, - Name: i18n.String{ - i18n.EN: "Self-billed", - i18n.IT: "Autofattura", - }, - }, { Key: TagCeilingExceeded, Name: i18n.String{ @@ -128,7 +114,7 @@ var invoiceTags = []*tax.KeyDefinition{ i18n.IT: "Beni ammortizzabili", }, }, -} +}) var scenarios = []*tax.ScenarioSet{ invoiceScenarios, diff --git a/regimes/mx/invoice_validator.go b/regimes/mx/invoice_validator.go index 00500726..16c10169 100644 --- a/regimes/mx/invoice_validator.go +++ b/regimes/mx/invoice_validator.go @@ -33,7 +33,6 @@ func (v *invoiceValidator) validate() error { validation.Skip, ), validation.Field(&inv.Customer, - validation.Required, validation.By(v.validCustomer), validation.Skip, ), diff --git a/regimes/mx/mx.go b/regimes/mx/mx.go index 0ab8cb86..12c9a3bc 100644 --- a/regimes/mx/mx.go +++ b/regimes/mx/mx.go @@ -48,6 +48,7 @@ func New() *tax.Regime { TimeZone: "America/Mexico_City", Validator: Validate, Calculator: Calculate, + Tags: common.InvoiceTags(), PaymentMeansKeys: paymentMeansKeyDefinitions, // pay.go Extensions: extensionKeys, // extensions.go Scenarios: scenarios, // scenarios.go diff --git a/regimes/nl/invoice_validator.go b/regimes/nl/invoice_validator.go index 7c22b713..64c90d03 100644 --- a/regimes/nl/invoice_validator.go +++ b/regimes/nl/invoice_validator.go @@ -4,7 +4,6 @@ import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/currency" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" "github.com/invopop/validation" ) @@ -23,13 +22,15 @@ func validateInvoice(inv *bill.Invoice) error { func (v *invoiceValidator) validate() error { inv := v.inv return validation.ValidateStruct(inv, - validation.Field(&inv.Currency, validation.In(currency.EUR)), - validation.Field(&inv.Supplier, validation.Required, validation.By(v.supplier)), - validation.Field(&inv.Customer, validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), - validation.Required, + validation.Field(&inv.Currency, + validation.In(currency.EUR), + ), + validation.Field(&inv.Supplier, + validation.By(v.supplier), + ), + validation.Field(&inv.Customer, validation.By(v.customer), - )), + ), ) } diff --git a/regimes/nl/nl.go b/regimes/nl/nl.go index bcaf4509..eb3d89c6 100644 --- a/regimes/nl/nl.go +++ b/regimes/nl/nl.go @@ -26,6 +26,7 @@ func New() *tax.Regime { TimeZone: "Europe/Amsterdam", Validator: Validate, Calculator: Calculate, + Tags: common.InvoiceTags(), Categories: []*tax.Category{ // // VAT diff --git a/regimes/pt/invoice_validator.go b/regimes/pt/invoice_validator.go index df5288a2..0f66398f 100644 --- a/regimes/pt/invoice_validator.go +++ b/regimes/pt/invoice_validator.go @@ -5,7 +5,6 @@ import ( "github.com/invopop/gobl/currency" "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/validation" ) @@ -22,11 +21,6 @@ func (v *invoiceValidator) validate() error { inv := v.inv return validation.ValidateStruct(inv, validation.Field(&inv.Currency, validation.In(currency.EUR)), - validation.Field(&inv.Supplier, validation.Required), - validation.Field(&inv.Customer, validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), - validation.Required, - )), validation.Field(&inv.Lines, validation.Each( validation.By(v.validLine), diff --git a/regimes/pt/scenarios.go b/regimes/pt/scenarios.go index d929a33c..19f9957c 100644 --- a/regimes/pt/scenarios.go +++ b/regimes/pt/scenarios.go @@ -17,7 +17,7 @@ var scenarios = []*tax.ScenarioSet{ invoiceScenarios, } -var invoiceTags = []*tax.KeyDefinition{ +var invoiceTags = common.InvoiceTagsWith([]*tax.KeyDefinition{ { Key: TagInvoiceReceipt, Name: i18n.String{ @@ -25,14 +25,7 @@ var invoiceTags = []*tax.KeyDefinition{ i18n.PT: "Fatura-recibo", }, }, - { - Key: common.TagSimplified, - Name: i18n.String{ - i18n.EN: "Simplified invoice", - i18n.PT: "Fatura simplificada", - }, - }, -} +}) var invoiceScenarios = &tax.ScenarioSet{ Schema: bill.ShortSchemaInvoice, diff --git a/regimes/us/invoice_validator.go b/regimes/us/invoice_validator.go index 53f115d3..751a199b 100644 --- a/regimes/us/invoice_validator.go +++ b/regimes/us/invoice_validator.go @@ -22,6 +22,5 @@ func (v *invoiceValidator) validate() error { return validation.ValidateStruct(inv, validation.Field(&inv.Currency, validation.In(currency.USD)), validation.Field(&inv.Supplier, validation.Required), - validation.Field(&inv.Customer), ) } diff --git a/regimes/us/us.go b/regimes/us/us.go index 61859dc5..0fbaa76f 100644 --- a/regimes/us/us.go +++ b/regimes/us/us.go @@ -30,6 +30,7 @@ func New() *tax.Regime { }, TimeZone: "America/Chicago", // Around the middle Validator: Validate, + Tags: common.InvoiceTags(), Categories: []*tax.Category{ // // Sales Tax From 5d7dce0fd42dd8e57ff463a479ebcd6b074bc6d8 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 21 Nov 2023 14:29:01 +0000 Subject: [PATCH 2/8] Adding 'Identities' to invoice Ordering and Delivery --- bill/delivery.go | 4 ++++ bill/ordering.go | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/bill/delivery.go b/bill/delivery.go index 2d5ad11d..730dd953 100644 --- a/bill/delivery.go +++ b/bill/delivery.go @@ -12,6 +12,9 @@ import ( type Delivery struct { // The party who will receive delivery of the goods defined in the invoice and is not responsible for taxes. Receiver *org.Party `json:"receiver,omitempty" jsonschema:"title=Receiver"` + // Identities is used to define specific codes or IDs that may be used to + // identify the delivery. + Identities []*org.Identity `json:"identities,omitempty" jsonschema:"title=Identities"` // When the goods should be expected. Date *cal.Date `json:"date,omitempty" jsonschema:"title=Date"` // Period of time in which to expect delivery if a specific date is not available. @@ -24,6 +27,7 @@ type Delivery struct { func (d *Delivery) Validate() error { return validation.ValidateStruct(d, validation.Field(&d.Receiver), + validation.Field(&d.Identities), validation.Field(&d.Date), validation.Field(&d.Period), validation.Field(&d.Meta), diff --git a/bill/ordering.go b/bill/ordering.go index 2651dc9e..42817d30 100644 --- a/bill/ordering.go +++ b/bill/ordering.go @@ -13,6 +13,10 @@ import ( type Ordering struct { // Identifier assigned by the customer or buyer for internal routing purposes. Code string `json:"code,omitempty" jsonschema:"title=Code"` + // Any additional Codes, IDs, SKUs, or other regional or custom + // identifiers that may be used to identify the order. + Identities []*org.Identity `json:"identities,omitempty" jsonschema:"title=Identities"` + // Period of time that the invoice document refers to often used in addition to the details // provided in the individual line items. Period *cal.Period `json:"period,omitempty" jsonschema:"title=Period"` @@ -54,6 +58,7 @@ type DocumentReference struct { // Validate the ordering details. func (o *Ordering) Validate() error { return validation.ValidateStruct(o, + validation.Field(&o.Identities), validation.Field(&o.Project), validation.Field(&o.Contract), validation.Field(&o.Purchase), From f10ffeb5a094d9e956f5497ebcc160e611ed45f2 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 21 Nov 2023 17:58:46 +0000 Subject: [PATCH 3/8] Identity 'key' property is back --- org/identity.go | 32 ++++++++------------------------ org/identity_test.go | 11 ----------- regimes/mx/item.go | 10 +++++++++- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/org/identity.go b/org/identity.go index d91ab074..0adf90a0 100644 --- a/org/identity.go +++ b/org/identity.go @@ -2,7 +2,6 @@ package org import ( "context" - "encoding/json" "fmt" "github.com/invopop/gobl/cbc" @@ -17,6 +16,8 @@ type Identity struct { UUID *uuid.UUID `json:"uuid,omitempty" jsonschema:"title=UUID"` // Optional label useful for non-standard identities to give a bit more context. Label string `json:"label,omitempty" jsonschema:"title=Label"` + // Uniquely classify this identity using a key instead of a code. + Key cbc.Key `json:"key,omitempty" jsonschema:"title=Key"` // The type of Code being represented and usually specific for // a particular context, country, or tax regime, and cannot be used // alongside the key. @@ -25,11 +26,6 @@ type Identity struct { Code cbc.Code `json:"code" jsonschema:"title=Code"` // Description adds details about what the code could mean or imply Description string `json:"description,omitempty" jsonschema:"title=Description"` - - // Key was previously available, but has now been migrated to extensions. - // This should not appear in schemas. - // Deprecated: Since 2023-08-25, use extensions (ext) instead. - Key cbc.Key `json:"-"` } // Validate ensures the identity looks valid. @@ -41,30 +37,18 @@ func (i *Identity) Validate() error { func (i *Identity) ValidateWithContext(ctx context.Context) error { return tax.ValidateStructWithRegime(ctx, i, validation.Field(&i.Label), - validation.Field(&i.Type), + validation.Field(&i.Key), + validation.Field(&i.Type, + validation.When(i.Key != "", + validation.Empty, + ), + ), validation.Field(&i.Code, validation.Required, ), ) } -// UnmarshalJSON overrides the default to help extract the key value which is no -// longer used. -func (i *Identity) UnmarshalJSON(data []byte) error { - type Alias Identity - a := &struct { - *Alias - Key cbc.Key `json:"key,omitempty"` - }{ - Alias: (*Alias)(i), - } - if err := json.Unmarshal(data, &a); err != nil { - return err - } - i.Key = a.Key - return nil -} - // HasIdentityType provides a validation rule that will determine if at least one // of the identities defined includes one with the defined type. func HasIdentityType(typ cbc.Code) validation.Rule { diff --git a/org/identity_test.go b/org/identity_test.go index 68b75684..110ebced 100644 --- a/org/identity_test.go +++ b/org/identity_test.go @@ -1,13 +1,11 @@ package org_test import ( - "encoding/json" "testing" "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/org" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestAddIdentity(t *testing.T) { @@ -29,12 +27,3 @@ func TestAddIdentity(t *testing.T) { assert.Len(t, st.Identities, 1) assert.Equal(t, "BARDOM", st.Identities[0].Code.String()) } - -func TestIdentityKeyExtraction(t *testing.T) { - data := `{"key":"example-key","code":"BAR"}` - i := new(org.Identity) - err := json.Unmarshal([]byte(data), i) - require.NoError(t, err) - assert.Equal(t, "example-key", i.Key.String()) - assert.Equal(t, "BAR", i.Code.String()) -} diff --git a/regimes/mx/item.go b/regimes/mx/item.go index 53a5a51e..324ec7c2 100644 --- a/regimes/mx/item.go +++ b/regimes/mx/item.go @@ -42,12 +42,20 @@ func validItemExtensions(value interface{}) error { return nil } +// extension keys that have been migrated from identities to +// extensions. +var migratedExtensionKeys = []cbc.Key{ + ExtKeyCFDIProdServ, + ExtKeyCFDIFiscalRegime, + ExtKeyCFDIUse, +} + func normalizeItem(item *org.Item) error { // 2023-08-25: Migrate identities to extensions // Pending removal after migrations completed. idents := make([]*org.Identity, 0) for _, v := range item.Identities { - if v.Key != "" { + if v.Key.In(migratedExtensionKeys...) { if item.Ext == nil { item.Ext = make(cbc.CodeMap) } From f0c84d5461172963a766d603acbe84ea9b0b9cfc Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Wed, 22 Nov 2023 18:11:49 +0000 Subject: [PATCH 4/8] Refactoring common tax categories with standard scenario set --- bill/invoice.go | 7 +- bill/invoice_correct_test.go | 5 +- bill/invoice_test.go | 21 +- build/schemas/regimes/mx/food-vouchers.json | 105 --------- .../regimes/mx/fuel-account-balance.json | 167 -------------- examples/simple/main.go | 3 +- regimes/co/tax_categories.go | 9 +- regimes/common/invoice_scenarios.go | 28 +++ .../common/{invoices.go => invoice_tags.go} | 8 +- regimes/es/scenarios.go | 12 +- regimes/es/scenarios_test.go | 5 +- regimes/es/tax_categories.go | 25 +- regimes/fr/scenarios.go | 5 +- regimes/fr/tax_categories.go | 13 +- regimes/gb/gb.go | 3 + regimes/gb/tax_categories.go | 9 +- regimes/it/categories.go | 15 +- regimes/it/invoice_validator_test.go | 3 +- regimes/it/scenarios.go | 34 +-- regimes/mx/tax_categories.go | 9 +- regimes/nl/nl.go | 13 +- regimes/pt/migrations.go | 3 +- regimes/pt/migrations_test.go | 6 +- regimes/pt/scenarios.go | 12 +- regimes/pt/tax_categories.go | 11 +- tax/constants.go | 30 +++ tax/set_test.go | 3 +- tax/totals_calculator_test.go | 215 +++++++++--------- 28 files changed, 282 insertions(+), 497 deletions(-) delete mode 100644 build/schemas/regimes/mx/food-vouchers.json delete mode 100644 build/schemas/regimes/mx/fuel-account-balance.json create mode 100644 regimes/common/invoice_scenarios.go rename regimes/common/{invoices.go => invoice_tags.go} (94%) create mode 100644 tax/constants.go diff --git a/bill/invoice.go b/bill/invoice.go index fff9fdfa..eb5b919e 100644 --- a/bill/invoice.go +++ b/bill/invoice.go @@ -10,7 +10,6 @@ import ( "github.com/invopop/gobl/currency" "github.com/invopop/gobl/internal" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/schema" "github.com/invopop/gobl/tax" "github.com/invopop/gobl/uuid" @@ -142,7 +141,7 @@ func (inv *Invoice) ValidateWithContext(ctx context.Context) error { validation.Field(&inv.Customer, // Customer is not required for simplified invoices. validation.When( - !inv.Tax.ContainsTag(common.TagSimplified), + !inv.Tax.ContainsTag(tax.TagSimplified), validation.Required, ), ), @@ -449,7 +448,7 @@ func (inv *Invoice) calculate(r *tax.Regime, tID *tax.Identity) error { } // Finally calculate the total with *all* the taxes. - if inv.Tax != nil && inv.Tax.ContainsTag(common.TagReverseCharge) { + if inv.Tax != nil && inv.Tax.ContainsTag(tax.TagReverseCharge) { t.Tax = zero } else { t.Tax = t.Taxes.PreciseSum() @@ -501,7 +500,7 @@ func calculateComplements(comps []*schema.Object) error { func (inv *Invoice) determineTaxIdentity() *tax.Identity { if inv.Tax != nil { - if inv.Tax.ContainsTag(common.TagCustomerRates) { + if inv.Tax.ContainsTag(tax.TagCustomerRates) { if inv.Customer == nil { return nil } diff --git a/bill/invoice_correct_test.go b/bill/invoice_correct_test.go index 79334034..3203de5f 100644 --- a/bill/invoice_correct_test.go +++ b/bill/invoice_correct_test.go @@ -12,7 +12,6 @@ import ( "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" "github.com/invopop/gobl/regimes/co" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/regimes/es" "github.com/invopop/gobl/tax" "github.com/invopop/jsonschema" @@ -194,7 +193,7 @@ func testInvoiceESForCorrection(t *testing.T) *bill.Invoice { Series: "TEST", Code: "123", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -283,7 +282,7 @@ func testInvoiceCOForCorrection(t *testing.T) *bill.Invoice { Series: "TEST", Code: "123", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ diff --git a/bill/invoice_test.go b/bill/invoice_test.go index e0872e3d..2ff7c932 100644 --- a/bill/invoice_test.go +++ b/bill/invoice_test.go @@ -15,7 +15,6 @@ import ( "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" "github.com/invopop/gobl/pay" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,7 +31,7 @@ func TestInvoiceRegimeCurrency(t *testing.T) { Taxes: tax.Set{ { Category: "VAT", - Rate: common.TaxRateStandard, + Rate: tax.RateStandard, }, }, }, @@ -164,7 +163,7 @@ func TestRemoveIncludedTax2(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -228,7 +227,7 @@ func TestRemoveIncludedTax3(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -321,7 +320,7 @@ func TestRemoveIncludedTax4(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -419,7 +418,7 @@ func TestRemoveIncludedTaxQuantity(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -493,7 +492,7 @@ func TestRemoveIncludedTaxDeep(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -574,7 +573,7 @@ func TestRemoveIncludedTaxDeep2(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -686,7 +685,7 @@ func TestCalculate(t *testing.T) { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ @@ -760,7 +759,7 @@ func TestValidation(t *testing.T) { Currency: currency.EUR, IssueDate: cal.MakeDate(2022, 6, 13), Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ Name: "Test Supplier", @@ -806,7 +805,7 @@ func baseInvoice(t *testing.T, lines ...*bill.Line) *bill.Invoice { i := &bill.Invoice{ Code: "123TEST", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ TaxID: &tax.Identity{ diff --git a/build/schemas/regimes/mx/food-vouchers.json b/build/schemas/regimes/mx/food-vouchers.json deleted file mode 100644 index 65bee0b1..00000000 --- a/build/schemas/regimes/mx/food-vouchers.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://gobl.org/draft-0/regimes/mx/food-vouchers", - "$ref": "#/$defs/FoodVouchers", - "$defs": { - "FoodVouchers": { - "properties": { - "employer_registration": { - "type": "string", - "title": "Employer Registration", - "description": "Customer's employer registration number (maps to `registroPatronal`)." - }, - "account_number": { - "type": "string", - "title": "Account Number", - "description": "Customer's account number (maps to `numeroDeCuenta`)." - }, - "total": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Total", - "description": "Sum of all line amounts (calculated, maps to `total`).", - "calculated": true - }, - "lines": { - "items": { - "$ref": "#/$defs/FoodVouchersLine" - }, - "type": "array", - "title": "Lines", - "description": "List of food vouchers issued to the customer's employees (maps to `Conceptos`)." - } - }, - "type": "object", - "required": [ - "account_number", - "total", - "lines" - ], - "description": "FoodVouchers carries the data to produce a CFDI's \"Complemento de Vales de Despensa\" (version 1.0) providing detailed information about food vouchers issued by an e-wallet supplier to its customer's employees." - }, - "FoodVouchersEmployee": { - "properties": { - "tax_code": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Tax Identity Code", - "description": "Employee's tax identity code (maps to `rfc`)." - }, - "curp": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's CURP", - "description": "Employee's CURP (\"Clave Única de Registro de Población\", maps to `curp`)." - }, - "name": { - "type": "string", - "title": "Employee's Name", - "description": "Employee's name (maps to `nombre`)." - }, - "social_security": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Employee's Social Security Number", - "description": "Employee's Social Security Number (maps to `numSeguridadSocial`)." - } - }, - "type": "object", - "required": [ - "tax_code", - "curp", - "name" - ], - "description": "FoodVouchersEmployee represents an employee that received a food voucher." - }, - "FoodVouchersLine": { - "properties": { - "e_wallet_id": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier", - "description": "Identifier of the e-wallet that received the food voucher (maps to `Identificador`)." - }, - "issue_date_time": { - "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Issue Date and Time", - "description": "Date and time of the food voucher's issue (maps to `Fecha`)." - }, - "employee": { - "$ref": "#/$defs/FoodVouchersEmployee", - "title": "Employee", - "description": "Employee that received the food voucher." - }, - "amount": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Amount", - "description": "Amount of the food voucher (maps to `importe`)." - } - }, - "type": "object", - "required": [ - "e_wallet_id", - "issue_date_time", - "amount" - ], - "description": "FoodVouchersLine represents a single food voucher issued to the e-wallet of one of the customer's employees." - } - }, - "$comment": "Generated with GOBL v0.61.0" -} \ No newline at end of file diff --git a/build/schemas/regimes/mx/fuel-account-balance.json b/build/schemas/regimes/mx/fuel-account-balance.json deleted file mode 100644 index b2702080..00000000 --- a/build/schemas/regimes/mx/fuel-account-balance.json +++ /dev/null @@ -1,167 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://gobl.org/draft-0/regimes/mx/fuel-account-balance", - "$ref": "#/$defs/FuelAccountBalance", - "$defs": { - "FuelAccountBalance": { - "properties": { - "account_number": { - "type": "string", - "title": "Account Number", - "description": "Customer's account number (maps to `NumeroDeCuenta`)." - }, - "subtotal": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Subtotal", - "description": "Sum of all line totals (i.e. taxes not included) (calculated, maps to `SubTotal`).", - "calculated": true - }, - "total": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Total", - "description": "Grand total after taxes have been applied (calculated, maps to `Total`).", - "calculated": true - }, - "lines": { - "items": { - "$ref": "#/$defs/FuelAccountLine" - }, - "type": "array", - "title": "Lines", - "description": "List of fuel purchases made with the customer's e-wallets (maps to `Conceptos`)." - } - }, - "type": "object", - "required": [ - "account_number", - "subtotal", - "total", - "lines" - ], - "description": "FuelAccountBalance carries the data to produce a CFDI's \"Complemento de Estado de Cuenta de Combustibles para Monederos Electrónicos\" (version 1.2 revision B) providing detailed information about fuel purchases made with electronic wallets." - }, - "FuelAccountItem": { - "properties": { - "type": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Type", - "description": "Type of fuel (one of `c_ClaveTipoCombustible` codes, maps to `TipoCombustible`)." - }, - "unit": { - "$ref": "https://gobl.org/draft-0/org/unit", - "title": "Unit", - "description": "Reference unit of measure used in the price and the quantity (maps to `Unidad`)." - }, - "name": { - "type": "string", - "title": "Name", - "description": "Name of the fuel (maps to `NombreCombustible`)." - }, - "price": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Price", - "description": "Base price of a single unit of the fuel without taxes (maps to `ValorUnitario`)." - } - }, - "type": "object", - "required": [ - "type", - "name", - "price" - ], - "description": "FuelAccountItem provides the details of a fuel purchase." - }, - "FuelAccountLine": { - "properties": { - "e_wallet_id": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "E-wallet Identifier", - "description": "Identifier of the e-wallet used to make the purchase (maps to `Identificador`)." - }, - "purchase_date_time": { - "$ref": "https://gobl.org/draft-0/cal/date-time", - "title": "Purchase Date and Time", - "description": "Date and time of the purchase (maps to `Fecha`)." - }, - "vendor_tax_code": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Vendor's Tax Identity Code", - "description": "Tax Identity Code of the fuel's vendor (maps to `Rfc`)" - }, - "service_station_code": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Service Station Code", - "description": "Code of the service station where the purchase was made (maps to `ClaveEstacion`)." - }, - "quantity": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Quantity", - "description": "Amount of fuel units purchased (maps to `Cantidad`)" - }, - "item": { - "$ref": "#/$defs/FuelAccountItem", - "title": "Item", - "description": "Details of the fuel purchased." - }, - "purchase_code": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Purchase Code", - "description": "Identifier of the purchase (maps to `FolioOperacion`)." - }, - "total": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Total", - "description": "Result of quantity multiplied by the unit price (maps to `Importe`)." - }, - "taxes": { - "items": { - "$ref": "#/$defs/FuelAccountTax" - }, - "type": "array", - "title": "Taxes", - "description": "Map of taxes applied to the purchase (maps to `Traslados`)." - } - }, - "type": "object", - "required": [ - "e_wallet_id", - "purchase_date_time", - "vendor_tax_code", - "service_station_code", - "quantity", - "item", - "purchase_code", - "total", - "taxes" - ], - "description": "FuelAccountLine represents a single fuel purchase made with an e-wallet issued by the invoice's supplier." - }, - "FuelAccountTax": { - "properties": { - "code": { - "$ref": "https://gobl.org/draft-0/cbc/code", - "title": "Code", - "description": "Code that identifies the tax (\"IVA\" or \"IEPS\", maps to `Impuesto`)" - }, - "rate": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Rate", - "description": "Rate applicable to either the line total (tasa) or the line quantity (cuota) (maps to `TasaOCuota`)." - }, - "amount": { - "$ref": "https://gobl.org/draft-0/num/amount", - "title": "Amount", - "description": "Total amount of the tax once the rate has been applied (maps to `Importe`)." - } - }, - "type": "object", - "required": [ - "code", - "rate", - "amount" - ], - "description": "FuelAccountTax represents a single tax applied to a fuel purchase." - } - }, - "$comment": "Generated with GOBL v0.61.0" -} \ No newline at end of file diff --git a/examples/simple/main.go b/examples/simple/main.go index 43d7b8a4..2f04aac2 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -9,7 +9,6 @@ import ( "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -59,7 +58,7 @@ func main() { }, Taxes: []*tax.Combo{ { - Category: common.TaxCategoryST, + Category: tax.CategoryST, Percent: num.NewPercentage(85, 3), }, }, diff --git a/regimes/co/tax_categories.go b/regimes/co/tax_categories.go index 65fe0e64..c3e4df86 100644 --- a/regimes/co/tax_categories.go +++ b/regimes/co/tax_categories.go @@ -5,7 +5,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -24,7 +23,7 @@ var taxCategories = []*tax.Category{ // VAT // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", i18n.ES: "IVA", @@ -36,7 +35,7 @@ var taxCategories = []*tax.Category{ Retained: false, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.ES: "Cero", @@ -48,7 +47,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.ES: "Estándar", @@ -65,7 +64,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.ES: "Reducido", diff --git a/regimes/common/invoice_scenarios.go b/regimes/common/invoice_scenarios.go new file mode 100644 index 00000000..8529996d --- /dev/null +++ b/regimes/common/invoice_scenarios.go @@ -0,0 +1,28 @@ +package common + +import ( + "github.com/invopop/gobl/bill" + "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/tax" +) + +var invoiceScenarios = &tax.ScenarioSet{ + Schema: bill.ShortSchemaInvoice, + List: []*tax.Scenario{ + // Reverse Charges + { + Tags: []cbc.Key{tax.TagReverseCharge}, + Note: &cbc.Note{ + Key: cbc.NoteKeyLegal, + Src: tax.TagReverseCharge, + Text: "Reverse charge: Customer to account for VAT to the relevant tax authority.", + }, + }, + }, +} + +// InvoiceScenarios provides a standard set of scenarios to either be extended +// or overridden by the regime. +func InvoiceScenarios() *tax.ScenarioSet { + return invoiceScenarios +} diff --git a/regimes/common/invoices.go b/regimes/common/invoice_tags.go similarity index 94% rename from regimes/common/invoices.go rename to regimes/common/invoice_tags.go index ff8376e4..8748ba86 100644 --- a/regimes/common/invoices.go +++ b/regimes/common/invoice_tags.go @@ -9,7 +9,7 @@ var invoiceTags = []*tax.KeyDefinition{ // Simplified invoices are issued when the complete fiscal details of // a customer are not available. { - Key: TagSimplified, + Key: tax.TagSimplified, Name: i18n.String{ i18n.EN: "Simplified Invoice", i18n.ES: "Factura Simplificada", @@ -26,7 +26,7 @@ var invoiceTags = []*tax.KeyDefinition{ // required to charge VAT on the invoice and the customer is // responsible for paying the VAT to the tax authorities. { - Key: TagReverseCharge, + Key: tax.TagReverseCharge, Name: i18n.String{ i18n.EN: "Reverse Charge", i18n.ES: "Inversión del Sujeto Pasivo", @@ -38,7 +38,7 @@ var invoiceTags = []*tax.KeyDefinition{ // supplier. This is usually done when the customer is a large // company and the supplier is a small company. { - Key: TagSelfBilled, + Key: tax.TagSelfBilled, Name: i18n.String{ i18n.EN: "Self-billed", i18n.ES: "Facturación por el destinatario", @@ -48,7 +48,7 @@ var invoiceTags = []*tax.KeyDefinition{ // Customer rates (mainly for digital goods inside EU) { - Key: TagCustomerRates, + Key: tax.TagCustomerRates, Name: i18n.String{ i18n.EN: "Customer rates", i18n.ES: "Tarifas aplicables al destinatario", diff --git a/regimes/es/scenarios.go b/regimes/es/scenarios.go index b25d1312..4da3f7e5 100644 --- a/regimes/es/scenarios.go +++ b/regimes/es/scenarios.go @@ -100,13 +100,13 @@ var invoiceScenarios = &tax.ScenarioSet{ }, }, { - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, Codes: cbc.CodeMap{ KeyFacturaEInvoiceDocumentType: "FA", }, }, { - Tags: []cbc.Key{common.TagSelfBilled}, + Tags: []cbc.Key{tax.TagSelfBilled}, Codes: cbc.CodeMap{ KeyFacturaEInvoiceDocumentType: "AF", }, @@ -154,10 +154,10 @@ var invoiceScenarios = &tax.ScenarioSet{ // ** Special Messages ** // Reverse Charges { - Tags: []cbc.Key{common.TagReverseCharge}, + Tags: []cbc.Key{tax.TagReverseCharge}, Note: &cbc.Note{ Key: cbc.NoteKeyLegal, - Src: common.TagReverseCharge, + Src: tax.TagReverseCharge, Text: "Reverse Charge / Inversión del sujeto pasivo.", }, }, @@ -172,10 +172,10 @@ var invoiceScenarios = &tax.ScenarioSet{ }, // Customer issued invoices { - Tags: []cbc.Key{common.TagSelfBilled}, + Tags: []cbc.Key{tax.TagSelfBilled}, Note: &cbc.Note{ Key: cbc.NoteKeyLegal, - Src: common.TagSelfBilled, + Src: tax.TagSelfBilled, Text: "Facturación por el destinatario.", }, }, diff --git a/regimes/es/scenarios_test.go b/regimes/es/scenarios_test.go index 65877fb3..3672e8f2 100644 --- a/regimes/es/scenarios_test.go +++ b/regimes/es/scenarios_test.go @@ -10,7 +10,6 @@ import ( "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" _ "github.com/invopop/gobl/regimes" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/regimes/es" "github.com/invopop/gobl/tax" @@ -24,7 +23,7 @@ func testInvoiceStandard(t *testing.T) *bill.Invoice { Code: "123TEST", Currency: "EUR", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Supplier: &org.Party{ Name: "Test Supplier", @@ -71,7 +70,7 @@ func testInvoiceSimplified(t *testing.T) *bill.Invoice { Currency: "EUR", Code: "123TEST", Tax: &bill.Tax{ - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, }, Supplier: &org.Party{ Name: "Test Supplier", diff --git a/regimes/es/tax_categories.go b/regimes/es/tax_categories.go index 673d356f..c4f32bd3 100644 --- a/regimes/es/tax_categories.go +++ b/regimes/es/tax_categories.go @@ -6,7 +6,6 @@ import ( "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" "github.com/invopop/gobl/pkg/here" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -15,7 +14,7 @@ var taxCategories = []*tax.Category{ // VAT // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Name: i18n.String{ i18n.EN: "VAT", @@ -39,7 +38,7 @@ var taxCategories = []*tax.Category{ }, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.ES: "Tipo Cero", @@ -54,7 +53,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.ES: "Tipo General", @@ -79,7 +78,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard.With(TaxRateEquivalence), + Key: tax.RateStandard.With(TaxRateEquivalence), Name: i18n.String{ i18n.EN: "Standard Rate + Equivalence Surcharge", i18n.ES: "Tipo General + Recargo de Equivalencia", @@ -98,7 +97,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.ES: "Tipo Reducido", @@ -123,7 +122,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced.With(TaxRateEquivalence), + Key: tax.RateReduced.With(TaxRateEquivalence), Name: i18n.String{ i18n.EN: "Reduced Rate + Equivalence Surcharge", i18n.ES: "Tipo Reducido + Recargo de Equivalencia", @@ -142,7 +141,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateSuperReduced, + Key: tax.RateSuperReduced, Name: i18n.String{ i18n.EN: "Super-Reduced Rate", i18n.ES: "Tipo Superreducido", @@ -159,7 +158,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateSuperReduced.With(TaxRateEquivalence), + Key: tax.RateSuperReduced.With(TaxRateEquivalence), Name: i18n.String{ i18n.EN: "Super-Reduced Rate + Equivalence Surcharge", i18n.ES: "Tipo Superreducido + Recargo de Equivalencia", @@ -173,7 +172,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Exempt: true, Name: i18n.String{ i18n.EN: "Exempt", @@ -204,7 +203,7 @@ var taxCategories = []*tax.Category{ // This is a subset of the possible rates. Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.ES: "Tipo Cero", @@ -216,7 +215,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.ES: "Tipo General", @@ -228,7 +227,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.ES: "Tipo Reducido", diff --git a/regimes/fr/scenarios.go b/regimes/fr/scenarios.go index cf83b282..b7a359bc 100644 --- a/regimes/fr/scenarios.go +++ b/regimes/fr/scenarios.go @@ -3,7 +3,6 @@ package fr import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -13,10 +12,10 @@ var invoiceScenarios = &tax.ScenarioSet{ // ** Special Messages ** // Reverse Charges { - Tags: []cbc.Key{common.TagReverseCharge}, + Tags: []cbc.Key{tax.TagReverseCharge}, Note: &cbc.Note{ Key: cbc.NoteKeyLegal, - Src: common.TagReverseCharge, + Src: tax.TagReverseCharge, Text: "Reverse Charge / Autoliquidation de la TVA - Article 283-1 du CGI. Le client est redevable de la TVA.", }, }, diff --git a/regimes/fr/tax_categories.go b/regimes/fr/tax_categories.go index 617d55d2..f86e3492 100644 --- a/regimes/fr/tax_categories.go +++ b/regimes/fr/tax_categories.go @@ -4,7 +4,6 @@ import ( "github.com/invopop/gobl/cal" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -13,7 +12,7 @@ var taxCategories = []*tax.Category{ // VAT // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", i18n.FR: "TVA", @@ -34,7 +33,7 @@ var taxCategories = []*tax.Category{ Retained: false, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", }, @@ -45,7 +44,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard rate", i18n.FR: "Taux normal", @@ -62,7 +61,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateIntermediate, + Key: tax.RateIntermediate, Name: i18n.String{ i18n.EN: "Intermediate rate", i18n.FR: "Taux intermédiaire", @@ -79,7 +78,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced rate", i18n.FR: "Taux réduit", @@ -96,7 +95,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateSpecial, + Key: tax.RateSpecial, Name: i18n.String{ i18n.EN: "Special rate", i18n.FR: "Taux particulier", diff --git a/regimes/gb/gb.go b/regimes/gb/gb.go index f69138e6..fc9e0f19 100644 --- a/regimes/gb/gb.go +++ b/regimes/gb/gb.go @@ -30,6 +30,9 @@ func New() *tax.Regime { TimeZone: "Europe/London", Validator: Validate, Calculator: Calculate, + Scenarios: []*tax.ScenarioSet{ + common.InvoiceScenarios(), + }, Tags: common.InvoiceTags(), Categories: taxCategories, } diff --git a/regimes/gb/tax_categories.go b/regimes/gb/tax_categories.go index 26640dca..e5c01c60 100644 --- a/regimes/gb/tax_categories.go +++ b/regimes/gb/tax_categories.go @@ -4,7 +4,6 @@ import ( "github.com/invopop/gobl/cal" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -13,7 +12,7 @@ var taxCategories = []*tax.Category{ // VAT // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", }, @@ -23,7 +22,7 @@ var taxCategories = []*tax.Category{ Retained: false, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", }, @@ -34,7 +33,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", }, @@ -46,7 +45,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", }, diff --git a/regimes/it/categories.go b/regimes/it/categories.go index 32ed5fe9..fe23e45f 100644 --- a/regimes/it/categories.go +++ b/regimes/it/categories.go @@ -4,7 +4,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -22,7 +21,7 @@ const ( var categories = []*tax.Category{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Name: i18n.String{ i18n.EN: "VAT", @@ -34,7 +33,7 @@ var categories = []*tax.Category{ }, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.IT: "Aliquota Zero", @@ -46,7 +45,7 @@ var categories = []*tax.Category{ }, }, { - Key: common.TaxRateSuperReduced, + Key: tax.RateSuperReduced, Name: i18n.String{ i18n.EN: "Minimum Rate", i18n.IT: "Aliquota Minima", @@ -58,7 +57,7 @@ var categories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.IT: "Aliquota Ridotta", @@ -70,7 +69,7 @@ var categories = []*tax.Category{ }, }, { - Key: common.TaxRateIntermediate, + Key: tax.RateIntermediate, Name: i18n.String{ i18n.EN: "Intermediate Rate", i18n.IT: "Aliquota Intermedia", @@ -82,7 +81,7 @@ var categories = []*tax.Category{ }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Ordinary Rate", i18n.IT: "Aliquota Ordinaria", @@ -94,7 +93,7 @@ var categories = []*tax.Category{ }, }, { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Exempt: true, Name: i18n.String{ i18n.EN: "None", diff --git a/regimes/it/invoice_validator_test.go b/regimes/it/invoice_validator_test.go index f2185967..ed7d7bf1 100644 --- a/regimes/it/invoice_validator_test.go +++ b/regimes/it/invoice_validator_test.go @@ -9,7 +9,6 @@ import ( "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/num" "github.com/invopop/gobl/org" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/regimes/it" "github.com/invopop/gobl/tax" "github.com/stretchr/testify/assert" @@ -22,7 +21,7 @@ func testInvoiceStandard(t *testing.T) *bill.Invoice { Code: "123TEST", Currency: "EUR", Tax: &bill.Tax{ - PricesInclude: common.TaxCategoryVAT, + PricesInclude: tax.CategoryVAT, }, Type: bill.InvoiceTypeStandard, Supplier: &org.Party{ diff --git a/regimes/it/scenarios.go b/regimes/it/scenarios.go index 81ed626d..90db59ec 100644 --- a/regimes/it/scenarios.go +++ b/regimes/it/scenarios.go @@ -136,7 +136,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagPartial}, + Tags: []cbc.Key{tax.TagPartial}, Name: i18n.String{ i18n.EN: "Advance or down payment on invoice", i18n.IT: "Acconto / anticipo su fattura", @@ -178,7 +178,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagPartial, TagFreelance}, + Tags: []cbc.Key{tax.TagPartial, TagFreelance}, Name: i18n.String{ i18n.EN: "Advance or down payment on freelance invoice", i18n.IT: "Acconto / anticipo su parcella", @@ -189,7 +189,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, Name: i18n.String{ i18n.EN: "Simplified Invoice", i18n.IT: "Fattura Semplificata", @@ -200,7 +200,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeCreditNote}, - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, Name: i18n.String{ i18n.EN: "Simplified Credit Note", i18n.IT: "Nota di credito semplificata", @@ -211,7 +211,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeDebitNote}, - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, Name: i18n.String{ i18n.EN: "Simplified Debit Note", i18n.IT: "Nota di debito semplificata", @@ -222,7 +222,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled}, + Tags: []cbc.Key{tax.TagSelfBilled}, Name: i18n.String{ i18n.EN: "Self-billed for self consumption or for free transfer without recourse", i18n.IT: "Fattura per autoconsumo o per cessioni gratuite senza rivalsa", @@ -233,7 +233,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, common.TagReverseCharge}, + Tags: []cbc.Key{tax.TagSelfBilled, tax.TagReverseCharge}, Name: i18n.String{ i18n.EN: "Reverse charge", i18n.IT: "Integrazione fattura reverse charge interno", @@ -244,7 +244,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagImport}, + Tags: []cbc.Key{tax.TagSelfBilled, TagImport}, Name: i18n.String{ i18n.EN: "Self-billed Import", i18n.IT: "Integrazione/autofattura per acquisto servizi da estero", @@ -255,7 +255,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagImport, TagGoodsEU}, + Tags: []cbc.Key{tax.TagSelfBilled, TagImport, TagGoodsEU}, Name: i18n.String{ i18n.EN: "Self-billed EU Goods Import", i18n.IT: "Integrazione per acquisto beni intracomunitari", @@ -266,7 +266,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagImport, TagGoods}, + Tags: []cbc.Key{tax.TagSelfBilled, TagImport, TagGoods}, Name: i18n.String{ i18n.EN: "Self-billed Goods Import", i18n.IT: "Integrazione/autofattura per acquisto beni ex art.17 c.2 DPR 633/72", @@ -277,7 +277,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagRegularization}, + Tags: []cbc.Key{tax.TagSelfBilled, TagRegularization}, Name: i18n.String{ i18n.EN: "Self-billed Regularization", i18n.IT: "Autofattura per regolarizzazione e integrazione delle fatture - art.6 c.8 d.lgs.471/97 o art.46 c.5 D.L.331/93", @@ -288,7 +288,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagCeilingExceeded}, + Tags: []cbc.Key{tax.TagSelfBilled, TagCeilingExceeded}, Name: i18n.String{ i18n.EN: "Self-billed invoice when ceiling exceeded", i18n.IT: "Autofattura per splafonamento", @@ -299,7 +299,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagGoodsExtracted}, + Tags: []cbc.Key{tax.TagSelfBilled, TagGoodsExtracted}, Name: i18n.String{ i18n.EN: "Self-billed for goods extracted from VAT warehouse", i18n.IT: "Estrazione beni da Deposito IVA", @@ -310,7 +310,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagGoodsWithTax}, + Tags: []cbc.Key{tax.TagSelfBilled, TagGoodsWithTax}, Name: i18n.String{ i18n.EN: "Self-billed for goods extracted from VAT warehouse with VAT payment", i18n.IT: "Estrazione beni da Deposito IVA con versamento IVA", @@ -355,7 +355,7 @@ var invoiceScenarios = &tax.ScenarioSet{ { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSelfBilled, TagSanMarinoPaper}, + Tags: []cbc.Key{tax.TagSelfBilled, TagSanMarinoPaper}, Name: i18n.String{ i18n.EN: "Purchases from San Marino with VAT (paper invoice)", i18n.IT: "Acquisti da San Marino con IVA (fattura cartacea)", @@ -367,10 +367,10 @@ var invoiceScenarios = &tax.ScenarioSet{ // **** MESSAGES **** { - Tags: []cbc.Key{common.TagReverseCharge}, + Tags: []cbc.Key{tax.TagReverseCharge}, Note: &cbc.Note{ Key: cbc.NoteKeyLegal, - Src: common.TagReverseCharge, + Src: tax.TagReverseCharge, Text: "Reverse Charge / Inversione del soggetto passivo", }, }, diff --git a/regimes/mx/tax_categories.go b/regimes/mx/tax_categories.go index cc298e49..6f50a47b 100644 --- a/regimes/mx/tax_categories.go +++ b/regimes/mx/tax_categories.go @@ -4,7 +4,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -26,7 +25,7 @@ var taxCategories = []*tax.Category{ // IVA // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", i18n.ES: "IVA", @@ -38,7 +37,7 @@ var taxCategories = []*tax.Category{ Retained: false, Rates: []*tax.Rate{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.ES: "Tasa General", @@ -50,7 +49,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced (Border) Rate", i18n.ES: "Tasa Reducida (Fronteriza)", @@ -62,7 +61,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.ES: "Tasa Cero", diff --git a/regimes/nl/nl.go b/regimes/nl/nl.go index eb3d89c6..cbdfd9bb 100644 --- a/regimes/nl/nl.go +++ b/regimes/nl/nl.go @@ -26,13 +26,16 @@ func New() *tax.Regime { TimeZone: "Europe/Amsterdam", Validator: Validate, Calculator: Calculate, - Tags: common.InvoiceTags(), + Scenarios: []*tax.ScenarioSet{ + common.InvoiceScenarios(), + }, + Tags: common.InvoiceTags(), Categories: []*tax.Category{ // // VAT // { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", i18n.NL: "BTW", @@ -44,7 +47,7 @@ func New() *tax.Regime { Retained: false, Rates: []*tax.Rate{ { - Key: common.TaxRateZero, + Key: tax.RateZero, Name: i18n.String{ i18n.EN: "Zero Rate", i18n.NL: `0%-tarief`, @@ -56,7 +59,7 @@ func New() *tax.Regime { }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.NL: "Standaardtarief", @@ -68,7 +71,7 @@ func New() *tax.Regime { }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.NL: "Gereduceerd Tarief", diff --git a/regimes/pt/migrations.go b/regimes/pt/migrations.go index b9734d08..52b575cc 100644 --- a/regimes/pt/migrations.go +++ b/regimes/pt/migrations.go @@ -5,7 +5,6 @@ import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -78,7 +77,7 @@ func migrateInvoiceTaxCombo(tc *tax.Combo) error { if tc.Rate.HasPrefix(TaxRateExempt) && tc.Rate != TaxRateExempt { for _, m := range taxRateVATExemptMigrationMap { if m.Key == tc.Rate { - tc.Rate = common.TaxRateExempt + tc.Rate = tax.RateExempt tc.Ext = m.Ext return nil } diff --git a/regimes/pt/migrations_test.go b/regimes/pt/migrations_test.go index 0c90ba94..0cf1eedd 100644 --- a/regimes/pt/migrations_test.go +++ b/regimes/pt/migrations_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/invopop/gobl/cbc" - "github.com/invopop/gobl/regimes/common" + "github.com/invopop/gobl/tax" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -18,7 +18,7 @@ func TestTaxRateMigration(t *testing.T) { require.NoError(t, err) t0 := inv.Lines[0].Taxes[0] - assert.Equal(t, common.TaxRateExempt, t0.Rate) + assert.Equal(t, tax.RateExempt, t0.Rate) assert.Equal(t, cbc.Code("M01"), t0.Ext["pt-exemption-code"]) // Invalid old rate @@ -38,6 +38,6 @@ func TestTaxRateMigration(t *testing.T) { require.NoError(t, err) t0 = inv.Lines[0].Taxes[0] - assert.Equal(t, common.TaxRateExempt, t0.Rate) + assert.Equal(t, tax.RateExempt, t0.Rate) assert.Equal(t, cbc.Code("M02"), t0.Ext["pt-exemption-code"]) } diff --git a/regimes/pt/scenarios.go b/regimes/pt/scenarios.go index 19f9957c..d3ba39d1 100644 --- a/regimes/pt/scenarios.go +++ b/regimes/pt/scenarios.go @@ -38,7 +38,7 @@ var invoiceScenarios = &tax.ScenarioSet{ }, { Types: []cbc.Key{bill.InvoiceTypeStandard}, - Tags: []cbc.Key{common.TagSimplified}, + Tags: []cbc.Key{tax.TagSimplified}, Codes: cbc.CodeMap{ KeyATInvoiceType: "FS", }, @@ -62,5 +62,15 @@ var invoiceScenarios = &tax.ScenarioSet{ KeyATInvoiceType: "NC", }, }, + + // Reverse Charges + { + Tags: []cbc.Key{tax.TagReverseCharge}, + Note: &cbc.Note{ + Key: cbc.NoteKeyLegal, + Src: tax.TagReverseCharge, + Text: "Reverse charge / Autoliquidação - Artigo 2.º n.º 1 alínea j) do Código do IVA", + }, + }, }, } diff --git a/regimes/pt/tax_categories.go b/regimes/pt/tax_categories.go index 64887a9a..59171fb7 100644 --- a/regimes/pt/tax_categories.go +++ b/regimes/pt/tax_categories.go @@ -6,7 +6,6 @@ import ( "github.com/invopop/gobl/i18n" "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -22,7 +21,7 @@ const ( var taxCategories = []*tax.Category{ // VAT { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Name: i18n.String{ i18n.EN: "VAT", i18n.PT: "IVA", @@ -35,7 +34,7 @@ var taxCategories = []*tax.Category{ RateRequired: true, Rates: []*tax.Rate{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Name: i18n.String{ i18n.EN: "Standard Rate", i18n.PT: "Tipo Geral", @@ -61,7 +60,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateIntermediate, + Key: tax.RateIntermediate, Name: i18n.String{ i18n.EN: "Intermediate Rate", i18n.PT: "Taxa Intermédia", //nolint:misspell @@ -87,7 +86,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Name: i18n.String{ i18n.EN: "Reduced Rate", i18n.PT: "Taxa Reduzida", @@ -113,7 +112,7 @@ var taxCategories = []*tax.Category{ }, }, { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Exempt: true, Map: cbc.CodeMap{ KeyATTaxCode: TaxCodeExempt, diff --git a/tax/constants.go b/tax/constants.go new file mode 100644 index 00000000..e15b895c --- /dev/null +++ b/tax/constants.go @@ -0,0 +1,30 @@ +package tax + +import "github.com/invopop/gobl/cbc" + +// Standard tax categories that may be shared between countries. +const ( + CategoryST cbc.Code = "ST" // Sales Tax + CategoryVAT cbc.Code = "VAT" // Value Added Tax + CategoryGST cbc.Code = "GST" // Goods and Services Tax +) + +// Most commonly used keys. Local regions may add their own rate keys. +const ( + RateExempt cbc.Key = "exempt" + RateZero cbc.Key = "zero" + RateStandard cbc.Key = "standard" + RateIntermediate cbc.Key = "intermediate" + RateReduced cbc.Key = "reduced" + RateSuperReduced cbc.Key = "super-reduced" + RateSpecial cbc.Key = "special" +) + +// Standard tax tags +const ( + TagSimplified cbc.Key = "simplified" + TagReverseCharge cbc.Key = "reverse-charge" + TagCustomerRates cbc.Key = "customer-rates" + TagSelfBilled cbc.Key = "self-billed" + TagPartial cbc.Key = "partial" +) diff --git a/tax/set_test.go b/tax/set_test.go index e0f0b4f9..d7933400 100644 --- a/tax/set_test.go +++ b/tax/set_test.go @@ -7,7 +7,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/regimes/es" "github.com/invopop/gobl/tax" "github.com/stretchr/testify/assert" @@ -81,7 +80,7 @@ func TestSetValidation(t *testing.T) { set: tax.Set{ { Category: "VAT", - Rate: common.TaxRateExempt, + Rate: tax.RateExempt, }, }, err: "es-tbai-exemption: required", diff --git a/tax/totals_calculator_test.go b/tax/totals_calculator_test.go index f9fc4fb9..a6804352 100644 --- a/tax/totals_calculator_test.go +++ b/tax/totals_calculator_test.go @@ -8,7 +8,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/l10n" "github.com/invopop/gobl/num" - "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/regimes/es" "github.com/invopop/gobl/regimes/it" "github.com/invopop/gobl/regimes/pt" @@ -51,8 +50,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -62,11 +61,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), @@ -84,8 +83,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E1", }, @@ -98,11 +97,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E1", }, @@ -123,8 +122,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, }, }, amount: num.MakeAmount(10000, 2), @@ -132,8 +131,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateReduced, + Category: tax.CategoryVAT, + Rate: tax.RateReduced, }, }, amount: num.MakeAmount(10000, 2), @@ -143,17 +142,17 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Base: num.MakeAmount(10000, 2), Percent: nil, Amount: num.MakeAmount(0, 2), }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1000, 2), @@ -171,8 +170,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, }, }, amount: num.MakeAmount(10000, 2), @@ -180,8 +179,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, }, }, amount: num.MakeAmount(10000, 2), @@ -191,11 +190,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Base: num.MakeAmount(20000, 2), Percent: nil, Amount: num.MakeAmount(0, 2), @@ -215,8 +214,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -226,11 +225,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(160, 3), Amount: num.MakeAmount(1600, 2), @@ -248,7 +247,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(210, 3), }, }, @@ -259,11 +258,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - // Key: common.TaxRateStandard, + // Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), @@ -282,8 +281,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, Percent: num.NewPercentage(20, 2), }, }, @@ -294,11 +293,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), @@ -316,8 +315,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -325,8 +324,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(15000, 2), @@ -336,11 +335,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(25000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(5250, 2), @@ -358,8 +357,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard.With(es.TaxRateEquivalence), + Category: tax.CategoryVAT, + Rate: tax.RateStandard.With(es.TaxRateEquivalence), }, }, amount: num.MakeAmount(10000, 2), @@ -367,8 +366,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard.With(es.TaxRateEquivalence), + Category: tax.CategoryVAT, + Rate: tax.RateStandard.With(es.TaxRateEquivalence), }, }, amount: num.MakeAmount(10000, 2), @@ -376,8 +375,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(15000, 2), @@ -387,11 +386,11 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard.With(es.TaxRateEquivalence), + Key: tax.RateStandard.With(es.TaxRateEquivalence), Base: num.MakeAmount(20000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(4200, 2), @@ -401,7 +400,7 @@ func TestTotalBySumCalculate(t *testing.T) { }, }, { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(15000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(3150, 2), @@ -420,7 +419,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(210, 3), }, }, @@ -429,7 +428,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(2100, 4), // different exp. }, }, @@ -440,7 +439,7 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { @@ -461,8 +460,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -470,8 +469,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateReduced, + Category: tax.CategoryVAT, + Rate: tax.RateReduced, }, }, amount: num.MakeAmount(15000, 2), @@ -481,17 +480,17 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Base: num.MakeAmount(15000, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1500, 2), @@ -509,7 +508,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(210, 3), }, }, @@ -518,7 +517,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(100, 3), }, }, @@ -529,17 +528,17 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - // Key: common.TaxRateStandard, + // Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), }, { - // Key: common.TaxRateReduced, + // Key: tax.RateReduced, Base: num.MakeAmount(15000, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1500, 2), @@ -557,8 +556,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -566,28 +565,28 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateReduced, + Category: tax.CategoryVAT, + Rate: tax.RateReduced, }, }, amount: num.MakeAmount(15000, 2), }, }, - taxIncluded: common.TaxCategoryVAT, + taxIncluded: tax.CategoryVAT, want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(8264, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(1736, 2), }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Base: num.MakeAmount(13636, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1364, 2), @@ -605,7 +604,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(21, 2), }, }, @@ -614,18 +613,18 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(10, 2), }, }, amount: num.MakeAmount(15000, 2), }, }, - taxIncluded: common.TaxCategoryVAT, + taxIncluded: tax.CategoryVAT, want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { @@ -651,8 +650,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, { Category: es.TaxCategoryIRPF, @@ -664,8 +663,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateReduced, + Category: tax.CategoryVAT, + Rate: tax.RateReduced, }, }, amount: num.MakeAmount(15000, 2), @@ -675,17 +674,17 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(10000, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(2100, 2), }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Base: num.MakeAmount(15000, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1500, 2), @@ -717,8 +716,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, }, { Category: es.TaxCategoryIRPF, @@ -730,28 +729,28 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateReduced, + Category: tax.CategoryVAT, + Rate: tax.RateReduced, }, }, amount: num.MakeAmount(15000, 2), }, }, - taxIncluded: common.TaxCategoryVAT, + taxIncluded: tax.CategoryVAT, want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Retained: false, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(8264, 2), Percent: num.NewPercentage(210, 3), Amount: num.MakeAmount(1736, 2), }, { - Key: common.TaxRateReduced, + Key: tax.RateReduced, Base: num.MakeAmount(13636, 2), Percent: num.NewPercentage(100, 3), Amount: num.MakeAmount(1364, 2), @@ -783,7 +782,7 @@ func TestTotalBySumCalculate(t *testing.T) { taxes: tax.Set{ { Category: cbc.Code("FOO"), - Rate: common.TaxRateStandard, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -799,7 +798,7 @@ func TestTotalBySumCalculate(t *testing.T) { taxes: tax.Set{ { Category: es.TaxCategoryIRPF, - Rate: common.TaxRateStandard, + Rate: tax.RateStandard, }, }, amount: num.MakeAmount(10000, 2), @@ -849,8 +848,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E1", }, @@ -859,14 +858,14 @@ func TestTotalBySumCalculate(t *testing.T) { amount: num.MakeAmount(10000, 2), }, }, - taxIncluded: common.TaxCategoryVAT, + taxIncluded: tax.CategoryVAT, want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Rates: []*tax.RateTotal{ { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E1", }, @@ -886,7 +885,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(21, 2), }, }, @@ -895,8 +894,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateExempt, + Category: tax.CategoryVAT, + Rate: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E2", }, @@ -905,11 +904,11 @@ func TestTotalBySumCalculate(t *testing.T) { amount: num.MakeAmount(10000, 2), }, }, - taxIncluded: common.TaxCategoryVAT, + taxIncluded: tax.CategoryVAT, want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Rates: []*tax.RateTotal{ { Base: num.MakeAmount(8264, 2), @@ -917,7 +916,7 @@ func TestTotalBySumCalculate(t *testing.T) { Amount: num.MakeAmount(1736, 2), }, { - Key: common.TaxRateExempt, + Key: tax.RateExempt, Ext: cbc.CodeMap{ es.ExtKeyTBAIExemption: "E2", }, @@ -938,8 +937,8 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, - Rate: common.TaxRateStandard, + Category: tax.CategoryVAT, + Rate: tax.RateStandard, Percent: num.NewPercentage(22, 2), }, { @@ -955,7 +954,7 @@ func TestTotalBySumCalculate(t *testing.T) { &taxableLine{ taxes: tax.Set{ { - Category: common.TaxCategoryVAT, + Category: tax.CategoryVAT, Percent: num.NewPercentage(22, 2), }, { @@ -972,10 +971,10 @@ func TestTotalBySumCalculate(t *testing.T) { want: &tax.Total{ Categories: []*tax.CategoryTotal{ { - Code: common.TaxCategoryVAT, + Code: tax.CategoryVAT, Rates: []*tax.RateTotal{ { - Key: common.TaxRateStandard, + Key: tax.RateStandard, Base: num.MakeAmount(20000, 2), Percent: num.NewPercentage(220, 3), Amount: num.MakeAmount(4400, 2), From 0758bb224e64548c3ae3d4d2e034ae83b691f8af Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Wed, 22 Nov 2023 20:40:32 +0000 Subject: [PATCH 5/8] Fixing payee typo --- bill/payment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bill/payment.go b/bill/payment.go index e643b91d..d9cc9cbd 100644 --- a/bill/payment.go +++ b/bill/payment.go @@ -13,7 +13,7 @@ import ( // Payment contains details as to how the invoice should be paid. type Payment struct { // The party responsible for receiving payment of the invoice, if not the supplier. - Payee *org.Party `json:"payee,omitempty" jsonschema:"title=Payer"` + Payee *org.Party `json:"payee,omitempty" jsonschema:"title=Payee"` // Payment terms or conditions. Terms *pay.Terms `json:"terms,omitempty" jsonschema:"title=Terms"` // Any amounts that have been paid in advance and should be deducted from the amount due. From f286dced98c1c49ddf1a5effe9b52ccb4906c930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Wed, 22 Nov 2023 22:42:13 +0000 Subject: [PATCH 6/8] Migrate only defined extension keys in MX parties --- regimes/mx/party.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regimes/mx/party.go b/regimes/mx/party.go index d83bbf55..1018addf 100644 --- a/regimes/mx/party.go +++ b/regimes/mx/party.go @@ -10,7 +10,7 @@ func normalizeParty(p *org.Party) error { // Pending removal after migrations completed. idents := make([]*org.Identity, 0) for _, v := range p.Identities { - if v.Key != "" { + if v.Key.In(migratedExtensionKeys...) { if p.Ext == nil { p.Ext = make(cbc.CodeMap) } From f193b3066d0634dc2e67a1c75f2b76693f70128a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Wed, 22 Nov 2023 22:43:20 +0000 Subject: [PATCH 7/8] Provide an Identity Key validation rule --- org/identity.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/org/identity.go b/org/identity.go index 0adf90a0..c7f0fe52 100644 --- a/org/identity.go +++ b/org/identity.go @@ -3,6 +3,7 @@ package org import ( "context" "fmt" + "strings" "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/tax" @@ -55,8 +56,15 @@ func HasIdentityType(typ cbc.Code) validation.Rule { return validateIdentitySet{typ: typ} } +// HasIdentityKey provides a validation rule that will determine if at least one +// of the identities defined includes one with the defined key. +func HasIdentityKey(key cbc.Key) validation.Rule { + return validateIdentitySet{key: key} +} + type validateIdentitySet struct { typ cbc.Code + key cbc.Key } func (v validateIdentitySet) Validate(value interface{}) error { @@ -65,11 +73,28 @@ func (v validateIdentitySet) Validate(value interface{}) error { return nil } for _, row := range ids { - if row.Type == v.typ { + if v.matches(row) { return nil } } - return fmt.Errorf("missing %s", v.typ) + + return fmt.Errorf("missing %s", v) +} + +func (v validateIdentitySet) matches(row *Identity) bool { + return (v.typ == cbc.CodeEmpty || row.Type == v.typ) && + (v.key == cbc.KeyEmpty || row.Key == v.key) +} + +func (v validateIdentitySet) String() string { + var parts []string + if v.typ != cbc.CodeEmpty { + parts = append(parts, fmt.Sprintf("type %s", v.typ)) + } + if v.key != cbc.KeyEmpty { + parts = append(parts, fmt.Sprintf("key %s", v.key)) + } + return strings.Join(parts, ", ") } // IdentityForType helps return the identity with a matching type code. From 1fb72b998199cb7d772cafa0038e4e9eb86b9a2b Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Thu, 23 Nov 2023 15:50:14 +0000 Subject: [PATCH 8/8] Updating missing changes for common constants refactor --- regimes/common/common.go | 27 --------------------------- regimes/us/us.go | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/regimes/common/common.go b/regimes/common/common.go index 76a048da..2cc68f81 100644 --- a/regimes/common/common.go +++ b/regimes/common/common.go @@ -9,33 +9,6 @@ import ( "github.com/invopop/gobl/tax" ) -// Standard tax categories that may be shared between countries. -const ( - TaxCategoryST cbc.Code = "ST" // Sales Tax - TaxCategoryVAT cbc.Code = "VAT" // Value Added Tax - TaxCategoryGST cbc.Code = "GST" // Goods and Services Tax -) - -// Most commonly used keys. Local regions may add their own rate keys. -const ( - TaxRateExempt cbc.Key = "exempt" - TaxRateZero cbc.Key = "zero" - TaxRateStandard cbc.Key = "standard" - TaxRateIntermediate cbc.Key = "intermediate" - TaxRateReduced cbc.Key = "reduced" - TaxRateSuperReduced cbc.Key = "super-reduced" - TaxRateSpecial cbc.Key = "special" -) - -// Standard tax tags -const ( - TagSimplified cbc.Key = "simplified" - TagReverseCharge cbc.Key = "reverse-charge" - TagCustomerRates cbc.Key = "customer-rates" - TagSelfBilled cbc.Key = "self-billed" - TagPartial cbc.Key = "partial" -) - // Common inbox keys const ( InboxKeyPEPPOL cbc.Key = "peppol-id" diff --git a/regimes/us/us.go b/regimes/us/us.go index 0fbaa76f..b35deb0f 100644 --- a/regimes/us/us.go +++ b/regimes/us/us.go @@ -36,7 +36,7 @@ func New() *tax.Regime { // Sales Tax // { - Code: common.TaxCategoryST, + Code: tax.CategoryST, Name: i18n.String{ i18n.EN: "ST", },