diff --git a/CHANGELOG.md b/CHANGELOG.md index c2aeea31..2f87c4e7 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 - `ae`: added UAE regime -- `br`: supplier extensions, validations & identities +- `br-nfse-v1`: new extensions, validations & identities for the typical service note and supplier. ## [v0.205.1] - 2024-11-19 diff --git a/addons/br/nfse/extensions.go b/addons/br/nfse/extensions.go index 5edd2598..4dbe1faa 100644 --- a/addons/br/nfse/extensions.go +++ b/addons/br/nfse/extensions.go @@ -12,14 +12,33 @@ 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" + 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" ) 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. + + 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 + `), + }, + Pattern: `^\d{2}[\s\.\-\/]?\d{2}[\s\.\-\/]?\d[\s\.\-\/]?\d{2}$`, + }, { Key: ExtKeyFiscalIncentive, Name: i18n.String{ @@ -46,12 +65,81 @@ 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: - https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download + 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) `), }, }, + { + 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 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) + `), + }, + }, { Key: ExtKeyMunicipality, Name: i18n.String{ @@ -63,7 +151,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 `), @@ -88,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{ @@ -111,10 +199,13 @@ 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: - 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) `), }, @@ -171,10 +262,11 @@ 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 from the national NFSe ABRASF (v2.04) model: - 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/addons/br/nfse/invoices.go b/addons/br/nfse/invoices.go index ec4946a6..04e2ffcf 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, @@ -60,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 222097bd..050e5984 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) @@ -172,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/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 062f8285..6d9ad191 100644 --- a/data/addons/br-nfse-v1.json +++ b/data/addons/br-nfse-v1.json @@ -5,6 +5,17 @@ "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\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", "name": { @@ -12,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:\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 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": [ { @@ -31,6 +42,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 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": [ + { + "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": { @@ -38,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}$" }, @@ -53,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 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” (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": [ { @@ -85,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:\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 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": [ { diff --git a/examples/br/invoice-services.json b/examples/br/invoice-services.json index b99630d0..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" } }, @@ -72,13 +72,17 @@ "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": [ { "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 a8252b84..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": "b1fc7b37b41dc9df2148365e52dd97cdc3a3847de92d5b7560b3cf40cda10f77" + "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" } }, @@ -83,6 +83,7 @@ "name": "Consultancy Services", "price": "100.00", "ext": { + "br-nfse-cnae": "62.01-5-01", "br-nfse-service": "10.5" } }, @@ -90,7 +91,10 @@ "taxes": [ { "cat": "ISS", - "percent": "15%" + "percent": "15%", + "ext": { + "br-nfse-iss-liability": "1" + } } ], "total": "1500.00" @@ -105,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 f9d4e34b..51272fed 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,28 @@ For example: ```js "supplier": { - //... +//... "ext": { - "br-igbe-municipality": "2927408" + "br-nfse-municipality": "2927408" }, - //... +//... +``` + +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 @@ -70,7 +87,7 @@ For example: ```js "supplier": { - //... +//... "identities": [ { "key": "br-nfse-municipal-reg", @@ -81,7 +98,7 @@ For example: "code": "12345012345678" } ], - //... +//... ``` #### “Simples Nacional” @@ -97,11 +114,11 @@ For example: ```js "supplier": { - //... +//... "ext": { "br-nfse-simples-nacional": "1", // Opt-in }, - //... +//... ``` #### Special Tax Regime @@ -121,11 +138,11 @@ For example: ```js "supplier": { - //... +//... "ext": { "br-nfse-special-regime": "4" // Cooperative }, - //... +//... ``` #### Fiscal Incentive @@ -141,9 +158,71 @@ 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" +//... +``` + +#### 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" + } +//... ```