Skip to content

Commit

Permalink
Merge pull request #403 from invopop/mx-cfdi-post-code-normalization
Browse files Browse the repository at this point in the history
Replace `mx-cfdi-post-code` with customer address
  • Loading branch information
cavalle authored Oct 30, 2024
2 parents 6812cfc + 53dd01f commit 1b4a1e7
Show file tree
Hide file tree
Showing 19 changed files with 209 additions and 146 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

- `br-nfse-v1`: added initial Brazil NFS-e addon

### Changed

- `mx` – deprecated the `mx-cfdi-post-code` extension in favor of the customer address post code.

## [v0.203.0]

### Added
Expand Down
19 changes: 1 addition & 18 deletions addons/mx/cfdi/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
// invoices and cannot be determined automatically.
const (
ExtKeyIssuePlace cbc.Key = "mx-cfdi-issue-place"
ExtKeyPostCode cbc.Key = "mx-cfdi-post-code"
ExtKeyFiscalRegime cbc.Key = "mx-cfdi-fiscal-regime"
ExtKeyUse cbc.Key = "mx-cfdi-use"
ExtKeyProdServ cbc.Key = "mx-cfdi-prod-serv" // name from XML field: ClaveProdServ
Expand Down Expand Up @@ -116,23 +115,7 @@ var extensions = []*cbc.KeyDefinition{
Código postal de donde se emitió la factura. En CFDI se traduce a 'LugarExpedicion'.
`),
},
Pattern: "^[0-9]{5}$",
},
{
Key: ExtKeyPostCode,
Name: i18n.String{
i18n.EN: "Post Code",
i18n.ES: "Código Postal",
},
Desc: i18n.String{
i18n.EN: here.Doc(`
Post code of a supplier or customer to use instead of an address. Example: "01000".
`),
i18n.ES: here.Doc(`
Código postal de un emisor o receptor para usar en lugar de una dirección. Ejemplo: "01000".
`),
},
Pattern: "^[0-9]{5}$",
Pattern: PostCodePattern,
},
{
Key: ExtKeyTaxType,
Expand Down
2 changes: 2 additions & 0 deletions addons/mx/cfdi/food_vouchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ const (
const (
CURPPattern = "^[A-Z][A,E,I,O,U,X][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][M,H][A-Z]{2}[B,C,D,F,G,H,J,K,L,M,N,Ñ,P,Q,R,S,T,V,W,X,Y,Z]{3}[0-9,A-Z][0-9]$"
SocialSecurityPattern = "^[0-9]{11}$"
PostCodePattern = "^[0-9]{5}$"
)

// Complement's Codes Regexps
var (
CURPRegexp = regexp.MustCompile(CURPPattern)
SocialSecurityRegexp = regexp.MustCompile(SocialSecurityPattern)
PostCodeRegexp = regexp.MustCompile(PostCodePattern)
)

// FoodVouchers carries the data to produce a CFDI's "Complemento de
Expand Down
31 changes: 29 additions & 2 deletions addons/mx/cfdi/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,34 @@ func validateInvoiceCustomer(value any) error {
),
validation.Field(&obj.Ext,
validation.When(
obj.TaxID != nil && obj.TaxID.Country.In("MX"),
isMexican(obj),
tax.ExtensionsRequires(
ExtKeyPostCode,
ExtKeyFiscalRegime,
ExtKeyUse,
),
),
),
validation.Field(&obj.Addresses,
validation.When(
isMexican(obj),
validation.Required,
validation.Each(
validation.By(validateMexicanCustomerAddress),
),
),
),
)
}

func validateMexicanCustomerAddress(value any) error {
obj, _ := value.(*org.Address)
if obj == nil {
return nil
}
return validation.ValidateStruct(obj,
validation.Field(&obj.Code,
validation.Required,
validation.Match(PostCodeRegexp)),
)
}

Expand Down Expand Up @@ -147,3 +167,10 @@ func validateInvoicePreceding(value interface{}) error {
),
)
}

func isMexican(party *org.Party) bool {
if party == nil || party.TaxID == nil {
return false
}
return party.TaxID.Country.In("MX")
}
29 changes: 25 additions & 4 deletions addons/mx/cfdi/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func validInvoice() *bill.Invoice {
Supplier: &org.Party{
Name: "Test Supplier",
Ext: tax.Extensions{
cfdi.ExtKeyPostCode: "21000",
"mx-cfdi-post-code": "21000",
cfdi.ExtKeyFiscalRegime: "601",
},
TaxID: &tax.Identity{
Expand All @@ -42,7 +42,7 @@ func validInvoice() *bill.Invoice {
Customer: &org.Party{
Name: "Test Customer",
Ext: tax.Extensions{
cfdi.ExtKeyPostCode: "65000",
"mx-cfdi-post-code": "65000",
cfdi.ExtKeyFiscalRegime: "608",
cfdi.ExtKeyUse: "G01",
},
Expand Down Expand Up @@ -91,7 +91,7 @@ func TestNormalizeInvoice(t *testing.T) {
t.Run("with supplier address code", func(t *testing.T) {
inv := validInvoice()
inv.Addons = tax.WithAddons(cfdi.V4)
delete(inv.Supplier.Ext, cfdi.ExtKeyPostCode)
delete(inv.Supplier.Ext, "mx-cfdi-post-code")
inv.Supplier.Addresses = append(inv.Supplier.Addresses,
&org.Address{
Locality: "Mexico",
Expand All @@ -116,6 +116,27 @@ func TestCustomerValidation(t *testing.T) {
assert.NoError(t, inv.Validate())
}

func TestCustomerAddressCodeValidation(t *testing.T) {
inv := validInvoice()
delete(inv.Customer.Ext, "mx-cfdi-post-code")
assertValidationError(t, inv, "customer: (addresses: cannot be blank.)")

inv.Customer.Addresses = []*org.Address{{}}
assertValidationError(t, inv, "customer: (addresses: (0: (code: cannot be blank.).).)")

inv.Customer.Addresses[0].Code = "ABC"
assertValidationError(t, inv, "customer: (addresses: (0: (code: must be in a valid format.).).)")

inv.Customer.Addresses[0].Code = "21000"
require.NoError(t, inv.Calculate())
require.NoError(t, inv.Validate())

inv.Customer.TaxID.Country = "US"
inv.Customer.Addresses = nil
require.NoError(t, inv.Calculate())
require.NoError(t, inv.Validate())
}

func TestLineValidation(t *testing.T) {
inv := validInvoice()

Expand Down Expand Up @@ -182,7 +203,7 @@ func TestUsoCFDIScenarioValidation(t *testing.T) {

inv.Customer.Ext = tax.Extensions{
cfdi.ExtKeyFiscalRegime: "601",
cfdi.ExtKeyPostCode: "21000",
"mx-cfdi-post-code": "21000",
}
assertValidationError(t, inv, "ext: (mx-cfdi-use: required.)")
}
Expand Down
12 changes: 0 additions & 12 deletions data/addons/mx-cfdi-v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,6 @@
},
"pattern": "^[0-9]{5}$"
},
{
"key": "mx-cfdi-post-code",
"name": {
"en": "Post Code",
"es": "Código Postal"
},
"desc": {
"en": "Post code of a supplier or customer to use instead of an address. Example: \"01000\".",
"es": "Código postal de un emisor o receptor para usar en lugar de una dirección. Ejemplo: \"01000\"."
},
"pattern": "^[0-9]{5}$"
},
{
"key": "mx-cfdi-tax-type",
"name": {
Expand Down
16 changes: 7 additions & 9 deletions regimes/mx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Here's how to include these codes in your GOBL documents:

### Issue Place (`LugarExpedicion`)

Specify the postal code where the invoice was issued using the `mx-cfdi-issue-place` extension under the `tax` field of the invoice.
Specify the postal code where the invoice was issued using the `mx-cfdi-issue-place` extension under the `tax` field of the invoice. If the extension is not provided, GOBL will set it automatically to the supplier's address post code.

#### Example

Expand Down Expand Up @@ -65,7 +65,7 @@ The following example will associate the supplier with the `601` fiscal regime c

### `DomicilioFiscalReceptor` - Receipt's Tax Address

In CFDI, `DomicilioFiscalReceptor` is a mandatory field that specifies the postal code of the recepient's tax address. In a GOBL Invoice, you can provide this value setting the `mx-cfdi-post-code` extension of the invoice's customer.
In CFDI, `DomicilioFiscalReceptor` is a mandatory field that specifies the postal code of the recepient's tax address. In a GOBL Invoice, you can provide this value setting the customer's address post code.

#### Example

Expand All @@ -81,11 +81,11 @@ In CFDI, `DomicilioFiscalReceptor` is a mandatory field that specifies the posta
"country": "MX",
"code": "URE180429TM6"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-use": "G01",
"mx-cfdi-post-code": "65000"
}
"addresses": [
{
"code": "65000"
}
]
}

// [...]
Expand Down Expand Up @@ -115,9 +115,7 @@ The following GOBL maps to the `G03` (Gastos en general) value of the `UsoCFDI`
"code": "URE180429TM6"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-use": "G01",
"mx-cfdi-post-code": "65000"
}
}

Expand Down
8 changes: 6 additions & 2 deletions regimes/mx/examples/out/credit-note.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": "d68dc80f2f6f0c4ac962961f2c367455b8b529ab67b81e8d5bc6a7f2972a5ae2"
"val": "cc154d9d8a558a6059fc50abbceab5ea88c32f8895c37a87738960fb8c626ef9"
}
},
"doc": {
Expand Down Expand Up @@ -64,9 +64,13 @@
"country": "MX",
"code": "URE180429TM6"
},
"addresses": [
{
"code": "65000"
}
],
"ext": {
"mx-cfdi-fiscal-regime": "624",
"mx-cfdi-post-code": "65000",
"mx-cfdi-use": "G01"
}
},
Expand Down
11 changes: 7 additions & 4 deletions regimes/mx/examples/out/food-vouchers.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": "ea065afce50358819fe68587f573dbad7c0d6ea1626d075b91faa924ebeb07db"
"val": "24c15ebc61934602b26715e5a0231761475ef5239084daee7eb2f021acbdc78b"
}
},
"doc": {
Expand Down Expand Up @@ -32,8 +32,7 @@
"code": "EKU9003173C9"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "21000"
"mx-cfdi-fiscal-regime": "601"
}
},
"customer": {
Expand All @@ -42,9 +41,13 @@
"country": "MX",
"code": "URE180429TM6"
},
"addresses": [
{
"code": "86991"
}
],
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "86991",
"mx-cfdi-use": "G01"
}
},
Expand Down
11 changes: 7 additions & 4 deletions regimes/mx/examples/out/fuel-account-balance.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": "d6590300e024e94dd50400b2c016c574222ca1f5259e3da6132e66ebb9d514c9"
"val": "a0b87156fa951dd596f01301595f2fd83eac97fb4395558ea77df63e4b7d0acd"
}
},
"doc": {
Expand Down Expand Up @@ -32,8 +32,7 @@
"code": "EKU9003173C9"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "21000"
"mx-cfdi-fiscal-regime": "601"
}
},
"customer": {
Expand All @@ -42,9 +41,13 @@
"country": "MX",
"code": "URE180429TM6"
},
"addresses": [
{
"code": "86991"
}
],
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "86991",
"mx-cfdi-use": "G01"
}
},
Expand Down
8 changes: 6 additions & 2 deletions regimes/mx/examples/out/invoice.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": "90c21ff26c2935d51911d7356689ceb7f6481f01449d7681e09c13904d6d1feb"
"val": "d4bfeb7dd784a992f7661fa4dd8f7836c7ea4f1ace86f61966a4568819c7e69c"
}
},
"doc": {
Expand Down Expand Up @@ -41,9 +41,13 @@
"country": "MX",
"code": "URE180429TM6"
},
"addresses": [
{
"code": "86991"
}
],
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "86991",
"mx-cfdi-use": "G01"
}
},
Expand Down
11 changes: 7 additions & 4 deletions regimes/mx/examples/out/prepaid-invoice.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": "98314830e2c99c01ae84557742d34583e07454f461d3bd585a3f70925d315fdf"
"val": "0e3602bdd9779e7bfa7ac2937dd3072ab16a813db6938d2bcda61f0a4136e544"
}
},
"doc": {
Expand Down Expand Up @@ -32,8 +32,7 @@
"code": "EKU9003173C9"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "21000"
"mx-cfdi-fiscal-regime": "601"
}
},
"customer": {
Expand All @@ -42,9 +41,13 @@
"country": "MX",
"code": "URE180429TM6"
},
"addresses": [
{
"code": "86991"
}
],
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-post-code": "86991",
"mx-cfdi-use": "G01"
}
},
Expand Down
Loading

0 comments on commit 1b4a1e7

Please sign in to comment.