Skip to content

Commit

Permalink
Merge pull request #396 from invopop/br-regime
Browse files Browse the repository at this point in the history
Brazil regime
  • Loading branch information
cavalle authored Oct 21, 2024
2 parents d8d5f16 + ab05aa7 commit 65c59e5
Show file tree
Hide file tree
Showing 12 changed files with 498 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

### Added

- `br`: added basic Brazil regime
- `uuid` - SQL library compatibility for type conversion.

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions regimes/br/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 🇧🇷 GOBL Brazil Tax Regime

Example BR GOBL files can be found in the [`examples`](./examples) (YAML uncalculated documents) and [`examples/out`](./examples/out) (JSON calculated envelopes) subdirectories.
60 changes: 60 additions & 0 deletions regimes/br/br.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Package br provides the tax region definition for Brazil.
package br

import (
"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/currency"
"github.com/invopop/gobl/i18n"
"github.com/invopop/gobl/regimes/common"
"github.com/invopop/gobl/tax"
)

func init() {
tax.RegisterRegimeDef(New())
}

// New provides the tax region definition
func New() *tax.RegimeDef {
return &tax.RegimeDef{
Country: "BR",
Currency: currency.BRL,
Name: i18n.String{
i18n.EN: "Brazil",
i18n.PT: "Brasil",
},
TimeZone: "America/Sao_Paulo",
Validator: Validate,
Tags: []*tax.TagSet{
common.InvoiceTags(),
},
Categories: taxCategories,
Corrections: []*tax.CorrectionDefinition{
{
Schema: bill.ShortSchemaInvoice,
Types: []cbc.Key{
bill.InvoiceTypeCreditNote,
},
},
},
}
}

// Validate checks the document type and determines if it can be validated.
func Validate(doc interface{}) error {
switch obj := doc.(type) {
case *bill.Invoice:
return validateInvoice(obj)
case *tax.Identity:
return validateTaxIdentity(obj)
}
return nil
}

// Normalize will attempt to clean the object passed to it.
func Normalize(doc interface{}) {
switch obj := doc.(type) {
case *tax.Identity:
tax.NormalizeIdentity(obj)
}
}
38 changes: 38 additions & 0 deletions regimes/br/examples/invoice-br-br.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
$schema: "https://gobl.org/draft-0/bill/invoice"
uuid: "3aea7b56-59d8-4beb-90bd-f8f280d852a0"
currency: "BRL"
issue_date: "2023-04-21"
series: "SAMPLE"
code: "001"

supplier:
tax_id:
country: "BR"
name: "TechSolutions Brasil Ltda."
emails:
- addr: "[email protected]"
addresses:
- num: "595"
street: "Rua Haddock Lobo"
locality: "São Paulo"
region: "SP"
code: "01311-000"
country: "BR"

customer:
name: "Sample Consumer"
emails:
- addr: "[email protected]"

lines:
- quantity: 20
item:
name: "Development services"
price: "90.00"
unit: "h"
discounts:
- percent: "10%"
reason: "Special discount"
taxes:
- cat: ISS
percent: "15%"
98 changes: 98 additions & 0 deletions regimes/br/examples/out/invoice-br-br.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"$schema": "https://gobl.org/draft-0/envelope",
"head": {
"uuid": "8a51fd30-2a27-11ee-be56-0242ac120002",
"dig": {
"alg": "sha256",
"val": "5b7bb418d378de68d73da3f12f211648c5238e8ba916f4f5f00b2712f24d2476"
}
},
"doc": {
"$schema": "https://gobl.org/draft-0/bill/invoice",
"$regime": "BR",
"uuid": "3aea7b56-59d8-4beb-90bd-f8f280d852a0",
"type": "standard",
"series": "SAMPLE",
"code": "001",
"issue_date": "2023-04-21",
"currency": "BRL",
"supplier": {
"name": "TechSolutions Brasil Ltda.",
"tax_id": {
"country": "BR"
},
"addresses": [
{
"num": "595",
"street": "Rua Haddock Lobo",
"locality": "São Paulo",
"region": "SP",
"code": "01311-000",
"country": "BR"
}
],
"emails": [
{
"addr": "[email protected]"
}
]
},
"customer": {
"name": "Sample Consumer",
"emails": [
{
"addr": "[email protected]"
}
]
},
"lines": [
{
"i": 1,
"quantity": "20",
"item": {
"name": "Development services",
"price": "90.00",
"unit": "h"
},
"sum": "1800.00",
"discounts": [
{
"percent": "10%",
"amount": "180.00",
"reason": "Special discount"
}
],
"taxes": [
{
"cat": "ISS",
"percent": "15%"
}
],
"total": "1620.00"
}
],
"totals": {
"sum": "1620.00",
"total": "1620.00",
"taxes": {
"categories": [
{
"code": "ISS",
"rates": [
{
"base": "1620.00",
"percent": "15%",
"amount": "243.00"
}
],
"amount": "243.00"
}
],
"sum": "243.00"
},
"tax": "243.00",
"total_with_tax": "1863.00",
"payable": "1863.00"
}
}
}
12 changes: 12 additions & 0 deletions regimes/br/invoices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package br

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

