Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing MX tax identity code validation with strange characters #407

Merged
merged 2 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

### Added

- `bill`: normalize line discounts and charges to remove empty rows.

### Fixed

- `tax`: identity code handling will skip default validation for specific countries that use special characters.

## [v0.204.0]

### Added

- `br-nfse-v1`: added initial Brazil NFS-e addon
- `bill`: normalize line discounts and charges to remove empty rows.

### Changed

Expand Down
4 changes: 2 additions & 2 deletions regimes/mx/examples/out/retentions.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": "df116edc1be8cffa73ea841b6fc36bb367ac300466c9f1e57f024cbefa410e45"
"val": "bf9567fd923b6a5962e91b333a730ef03759c3e82d42827de31796249266f11e"
}
},
"doc": {
Expand Down Expand Up @@ -39,7 +39,7 @@
"name": "UNIVERSIDAD ROBOTICA ESPAÑOLA",
"tax_id": {
"country": "MX",
"code": "URE180429TM6"
"code": "K\u0026A010301I16"
},
"addresses": [
{
Expand Down
2 changes: 1 addition & 1 deletion regimes/mx/examples/retentions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ customer:
mx-cfdi-use: "G01"
tax_id:
country: "MX"
code: "URE180429TM6"
code: "K&A010301I16"
zone: "86991"
lines:
- quantity: "1"
Expand Down
8 changes: 2 additions & 6 deletions regimes/mx/tax_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,10 @@ func NormalizeTaxCode(code cbc.Code) cbc.Code {
// defined by the Mexican SAT.
func ValidateTaxCode(value interface{}) error {
code, ok := value.(cbc.Code)
if !ok {
if !ok || code == "" {
return nil
}
if code == "" {
return nil
}
typ := DetermineTaxCodeType(code)
if typ.IsEmpty() {
if typ := DetermineTaxCodeType(code); typ.IsEmpty() {
return tax.ErrIdentityCodeInvalid
}
return nil
Expand Down
30 changes: 21 additions & 9 deletions tax/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ var (

// IdentityCodeBadCharsRegexp is used to remove any characters that are not valid in a tax code.
IdentityCodeBadCharsRegexp = regexp.MustCompile(`[^A-Z0-9]+`)

// IdentityCodeValidationIgnore is a list of countries that should not have their tax identity
// codes validated due to local rules.
IdentityCodeValidationIgnore = []l10n.TaxCountryCode{"MX"}
)

// RequireIdentityCode is an additional check to use alongside
Expand Down Expand Up @@ -108,13 +112,12 @@ func (id *Identity) Calculate() error {
// on the tax identity. Identities are an exception to the normal
// normalization rules as they cannot be normalized using addons.
func (id *Identity) Normalize() {
r := id.Regime()
if r == nil {
if r := id.Regime(); r != nil {
r.NormalizeObject(id)
} else {
// Fallback to common normalization
NormalizeIdentity(id)
return
}
r.NormalizeObject(id)
}

// Validate checks to ensure the tax ID contains all the required
Expand All @@ -123,7 +126,12 @@ func (id *Identity) Normalize() {
func (id *Identity) Validate() error {
err := validation.ValidateStruct(id,
validation.Field(&id.Country, validation.Required),
validation.Field(&id.Code, validation.Match(IdentityCodePatternRegexp)),
validation.Field(&id.Code,
validation.Skip.When(
id.Country.In(IdentityCodeValidationIgnore...),
),
validation.Match(IdentityCodePatternRegexp),
),
validation.Field(&id.Zone, validation.Empty),
validation.Field(&id.Type),
)
Expand All @@ -142,10 +150,14 @@ func (v validateTaxID) Validate(value interface{}) error {
if id == nil || !ok {
return nil
}
rules := []*validation.FieldRules{
validation.Field(&id.Code,
validation.When(v.requireCode, validation.Required),
),
rules := []*validation.FieldRules{}
if v.requireCode {
rules = append(rules,
validation.Field(&id.Code,
validation.Required,
validation.Skip,
),
)
}
return validation.ValidateStruct(id, rules...)
}
Expand Down
38 changes: 32 additions & 6 deletions tax/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ func TestTaxIdentity(t *testing.T) {
assert.NotPanics(t, func() {
tID.Normalize()
})

t.Run("mexican case with custom validation", func(t *testing.T) {
tID := &tax.Identity{
Country: "MX",
Code: "K&A010301I16",
}
assert.NoError(t, tID.Validate())
})

t.Run("invalid non-exception case", func(t *testing.T) {
tID := &tax.Identity{
Country: "ZW", // update when ZW regime is added
Code: "AB&DE",
}
assert.ErrorContains(t, tID.Validate(), "code: must be in a valid format")
})
}

func TestParseIdentity(t *testing.T) {
Expand Down Expand Up @@ -77,12 +93,22 @@ func TestValidationRules(t *testing.T) {
}

func TestNormalizeIdentity(t *testing.T) {
tID := &tax.Identity{
Country: "AU",
Code: " x315-7928 m ",
}
tax.NormalizeIdentity(tID)
assert.Equal(t, tID.Code.String(), "X3157928M")
t.Run("regular case", func(t *testing.T) {
tID := &tax.Identity{
Country: "AU",
Code: " x315-7928 m ",
}
tax.NormalizeIdentity(tID)
assert.Equal(t, tID.Code.String(), "X3157928M")
})
t.Run("with alt country", func(t *testing.T) {
tID := &tax.Identity{
Country: "EL",
Code: "GR925667500",
}
tax.NormalizeIdentity(tID, "GR")
assert.Equal(t, tID.Code.String(), "925667500")
})
}

func TestIdentityNormalize(t *testing.T) {
Expand Down
Loading