Skip to content

Commit

Permalink
Merge pull request #427 from invopop/br-issuer
Browse files Browse the repository at this point in the history
Brazil suppliers
  • Loading branch information
cavalle authored Nov 21, 2024
2 parents e049a88 + 20f27e6 commit 42442af
Show file tree
Hide file tree
Showing 27 changed files with 1,178 additions and 145 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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

### Changes

Expand Down
154 changes: 152 additions & 2 deletions addons/br/nfse/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,70 @@ import (
"github.com/invopop/gobl/pkg/here"
)

// Brazilian extension keys required to issue NFS-e documents.
// Brazilian extension keys required to issue NFS-e documents. In an initial
// assessment, these extensions do not seem to apply to documents other than
// NFS-e. However, if when implementing other Fiscal Notes it is found that some
// of these extensions are common, they can be moved to the regime or to a
// shared addon.
const (
ExtKeyService = "br-nfse-service"
ExtKeyFiscalIncentive = "br-nfse-fiscal-incentive"
ExtKeyMunicipality = "br-nfse-municipality"
ExtKeyService = "br-nfse-service"
ExtKeySimplesNacional = "br-nfse-simples-nacional"
ExtKeySpecialRegime = "br-nfse-special-regime"
)

var extensions = []*cbc.KeyDefinition{
{
Key: ExtKeyFiscalIncentive,
Name: i18n.String{
i18n.EN: "Fiscal Incentive",
i18n.PT: "Incentivo Fiscal",
},
Values: []*cbc.ValueDefinition{
{
Value: "1",
Name: i18n.String{
i18n.EN: "Has incentive",
i18n.PT: "Possui incentivo",
},
},
{
Value: "2",
Name: i18n.String{
i18n.EN: "Does not have incentive",
i18n.PT: "Não possui incentivo",
},
},
},
Desc: i18n.String{
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
(Section 10.2, Field B-68)
`),
},
},
{
Key: ExtKeyMunicipality,
Name: i18n.String{
i18n.EN: "IGBE Municipality Code",
i18n.PT: "Código do Município do IBGE",
},
Desc: i18n.String{
i18n.EN: here.Doc(`
The municipality code as defined by the IGBE (Brazilian Institute of Geography and
Statistics).
For further details on the list of possible codes, see:
* https://www.ibge.gov.br/explica/codigos-dos-municipios.php
`),
},
Pattern: `^\d{7}$`,
},
{
Key: ExtKeyService,
Name: i18n.String{
Expand All @@ -29,4 +87,96 @@ var extensions = []*cbc.KeyDefinition{
`),
},
},
{
Key: ExtKeySimplesNacional,
Name: i18n.String{
i18n.EN: "Opting for “Simples Nacional”",
i18n.PT: "Optante pelo Simples Nacional",
},
Values: []*cbc.ValueDefinition{
{
Value: "1",
Name: i18n.String{
i18n.EN: "Opt-in",
i18n.PT: "Optante",
},
},
{
Value: "2",
Name: i18n.String{
i18n.EN: "Opt-out",
i18n.PT: "Não optante",
},
},
},
Desc: i18n.String{
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:
https://abrasf.org.br/biblioteca/arquivos-publicos/nfs-e-manual-de-orientacao-do-contribuinte-2-04/download
(Section 10.2, Field B-67)
`),
},
},
{
Key: ExtKeySpecialRegime,
Name: i18n.String{
i18n.EN: "Special Tax Regime",
i18n.PT: "Regime Especial de Tributação",
},
Values: []*cbc.ValueDefinition{
{
Value: "1",
Name: i18n.String{
i18n.EN: "Municipal micro-enterprise",
i18n.PT: "Microempresa municipal",
},
},
{
Value: "2",
Name: i18n.String{
i18n.EN: "Estimated",
i18n.PT: "Estimativa",
},
},
{
Value: "3",
Name: i18n.String{
i18n.EN: "Professional Society",
i18n.PT: "Sociedade de profissionais",
},
},
{
Value: "4",
Name: i18n.String{
i18n.EN: "Cooperative",
i18n.PT: "Cooperativa",
},
},
{
Value: "5",
Name: i18n.String{
i18n.EN: "Single micro-entrepreneur (MEI)",
i18n.PT: "Microempreendedor individual (MEI)",
},
},
{
Value: "6",
Name: i18n.String{
i18n.EN: "Micro-enterprise or Small Business (ME EPP)",
i18n.PT: "Microempresa ou Empresa de Pequeno Porte (ME EPP).",
},
},
},
Desc: i18n.String{
i18n.EN: here.Doc(`
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
(Section 10.2, Field B-66)
`),
},
},
}
33 changes: 33 additions & 0 deletions addons/br/nfse/identities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package nfse

import (
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/i18n"
)

// Brazilian identity keys required to issue NFS-e documents. In an initial
// assessment, these identities do not seem to apply to documents other than
// NFS-e. However, if when implementing other Fiscal Notes it is found that some
// of these extensions are common, they can be moved to the regime or to a
// shared addon.
const (
IdentityKeyMunicipalReg = "br-nfse-municipal-reg"
IdentityKeyNationalReg = "br-nfse-national-reg"
)

var identities = []*cbc.KeyDefinition{
{
Key: IdentityKeyMunicipalReg,
Name: i18n.String{
i18n.EN: "Company Municipal Registration",
i18n.PT: "Inscrição Municipal da Empresa",
},
},
{
Key: IdentityKeyNationalReg,
Name: i18n.String{
i18n.EN: "Company National Registration",
i18n.PT: "Inscrição Nacional da Empresa",
},
},
}
75 changes: 75 additions & 0 deletions addons/br/nfse/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ package nfse

import (
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/org"
"github.com/invopop/gobl/tax"
"github.com/invopop/validation"
)

const (
// FiscalIncentiveDefault is the default value for the fiscal incentive extenstion
FiscalIncentiveDefault = "2" // No incentiva
)

func validateInvoice(inv *bill.Invoice) error {
if inv == nil {
return nil
}

return validation.ValidateStruct(inv,
validation.Field(&inv.Supplier,
validation.By(validateSupplier),
validation.Skip,
),
validation.Field(&inv.Charges,
validation.Empty.Error("not supported by nfse"),
validation.Skip,
Expand All @@ -21,3 +32,67 @@ func validateInvoice(inv *bill.Invoice) error {
),
)
}

func validateSupplier(value interface{}) error {
obj, _ := value.(*org.Party)
if obj == nil {
return nil
}

return validation.ValidateStruct(obj,
validation.Field(&obj.TaxID,
validation.Required,
tax.RequireIdentityCode,
validation.Skip,
),
validation.Field(&obj.Identities,
org.RequireIdentityKey(IdentityKeyMunicipalReg),
validation.Skip,
),
validation.Field(&obj.Name, validation.Required),
validation.Field(&obj.Addresses,
validation.Required,
validation.Each(
validation.Required,
validation.By(validateSupplierAddress),
),
validation.Skip,
),
validation.Field(&obj.Ext,
tax.ExtensionsRequires(
ExtKeySimplesNacional,
ExtKeyMunicipality,
ExtKeyFiscalIncentive,
),
validation.Skip,
),
)
}

func validateSupplierAddress(value interface{}) error {
obj, _ := value.(*org.Address)
if obj == nil {
return nil
}

return validation.ValidateStruct(obj,
validation.Field(&obj.Street, validation.Required),
validation.Field(&obj.Number, validation.Required),
validation.Field(&obj.Locality, validation.Required),
validation.Field(&obj.State, validation.Required),
validation.Field(&obj.Code, validation.Required),
)
}

func normalizeSupplier(sup *org.Party) {
if sup == nil {
return
}

if !sup.Ext.Has(ExtKeyFiscalIncentive) {
if sup.Ext == nil {
sup.Ext = make(tax.Extensions)
}
sup.Ext[ExtKeyFiscalIncentive] = FiscalIncentiveDefault
}
}
Loading

0 comments on commit 42442af

Please sign in to comment.