func validateInvoice(inv *bill.Invoice) error {
return validation.ValidateStruct(inv,
validation.Field(&inv.Supplier, validation.Required),
)
}
30 changes: 30 additions & 0 deletions regimes/br/invoices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package br_test

import (
"testing"

"github.com/invopop/gobl/bill"
"github.com/invopop/gobl/org"
"github.com/invopop/gobl/regimes/br"
"github.com/stretchr/testify/assert"
)

func TestInvoiceValidation(t *testing.T) {
t.Run("supplier required", func(t *testing.T) {
inv := new(bill.Invoice)
err := br.Validate(inv)
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "supplier: cannot be blank")
}
})

t.Run("valid invoice", func(t *testing.T) {
inv := &bill.Invoice{
Supplier: &org.Party{
Name: "Test Supplier",
},
}
err := br.Validate(inv)
assert.NoError(t, err)
})
}
94 changes: 94 additions & 0 deletions regimes/br/tax_categories.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package br

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

// Tax categories specific for Brazil.
const (
TaxCategoryISS cbc.Code = "ISS"
TaxCategoryICMS cbc.Code = "ICMS"
TaxCategoryIPI cbc.Code = "IPI"
TaxCategoryPIS cbc.Code = "PIS"
TaxCategoryCOFINS cbc.Code = "COFINS"
)

var taxCategories = []*tax.CategoryDef{
//
// Municipal Service Tax (ISS)
//
{
Code: TaxCategoryISS,
Name: i18n.String{
i18n.EN: "ISS",
i18n.PT: "ISS",
},
Title: i18n.String{
i18n.EN: "Municipal Service Tax",
i18n.PT: "Imposto Sobre Serviços",
},
Retained: false,
},
//
// State value-added tax (ICMS)
//
{
Code: TaxCategoryICMS,
Name: i18n.String{
i18n.EN: "ICMS",
i18n.PT: "ICMS",
},
Title: i18n.String{
i18n.EN: "State value-added tax",
i18n.PT: "Imposto sobre Circulação de Mercadorias e Serviços",
},
Retained: false,
},
//
// Federal value-added Tax (IPI)
//
{
Code: TaxCategoryIPI,
Name: i18n.String{
i18n.EN: "IPI",
i18n.PT: "IPI",
},
Title: i18n.String{
i18n.EN: "Federal value-added Tax",
i18n.PT: "Imposto sobre Produtos Industrializados",
},
Retained: false,
},
//
// Social Integration Program (PIS)
//
{
Code: TaxCategoryPIS,
Name: i18n.String{
i18n.EN: "PIS",
i18n.PT: "PIS",
},
Title: i18n.String{
i18n.EN: "Social Integration Program",
i18n.PT: "Programa de Integração Social",
},
Retained: true,
},
//
// Contribution for the Financing of Social Security (COFINS)
//
{
Code: TaxCategoryCOFINS,
Name: i18n.String{
i18n.EN: "COFINS",
i18n.PT: "COFINS",
},
Title: i18n.String{
i18n.EN: "Contribution for the Financing of Social Security",
i18n.PT: "Contribuição para o Financiamento da Seguridade Social",
},
Retained: true,
},
}
Loading

0 comments on commit 65c59e5

Please sign in to comment.