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

Food vouchers complement in MX #211

Merged
merged 4 commits into from
Oct 4, 2023
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
56 changes: 56 additions & 0 deletions regimes/mx/examples/food-vouchers-complement.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
$schema: "https://gobl.org/draft-0/bill/invoice"
issue_date: "2023-07-10"
series: "TEST"
code: "00002"
supplier:
name: "ESCUELA KEMPER URGATE"
ext:
mx-cfdi-fiscal-regime: "601"
tax_id:
country: "MX"
code: "EKU9003173C9"
zone: "21000"
customer:
name: "UNIVERSIDAD ROBOTICA ESPAÑOLA"
ext:
mx-cfdi-fiscal-regime: "601"
mx-cfdi-use: "G01"
tax_id:
country: "MX"
code: "URE180429TM6"
zone: "86991"
lines:
- quantity: "1"
item:
name: "Comisión servicio de monedero electrónico"
price: "10.00"
ext:
mx-cfdi-prod-serv: "84141602"
taxes:
- cat: "VAT"
rate: "standard"
payment:
terms:
notes: "Condiciones de pago"
instructions:
key: "online+wallet"
complements:
- $schema: "https://gobl.org/draft-0/regimes/mx/food-vouchers-complement"
employer_registration: "12345678901234567890"
account_number: "0123456789"
lines:
- e_wallet_id: "ABC1234"
issue_date_time: "2022-07-19T10:20:30"
employee:
tax_code: "JUFA7608212V6"
curp: "JUFA760821MDFRRR00"
name: "Adriana Juarez Fernández"
social_security: "12345678901"
amount: 10.123
- e_wallet_id: "BCD4321"
issue_date_time: "2022-08-20T11:20:30"
employee:
tax_code: "KAHO641101B39"
curp: "KAHO641101HDFRRR00"
name: "Oscar Kala Haak"
amount: 20.4
127 changes: 127 additions & 0 deletions regimes/mx/examples/out/food-vouchers-complement.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"$schema": "https://gobl.org/draft-0/envelope",
"head": {
"uuid": "8a51fd30-2a27-11ee-be56-0242ac120002",
"dig": {
"alg": "sha256",
"val": "91514018429e4d46deb1f689658d361ae4870ac5e5aa76133018086fb923e31b"
},
"draft": true
},
"doc": {
"$schema": "https://gobl.org/draft-0/bill/invoice",
"type": "standard",
"series": "TEST",
"code": "00002",
"issue_date": "2023-07-10",
"currency": "MXN",
"supplier": {
"name": "ESCUELA KEMPER URGATE",
"tax_id": {
"country": "MX",
"zone": "21000",
"code": "EKU9003173C9"
},
"ext": {
"mx-cfdi-fiscal-regime": "601"
}
},
"customer": {
"name": "UNIVERSIDAD ROBOTICA ESPAÑOLA",
"tax_id": {
"country": "MX",
"zone": "86991",
"code": "URE180429TM6"
},
"ext": {
"mx-cfdi-fiscal-regime": "601",
"mx-cfdi-use": "G01"
}
},
"lines": [
{
"i": 1,
"quantity": "1",
"item": {
"name": "Comisión servicio de monedero electrónico",
"price": "10.00",
"ext": {
"mx-cfdi-prod-serv": "84141602"
}
},
"sum": "10.00",
"taxes": [
{
"cat": "VAT",
"rate": "standard",
"percent": "16.0%"
}
],
"total": "10.00"
}
],
"payment": {
"terms": {
"notes": "Condiciones de pago"
},
"instructions": {
"key": "online+wallet"
}
},
"totals": {
"sum": "10.00",
"total": "10.00",
"taxes": {
"categories": [
{
"code": "VAT",
"rates": [
{
"key": "standard",
"base": "10.00",
"percent": "16.0%",
"amount": "1.60"
}
],
"amount": "1.60"
}
],
"sum": "1.60"
},
"tax": "1.60",
"total_with_tax": "11.60",
"payable": "11.60"
},
"complements": [
{
"$schema": "https://gobl.org/draft-0/regimes/mx/food-vouchers-complement",
"employer_registration": "12345678901234567890",
"account_number": "0123456789",
"total": "30.52",
"lines": [
{
"e_wallet_id": "ABC1234",
"issue_date_time": "2022-07-19T10:20:30",
"employee": {
"tax_code": "JUFA7608212V6",
"curp": "JUFA760821MDFRRR00",
"name": "Adriana Juarez Fernández",
"social_security": "12345678901"
},
"amount": "10.12"
},
{
"e_wallet_id": "BCD4321",
"issue_date_time": "2022-08-20T11:20:30",
"employee": {
"tax_code": "KAHO641101B39",
"curp": "KAHO641101HDFRRR00",
"name": "Oscar Kala Haak"
},
"amount": "20.40"
}
]
}
]
}
}
132 changes: 132 additions & 0 deletions regimes/mx/food_vouchers_complement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package mx

