cfdi40 0.0.1.alfa → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +10 -5
- data/README_es-MX.md +84 -0
- data/cfdi40.gemspec +2 -2
- data/lib/cfdi40/complemento_concepto.rb +12 -0
- data/lib/cfdi40/comprobante.rb +225 -20
- data/lib/cfdi40/concepto.rb +164 -0
- data/lib/cfdi40/conceptos.rb +4 -0
- data/lib/cfdi40/emisor.rb +7 -0
- data/lib/cfdi40/impuestos.rb +29 -0
- data/lib/cfdi40/inst_educativas.rb +17 -0
- data/lib/cfdi40/node.rb +155 -0
- data/lib/cfdi40/receptor.rb +11 -0
- data/lib/cfdi40/sat_csd.rb +89 -0
- data/lib/cfdi40/schema_validator.rb +29 -0
- data/lib/cfdi40/traslado.rb +9 -0
- data/lib/cfdi40/traslados.rb +18 -0
- data/lib/cfdi40/version.rb +1 -1
- data/lib/cfdi40.rb +14 -0
- data/lib/xsd/README.md +34 -0
- data/lib/xsd/catCFDI.xsd +162329 -0
- data/lib/xsd/cfdv40.xsd +857 -0
- data/lib/xsd/iedu.xsd +84 -0
- data/lib/xsd/tdCFDI.xsd +157 -0
- data/lib/xslt/CartaPorte20.xslt +615 -0
- data/lib/xslt/GastosHidrocarburos10.xslt +171 -0
- data/lib/xslt/IngresosHidrocarburos.xslt +39 -0
- data/lib/xslt/Pagos20.xslt +233 -0
- data/lib/xslt/TuristaPasajeroExtranjero.xslt +40 -0
- data/lib/xslt/aerolineas.xslt +50 -0
- data/lib/xslt/cadenaoriginal.xslt +405 -0
- data/lib/xslt/cadenaoriginal_local.xslt +405 -0
- data/lib/xslt/certificadodedestruccion.xslt +60 -0
- data/lib/xslt/cfdiregistrofiscal.xslt +19 -0
- data/lib/xslt/consumodeCombustibles11.xslt +94 -0
- data/lib/xslt/detallista.xslt +42 -0
- data/lib/xslt/divisas.xslt +13 -0
- data/lib/xslt/donat11.xslt +13 -0
- data/lib/xslt/iedu.xslt +26 -0
- data/lib/xslt/implocal.xslt +39 -0
- data/lib/xslt/ine11.xslt +30 -0
- data/lib/xslt/leyendasFisc.xslt +28 -0
- data/lib/xslt/nomina12.xslt +412 -0
- data/lib/xslt/notariospublicos.xslt +301 -0
- data/lib/xslt/obrasarteantiguedades.xslt +33 -0
- data/lib/xslt/pagoenespecie.xslt +39 -0
- data/lib/xslt/pfic.xslt +13 -0
- data/lib/xslt/renovacionysustitucionvehiculos.xslt +152 -0
- data/lib/xslt/servicioparcialconstruccion.xslt +44 -0
- data/lib/xslt/utilerias.xslt +22 -0
- data/lib/xslt/valesdedespensa.xslt +70 -0
- data/lib/xslt/vehiculousado.xslt +63 -0
- metadata +53 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb9a12ec72926735a554f302b13993914393c626cc4dcd797791dfcf0ed8191a
|
4
|
+
data.tar.gz: 4bc4708eb4c2c0671fa11c81dbcb40a8a8c9a585d2fa02531137efeee942a9d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f41d5ee33e1e705d2b12b2c623cd6b197d2c93c1a9e5d994ffc38630954fc4cb6ed6a1c2ae084513bfa6991f7c1bf67b4cc609ebbca3c2d1c99d74167897f470
|
7
|
+
data.tar.gz: 9f55e7a93233c1c8121d26424c2a7604d36b58f6695b104dc64ea39091e16bb45f3da10231ff4367e75c56d843fd724284b2adae053617244a2295227d3bc41d
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,19 @@
|
|
1
1
|
# Cfdi40
|
2
2
|
|
3
|
-
Tool for read, create,
|
3
|
+
Tool for read, create, validate and sign CFDIs version 4.0
|
4
4
|
|
5
5
|
CFDI (Comprobante Fiscal Digital por Internet) are XML documents
|
6
6
|
regulated by mexican goverment for tax purpouses.
|
7
7
|
|
8
|
-
|
8
|
+
Please see `README_es-MX.md`
|
9
|
+
|
10
|
+
TODO: Document, document, document
|
11
|
+
|
12
|
+
## Features
|
9
13
|
|
10
|
-
*
|
11
|
-
|
14
|
+
* XML generation and sign.
|
15
|
+
|
16
|
+
## Future features
|
12
17
|
|
13
18
|
## Installation
|
14
19
|
|
@@ -48,7 +53,7 @@ Bug reports and pull requests are welcome on GitHub at
|
|
48
53
|
https://github.com/[USERNAME]/cfdi40. This project is intended to be a
|
49
54
|
safe, welcoming space for collaboration, and contributors are expected
|
50
55
|
to adhere to the [code of
|
51
|
-
conduct](https://github.com/
|
56
|
+
conduct](https://github.com/israelbz/cfdi40/blob/master/CODE_OF_CONDUCT.md).
|
52
57
|
|
53
58
|
## License
|
54
59
|
|
data/README_es-MX.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Cfdi40
|
2
|
+
|
3
|
+
Herramienta para crear, leer, validar y firmam CFDis en
|
4
|
+
versión 4.0.
|
5
|
+
|
6
|
+
El CFDi (Comprobante Fiscal Digital por internet) es un
|
7
|
+
documento en formato XML usado en México.
|
8
|
+
|
9
|
+
Esta herramienta tiene la intención de simplificar la
|
10
|
+
generación de los archivos XML, para ello, se pretende
|
11
|
+
que esta herramienta:
|
12
|
+
|
13
|
+
* Ofrezca una interfaz simple para colocar la
|
14
|
+
información esencial para la elaboración del CFDi.
|
15
|
+
* Realice los cálculos complementarios como impuestos,
|
16
|
+
totales, etcétera.
|
17
|
+
* Valide el CFDi contra los CSD
|
18
|
+
* Selle el CFDi.
|
19
|
+
|
20
|
+
# Uso
|
21
|
+
|
22
|
+
## Ejemplo básico
|
23
|
+
|
24
|
+
# Inicia un cfdi
|
25
|
+
cfdi = Cfdi40.new
|
26
|
+
|
27
|
+
# Datos del emisor. RFC y Nombre se extraen del certificado
|
28
|
+
cfdi.lugar_expedicion = '06000'
|
29
|
+
cfdi.emisor.regimen_fiscal = '612'
|
30
|
+
|
31
|
+
# Datos del receptor
|
32
|
+
cfdi.receptor.nombre = 'JUAN PUEBLO BUENO'
|
33
|
+
cfdi.receptor.rfc = 'XAXX010101000'
|
34
|
+
cfdi.receptor.domicilio_fiscal = '06000'
|
35
|
+
cfdi.receptor.regimen_fiscal = '616'
|
36
|
+
cfdi.receptor.uso_cfdi = 'G03'
|
37
|
+
|
38
|
+
# Agrega un concepto en pesos,
|
39
|
+
# precio final al cliente (neto)
|
40
|
+
# causa IVA con tasa de 16% (default)
|
41
|
+
cfdi.add_concepto(
|
42
|
+
clave_prod_serv: '81111500',
|
43
|
+
clave_unidad: "E48",
|
44
|
+
descripcion: 'Prueba de concepto',
|
45
|
+
precio_neto: 40
|
46
|
+
)
|
47
|
+
|
48
|
+
# Archivos CSD
|
49
|
+
cfdi.cert_path = '/path_to/certificado.cer'
|
50
|
+
cfdi.key_path = '/path_to/llave_privada.key'
|
51
|
+
cfdi.key_pass = 'contraseña'
|
52
|
+
|
53
|
+
# Genera CFDI firmado
|
54
|
+
xml_string = cfdi.to_xml
|
55
|
+
|
56
|
+
# Cambios
|
57
|
+
|
58
|
+
# 0.0.3
|
59
|
+
|
60
|
+
* Lee RFC en certificados de personas morales. Los certificados de
|
61
|
+
personas morales tienen el RFC de la persona moral y el del
|
62
|
+
representante legal en el `UniqueIdentifier` del `Subject` del
|
63
|
+
certificado
|
64
|
+
* Actepta llaves previamente descifradas en formato PEM.
|
65
|
+
|
66
|
+
# 0.0.2
|
67
|
+
|
68
|
+
* Definición básica de la intefaz.
|
69
|
+
* Carga certificado y llave desde archivo.
|
70
|
+
* Acepta certificado y llave previamente leídos.
|
71
|
+
* Genera CFDI de ingresos básico. Con desglose de impuestos, sello
|
72
|
+
digital
|
73
|
+
* Valida correspondencia de certificado y llave
|
74
|
+
|
75
|
+
# 0.0.1
|
76
|
+
|
77
|
+
* Versión inicial. Esqueleto para desarrollo
|
78
|
+
|
79
|
+
# ¿Que sigue?
|
80
|
+
|
81
|
+
* Complemento de pagos
|
82
|
+
* Retenciones
|
83
|
+
* IEPS
|
84
|
+
* Complemento para colegiaturas
|
data/cfdi40.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
"regulated by Mexican Government"
|
15
15
|
spec.homepage = "https://github.com/israelbz/cfdi40"
|
16
16
|
spec.license = "MIT"
|
17
|
-
spec.required_ruby_version = ">= 2.
|
17
|
+
spec.required_ruby_version = ">= 2.3.3"
|
18
18
|
|
19
19
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
20
|
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.require_paths = ["lib"]
|
35
35
|
|
36
36
|
# Uncomment to register a new dependency of your gem
|
37
|
-
spec.add_dependency "nokogiri", "
|
37
|
+
spec.add_dependency "nokogiri", ">= 1.10.10"
|
38
38
|
|
39
39
|
# For more information and examples about making a new gem, check out our
|
40
40
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class ComplementoConcepto < Node
|
3
|
+
def inst_educativas_node
|
4
|
+
return @inst_educativas_node if defined?(@inst_educativas_node)
|
5
|
+
|
6
|
+
@inst_educativas_node = InstEducativas.new
|
7
|
+
@inst_educativas_node.parent_node = self
|
8
|
+
self.children_nodes << @inst_educativas_node
|
9
|
+
@inst_educativas_node
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/cfdi40/comprobante.rb
CHANGED
@@ -1,30 +1,235 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Create and Read XML documents
|
4
|
-
|
5
|
-
|
4
|
+
module Cfdi40
|
5
|
+
class Comprobante < Node
|
6
|
+
define_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
|
7
|
+
define_namespace "cfdi", "http://www.sat.gob.mx/cfd/4"
|
8
|
+
define_attribute :schema_location,
|
9
|
+
xml_attribute: 'xsi:schemaLocation',
|
10
|
+
readonly: true,
|
11
|
+
default: "http://www.sat.gob.mx/cfd/4 " \
|
12
|
+
"http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd"
|
13
|
+
define_attribute :version, xml_attribute: 'Version', readonly: true, default: '4.0'
|
14
|
+
define_attribute :serie, xml_attribute: 'Serie'
|
15
|
+
define_attribute :folio, xml_attribute: 'Folio'
|
16
|
+
define_attribute :fecha, xml_attribute: 'Fecha'
|
17
|
+
define_attribute :sello, xml_attribute: 'Sello', readonly: true
|
18
|
+
define_attribute :forma_pago, xml_attribute: 'FormaPago'
|
19
|
+
define_attribute :no_certificado, xml_attribute: 'NoCertificado'
|
20
|
+
define_attribute :certificado, xml_attribute: 'Certificado'
|
21
|
+
define_attribute :condiciones_de_pago, xml_attribute: 'CondicionesDePago'
|
22
|
+
define_attribute :subtotal, xml_attribute: 'SubTotal', format: :t_Importe
|
23
|
+
define_attribute :descuento, xml_attribute: 'Descuento', format: :t_Importe
|
24
|
+
define_attribute :moneda, xml_attribute: 'Moneda', default: 'MXN'
|
25
|
+
define_attribute :tipo_cambio, xml_attribute: 'TipoCambio'
|
26
|
+
define_attribute :total, xml_attribute: 'Total', format: :t_Importe
|
27
|
+
define_attribute :tipo_de_comprobante, xml_attribute: 'TipoDeComprobante', default: 'I'
|
28
|
+
define_attribute :exportacion, xml_attribute: 'Exportacion', default: '01'
|
29
|
+
define_attribute :metodo_pago, xml_attribute: 'MetodoPago'
|
30
|
+
define_attribute :lugar_expedicion, xml_attribute: 'LugarExpedicion'
|
31
|
+
define_attribute :confirmacion, xml_attribute: 'Confirmacion'
|
6
32
|
|
7
|
-
|
8
|
-
|
9
|
-
|
33
|
+
attr_reader :emisor, :receptor, :x509_cert, :conceptos, :private_key
|
34
|
+
attr_reader :errors
|
35
|
+
attr_writer :key_data, :key_pass
|
10
36
|
|
11
|
-
|
12
|
-
|
13
|
-
|
37
|
+
def initialize
|
38
|
+
super
|
39
|
+
@errors = []
|
40
|
+
@conceptos = Conceptos.new
|
41
|
+
@conceptos.parent_node = self
|
42
|
+
@emisor = Emisor.new
|
43
|
+
@emisor.parent_node = self
|
44
|
+
@receptor = Receptor.new
|
45
|
+
@receptor.parent_node = self
|
46
|
+
@sat_csd = SatCsd.new
|
47
|
+
@fecha ||= Time.now.strftime("%Y-%m-%dT%H:%M:%S")
|
48
|
+
@children_nodes = [@emisor, @receptor, @conceptos]
|
49
|
+
set_defaults
|
50
|
+
end
|
51
|
+
|
52
|
+
# Accept a path to read the certificate.
|
53
|
+
# Certificate is a X509 file. SAT generates those files in
|
54
|
+
# DER format.
|
55
|
+
def cert_path=(path)
|
56
|
+
self.cert_der = File.read(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def cert_der=(cert_data)
|
60
|
+
@sat_csd ||= SatCsd.new
|
61
|
+
@sat_csd.cert_der = cert_data
|
62
|
+
emisor.rfc = @sat_csd.rfc
|
63
|
+
emisor.nombre = @sat_csd.name
|
64
|
+
@no_certificado = @sat_csd.no_certificado
|
65
|
+
@certificado = @sat_csd.cert64
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def key_path=(path)
|
70
|
+
@key_data = File.read(path)
|
71
|
+
end
|
72
|
+
|
73
|
+
def sign
|
74
|
+
@sat_csd ||= SatCsd.new
|
75
|
+
load_private_key if @sat_csd.private_key.nil?
|
76
|
+
return unless @sat_csd.private_key
|
77
|
+
|
78
|
+
raise Error, 'Key and certificate not match' unless @sat_csd.valid_pair?
|
79
|
+
|
80
|
+
digest = @sat_csd.private_key.sign(OpenSSL::Digest.new('SHA256'), original_content)
|
81
|
+
@sello = Base64.strict_encode64 digest
|
82
|
+
@docxml = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# clave_prod_serv
|
86
|
+
# no_identificacion
|
87
|
+
# cantidad
|
88
|
+
# clave_unidad
|
89
|
+
# unidad
|
90
|
+
# descripcion
|
91
|
+
# valor_unitario
|
92
|
+
# importe
|
93
|
+
# descuento
|
94
|
+
# objeto_imp
|
95
|
+
#
|
96
|
+
# TODO: Document accepted attributes and its use
|
97
|
+
def add_concepto(attributes = {})
|
98
|
+
concepto = Concepto.new
|
99
|
+
concepto.parent_node = @conceptos
|
100
|
+
attributes.each do |key, value|
|
101
|
+
method_name = "#{key}=".to_sym
|
102
|
+
if concepto.respond_to?(method_name)
|
103
|
+
concepto.public_send(method_name, value)
|
104
|
+
else
|
105
|
+
raise Error, ":#{key} no se puede asignar al concepto"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
concepto.calculate!
|
109
|
+
@conceptos.children_nodes << concepto
|
110
|
+
calculate!
|
111
|
+
concepto
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
to_xml
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_xml
|
119
|
+
sign
|
120
|
+
docxml.to_xml
|
121
|
+
end
|
122
|
+
|
123
|
+
def valid?
|
124
|
+
schema_validator = SchemaValidator.new(to_s)
|
125
|
+
return true if schema_validator.valid?
|
126
|
+
|
127
|
+
@errors = schema_validator.errors
|
128
|
+
@errors.empty?
|
129
|
+
end
|
130
|
+
|
131
|
+
def cadena_original
|
132
|
+
original_content
|
133
|
+
end
|
134
|
+
|
135
|
+
def original_content
|
136
|
+
xslt = Nokogiri::XSLT(File.open('lib/xslt/cadenaoriginal_local.xslt'))
|
137
|
+
transformed = xslt.transform(docxml)
|
138
|
+
# The ampersand (&) char must be used in original content
|
139
|
+
# even though the documentation indicates otherwise
|
140
|
+
transformed.children.to_s.gsub('&', '&').strip
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def docxml
|
146
|
+
return @docxml if defined?(@docxml) && !@docxml.nil?
|
147
|
+
|
148
|
+
@docxml = Nokogiri::XML::Document.new("1.0")
|
149
|
+
@docxml.encoding = "utf-8"
|
150
|
+
add_root_node
|
151
|
+
@docxml
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_root_node
|
155
|
+
self.xml_document = @docxml
|
156
|
+
self.xml_parent = @docxml
|
157
|
+
create_xml_node
|
158
|
+
end
|
159
|
+
|
160
|
+
def calculate!
|
161
|
+
@subtotal = @conceptos.children_nodes.map(&:importe).sum
|
162
|
+
@total = @conceptos.children_nodes.map(&:importe_neto).sum
|
163
|
+
add_traslados_summary_node
|
164
|
+
end
|
165
|
+
|
166
|
+
def add_traslados_summary_node
|
167
|
+
return if traslados_summary.empty?
|
168
|
+
|
169
|
+
impuestos.total_impuestos_trasladados = 0
|
170
|
+
traslados.children_nodes = []
|
171
|
+
traslados_summary.each do |key, value|
|
172
|
+
#TODO: Sumar los impuestos y agregarlos a los nodos globales de traslados
|
173
|
+
traslado = Traslado.new
|
174
|
+
traslado.parent_node = impuestos
|
175
|
+
traslado.impuesto, traslado.tasa_o_cuota, traslado.tipo_factor = key
|
176
|
+
traslado.base = value[:base]
|
177
|
+
traslado.importe = value[:importe]
|
178
|
+
traslados.children_nodes << traslado
|
179
|
+
impuestos.total_impuestos_trasladados += value[:importe]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def concepto_nodes
|
184
|
+
@conceptos.children_nodes
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns a hash with a summary.
|
188
|
+
# The key is an Array ['impuesto, 'tasa_o_cuota', 'TipoFactor] and the value is
|
189
|
+
# another hash the sum of 'Importe' and 'Base'
|
190
|
+
def traslados_summary
|
191
|
+
summary = {}
|
192
|
+
concepto_nodes.map(&:traslado_nodes).flatten.each do |traslado|
|
193
|
+
key = [traslado.impuesto, traslado.tasa_o_cuota, traslado.tipo_factor]
|
194
|
+
summary[key] ||= { base: 0, importe: 0 }
|
195
|
+
summary[key][:base] += traslado.base
|
196
|
+
summary[key][:importe] += traslado.importe
|
197
|
+
end
|
198
|
+
summary
|
199
|
+
end
|
200
|
+
|
201
|
+
# TODO: Este método tiene que ser 'impuestos'
|
202
|
+
# si nos atenemos a que los que acaban con _node buscan en los hijos
|
203
|
+
# y los que no terminan con _node crean el nodo
|
204
|
+
def impuestos
|
205
|
+
return @impuestos if defined?(@impuestos)
|
206
|
+
|
207
|
+
@impuestos = Impuestos.new
|
208
|
+
@impuestos.parent_node = self
|
209
|
+
@children_nodes << @impuestos
|
210
|
+
@impuestos
|
211
|
+
end
|
212
|
+
|
213
|
+
def impuestos_node
|
214
|
+
children_nodes.select { |n| n.is_a?(Impuestos)}.first
|
215
|
+
end
|
216
|
+
|
217
|
+
def traslados
|
218
|
+
return nil if impuestos_node.nil?
|
14
219
|
|
15
|
-
|
220
|
+
impuestos_node.traslados
|
221
|
+
end
|
16
222
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
root.add_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
|
22
|
-
root.add_namespace "cfdi", "http://www.sat.gob.mx/cfd/4"
|
23
|
-
root["xsi:schemaLocation"] = "http://www.sat.gob.mx/cfd/3 " \
|
24
|
-
"http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd"
|
25
|
-
root["Version"] = "4.0"
|
223
|
+
# Eliminar
|
224
|
+
def traslado_iva_node
|
225
|
+
impuestos_node.traslado_iva
|
226
|
+
end
|
26
227
|
|
27
|
-
|
28
|
-
|
228
|
+
def load_private_key
|
229
|
+
return unless defined?(@key_data) && defined?(@key_pass)
|
230
|
+
|
231
|
+
@sat_csd ||= SatCsd.new
|
232
|
+
@sat_csd.set_private_key(@key_data, @key_pass)
|
233
|
+
end
|
29
234
|
end
|
30
235
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# Represents node 'concepto'
|
2
|
+
#
|
3
|
+
# * Attribute +Importe+ represente gross amount. Gross amount id before taxes and the result of multiply
|
4
|
+
# +ValorUnitario+ by +Cantidad+
|
5
|
+
#
|
6
|
+
module Cfdi40
|
7
|
+
class Concepto < Node
|
8
|
+
define_attribute :clave_prod_serv, xml_attribute: 'ClaveProdServ'
|
9
|
+
define_attribute :no_identificacion,xml_attribute: 'NoIdentificacion'
|
10
|
+
define_attribute :cantidad, xml_attribute: 'Cantidad', default: 1
|
11
|
+
define_attribute :clave_unidad, xml_attribute: 'ClaveUnidad'
|
12
|
+
define_attribute :unidad, xml_attribute: 'Unidad'
|
13
|
+
define_attribute :descripcion, xml_attribute: 'Descripcion'
|
14
|
+
define_attribute :valor_unitario, xml_attribute: 'ValorUnitario', format: :t_Importe
|
15
|
+
define_attribute :importe, xml_attribute: 'Importe', format: :t_Importe
|
16
|
+
define_attribute :descuento, xml_attribute: 'Descuento', format: :t_Importe
|
17
|
+
define_attribute :objeto_impuestos, xml_attribute: 'ObjetoImp', default: '01'
|
18
|
+
|
19
|
+
attr_accessor :tasa_iva, :tasa_ieps, :precio_neto, :precio_bruto
|
20
|
+
attr_reader :iva, :ieps, :base_iva, :importe_neto, :importe_bruto
|
21
|
+
|
22
|
+
# accesors for instEducativas
|
23
|
+
attr_accessor :iedu_nombre_alumno, :iedu_curp, :iedu_nivel_educativo, :iedu_aut_rvoe, :iedu_rfc_pago
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@tasa_iva = 0.16
|
27
|
+
@tasa_ieps = 0
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
# Calculate taxes, amounts from gross price
|
32
|
+
# or net price
|
33
|
+
def calculate!
|
34
|
+
set_defaults
|
35
|
+
assign_objeto_imp
|
36
|
+
if defined?(@precio_neto) && !@precio_neto.nil?
|
37
|
+
calculate_from_net_price
|
38
|
+
elsif defined?(@precio_bruto) && !@precio_bruto.nil?
|
39
|
+
calculate_from_gross_price
|
40
|
+
elsif !self.valor_unitario.nil?
|
41
|
+
@precio_bruto = valor_unitario
|
42
|
+
calculate_from_gross_price
|
43
|
+
end
|
44
|
+
add_info_to_traslado_iva
|
45
|
+
# TODO: add_info_to_traslado_ieps if @ieps > 0
|
46
|
+
add_inst_educativas
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def objeto_impuestos?
|
51
|
+
objeto_impuestos == '02'
|
52
|
+
end
|
53
|
+
|
54
|
+
def traslado_nodes
|
55
|
+
return [] if impuestos_node.nil?
|
56
|
+
|
57
|
+
impuestos_node.traslado_nodes
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def calculate_from_net_price
|
63
|
+
set_defaults
|
64
|
+
@importe_neto = precio_neto * cantidad
|
65
|
+
breakdown_taxes
|
66
|
+
update_xml_attributes
|
67
|
+
end
|
68
|
+
|
69
|
+
def breakdown_taxes
|
70
|
+
@base_iva = @importe_neto / (1 + tasa_iva)
|
71
|
+
@iva = @importe_neto - @base_iva
|
72
|
+
@base_ieps = @base_iva / (1 + tasa_ieps)
|
73
|
+
@ieps = @base_iva - @base_ieps
|
74
|
+
@importe_bruto = @base_ieps
|
75
|
+
@precio_bruto = @importe_bruto / @cantidad
|
76
|
+
end
|
77
|
+
|
78
|
+
def calculate_from_gross_price
|
79
|
+
@importe_bruto = @precio_bruto * cantidad
|
80
|
+
add_taxes
|
81
|
+
update_xml_attributes
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_taxes
|
85
|
+
@base_ieps = @importe_bruto
|
86
|
+
@ieps = @base_ieps * tasa_ieps
|
87
|
+
@base_iva = @base_ieps + @ieps
|
88
|
+
@iva = @base_iva * tasa_iva
|
89
|
+
@importe_neto = @base_iva + @iva
|
90
|
+
@precio_neto = @importe_neto / cantidad
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_xml_attributes
|
94
|
+
self.importe = @importe_bruto
|
95
|
+
self.valor_unitario = @precio_bruto
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_info_to_traslado_iva
|
99
|
+
return unless @iva > 0
|
100
|
+
|
101
|
+
traslado_iva_node.importe = @iva
|
102
|
+
traslado_iva_node.base = @base_iva
|
103
|
+
traslado_iva_node.tasa_o_cuota = @tasa_iva
|
104
|
+
end
|
105
|
+
|
106
|
+
def assign_objeto_imp
|
107
|
+
return if objeto_impuestos == '03'
|
108
|
+
|
109
|
+
self.objeto_impuestos = (@tasa_iva > 0 || @tasa_ieps > 0 ? '02' : '01')
|
110
|
+
end
|
111
|
+
|
112
|
+
def impuestos_node
|
113
|
+
return @impuestos_node if defined?(@impuestos_node)
|
114
|
+
return nil unless objeto_impuestos?
|
115
|
+
|
116
|
+
@impuestos_node = children_nodes.select { |child| child.is_a?(Impuestos) }.first
|
117
|
+
return if @impuestos_node
|
118
|
+
|
119
|
+
@impuestos_node = Impuestos.new
|
120
|
+
@impuestos_node.parent_node = self
|
121
|
+
self.children_nodes << @impuestos_node
|
122
|
+
@impuestos_node
|
123
|
+
end
|
124
|
+
|
125
|
+
def traslado_iva_node
|
126
|
+
return nil unless impuestos_node.is_a?(Impuestos)
|
127
|
+
|
128
|
+
impuestos_node.traslado_iva
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_inst_educativas
|
132
|
+
return unless inst_educativas_present?
|
133
|
+
|
134
|
+
inst_educativas_node.nombre_alumno = iedu_nombre_alumno
|
135
|
+
inst_educativas_node.curp = iedu_curp
|
136
|
+
inst_educativas_node.nivel_educativo = iedu_nivel_educativo
|
137
|
+
inst_educativas_node.aut_rvoe = iedu_aut_rvoe
|
138
|
+
inst_educativas_node.rfc_pago = iedu_rfc_pago
|
139
|
+
end
|
140
|
+
|
141
|
+
def inst_educativas_present?
|
142
|
+
return false if iedu_nombre_alumno.nil?
|
143
|
+
return false if iedu_nivel_educativo.nil?
|
144
|
+
return false if iedu_aut_rvoe.nil?
|
145
|
+
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def concepto_complemento_node
|
150
|
+
return @complemento_concepto_node if defined?(@complemento_concepto_node)
|
151
|
+
|
152
|
+
@complemento_concepto_node = ComplementoConcepto.new
|
153
|
+
@complemento_concepto_node.parent_node = self
|
154
|
+
self.children_nodes << @complemento_concepto_node
|
155
|
+
@complemento_concepto_node
|
156
|
+
end
|
157
|
+
|
158
|
+
def inst_educativas_node
|
159
|
+
return nil unless concepto_complemento_node.is_a?(ComplementoConcepto)
|
160
|
+
|
161
|
+
concepto_complemento_node.inst_educativas_node
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Impuestos < Node
|
3
|
+
define_attribute :total_impuestos_retenidos, xml_attribute: 'TotalImpuestosRetenidos', format: :t_Importe
|
4
|
+
define_attribute :total_impuestos_trasladados, xml_attribute: 'TotalImpuestosTrasladados', format: :t_Importe
|
5
|
+
|
6
|
+
def traslados
|
7
|
+
return @traslados if defined?(@traslados)
|
8
|
+
|
9
|
+
@traslados = Traslados.new
|
10
|
+
@traslados.parent_node = self
|
11
|
+
self.children_nodes << @traslados
|
12
|
+
@traslados
|
13
|
+
end
|
14
|
+
|
15
|
+
def traslados_node
|
16
|
+
children_nodes.select { |n| n.is_a?(Traslados)}.first
|
17
|
+
end
|
18
|
+
|
19
|
+
def traslado_nodes
|
20
|
+
return [] if traslados_node.nil?
|
21
|
+
|
22
|
+
traslados_node.traslado_nodes
|
23
|
+
end
|
24
|
+
|
25
|
+
def traslado_iva
|
26
|
+
traslados.traslado_iva
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class InstEducativas < Node
|
3
|
+
define_element_name 'instEducativas'
|
4
|
+
define_namespace "xsi", "http://www.w3.org/2001/XMLSchema-instance"
|
5
|
+
define_namespace "iedu", "http://www.sat.gob.mx/iedu"
|
6
|
+
define_attribute :schema_location,
|
7
|
+
xml_attribute: 'xsi:schemaLocation',
|
8
|
+
readonly: true,
|
9
|
+
default: "http://www.sat.gob.mx/iedu http://www.sat.gob.mx/sitio_internet/cfd/iedu/iedu.xsd"
|
10
|
+
define_attribute :version, xml_attribute: 'version', readonly: true, default: '1.0'
|
11
|
+
define_attribute :nombre_alumno, xml_attribute: 'nombreAlumno'
|
12
|
+
define_attribute :curp, xml_attribute: 'CURP'
|
13
|
+
define_attribute :nivel_educativo, xml_attribute: 'nivelEducativo'
|
14
|
+
define_attribute :aut_rvoe, xml_attribute: 'autRVOE'
|
15
|
+
define_attribute :rfc_pago, xml_attribute: 'rfcPago'
|
16
|
+
end
|
17
|
+
end
|