Skip to content

Commit

Permalink
Add ISS liability extension to br-nfse addon
Browse files Browse the repository at this point in the history
  • Loading branch information
cavalle committed Nov 19, 2024
1 parent a1871c8 commit ebd7c83
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 3 deletions.
69 changes: 69 additions & 0 deletions addons/br/nfse/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{
Expand Down
4 changes: 4 additions & 0 deletions addons/br/nfse/nfse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -48,5 +50,7 @@ func normalize(doc any) {
switch obj := doc.(type) {
case *bill.Invoice:
normalizeSupplier(obj.Supplier)
case *tax.Combo:
normalizeTaxCombo(obj)
}
}
35 changes: 35 additions & 0 deletions addons/br/nfse/tax_combo.go
Original file line number Diff line number Diff line change
@@ -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
}
}
105 changes: 105 additions & 0 deletions addons/br/nfse/tax_combo_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
})
}

}
61 changes: 61 additions & 0 deletions data/addons/br-nfse-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
5 changes: 4 additions & 1 deletion examples/br/invoice-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@
"taxes": [
{
"cat": "ISS",
"percent": "15%"
"percent": "15%",
"ext": {
"br-nfse-iss-liability": "1"
}
}
]
}
Expand Down
10 changes: 8 additions & 2 deletions examples/br/out/invoice-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"uuid": "8a51fd30-2a27-11ee-be56-0242ac120002",
"dig": {
"alg": "sha256",
"val": "31573fb261adbfa6011c8c21e5e53c4a7546389e6a39fc47124d0e34fb698811"
"val": "6b9933fc6209359b95583878eb64c358ac1b9028727c3e992ec4e78f7a196687"
}
},
"doc": {
Expand Down Expand Up @@ -91,7 +91,10 @@
"taxes": [
{
"cat": "ISS",
"percent": "15%"
"percent": "15%",
"ext": {
"br-nfse-iss-liability": "1"
}
}
],
"total": "1500.00"
Expand All @@ -106,6 +109,9 @@
"code": "ISS",
"rates": [
{
"ext": {
"br-nfse-iss-liability": "1"
},
"base": "1500.00",
"percent": "15%",
"amount": "225.00"
Expand Down
27 changes: 27 additions & 0 deletions regimes/br/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
//...
```

0 comments on commit ebd7c83

Please sign in to comment.