From a1871c8327f53f1c33e0dd3ea81c1ff190b91bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Tue, 19 Nov 2024 06:01:28 +0000 Subject: [PATCH 1/8] Add CNAE extension for BR items --- CHANGELOG.md | 2 +- addons/br/nfse/extensions.go | 26 ++++++++++-- data/addons/br-nfse-v1.json | 16 +++++-- examples/br/invoice-services.json | 3 +- examples/br/out/invoice-services.json | 3 +- regimes/br/README.md | 61 +++++++++++++++++++++------ 6 files changed, 89 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f3df39..24194897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Added - `org`: `Address` includes `LineOne()`, `LineTwo()`, `CompleteNumber()` methods to help with conversion to other formats with some regional formatting. -- `br`: supplier extensions, validations & identities +- `br-nfse-v1`: new extensions, validations & identities for the typical service note and supplier. ### Changes diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index 5edd2598..86d7c8b2 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -12,6 +12,7 @@ import ( // of these extensions are common, they can be moved to the regime or to a // shared addon. const ( + ExtKeyCNAE = "br-nfse-cnae" ExtKeyFiscalIncentive = "br-nfse-fiscal-incentive" ExtKeyMunicipality = "br-nfse-municipality" ExtKeyService = "br-nfse-service" @@ -20,6 +21,22 @@ const ( ) var extensions = []*cbc.KeyDefinition{ + { + Key: ExtKeyCNAE, + Name: i18n.String{ + i18n.EN: "CNAE code", + i18n.PT: "Código CNAE", + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + The CNAE (National Classification of Economic Activities) code for a service. + + For further details on the list of possible codes, see: + + * https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html + `), + }, + }, { Key: ExtKeyFiscalIncentive, Name: i18n.String{ @@ -47,7 +64,8 @@ var extensions = []*cbc.KeyDefinition{ Indicates whether a party benefits from a fiscal incentive. List of codes taken from the national NFSe standard: - https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download + + * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-68) `), }, @@ -114,7 +132,8 @@ var extensions = []*cbc.KeyDefinition{ Indicates whether a party is opting for the “Simples Nacional” tax regime. List of codes taken from the national NFSe standard: - https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download + + * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-67) `), }, @@ -174,7 +193,8 @@ var extensions = []*cbc.KeyDefinition{ Indicates a special tax regime that the party is subject to. List of codes taken from the national NFSe standard: - https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download + + * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-66) `), }, diff --git a/data/addons/br-nfse-v1.json b/data/addons/br-nfse-v1.json index 062f8285..1230978e 100644 --- a/data/addons/br-nfse-v1.json +++ b/data/addons/br-nfse-v1.json @@ -5,6 +5,16 @@ "en": "Brazil NFS-e 1.X" }, "extensions": [ + { + "key": "br-nfse-cnae", + "name": { + "en": "CNAE code", + "pt": "Código CNAE" + }, + "desc": { + "en": "The CNAE (National Classification of Economic Activities) code for a service.\n\nFor further details on the list of possible codes, see:\n\n* https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html" + } + }, { "key": "br-nfse-fiscal-incentive", "name": { @@ -12,7 +22,7 @@ "pt": "Incentivo Fiscal" }, "desc": { - "en": "Indicates whether a party benefits from a fiscal incentive.\n\nList of codes taken from the national NFSe standard:\nhttps://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-68)" + "en": "Indicates whether a party benefits from a fiscal incentive.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-68)" }, "values": [ { @@ -59,7 +69,7 @@ "pt": "Optante pelo Simples Nacional" }, "desc": { - "en": "Indicates whether a party is opting for the “Simples Nacional” tax regime.\n\nList of codes taken from the national NFSe standard:\nhttps://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" + "en": "Indicates whether a party is opting for the “Simples Nacional” tax regime.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" }, "values": [ { @@ -85,7 +95,7 @@ "pt": "Regime Especial de Tributação" }, "desc": { - "en": "Indicates a special tax regime that the party is subject to.\n\nList of codes taken from the national NFSe standard:\nhttps://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-66)" + "en": "Indicates a special tax regime that the party is subject to.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-66)" }, "values": [ { diff --git a/examples/br/invoice-services.json b/examples/br/invoice-services.json index b99630d0..1c7be41f 100644 --- a/examples/br/invoice-services.json +++ b/examples/br/invoice-services.json @@ -72,7 +72,8 @@ "name": "Consultancy Services", "price": "100.00", "ext": { - "br-nfse-service": "10.5" + "br-nfse-service": "10.5", + "br-nfse-cnae": "62.01-5-01" } }, "taxes": [ diff --git a/examples/br/out/invoice-services.json b/examples/br/out/invoice-services.json index a8252b84..90fc13be 100644 --- a/examples/br/out/invoice-services.json +++ b/examples/br/out/invoice-services.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "b1fc7b37b41dc9df2148365e52dd97cdc3a3847de92d5b7560b3cf40cda10f77" + "val": "31573fb261adbfa6011c8c21e5e53c4a7546389e6a39fc47124d0e34fb698811" } }, "doc": { @@ -83,6 +83,7 @@ "name": "Consultancy Services", "price": "100.00", "ext": { + "br-nfse-cnae": "62.01-5-01", "br-nfse-service": "10.5" } }, diff --git a/regimes/br/README.md b/regimes/br/README.md index f9d4e34b..6530e6ea 100644 --- a/regimes/br/README.md +++ b/regimes/br/README.md @@ -25,7 +25,7 @@ For example: ```js "supplier": { - //... +//... "addresses": [ { "num": "75", @@ -38,7 +38,7 @@ For example: "country": "BR" } ], - //... +//... ``` ### Service Notes @@ -55,11 +55,11 @@ For example: ```js "supplier": { - //... +//... "ext": { - "br-igbe-municipality": "2927408" + "br-nfse-municipality": "2927408" }, - //... +//... ``` #### National and municipal registration @@ -70,7 +70,7 @@ For example: ```js "supplier": { - //... +//... "identities": [ { "key": "br-nfse-municipal-reg", @@ -81,7 +81,7 @@ For example: "code": "12345012345678" } ], - //... +//... ``` #### “Simples Nacional” @@ -97,11 +97,11 @@ For example: ```js "supplier": { - //... +//... "ext": { "br-nfse-simples-nacional": "1", // Opt-in }, - //... +//... ``` #### Special Tax Regime @@ -121,11 +121,11 @@ For example: ```js "supplier": { - //... +//... "ext": { "br-nfse-special-regime": "4" // Cooperative }, - //... +//... ``` #### Fiscal Incentive @@ -141,9 +141,44 @@ For example: ```js "supplier": { - //... +//... "ext": { "br-nfse-fiscal-incentive": "2" // No tax incentive }, - //... +//... ``` + +#### Municipality service code + +Specify the code assigned by the municipality to a service using the +`br-nfse-service` extension at item level. Typically, one of the codes listed in the [Lei Complementar 116/2003](https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp116.htm), but municipalities can make their own changes. + +For example: + +```js +"lines": [ + { + "item": { +//... + "ext": { + "br-nfse-service": "1.01" +//... +``` + +#### CNAE code + +Set the CNAE code (National Classification of Economic Activities) for a service using the `br-nfse-cnae` at item level. Find the list of possible codes [at the IGBE](https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html). + +For example: + +```js +"lines": [ + { + "item": { +//... + "ext": { + "br-nfse-cnae": "62.01-5-01" +//... +``` + + From ebd7c83982887c73f1fae0405822a065131c091d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Tue, 19 Nov 2024 16:06:41 +0000 Subject: [PATCH 2/8] Add ISS liability extension to br-nfse addon --- addons/br/nfse/extensions.go | 69 +++++++++++++++++ addons/br/nfse/nfse.go | 4 + addons/br/nfse/tax_combo.go | 35 +++++++++ addons/br/nfse/tax_combo_test.go | 105 ++++++++++++++++++++++++++ data/addons/br-nfse-v1.json | 61 +++++++++++++++ examples/br/invoice-services.json | 5 +- examples/br/out/invoice-services.json | 10 ++- regimes/br/README.md | 27 +++++++ 8 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 addons/br/nfse/tax_combo.go create mode 100644 addons/br/nfse/tax_combo_test.go diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index 86d7c8b2..607a6f99 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -14,6 +14,7 @@ import ( const ( ExtKeyCNAE = "br-nfse-cnae" ExtKeyFiscalIncentive = "br-nfse-fiscal-incentive" + ExtKeyISSLiability = "br-nfse-iss-liability" ExtKeyMunicipality = "br-nfse-municipality" ExtKeyService = "br-nfse-service" ExtKeySimplesNacional = "br-nfse-simples-nacional" @@ -70,6 +71,74 @@ var extensions = []*cbc.KeyDefinition{ `), }, }, + { + Key: ExtKeyISSLiability, + Name: i18n.String{ + i18n.EN: "ISS Liability", + i18n.PT: "Exigibilidade ISS", + }, + Values: []*cbc.ValueDefinition{ + { + Value: "1", + Name: i18n.String{ + i18n.EN: "Liable", + i18n.PT: "Exigível", + }, + }, + { + Value: "2", + Name: i18n.String{ + i18n.EN: "Not subject", + i18n.PT: "Não incidência", + }, + }, + { + Value: "3", + Name: i18n.String{ + i18n.EN: "Exempt", + i18n.PT: "Isenção", + }, + }, + { + Value: "4", + Name: i18n.String{ + i18n.EN: "Export", + i18n.PT: "Exportação", + }, + }, + { + Value: "5", + Name: i18n.String{ + i18n.EN: "Immune", + i18n.PT: "Imunidade", + }, + }, + { + Value: "6", + Name: i18n.String{ + i18n.EN: "Suspended Judicially", + i18n.PT: "Suspensa por Decisão Judicial", + }, + }, + { + Value: "7", + Name: i18n.String{ + i18n.EN: "Suspended Administratively", + i18n.PT: "Suspensa por Processo Administrativo", + }, + }, + }, + Desc: i18n.String{ + i18n.EN: here.Doc(` + Indicates the ISS liability status, i.e., whether the ISS tax is due or not and why. + + List of codes taken from the national NFSe standard: + + * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download + (Section 10.2, Field B-38) + `), + }, + }, { Key: ExtKeyMunicipality, Name: i18n.String{ diff --git a/addons/br/nfse/nfse.go b/addons/br/nfse/nfse.go index 4497e0bc..fe4c1727 100644 --- a/addons/br/nfse/nfse.go +++ b/addons/br/nfse/nfse.go @@ -40,6 +40,8 @@ func validate(doc any) error { return validateLine(obj) case *org.Item: return validateItem(obj) + case *tax.Combo: + return validateTaxCombo(obj) } return nil } @@ -48,5 +50,7 @@ func normalize(doc any) { switch obj := doc.(type) { case *bill.Invoice: normalizeSupplier(obj.Supplier) + case *tax.Combo: + normalizeTaxCombo(obj) } } diff --git a/addons/br/nfse/tax_combo.go b/addons/br/nfse/tax_combo.go new file mode 100644 index 00000000..2879c08d --- /dev/null +++ b/addons/br/nfse/tax_combo.go @@ -0,0 +1,35 @@ +package nfse + +import ( + "github.com/invopop/gobl/regimes/br" + "github.com/invopop/gobl/tax" + "github.com/invopop/validation" +) + +const ( + // ISSLiabilityDefault is the default value for the ISS liability extension + ISSLiabilityDefault = "1" // Liable +) + +func validateTaxCombo(tc *tax.Combo) error { + return validation.ValidateStruct(tc, + validation.Field(&tc.Ext, + validation.When(tc.Category == br.TaxCategoryISS, + tax.ExtensionsRequires(ExtKeyISSLiability), + ), + ), + ) +} + +func normalizeTaxCombo(tc *tax.Combo) { + if tc == nil || tc.Category != br.TaxCategoryISS { + return + } + + if !tc.Ext.Has(ExtKeyISSLiability) { + if tc.Ext == nil { + tc.Ext = make(tax.Extensions) + } + tc.Ext[ExtKeyISSLiability] = ISSLiabilityDefault + } +} diff --git a/addons/br/nfse/tax_combo_test.go b/addons/br/nfse/tax_combo_test.go new file mode 100644 index 00000000..86e6b982 --- /dev/null +++ b/addons/br/nfse/tax_combo_test.go @@ -0,0 +1,105 @@ +package nfse_test + +import ( + "testing" + + "github.com/invopop/gobl/addons/br/nfse" + "github.com/invopop/gobl/regimes/br" + "github.com/invopop/gobl/tax" + "github.com/stretchr/testify/assert" +) + +func TestTaxComboValidation(t *testing.T) { + addon := tax.AddonForKey(nfse.V1) + + tests := []struct { + name string + tc *tax.Combo + err string + }{ + { + name: "valid ISS tax combo", + tc: &tax.Combo{ + Category: br.TaxCategoryISS, + Ext: tax.Extensions{ + nfse.ExtKeyISSLiability: "1", + }, + }, + }, + { + name: "valid non-ISS tax combo", + tc: &tax.Combo{ + Category: br.TaxCategoryPIS, + }, + }, + { + name: "missing ISS liability", + tc: &tax.Combo{ + Category: br.TaxCategoryISS, + }, + err: "br-nfse-iss-liability: required", + }, + } + for _, ts := range tests { + t.Run(ts.name, func(t *testing.T) { + err := addon.Validator(ts.tc) + if ts.err == "" { + assert.NoError(t, err) + } else { + if assert.Error(t, err) { + assert.ErrorContains(t, err, ts.err) + } + } + }) + } +} + +func TestTaxComboNormalization(t *testing.T) { + addon := tax.AddonForKey(nfse.V1) + + tests := []struct { + name string + tc *tax.Combo + out tax.ExtValue + }{ + { + name: "no tax combo", + tc: nil, + }, + { + name: "sets default ISS liability", + tc: &tax.Combo{ + Category: br.TaxCategoryISS, + }, + out: "1", + }, + { + name: "does not override ISS liability", + tc: &tax.Combo{ + Category: br.TaxCategoryISS, + Ext: tax.Extensions{ + nfse.ExtKeyISSLiability: "2", + }, + }, + out: "2", + }, + { + name: "non-ISS tax combo", + tc: &tax.Combo{ + Category: br.TaxCategoryPIS, + }, + }, + } + for _, ts := range tests { + t.Run(ts.name, func(t *testing.T) { + addon.Normalizer(ts.tc) + if ts.tc == nil { + assert.Nil(t, ts.tc) + } else { + assert.NotNil(t, ts.tc) + assert.Equal(t, ts.out, ts.tc.Ext[nfse.ExtKeyISSLiability]) + } + }) + } + +} diff --git a/data/addons/br-nfse-v1.json b/data/addons/br-nfse-v1.json index 1230978e..7dca06f4 100644 --- a/data/addons/br-nfse-v1.json +++ b/data/addons/br-nfse-v1.json @@ -41,6 +41,67 @@ } ] }, + { + "key": "br-nfse-iss-liability", + "name": { + "en": "ISS Liability", + "pt": "Exigibilidade ISS" + }, + "desc": { + "en": "Indicates the ISS liability status, i.e., whether the ISS tax is due or not and why.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-38)" + }, + "values": [ + { + "value": "1", + "name": { + "en": "Liable", + "pt": "Exigível" + } + }, + { + "value": "2", + "name": { + "en": "Not subject", + "pt": "Não incidência" + } + }, + { + "value": "3", + "name": { + "en": "Exempt", + "pt": "Isenção" + } + }, + { + "value": "4", + "name": { + "en": "Export", + "pt": "Exportação" + } + }, + { + "value": "5", + "name": { + "en": "Immune", + "pt": "Imunidade" + } + }, + { + "value": "6", + "name": { + "en": "Suspended Judicially", + "pt": "Suspensa por Decisão Judicial" + } + }, + { + "value": "7", + "name": { + "en": "Suspended Administratively", + "pt": "Suspensa por Processo Administrativo" + } + } + ] + }, { "key": "br-nfse-municipality", "name": { diff --git a/examples/br/invoice-services.json b/examples/br/invoice-services.json index 1c7be41f..9c985ff7 100644 --- a/examples/br/invoice-services.json +++ b/examples/br/invoice-services.json @@ -79,7 +79,10 @@ "taxes": [ { "cat": "ISS", - "percent": "15%" + "percent": "15%", + "ext": { + "br-nfse-iss-liability": "1" + } } ] } diff --git a/examples/br/out/invoice-services.json b/examples/br/out/invoice-services.json index 90fc13be..d93efb63 100644 --- a/examples/br/out/invoice-services.json +++ b/examples/br/out/invoice-services.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "31573fb261adbfa6011c8c21e5e53c4a7546389e6a39fc47124d0e34fb698811" + "val": "6b9933fc6209359b95583878eb64c358ac1b9028727c3e992ec4e78f7a196687" } }, "doc": { @@ -91,7 +91,10 @@ "taxes": [ { "cat": "ISS", - "percent": "15%" + "percent": "15%", + "ext": { + "br-nfse-iss-liability": "1" + } } ], "total": "1500.00" @@ -106,6 +109,9 @@ "code": "ISS", "rates": [ { + "ext": { + "br-nfse-iss-liability": "1" + }, "base": "1500.00", "percent": "15%", "amount": "225.00" diff --git a/regimes/br/README.md b/regimes/br/README.md index 6530e6ea..57a38e53 100644 --- a/regimes/br/README.md +++ b/regimes/br/README.md @@ -181,4 +181,31 @@ For example: //... ``` +#### ISS Liability +Report the ISS liability -i.e. whether the tax is due or not and why– using the `br-nfse-iss-liability` extension at ISS tax level. Find the list of possible codes below: + +| Code | Description | +| ---- | -------------------------- | +| `1` | Liable (Default) | +| `2` | Not subject | +| `3` | Exempt | +| `4` | Export | +| `5` | Immune | +| `6` | Suspended Judicially | +| `7` | Suspended Administratively | + +For example: + +```js +"lines": [ + { +//... + "taxes": [ + { + "cat": "ISS", + "ext": { + "br-nfse-iss-liability": "1" + } +//... +``` From fa0e310c41c95cf6ed00f73f1b92758e0b66e431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 07:07:37 +0000 Subject: [PATCH 3/8] Add invoice validations to br-nfse --- addons/br/nfse/invoices.go | 9 +++++++ addons/br/nfse/invoices_test.go | 43 ++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/addons/br/nfse/invoices.go b/addons/br/nfse/invoices.go index ec4946a6..a106a8cf 100644 --- a/addons/br/nfse/invoices.go +++ b/addons/br/nfse/invoices.go @@ -1,6 +1,8 @@ package nfse import ( + "regexp" + "github.com/invopop/gobl/bill" "github.com/invopop/gobl/org" "github.com/invopop/gobl/tax" @@ -12,12 +14,19 @@ const ( FiscalIncentiveDefault = "2" // No incentiva ) +var ( + // CodeRegexp is the regular expression used to validate the invoice code + CodeRegexp = regexp.MustCompile(`^[1-9][0-9]*$`) +) + func validateInvoice(inv *bill.Invoice) error { if inv == nil { return nil } return validation.ValidateStruct(inv, + validation.Field(&inv.Series, validation.Required), + validation.Field(&inv.Code, validation.Match(CodeRegexp)), validation.Field(&inv.Supplier, validation.By(validateSupplier), validation.Skip, diff --git a/addons/br/nfse/invoices_test.go b/addons/br/nfse/invoices_test.go index 222097bd..bf491c06 100644 --- a/addons/br/nfse/invoices_test.go +++ b/addons/br/nfse/invoices_test.go @@ -19,12 +19,40 @@ func TestInvoicesValidation(t *testing.T) { }{ { name: "valid invoice", - inv: &bill.Invoice{}, + inv: &bill.Invoice{ + Series: "SAMPLE", + }, }, { name: "nil invoice", inv: nil, }, + { + name: "missing series", + inv: &bill.Invoice{}, + err: "series: cannot be blank", + }, + { + name: "invalid code (non-digits)", + inv: &bill.Invoice{ + Code: "ABC-123", + }, + err: "code: must be in a valid format", + }, + { + name: "invalid code (padding zeroes)", + inv: &bill.Invoice{ + Code: "000123", + }, + err: "code: must be in a valid format", + }, + { + name: "valid code", + inv: &bill.Invoice{ + Series: "SAMPLE", + Code: "123000", + }, + }, { name: "charges present", inv: &bill.Invoice{ @@ -34,7 +62,7 @@ func TestInvoicesValidation(t *testing.T) { }, }, }, - err: "charges: not supported by nfse.", + err: "charges: not supported by nfse", }, { name: "discounts present", @@ -45,7 +73,12 @@ func TestInvoicesValidation(t *testing.T) { }, }, }, - err: "discounts: not supported by nfse.", + err: "discounts: not supported by nfse", + }, + { + name: "series missing", + inv: &bill.Invoice{}, + err: "series: cannot be blank", }, } @@ -54,7 +87,9 @@ func TestInvoicesValidation(t *testing.T) { t.Run(ts.name, func(t *testing.T) { err := addon.Validator(ts.inv) if ts.err == "" { - assert.NoError(t, err) + if err != nil { + assert.NotContains(t, err.Error(), ts.err) + } } else { if assert.Error(t, err) { assert.Contains(t, err.Error(), ts.err) From 78ccade06db25902ce95925f840d6a916239968d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 07:08:00 +0000 Subject: [PATCH 4/8] Improve br-nfse extensions documentation --- addons/br/nfse/extensions.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index 607a6f99..bc089f42 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -32,7 +32,7 @@ var extensions = []*cbc.KeyDefinition{ i18n.EN: here.Doc(` The CNAE (National Classification of Economic Activities) code for a service. - For further details on the list of possible codes, see: + List of codes from the IBGE (Brazilian Institute of Geography and Statistics): * https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html `), @@ -64,7 +64,7 @@ var extensions = []*cbc.KeyDefinition{ i18n.EN: here.Doc(` Indicates whether a party benefits from a fiscal incentive. - List of codes taken from the national NFSe standard: + List of codes from the national NFSe ABRASF (v2.04) model: * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-68) @@ -132,7 +132,7 @@ var extensions = []*cbc.KeyDefinition{ i18n.EN: here.Doc(` Indicates the ISS liability status, i.e., whether the ISS tax is due or not and why. - List of codes taken from the national NFSe standard: + List of codes from the national NFSe ABRASF (v2.04) model: * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-38) @@ -150,7 +150,7 @@ var extensions = []*cbc.KeyDefinition{ The municipality code as defined by the IGBE (Brazilian Institute of Geography and Statistics). - For further details on the list of possible codes, see: + List of codes from the IGBE: * https://www.ibge.gov.br/explica/codigos-dos-municipios.php `), @@ -200,7 +200,7 @@ var extensions = []*cbc.KeyDefinition{ i18n.EN: here.Doc(` Indicates whether a party is opting for the “Simples Nacional” tax regime. - List of codes taken from the national NFSe standard: + List of codes from the national NFSe ABRASF (v2.04) model: * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-67) @@ -259,9 +259,9 @@ var extensions = []*cbc.KeyDefinition{ }, Desc: i18n.String{ i18n.EN: here.Doc(` - Indicates a special tax regime that the party is subject to. + Indicates a special tax regime that a party is subject to. - List of codes taken from the national NFSe standard: + List of codes from the national NFSe ABRASF (v2.04) model: * https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download (Section 10.2, Field B-66) From bb73f9a847e2bcff69808f0015dbc2c737c9b402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 10:38:03 +0000 Subject: [PATCH 5/8] Add pattern for br-nfse CNAE extension --- addons/br/nfse/extensions.go | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index bc089f42..07cbf99f 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -37,6 +37,7 @@ var extensions = []*cbc.KeyDefinition{ * https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html `), }, + Pattern: `^\d{2}[\s\.\-\/]?\d{2}[\s\.\-\/]?\d[\s\.\-\/]?\d{2}$`, }, { Key: ExtKeyFiscalIncentive, From 08d41a80a1bf55e11cfefa4abde6e12790c7f553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 11:04:26 +0000 Subject: [PATCH 6/8] Refresh generated JSONs --- data/addons/br-nfse-v1.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/data/addons/br-nfse-v1.json b/data/addons/br-nfse-v1.json index 7dca06f4..c74084ca 100644 --- a/data/addons/br-nfse-v1.json +++ b/data/addons/br-nfse-v1.json @@ -12,8 +12,9 @@ "pt": "Código CNAE" }, "desc": { - "en": "The CNAE (National Classification of Economic Activities) code for a service.\n\nFor further details on the list of possible codes, see:\n\n* https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html" - } + "en": "The CNAE (National Classification of Economic Activities) code for a service.\n\nList of codes from the IBGE (Brazilian Institute of Geography and Statistics):\n\n* https://www.ibge.gov.br/en/statistics/technical-documents/statistical-lists-and-classifications/17245-national-classification-of-economic-activities.html" + }, + "pattern": "^\\d{2}[\\s\\.\\-\\/]?\\d{2}[\\s\\.\\-\\/]?\\d[\\s\\.\\-\\/]?\\d{2}$" }, { "key": "br-nfse-fiscal-incentive", @@ -22,7 +23,7 @@ "pt": "Incentivo Fiscal" }, "desc": { - "en": "Indicates whether a party benefits from a fiscal incentive.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-68)" + "en": "Indicates whether a party benefits from a fiscal incentive.\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-68)" }, "values": [ { @@ -48,7 +49,7 @@ "pt": "Exigibilidade ISS" }, "desc": { - "en": "Indicates the ISS liability status, i.e., whether the ISS tax is due or not and why.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-38)" + "en": "Indicates the ISS liability status, i.e., whether the ISS tax is due or not and why.\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-38)" }, "values": [ { @@ -109,7 +110,7 @@ "pt": "Código do Município do IBGE" }, "desc": { - "en": "The municipality code as defined by the IGBE (Brazilian Institute of Geography and\nStatistics).\n\nFor further details on the list of possible codes, see:\n\n* https://www.ibge.gov.br/explica/codigos-dos-municipios.php" + "en": "The municipality code as defined by the IGBE (Brazilian Institute of Geography and\nStatistics).\n\nList of codes from the IGBE:\n\n* https://www.ibge.gov.br/explica/codigos-dos-municipios.php" }, "pattern": "^\\d{7}$" }, @@ -130,7 +131,7 @@ "pt": "Optante pelo Simples Nacional" }, "desc": { - "en": "Indicates whether a party is opting for the “Simples Nacional” tax regime.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" + "en": "Indicates whether a party is opting for the “Simples Nacional” tax regime.\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" }, "values": [ { @@ -156,7 +157,7 @@ "pt": "Regime Especial de Tributação" }, "desc": { - "en": "Indicates a special tax regime that the party is subject to.\n\nList of codes taken from the national NFSe standard:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-66)" + "en": "Indicates a special tax regime that a party is subject to.\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-66)" }, "values": [ { From 111ca34f075201e22388582815c52ba07b601684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 11:10:55 +0000 Subject: [PATCH 7/8] Document br-nfse-municipality used at tax level --- regimes/br/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/regimes/br/README.md b/regimes/br/README.md index 57a38e53..51272fed 100644 --- a/regimes/br/README.md +++ b/regimes/br/README.md @@ -62,6 +62,23 @@ For example: //... ``` +Use the same extension at tax level when the municipality where the ISS is levied differs from the supplier's. + +For example: + +```js +"lines": [ + { +//... + "taxes": [ + { + "cat": "ISS", + "ext": { + "br-nfse-municipality": "2927408" + } +//... +``` + #### National and municipal registration Specify the party's municipal and national registration numbers as identities using the `br-nfse-municipal-reg` and `br-nfse-national-reg` keys. From 44a3b9ee9e6c524f59385ad1886f05340421f7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Thu, 21 Nov 2024 16:01:49 +0000 Subject: [PATCH 8/8] Rename and better document br-nfse-simples ext --- addons/br/nfse/extensions.go | 10 ++++++---- addons/br/nfse/invoices.go | 2 +- addons/br/nfse/invoices_test.go | 6 +++--- data/addons/br-nfse-v1.json | 6 +++--- examples/br/invoice-services.json | 2 +- examples/br/out/invoice-services.json | 4 ++-- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index 07cbf99f..4dbe1faa 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -17,7 +17,7 @@ const ( ExtKeyISSLiability = "br-nfse-iss-liability" ExtKeyMunicipality = "br-nfse-municipality" ExtKeyService = "br-nfse-service" - ExtKeySimplesNacional = "br-nfse-simples-nacional" + ExtKeySimples = "br-nfse-simples" ExtKeySpecialRegime = "br-nfse-special-regime" ) @@ -176,9 +176,9 @@ var extensions = []*cbc.KeyDefinition{ }, }, { - Key: ExtKeySimplesNacional, + Key: ExtKeySimples, Name: i18n.String{ - i18n.EN: "Opting for “Simples Nacional”", + i18n.EN: "Opting for “Simples Nacional” regime", i18n.PT: "Optante pelo Simples Nacional", }, Values: []*cbc.ValueDefinition{ @@ -199,7 +199,9 @@ var extensions = []*cbc.KeyDefinition{ }, Desc: i18n.String{ i18n.EN: here.Doc(` - Indicates whether a party is opting for the “Simples Nacional” tax regime. + Indicates whether a party is opting for the “Simples Nacional” (Regime Especial + Unificado de Arrecadação de Tributos e Contribuições devidos pelas Microempresas e + Empresas de Pequeno Porte) tax regime List of codes from the national NFSe ABRASF (v2.04) model: diff --git a/addons/br/nfse/invoices.go b/addons/br/nfse/invoices.go index a106a8cf..04e2ffcf 100644 --- a/addons/br/nfse/invoices.go +++ b/addons/br/nfse/invoices.go @@ -69,7 +69,7 @@ func validateSupplier(value interface{}) error { ), validation.Field(&obj.Ext, tax.ExtensionsRequires( - ExtKeySimplesNacional, + ExtKeySimples, ExtKeyMunicipality, ExtKeyFiscalIncentive, ), diff --git a/addons/br/nfse/invoices_test.go b/addons/br/nfse/invoices_test.go index bf491c06..050e5984 100644 --- a/addons/br/nfse/invoices_test.go +++ b/addons/br/nfse/invoices_test.go @@ -207,19 +207,19 @@ func TestSuppliersValidation(t *testing.T) { } err := addon.Validator(inv) if assert.Error(t, err) { - assert.Contains(t, err.Error(), "br-nfse-simples-nacional: required") + assert.Contains(t, err.Error(), "br-nfse-simples: required") assert.Contains(t, err.Error(), "br-nfse-municipality: required") assert.Contains(t, err.Error(), "br-nfse-fiscal-incentive: required") } sup.Ext = tax.Extensions{ - nfse.ExtKeySimplesNacional: "1", + nfse.ExtKeySimples: "1", nfse.ExtKeyMunicipality: "12345678", nfse.ExtKeyFiscalIncentive: "2", } err = addon.Validator(inv) if assert.Error(t, err) { - assert.NotContains(t, err.Error(), "br-nfse-simples-nacional: required") + assert.NotContains(t, err.Error(), "br-nfse-simples: required") assert.NotContains(t, err.Error(), "br-nfse-municipality: required") assert.NotContains(t, err.Error(), "br-nfse-fiscal-incentive: required") } diff --git a/data/addons/br-nfse-v1.json b/data/addons/br-nfse-v1.json index c74084ca..6d9ad191 100644 --- a/data/addons/br-nfse-v1.json +++ b/data/addons/br-nfse-v1.json @@ -125,13 +125,13 @@ } }, { - "key": "br-nfse-simples-nacional", + "key": "br-nfse-simples", "name": { - "en": "Opting for “Simples Nacional”", + "en": "Opting for “Simples Nacional” regime", "pt": "Optante pelo Simples Nacional" }, "desc": { - "en": "Indicates whether a party is opting for the “Simples Nacional” tax regime.\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" + "en": "Indicates whether a party is opting for the “Simples Nacional” (Regime Especial\nUnificado de Arrecadação de Tributos e Contribuições devidos pelas Microempresas e\nEmpresas de Pequeno Porte) tax regime\n\nList of codes from the national NFSe ABRASF (v2.04) model:\n\n* https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download\n(Section 10.2, Field B-67)" }, "values": [ { diff --git a/examples/br/invoice-services.json b/examples/br/invoice-services.json index 9c985ff7..dc49ef87 100644 --- a/examples/br/invoice-services.json +++ b/examples/br/invoice-services.json @@ -41,7 +41,7 @@ "ext": { "br-nfse-municipality": "3304557", "br-nfse-fiscal-incentive": "1", - "br-nfse-simples-nacional": "1", + "br-nfse-simples": "1", "br-nfse-special-regime": "4" } }, diff --git a/examples/br/out/invoice-services.json b/examples/br/out/invoice-services.json index d93efb63..f87919ea 100644 --- a/examples/br/out/invoice-services.json +++ b/examples/br/out/invoice-services.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "6b9933fc6209359b95583878eb64c358ac1b9028727c3e992ec4e78f7a196687" + "val": "d7ef87ccb18475563f8ce2aa804cd51e46b94c3adc6af2bb0867a72f13ad5ac1" } }, "doc": { @@ -52,7 +52,7 @@ "ext": { "br-nfse-fiscal-incentive": "1", "br-nfse-municipality": "3304557", - "br-nfse-simples-nacional": "1", + "br-nfse-simples": "1", "br-nfse-special-regime": "4" } },