From 53dd01f77dfe06541164059af60b69349d64ad72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luismi=20Cavall=C3=A9?= Date: Tue, 29 Oct 2024 14:18:44 +0000 Subject: [PATCH] Replace `mx-cfdi-post-code` with customer address --- CHANGELOG.md | 4 ++ addons/mx/cfdi/extensions.go | 19 +---- addons/mx/cfdi/food_vouchers.go | 2 + addons/mx/cfdi/invoice.go | 31 ++++++++- addons/mx/cfdi/invoice_test.go | 29 ++++++-- data/addons/mx-cfdi-v4.json | 12 ---- regimes/mx/README.md | 16 ++--- regimes/mx/examples/out/credit-note.json | 8 ++- regimes/mx/examples/out/food-vouchers.json | 11 +-- .../mx/examples/out/fuel-account-balance.json | 11 +-- regimes/mx/examples/out/invoice.json | 8 ++- regimes/mx/examples/out/prepaid-invoice.json | 11 +-- regimes/mx/examples/out/retentions.json | 11 +-- regimes/mx/invoice.go | 53 ++++++++++++-- regimes/mx/invoice_test.go | 69 +++++++++++++------ regimes/mx/mx.go | 3 - regimes/mx/party.go | 20 ------ regimes/mx/party_test.go | 27 -------- tax/extensions_test.go | 10 +-- 19 files changed, 209 insertions(+), 146 deletions(-) delete mode 100644 regimes/mx/party.go delete mode 100644 regimes/mx/party_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b6da98..2731e2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/addons/mx/cfdi/extensions.go b/addons/mx/cfdi/extensions.go index f889469d..ec5c1473 100644 --- a/addons/mx/cfdi/extensions.go +++ b/addons/mx/cfdi/extensions.go @@ -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 @@ -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, diff --git a/addons/mx/cfdi/food_vouchers.go b/addons/mx/cfdi/food_vouchers.go index 903cbb61..e64e542a 100644 --- a/addons/mx/cfdi/food_vouchers.go +++ b/addons/mx/cfdi/food_vouchers.go @@ -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 diff --git a/addons/mx/cfdi/invoice.go b/addons/mx/cfdi/invoice.go index 23394650..b2190c97 100644 --- a/addons/mx/cfdi/invoice.go +++ b/addons/mx/cfdi/invoice.go @@ -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)), ) } @@ -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") +} diff --git a/addons/mx/cfdi/invoice_test.go b/addons/mx/cfdi/invoice_test.go index a8cd80c9..9879bb13 100644 --- a/addons/mx/cfdi/invoice_test.go +++ b/addons/mx/cfdi/invoice_test.go @@ -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{ @@ -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", }, @@ -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", @@ -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() @@ -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.)") } diff --git a/data/addons/mx-cfdi-v4.json b/data/addons/mx-cfdi-v4.json index 6b2794e5..b6e8e204 100644 --- a/data/addons/mx-cfdi-v4.json +++ b/data/addons/mx-cfdi-v4.json @@ -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": { diff --git a/regimes/mx/README.md b/regimes/mx/README.md index ad682fed..290c4e19 100644 --- a/regimes/mx/README.md +++ b/regimes/mx/README.md @@ -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 @@ -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 @@ -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" + } + ] } // [...] @@ -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" } } diff --git a/regimes/mx/examples/out/credit-note.json b/regimes/mx/examples/out/credit-note.json index b4ff893e..b3a40853 100644 --- a/regimes/mx/examples/out/credit-note.json +++ b/regimes/mx/examples/out/credit-note.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "d68dc80f2f6f0c4ac962961f2c367455b8b529ab67b81e8d5bc6a7f2972a5ae2" + "val": "cc154d9d8a558a6059fc50abbceab5ea88c32f8895c37a87738960fb8c626ef9" } }, "doc": { @@ -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" } }, diff --git a/regimes/mx/examples/out/food-vouchers.json b/regimes/mx/examples/out/food-vouchers.json index cdf730d9..7ddaf99d 100644 --- a/regimes/mx/examples/out/food-vouchers.json +++ b/regimes/mx/examples/out/food-vouchers.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "ea065afce50358819fe68587f573dbad7c0d6ea1626d075b91faa924ebeb07db" + "val": "24c15ebc61934602b26715e5a0231761475ef5239084daee7eb2f021acbdc78b" } }, "doc": { @@ -32,8 +32,7 @@ "code": "EKU9003173C9" }, "ext": { - "mx-cfdi-fiscal-regime": "601", - "mx-cfdi-post-code": "21000" + "mx-cfdi-fiscal-regime": "601" } }, "customer": { @@ -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" } }, diff --git a/regimes/mx/examples/out/fuel-account-balance.json b/regimes/mx/examples/out/fuel-account-balance.json index 13ae6e58..8336c02d 100644 --- a/regimes/mx/examples/out/fuel-account-balance.json +++ b/regimes/mx/examples/out/fuel-account-balance.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "d6590300e024e94dd50400b2c016c574222ca1f5259e3da6132e66ebb9d514c9" + "val": "a0b87156fa951dd596f01301595f2fd83eac97fb4395558ea77df63e4b7d0acd" } }, "doc": { @@ -32,8 +32,7 @@ "code": "EKU9003173C9" }, "ext": { - "mx-cfdi-fiscal-regime": "601", - "mx-cfdi-post-code": "21000" + "mx-cfdi-fiscal-regime": "601" } }, "customer": { @@ -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" } }, diff --git a/regimes/mx/examples/out/invoice.json b/regimes/mx/examples/out/invoice.json index 7c2ccda1..a68f136d 100644 --- a/regimes/mx/examples/out/invoice.json +++ b/regimes/mx/examples/out/invoice.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "90c21ff26c2935d51911d7356689ceb7f6481f01449d7681e09c13904d6d1feb" + "val": "d4bfeb7dd784a992f7661fa4dd8f7836c7ea4f1ace86f61966a4568819c7e69c" } }, "doc": { @@ -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" } }, diff --git a/regimes/mx/examples/out/prepaid-invoice.json b/regimes/mx/examples/out/prepaid-invoice.json index 8de9327e..f24fa54c 100644 --- a/regimes/mx/examples/out/prepaid-invoice.json +++ b/regimes/mx/examples/out/prepaid-invoice.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "98314830e2c99c01ae84557742d34583e07454f461d3bd585a3f70925d315fdf" + "val": "0e3602bdd9779e7bfa7ac2937dd3072ab16a813db6938d2bcda61f0a4136e544" } }, "doc": { @@ -32,8 +32,7 @@ "code": "EKU9003173C9" }, "ext": { - "mx-cfdi-fiscal-regime": "601", - "mx-cfdi-post-code": "21000" + "mx-cfdi-fiscal-regime": "601" } }, "customer": { @@ -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" } }, diff --git a/regimes/mx/examples/out/retentions.json b/regimes/mx/examples/out/retentions.json index fb8583e1..f5569cd1 100644 --- a/regimes/mx/examples/out/retentions.json +++ b/regimes/mx/examples/out/retentions.json @@ -4,7 +4,7 @@ "uuid": "8a51fd30-2a27-11ee-be56-0242ac120002", "dig": { "alg": "sha256", - "val": "b1a1c1719f099bbfa25f5b835008a0c1a97ed649e0b045459c9157eecfb4f966" + "val": "df116edc1be8cffa73ea841b6fc36bb367ac300466c9f1e57f024cbefa410e45" } }, "doc": { @@ -32,8 +32,7 @@ "code": "FUNK671228PH6" }, "ext": { - "mx-cfdi-fiscal-regime": "612", - "mx-cfdi-post-code": "01160" + "mx-cfdi-fiscal-regime": "612" } }, "customer": { @@ -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" } }, diff --git a/regimes/mx/invoice.go b/regimes/mx/invoice.go index 9698ee3e..6f657afe 100644 --- a/regimes/mx/invoice.go +++ b/regimes/mx/invoice.go @@ -3,6 +3,7 @@ package mx import ( "github.com/invopop/gobl/bill" "github.com/invopop/gobl/cbc" + "github.com/invopop/gobl/org" "github.com/invopop/gobl/tax" ) @@ -13,17 +14,40 @@ const ( ) func normalizeInvoice(inv *bill.Invoice) { - // 2024-04-26: copy suppliers post code to invoice, if not already - // set. - normalizeParty(inv.Supplier) // first do party + migrateTaxIDZoneToExtPostCode(inv.Supplier) + migrateTaxIDZoneToExtPostCode(inv.Customer) + + migrateSupplierExtPostCodeToInvoice(inv) + migrateCustomerExtPostCodeToAddress(inv) + + deleteExtPostCode(inv.Supplier) + deleteExtPostCode(inv.Customer) +} + +// 2024-03-14: Migrate Tax ID Zone to extensions "mx-cfdi-post-code" +func migrateTaxIDZoneToExtPostCode(p *org.Party) { + if p == nil { + return + } + if p.TaxID != nil && p.TaxID.Zone != "" { //nolint:staticcheck + if p.Ext == nil { + p.Ext = make(tax.Extensions) + } + p.Ext[extKeyPostCode] = tax.ExtValue(p.TaxID.Zone) //nolint:staticcheck + p.TaxID.Zone = "" //nolint:staticcheck + } +} + +// 2024-04-26: copy suppliers post code to invoice, if not alread set. +func migrateSupplierExtPostCodeToInvoice(inv *bill.Invoice) { ext := make(tax.Extensions) if inv.Tax != nil && inv.Tax.Ext != nil { ext = inv.Tax.Ext } - if ext.Has(extKeyIssuePlace) { + if ext.Has(extKeyIssuePlace) || inv.Supplier == nil { return } - if inv.Supplier != nil && inv.Supplier.Ext.Has(extKeyPostCode) { + if inv.Supplier.Ext.Has(extKeyPostCode) { ext[extKeyIssuePlace] = inv.Supplier.Ext[extKeyPostCode] } else if len(inv.Supplier.Addresses) > 0 { addr := inv.Supplier.Addresses[0] @@ -38,3 +62,22 @@ func normalizeInvoice(inv *bill.Invoice) { inv.Tax.Ext = ext } } + +// 2024-10-29: move customers post code from extension to address +func migrateCustomerExtPostCodeToAddress(inv *bill.Invoice) { + if inv.Customer != nil && inv.Customer.Ext.Has(extKeyPostCode) { + if len(inv.Customer.Addresses) == 0 { + inv.Customer.Addresses = []*org.Address{{}} + } + inv.Customer.Addresses[0].Code = inv.Customer.Ext[extKeyPostCode].String() + } +} + +// 2024-10-29: remove post codes from extensions (no longer valid extension) +func deleteExtPostCode(p *org.Party) { + if p == nil { + return + } + + delete(p.Ext, extKeyPostCode) +} diff --git a/regimes/mx/invoice_test.go b/regimes/mx/invoice_test.go index 5ffd1c07..d217519a 100644 --- a/regimes/mx/invoice_test.go +++ b/regimes/mx/invoice_test.go @@ -14,30 +14,41 @@ import ( ) func TestNormalizeInvoice(t *testing.T) { - t.Run("no tax", func(t *testing.T) { + t.Run("does not migrate issue place when already present", func(t *testing.T) { inv := baseInvoice() require.NoError(t, inv.Calculate()) require.NoError(t, inv.Validate()) require.NotNil(t, inv.Tax) assert.Equal(t, tax.ExtValue("21000"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) + assert.False(t, inv.Supplier.Ext.Has("mx-cfdi-post-code")) }) - - t.Run("no ext", func(t *testing.T) { + t.Run("migrate issue place from supplier ext when no tax ext", func(t *testing.T) { + inv := baseInvoice() + inv.Tax = &bill.Tax{} + require.NoError(t, inv.Calculate()) + require.NoError(t, inv.Validate()) + require.NotNil(t, inv.Tax) + assert.Equal(t, tax.ExtValue("22000"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) + assert.False(t, inv.Supplier.Ext.Has("mx-cfdi-post-code")) + }) + t.Run("migrate issue place from supplier tax ID zone", func(t *testing.T) { inv := baseInvoice() + inv.Supplier.Ext = nil + inv.Supplier.TaxID.Zone = "21000" //nolint:staticcheck inv.Tax = &bill.Tax{} require.NoError(t, inv.Calculate()) require.NoError(t, inv.Validate()) require.NotNil(t, inv.Tax) assert.Equal(t, tax.ExtValue("21000"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) + assert.False(t, inv.Supplier.Ext.Has("mx-cfdi-post-code")) }) - - t.Run("with supplier address code", func(t *testing.T) { + t.Run("does not migrate issue place from address when already present", func(t *testing.T) { inv := baseInvoice() - 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", - Code: "21000", + Code: "22000", }, ) require.NoError(t, inv.Calculate()) @@ -45,18 +56,7 @@ func TestNormalizeInvoice(t *testing.T) { require.NotNil(t, inv.Tax) assert.Equal(t, tax.ExtValue("21000"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) }) - t.Run("migrate supplier issue place", func(t *testing.T) { - inv := baseInvoice() - inv.Tax = nil - inv.Supplier.Ext = tax.Extensions{ - cfdi.ExtKeyPostCode: "12345", - } - require.NoError(t, inv.Calculate()) - require.NoError(t, inv.Validate()) - require.NotNil(t, inv.Tax) - assert.Equal(t, tax.ExtValue("12345"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) - }) - t.Run("migrate supplier issue place", func(t *testing.T) { + t.Run("migrate issue place from supplier address code", func(t *testing.T) { inv := baseInvoice() inv.Tax = nil inv.Supplier.Ext = nil @@ -71,6 +71,33 @@ func TestNormalizeInvoice(t *testing.T) { require.NotNil(t, inv.Tax) assert.Equal(t, tax.ExtValue("12345"), inv.Tax.Ext[cfdi.ExtKeyIssuePlace]) }) + t.Run("migrate customer post code from ext", func(t *testing.T) { + inv := baseInvoice() + inv.Customer.Ext = tax.Extensions{ + "mx-cfdi-post-code": "12345", + } + require.NoError(t, inv.Calculate()) + require.NoError(t, inv.Validate()) + require.NotNil(t, inv.Customer.Addresses) + assert.Equal(t, "12345", inv.Customer.Addresses[0].Code) + assert.False(t, inv.Customer.Ext.Has("mx-cfdi-post-code")) + }) + t.Run("migrate customer post code from zone", func(t *testing.T) { + inv := baseInvoice() + inv.Customer.TaxID.Zone = "12345" //nolint:staticcheck + require.NoError(t, inv.Calculate()) + require.NoError(t, inv.Validate()) + require.NotNil(t, inv.Customer.Addresses) + assert.Equal(t, "12345", inv.Customer.Addresses[0].Code) + assert.False(t, inv.Customer.Ext.Has("mx-cfdi-post-code")) + }) + t.Run("does not migrate anything when the customer is missing", func(t *testing.T) { + inv := baseInvoice() + inv.Customer = nil + require.NoError(t, inv.Calculate()) + require.NoError(t, inv.Validate()) + assert.Nil(t, inv.Customer) + }) } func baseInvoice() *bill.Invoice { @@ -87,7 +114,7 @@ func baseInvoice() *bill.Invoice { Supplier: &org.Party{ Name: "Test Supplier", Ext: tax.Extensions{ - cfdi.ExtKeyPostCode: "21000", + "mx-cfdi-post-code": "22000", cfdi.ExtKeyFiscalRegime: "601", }, TaxID: &tax.Identity{ @@ -98,7 +125,7 @@ func baseInvoice() *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", }, diff --git a/regimes/mx/mx.go b/regimes/mx/mx.go index 8074ac23..f065c5fb 100644 --- a/regimes/mx/mx.go +++ b/regimes/mx/mx.go @@ -6,7 +6,6 @@ import ( "github.com/invopop/gobl/cbc" "github.com/invopop/gobl/currency" "github.com/invopop/gobl/i18n" - "github.com/invopop/gobl/org" "github.com/invopop/gobl/regimes/common" "github.com/invopop/gobl/tax" ) @@ -60,8 +59,6 @@ func Normalize(doc any) { normalizeInvoice(obj) case *tax.Identity: tax.NormalizeIdentity(obj) - case *org.Party: - normalizeParty(obj) } } diff --git a/regimes/mx/party.go b/regimes/mx/party.go deleted file mode 100644 index b10b397c..00000000 --- a/regimes/mx/party.go +++ /dev/null @@ -1,20 +0,0 @@ -package mx - -import ( - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" -) - -func normalizeParty(p *org.Party) { - if p == nil { - return - } - // 2024-03-14: Migrate Tax ID Zone to extensions "mx-cfdi-post-code" - if p.TaxID != nil && p.TaxID.Zone != "" { //nolint:staticcheck - if p.Ext == nil { - p.Ext = make(tax.Extensions) - } - p.Ext[extKeyPostCode] = tax.ExtValue(p.TaxID.Zone) //nolint:staticcheck - p.TaxID.Zone = "" //nolint:staticcheck - } -} diff --git a/regimes/mx/party_test.go b/regimes/mx/party_test.go deleted file mode 100644 index 49ffa8df..00000000 --- a/regimes/mx/party_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package mx_test - -import ( - "testing" - - "github.com/invopop/gobl/addons/mx/cfdi" - "github.com/invopop/gobl/org" - "github.com/invopop/gobl/tax" - "github.com/stretchr/testify/assert" -) - -func TestMigratePartyIdentities(t *testing.T) { - customer := &org.Party{ - Name: "Test Customer", - TaxID: &tax.Identity{ - Country: "MX", - Code: "ZZZ010101ZZZ", - Zone: "65000", - }, - } - - mx := tax.RegimeDefFor("MX") - mx.Normalizer(customer) - - assert.Equal(t, "65000", customer.Ext[cfdi.ExtKeyPostCode].String()) - assert.Empty(t, customer.TaxID.Zone) //nolint:staticcheck -} diff --git a/tax/extensions_test.go b/tax/extensions_test.go index 5e7e443d..77753874 100644 --- a/tax/extensions_test.go +++ b/tax/extensions_test.go @@ -54,24 +54,24 @@ func TestExtValidation(t *testing.T) { t.Run("with mexico", func(t *testing.T) { t.Run("test patterns", func(t *testing.T) { em := tax.Extensions{ - cfdi.ExtKeyPostCode: "12345", + cfdi.ExtKeyIssuePlace: "12345", } err := em.Validate() assert.NoError(t, err) em = tax.Extensions{ - cfdi.ExtKeyPostCode: "123457", + cfdi.ExtKeyIssuePlace: "123457", } err = em.Validate() assert.Error(t, err) - assert.Contains(t, err.Error(), "mx-cfdi-post-code: does not match pattern") + assert.Contains(t, err.Error(), "mx-cfdi-issue-place: does not match pattern") - kd := tax.ExtensionForKey(cfdi.ExtKeyPostCode) + kd := tax.ExtensionForKey(cfdi.ExtKeyIssuePlace) pt := kd.Pattern kd.Pattern = "[][" // invalid err = em.Validate() assert.Error(t, err) - assert.Contains(t, err.Error(), "mx-cfdi-post-code: error parsing regexp: missing closing ]: `[][`") + assert.Contains(t, err.Error(), "mx-cfdi-issue-place: error parsing regexp: missing closing ]: `[][`") kd.Pattern = pt // put back! })