import (
"regexp"

"github.com/invopop/gobl/cal"
"github.com/invopop/gobl/cbc"
"github.com/invopop/gobl/num"
"github.com/invopop/validation"
)

// Constants for the precision of the complement's amounts
const (
FoodVouchersFinalPrecision = 2
)

// Complement's Codes Patterns
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}$"
)

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

// FoodVouchersComplement carries the data to produce a CFDI's "Complemento de
// Vales de Despensa" (version 1.0) providing detailed information about food
// vouchers issued by an e-wallet supplier to its customer's employees.
//
// This struct maps to the `ValesDeDespensa` root node in the CFDI's complement.
type FoodVouchersComplement struct {
// Customer's employer registration number (maps to `registroPatronal`).
EmployerRegistration string `json:"employer_registration,omitempty" jsonschema:"title=Employer Registration"`
// Customer's account number (maps to `numeroDeCuenta`).
AccountNumber string `json:"account_number" jsonschema:"title=Account Number"`
// Sum of all line amounts (calculated, maps to `total`).
Total num.Amount `json:"total" jsonschema:"title=Total" jsonschema_extras:"calculated=true"`
// List of food vouchers issued to the customer's employees (maps to `Conceptos`).
Lines []*FoodVouchersLine `json:"lines" jsonschema:"title=Lines"`
}

// FoodVouchersLine represents a single food voucher issued to the e-wallet of
// one of the customer's employees. It maps to one `Concepto` node in the CFDI's
// complement.
type FoodVouchersLine struct {
// Identifier of the e-wallet that received the food voucher (maps to `Identificador`).
EWalletID cbc.Code `json:"e_wallet_id" jsonschema:"title=E-wallet Identifier"`
// Date and time of the food voucher's issue (maps to `Fecha`).
IssueDateTime cal.DateTime `json:"issue_date_time" jsonschema:"title=Issue Date and Time"`
// Employee that received the food voucher.
Employee *FoodVouchersEmployee `json:"employee,omitempty" jsonschema:"title=Employee"`
// Amount of the food voucher (maps to `importe`).
Amount num.Amount `json:"amount" jsonschema:"title=Amount"`
}

// FoodVouchersEmployee represents an employee that received a food voucher. It
// groups employee related field that appears under the `Concepto` node in the
// CFDI's complement.
type FoodVouchersEmployee struct {
// Employee's tax identity code (maps to `rfc`).
TaxCode cbc.Code `json:"tax_code" jsonschema:"title=Employee's Tax Identity Code"`
// Employee's CURP ("Clave Única de Registro de Población", maps to `curp`).
CURP cbc.Code `json:"curp" jsonschema:"title=Employee's CURP"`
// Employee's name (maps to `nombre`).
Name string `json:"name" jsonschema:"title=Employee's Name"`
// Employee's Social Security Number (maps to `numSeguridadSocial`).
SocialSecurity cbc.Code `json:"social_security,omitempty" jsonschema:"title=Employee's Social Security Number"`
}

// Validate checks the FoodVouchersComplement data according to the SAT's
// rules for the "Complemento de Vales de Despensa".
func (fvc *FoodVouchersComplement) Validate() error {
return validation.ValidateStruct(fvc,
validation.Field(&fvc.EmployerRegistration, validation.Length(0, 20)),
validation.Field(&fvc.AccountNumber,
validation.Required,
validation.Length(0, 20),
),
validation.Field(&fvc.Total, validation.Required),
validation.Field(&fvc.Lines, validation.Required),
)
}

// Validate checks the FoodVouchersLine data is valid.
func (fvl *FoodVouchersLine) Validate() error {
return validation.ValidateStruct(fvl,
validation.Field(&fvl.EWalletID,
validation.Required,
validation.Length(0, 20),
),
validation.Field(&fvl.IssueDateTime, cal.DateTimeNotZero()),
validation.Field(&fvl.Employee, validation.Required),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great now 👍

validation.Field(&fvl.Amount, validation.Required),
)
}

// Validate checks the FoodVouchersEmployee data is valid.
func (fve *FoodVouchersEmployee) Validate() error {
return validation.ValidateStruct(fve,
validation.Field(&fve.TaxCode,
validation.Required,
validation.By(validateTaxCode),
),
validation.Field(&fve.CURP,
validation.Required,
validation.Match(CURPRegexp),
),
validation.Field(&fve.Name,
validation.Required,
validation.Length(0, 100),
),
validation.Field(&fve.SocialSecurity,
validation.Match(SocialSecurityRegexp),
),
)
}

// Calculate performs the complement's calculations and normalisations.
func (fvc *FoodVouchersComplement) Calculate() error {
fvc.Total = num.MakeAmount(0, FoodVouchersFinalPrecision)

for _, l := range fvc.Lines {
l.Amount = l.Amount.Rescale(FoodVouchersFinalPrecision)

fvc.Total = fvc.Total.Add(l.Amount)
}

return nil
}
Loading
Loading