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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +10 -5
  4. data/README_es-MX.md +84 -0
  5. data/cfdi40.gemspec +2 -2
  6. data/lib/cfdi40/complemento_concepto.rb +12 -0
  7. data/lib/cfdi40/comprobante.rb +225 -20
  8. data/lib/cfdi40/concepto.rb +164 -0
  9. data/lib/cfdi40/conceptos.rb +4 -0
  10. data/lib/cfdi40/emisor.rb +7 -0
  11. data/lib/cfdi40/impuestos.rb +29 -0
  12. data/lib/cfdi40/inst_educativas.rb +17 -0
  13. data/lib/cfdi40/node.rb +155 -0
  14. data/lib/cfdi40/receptor.rb +11 -0
  15. data/lib/cfdi40/sat_csd.rb +89 -0
  16. data/lib/cfdi40/schema_validator.rb +29 -0
  17. data/lib/cfdi40/traslado.rb +9 -0
  18. data/lib/cfdi40/traslados.rb +18 -0
  19. data/lib/cfdi40/version.rb +1 -1
  20. data/lib/cfdi40.rb +14 -0
  21. data/lib/xsd/README.md +34 -0
  22. data/lib/xsd/catCFDI.xsd +162329 -0
  23. data/lib/xsd/cfdv40.xsd +857 -0
  24. data/lib/xsd/iedu.xsd +84 -0
  25. data/lib/xsd/tdCFDI.xsd +157 -0
  26. data/lib/xslt/CartaPorte20.xslt +615 -0
  27. data/lib/xslt/GastosHidrocarburos10.xslt +171 -0
  28. data/lib/xslt/IngresosHidrocarburos.xslt +39 -0
  29. data/lib/xslt/Pagos20.xslt +233 -0
  30. data/lib/xslt/TuristaPasajeroExtranjero.xslt +40 -0
  31. data/lib/xslt/aerolineas.xslt +50 -0
  32. data/lib/xslt/cadenaoriginal.xslt +405 -0
  33. data/lib/xslt/cadenaoriginal_local.xslt +405 -0
  34. data/lib/xslt/certificadodedestruccion.xslt +60 -0
  35. data/lib/xslt/cfdiregistrofiscal.xslt +19 -0
  36. data/lib/xslt/consumodeCombustibles11.xslt +94 -0
  37. data/lib/xslt/detallista.xslt +42 -0
  38. data/lib/xslt/divisas.xslt +13 -0
  39. data/lib/xslt/donat11.xslt +13 -0
  40. data/lib/xslt/iedu.xslt +26 -0
  41. data/lib/xslt/implocal.xslt +39 -0
  42. data/lib/xslt/ine11.xslt +30 -0
  43. data/lib/xslt/leyendasFisc.xslt +28 -0
  44. data/lib/xslt/nomina12.xslt +412 -0
  45. data/lib/xslt/notariospublicos.xslt +301 -0
  46. data/lib/xslt/obrasarteantiguedades.xslt +33 -0
  47. data/lib/xslt/pagoenespecie.xslt +39 -0
  48. data/lib/xslt/pfic.xslt +13 -0
  49. data/lib/xslt/renovacionysustitucionvehiculos.xslt +152 -0
  50. data/lib/xslt/servicioparcialconstruccion.xslt +44 -0
  51. data/lib/xslt/utilerias.xslt +22 -0
  52. data/lib/xslt/valesdedespensa.xslt +70 -0
  53. data/lib/xslt/vehiculousado.xslt +63 -0
  54. metadata +53 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c30e39f4584ad4ec24f70ae950700568d72b0941adb3535ece9132f0ff7f3d19
4
- data.tar.gz: 9dc2cf08802bee96c4a2cfa362101be5ac6926bf67f613f1d2293b6480c6316c
3
+ metadata.gz: bb9a12ec72926735a554f302b13993914393c626cc4dcd797791dfcf0ed8191a
4
+ data.tar.gz: 4bc4708eb4c2c0671fa11c81dbcb40a8a8c9a585d2fa02531137efeee942a9d0
5
5
  SHA512:
6
- metadata.gz: '070922f0002e8948bec5ce379e198b61a442bcc54ba47ac45f9f46f2bd58bbae9bcdb4b54b5527bc2996a32c793d8f7ea75235bc2b96925f0cdb4d0ef3c4783a'
7
- data.tar.gz: 552352ed70288e9307c6eb065fe57d0d45f84be27085ceb4698fd19a16ec3f0042ced1f57d7acdb467c254ff504a5bb60522402874e736da1e2f2d6b7cf30271
6
+ metadata.gz: f41d5ee33e1e705d2b12b2c623cd6b197d2c93c1a9e5d994ffc38630954fc4cb6ed6a1c2ae084513bfa6991f7c1bf67b4cc609ebbca3c2d1c99d74167897f470
7
+ data.tar.gz: 9f55e7a93233c1c8121d26424c2a7604d36b58f6695b104dc64ea39091e16bb45f3da10231ff4367e75c56d843fd724284b2adae053617244a2295227d3bc41d
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cfdi40 (0.0.1.alfa)
5
- nokogiri (~> 1.13, >= 1.13.9)
4
+ cfdi40 (0.0.3)
5
+ nokogiri (>= 1.10.10)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,14 +1,19 @@
1
1
  # Cfdi40
2
2
 
3
- Tool for read, create, edit, validate and sign CFDIs version 4.0
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
- ## Future features
8
+ Please see `README_es-MX.md`
9
+
10
+ TODO: Document, document, document
11
+
12
+ ## Features
9
13
 
10
- * Create and sign a valid XML of an invoice for general public
11
- (público en general) with minimum data
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/[USERNAME]/cfdi40/blob/master/CODE_OF_CONDUCT.md).
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.6.0"
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", "~> 1.13", ">= 1.13.9"
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
@@ -1,30 +1,235 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Create and Read XML documents
4
- class Comprobante
5
- attr_reader :docxml
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
- def initialize
8
- new_docxml
9
- end
33
+ attr_reader :emisor, :receptor, :x509_cert, :conceptos, :private_key
34
+ attr_reader :errors
35
+ attr_writer :key_data, :key_pass
10
36
 
11
- def to_s
12
- docxml.to_xml
13
- end
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('&amp;', '&').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
- private
220
+ impuestos_node.traslados
221
+ end
16
222
 
17
- def new_docxml
18
- @docxml = Nokogiri::XML::Document.new("1.0")
19
- @docxml.encoding = "utf-8"
20
- root = @docxml.create_element "cfdi:Comprobante"
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
- @docxml.add_child root
28
- @docxml
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,4 @@
1
+ module Cfdi40
2
+ class Conceptos < Node
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Cfdi40
2
+ class Emisor < Node
3
+ define_attribute :rfc, xml_attribute: 'Rfc'
4
+ define_attribute :nombre, xml_attribute: 'Nombre'
5
+ define_attribute :regimen_fiscal, xml_attribute: 'RegimenFiscal'
6
+ end
7
+ 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