Skip to content

Commit

Permalink
Convert to EstadoDeCuentaCombustible complement
Browse files Browse the repository at this point in the history
  • Loading branch information
cavalle committed Oct 4, 2023
1 parent fd82583 commit 0ef4e31
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 2,389 deletions.
35 changes: 30 additions & 5 deletions cfdi.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ import (
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/num"
"github.com/invopop/gobl/regimes/mx"
"github.com/invopop/gobl/schema"
"github.com/invopop/gobl/tax"
)

// CFDI schema constants
const (
CFDINamespace = "http://www.sat.gob.mx/cfd/4"
XSINamespace = "http://www.w3.org/2001/XMLSchema-instance"
SchemaLocation = "http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd"
CFDIVersion = "4.0"
CFDINamespace = "http://www.sat.gob.mx/cfd/4"
CFDISchemaLocation = "http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd"
XSINamespace = "http://www.w3.org/2001/XMLSchema-instance"
CFDIVersion = "4.0"
)

// Hard-coded values for (yet) unsupported mappings
Expand All @@ -38,6 +39,7 @@ type Document struct {
XMLName xml.Name `xml:"cfdi:Comprobante"`
CFDINamespace string `xml:"xmlns:cfdi,attr"`
XSINamespace string `xml:"xmlns:xsi,attr"`
ECCNamespace string `xml:"xmlns:ecc12,attr,omitempty"`
SchemaLocation string `xml:"xsi:schemaLocation,attr"`
Version string `xml:"Version,attr"`

Expand All @@ -63,6 +65,8 @@ type Document struct {
Receptor *Receptor `xml:"cfdi:Receptor"`
Conceptos *Conceptos `xml:"cfdi:Conceptos"` //nolint:misspell
Impuestos *Impuestos `xml:"cfdi:Impuestos,omitempty"`

Complementos []interface{} `xml:"cfdi:Complemento>*,omitempty"`
}

// NewDocument converts a GOBL envelope into a CFDI document
Expand All @@ -78,7 +82,7 @@ func NewDocument(env *gobl.Envelope) (*Document, error) {
document := &Document{
CFDINamespace: CFDINamespace,
XSINamespace: XSINamespace,
SchemaLocation: SchemaLocation,
SchemaLocation: formatSchemaLocation(CFDINamespace, CFDISchemaLocation),
Version: CFDIVersion,

TipoDeComprobante: lookupTipoDeComprobante(inv),
Expand All @@ -104,6 +108,10 @@ func NewDocument(env *gobl.Envelope) (*Document, error) {
Impuestos: newImpuestos(inv.Totals, &inv.Currency),
}

if err := addComplementos(document, inv.Complements); err != nil {
return nil, err
}

return document, nil
}

Expand All @@ -117,6 +125,19 @@ func (d *Document) Bytes() ([]byte, error) {
return append([]byte(xml.Header), bytes...), nil
}

func addComplementos(d *Document, complements []*schema.Object) error {
for _, c := range complements {
switch o := c.Instance().(type) {
case *mx.FuelAccountBalance:

Check failure on line 131 in cfdi.go

View workflow job for this annotation

GitHub Actions / Test, Build

undefined: mx.FuelAccountBalance

Check failure on line 131 in cfdi.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountBalance (typecheck)

Check failure on line 131 in cfdi.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountBalance) (typecheck)
addEstadoCuentaCombustible(d, o)
default:
return fmt.Errorf("unsupported complement %T", o)
}
}

return nil
}

func formatIssueDate(date cal.Date) string {
dateTime := civil.DateTime{Date: date.Date, Time: civil.Time{}}
return dateTime.String()
Expand Down Expand Up @@ -173,6 +194,10 @@ func formatOptionalAmount(a num.Amount) string {
return a.String()
}

func formatSchemaLocation(namespace, schemaLocation string) string {
return fmt.Sprintf("%s %s", namespace, schemaLocation)
}

func totalInvoiceDiscount(i *bill.Invoice) num.Amount {
td := i.Currency.Def().Zero() // currency's precision is required by the SAT
for _, l := range i.Lines {
Expand Down
107 changes: 107 additions & 0 deletions fuel_account_balance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package cfdi

import (
"encoding/xml"

"github.com/invopop/gobl/regimes/mx"
)

// ECC Schema constants
const (
ECCVersion = "1.2"
ECCTipoOperacion = "Tarjeta"
ECCNamespace = "http://www.sat.gob.mx/EstadoDeCuentaCombustible12"
ECCSchemaLocation = "http://www.sat.gob.mx/sitio_internet/cfd/EstadoDeCuentaCombustible/ecc12.xsd"
)

// EstadoDeCuentaCombustible stores the fuel account balance data
type EstadoDeCuentaCombustible struct {
XMLName xml.Name `xml:"ecc12:EstadoDeCuentaCombustible"`
Version string `xml:",attr"`
TipoOperacion string `xml:",attr"`

NumeroDeCuenta string `xml:",attr"`
SubTotal string `xml:",attr"`
Total string `xml:",attr"`

Conceptos []*ECCConcepto `xml:"ecc12:Conceptos>ecc12:ConceptoEstadoDeCuentaCombustible"` // nolint:misspell
}

// ECCConcepto stores the data of a fuel purchase
type ECCConcepto struct {
Identificador string `xml:",attr"`
Fecha string `xml:",attr"`
Rfc string `xml:",attr"`
ClaveEstacion string `xml:",attr"`
Cantidad string `xml:",attr"`
TipoCombustible string `xml:",attr"`
Unidad string `xml:",attr,omitempty"`
NombreCombustible string `xml:",attr"`
FolioOperacion string `xml:",attr"`
ValorUnitario string `xml:",attr"`
Importe string `xml:",attr"`

Traslados []*ECCTraslado `xml:"ecc12:Traslados>ecc12:Traslado"`
}

// ECCTraslado stores the tax data of a fuel purchase
type ECCTraslado struct {
Impuesto string `xml:",attr"`
TasaOCuota string `xml:",attr"`
Importe string `xml:",attr"`
}

func addEstadoCuentaCombustible(doc *Document, fc *mx.FuelAccountBalance) {

Check failure on line 54 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / Test, Build

undefined: mx.FuelAccountBalance

Check failure on line 54 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountBalance

Check failure on line 54 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountBalance
ecc := &EstadoDeCuentaCombustible{
Version: ECCVersion,
TipoOperacion: ECCTipoOperacion,

NumeroDeCuenta: fc.AccountNumber,
SubTotal: fc.Subtotal.String(),
Total: fc.Total.String(),

Conceptos: newECCConceptos(fc.Lines), // nolint:misspell
}

doc.ECCNamespace = ECCNamespace
doc.SchemaLocation = doc.SchemaLocation + " " + formatSchemaLocation(ECCNamespace, ECCSchemaLocation)
doc.Complementos = append(doc.Complementos, ecc)
}

// nolint:misspell
func newECCConceptos(lines []*mx.FuelAccountLine) []*ECCConcepto {

Check failure on line 72 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / Test, Build

undefined: mx.FuelAccountLine

Check failure on line 72 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountLine

Check failure on line 72 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountLine
cs := make([]*ECCConcepto, len(lines))

for i, l := range lines {
cs[i] = &ECCConcepto{
Identificador: l.EWalletID.String(),
Fecha: l.PurchaseDateTime.String(),
Rfc: l.VendorTaxCode.String(),
ClaveEstacion: l.ServiceStationCode.String(),
Cantidad: l.Quantity.String(),
TipoCombustible: l.Item.Type.String(),
Unidad: l.Item.Unit.UNECE().String(),
NombreCombustible: l.Item.Name,
FolioOperacion: l.PurchaseCode.String(),
ValorUnitario: l.Item.Price.String(),
Importe: l.Total.String(),
Traslados: newECCTraslados(l.Taxes),
}
}

return cs
}

func newECCTraslados(taxes []*mx.FuelAccountTax) []*ECCTraslado {

Check failure on line 95 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / Test, Build

undefined: mx.FuelAccountTax

Check failure on line 95 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountTax

Check failure on line 95 in fuel_account_balance.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: mx.FuelAccountTax
ts := make([]*ECCTraslado, len(taxes))

for i, t := range taxes {
ts[i] = &ECCTraslado{
Impuesto: t.Code.String(),
TasaOCuota: t.Rate.String(),
Importe: t.Amount.String(),
}
}

return ts
}
49 changes: 49 additions & 0 deletions fuel_account_balance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cfdi_test

import (
"testing"

cfdi "github.com/invopop/gobl.cfdi"
"github.com/invopop/gobl.cfdi/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEstadoDeCuentaCombustible(t *testing.T) {
t.Run("should return a Document with the EstadoDeCuentaCombustible data", func(t *testing.T) {
doc, err := test.NewDocumentFrom("fuel-account-balance.json")
require.NoError(t, err)

require.Equal(t, 1, len(doc.Complementos))

ecc := doc.Complementos[0].(*cfdi.EstadoDeCuentaCombustible)

assert.Equal(t, "0123456789", ecc.NumeroDeCuenta)
assert.Equal(t, "246.13", ecc.SubTotal)
assert.Equal(t, "400.00", ecc.Total)

require.Equal(t, 2, len(ecc.Conceptos))

c := ecc.Conceptos[0]

assert.Equal(t, "1234", c.Identificador)
assert.Equal(t, "2022-07-19T10:20:30", c.Fecha)
assert.Equal(t, "RWT860605OF5", c.Rfc)
assert.Equal(t, "8171650", c.ClaveEstacion)
assert.Equal(t, "9.661", c.Cantidad)
assert.Equal(t, "3", c.TipoCombustible)
assert.Equal(t, "Diesel", c.NombreCombustible)
assert.Equal(t, "2794668", c.FolioOperacion)
assert.Equal(t, "12.743", c.ValorUnitario)
assert.Equal(t, "123.11", c.Importe)
assert.Equal(t, "LTR", c.Unidad)

require.Equal(t, 2, len(c.Traslados))

ct := c.Traslados[0]

assert.Equal(t, "IVA", ct.Impuesto)
assert.Equal(t, "0.160000", ct.TasaOCuota)
assert.Equal(t, "19.70", ct.Importe)
})
}
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ module github.com/invopop/gobl.cfdi
go 1.20

require (
github.com/invopop/gobl v0.55.0
github.com/invopop/gobl v0.59.0
github.com/joho/godotenv v1.5.1
github.com/magefile/mage v1.15.0
github.com/spf13/cobra v1.7.0
gitlab.com/flimzy/testy v0.12.4
)

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/otiai10/copy v1.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
gopkg.in/xmlpath.v1 v1.0.0-20140413065638-a146725ea6e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Expand All @@ -26,8 +30,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/invopop/jsonschema v0.7.0 // indirect
github.com/invopop/jsonschema v0.9.0 // indirect
github.com/invopop/validation v0.3.0 // indirect
github.com/lestrrat-go/libxml2 v0.0.0-20201123224832-e6d9de61b80d
github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 // indirect
Expand Down
Loading

0 comments on commit 0ef4e31

Please sign in to comment.