cfdi40 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/cfdi40/comprobante.rb +11 -6
- data/lib/cfdi40/concepto_rep.rb +192 -0
- data/lib/cfdi40/node.rb +5 -0
- data/lib/cfdi40/version.rb +1 -1
- data/lib/cfdi40/xml_loader.rb +9 -0
- data/lib/cfdi40.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3547682f502971438bc3e204ed001742c7695ba0b9f4b359b702c74494442b77
|
|
4
|
+
data.tar.gz: 22b21e8aaee290803d4e5d87e33275cb0e70389f9f5ec8112e1b0753a701d5e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29fcd6e0b992bbbe6e8372585bd040fea651c945942d18015088d5633a316938b351d3d662cb44cf4de8acafc070ce0beba7e6b889082c7e0fa597fc8e6676cb
|
|
7
|
+
data.tar.gz: b07b1b62bfee3040b54bf1412f8446e92854cbee963b7ef45a4e95ed44e176c4b54de758362fb939db0ff43c43e768feea31c1ca0eea684fd725dc7bcd002172
|
data/Gemfile.lock
CHANGED
data/lib/cfdi40/comprobante.rb
CHANGED
|
@@ -162,6 +162,16 @@ module Cfdi40
|
|
|
162
162
|
concepto
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
+
# Load node 'Concepto' (rep) from a Nokogiri::XML::Element
|
|
166
|
+
def load_concepto_rep(ng_node)
|
|
167
|
+
concepto = ConceptoRep.new
|
|
168
|
+
concepto.parent_node = @conceptos
|
|
169
|
+
concepto.load_from_ng_node(ng_node)
|
|
170
|
+
concepto.precio_bruto = concepto.valor_unitario.to_f
|
|
171
|
+
@conceptos.children_nodes << concepto
|
|
172
|
+
concepto
|
|
173
|
+
end
|
|
174
|
+
|
|
165
175
|
# Load node cfdi:Comprobante/cfdi:Impuestos
|
|
166
176
|
#
|
|
167
177
|
# Normally this node is calculated but must be read from the
|
|
@@ -317,14 +327,9 @@ module Cfdi40
|
|
|
317
327
|
return if @conceptos.children_nodes.size > 0
|
|
318
328
|
|
|
319
329
|
@receptor.uso_cfdi = "CP01"
|
|
320
|
-
@concepto_actividad =
|
|
321
|
-
@concepto_actividad.clave_prod_serv = "84111506"
|
|
322
|
-
@concepto_actividad.cantidad = 1
|
|
323
|
-
@concepto_actividad.clave_unidad = "ACT"
|
|
324
|
-
@concepto_actividad.descripcion = "Pago"
|
|
330
|
+
@concepto_actividad = ConceptoRep.new
|
|
325
331
|
@concepto_actividad.precio_bruto = 0
|
|
326
332
|
@concepto_actividad.tasa_iva = nil
|
|
327
|
-
@concepto_actividad.objeto_impuestos = "01"
|
|
328
333
|
@concepto_actividad.calculate!
|
|
329
334
|
@conceptos.add_child_node @concepto_actividad
|
|
330
335
|
calculate!
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Represents node 'concepto'
|
|
4
|
+
#
|
|
5
|
+
# * Attribute +Importe+ represente gross amount. Gross amount is before taxes and the result of multiply
|
|
6
|
+
# +ValorUnitario+ by +Cantidad+
|
|
7
|
+
#
|
|
8
|
+
module Cfdi40
|
|
9
|
+
class ConceptoRep < Node
|
|
10
|
+
define_element_name 'Concepto'
|
|
11
|
+
define_attribute :clave_prod_serv, xml_attribute: "ClaveProdServ", default: "84111506"
|
|
12
|
+
define_attribute :no_identificacion, xml_attribute: "NoIdentificacion"
|
|
13
|
+
define_attribute :cantidad, xml_attribute: "Cantidad", default: 1, format: :integer
|
|
14
|
+
define_attribute :clave_unidad, xml_attribute: "ClaveUnidad", default: "ACT"
|
|
15
|
+
define_attribute :unidad, xml_attribute: "Unidad"
|
|
16
|
+
define_attribute :descripcion, xml_attribute: "Descripcion", no_writer: true, default: "Pago"
|
|
17
|
+
define_attribute :valor_unitario, xml_attribute: "ValorUnitario", format: :t_Importe
|
|
18
|
+
define_attribute :importe, xml_attribute: "Importe", format: :t_Importe
|
|
19
|
+
define_attribute :descuento, xml_attribute: "Descuento", format: :t_Importe
|
|
20
|
+
define_attribute :objeto_impuestos, xml_attribute: "ObjetoImp", default: "01"
|
|
21
|
+
|
|
22
|
+
attr_accessor :tasa_iva, :tasa_ieps, :precio_neto, :precio_bruto
|
|
23
|
+
attr_reader :iva, :ieps, :base_iva, :importe_neto, :importe_bruto
|
|
24
|
+
|
|
25
|
+
# accesors for instEducativas
|
|
26
|
+
attr_accessor :iedu_nombre_alumno, :iedu_curp, :iedu_nivel_educativo, :iedu_aut_rvoe, :iedu_rfc_pago
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@tasa_iva = 0.16
|
|
30
|
+
@tasa_ieps = nil
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Ver Comprobante#add_concepto
|
|
35
|
+
def update(attributes = {})
|
|
36
|
+
attributes.each do |key, value|
|
|
37
|
+
method_name = "#{key}=".to_sym
|
|
38
|
+
raise Error, ":#{key} no se puede asignar al concepto" unless respond_to?(method_name)
|
|
39
|
+
|
|
40
|
+
public_send(method_name, value)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
calculate!
|
|
44
|
+
parent_node.parent_node.calculate!
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Calculate taxes, amounts from gross price
|
|
49
|
+
# or net price
|
|
50
|
+
def calculate!
|
|
51
|
+
set_defaults
|
|
52
|
+
assign_objeto_imp
|
|
53
|
+
# TODO: accept discount
|
|
54
|
+
if defined?(@precio_neto) && !@precio_neto.nil?
|
|
55
|
+
calculate_from_net_price
|
|
56
|
+
elsif defined?(@precio_bruto) && !@precio_bruto.nil?
|
|
57
|
+
calculate_from_gross_price
|
|
58
|
+
elsif !valor_unitario.nil?
|
|
59
|
+
@precio_bruto = valor_unitario
|
|
60
|
+
calculate_from_gross_price
|
|
61
|
+
end
|
|
62
|
+
add_info_to_traslado_iva
|
|
63
|
+
# TODO: add_info_to_traslado_ieps if @ieps > 0
|
|
64
|
+
add_inst_educativas
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def objeto_impuestos?
|
|
69
|
+
objeto_impuestos == "02"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def traslado_nodes
|
|
73
|
+
return [] if impuestos_node.nil?
|
|
74
|
+
|
|
75
|
+
impuestos_node.traslado_nodes
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def traslado_iva_node
|
|
79
|
+
return nil unless impuestos_node.is_a?(ConceptoImpuestos)
|
|
80
|
+
|
|
81
|
+
impuestos_node.traslado_iva
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def load_traslado_iva(ng_node)
|
|
85
|
+
traslado_iva_node.load_from_ng_node(ng_node)
|
|
86
|
+
calculate_from_gross_price
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def descripcion=(desc)
|
|
90
|
+
@descripcion = desc.to_s[0..999]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def calculate_from_net_price
|
|
96
|
+
set_defaults
|
|
97
|
+
@precio_neto = @precio_neto.round(6)
|
|
98
|
+
@precio_bruto = (@precio_neto / ((1 + tasa_iva.to_f) * (1 + tasa_ieps.to_f)))
|
|
99
|
+
calculate_taxes
|
|
100
|
+
|
|
101
|
+
@precio_bruto = (((@precio_neto * cantidad.to_f) - @iva - @ieps) / cantidad.to_f.round(6)).round(6)
|
|
102
|
+
update_xml_attributes
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def calculate_from_gross_price
|
|
106
|
+
@precio_bruto = @precio_bruto.round(6)
|
|
107
|
+
calculate_taxes
|
|
108
|
+
# May be can not be rounded with 2 decimals.
|
|
109
|
+
# Example gross_price = 1.99512
|
|
110
|
+
@precio_neto = (@importe_neto / cantidad.to_f).round(6)
|
|
111
|
+
update_xml_attributes
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def calculate_taxes
|
|
115
|
+
@base_ieps = (@precio_bruto * cantidad.to_f).round(6)
|
|
116
|
+
@ieps = (@base_ieps * tasa_ieps.to_f).round(6)
|
|
117
|
+
@base_iva = (@base_ieps + @ieps).round(6)
|
|
118
|
+
@iva = (@base_iva * tasa_iva.to_f).round(6)
|
|
119
|
+
@importe_bruto = @base_ieps
|
|
120
|
+
@importe_neto = (@base_iva + @iva).round(2)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def update_xml_attributes
|
|
124
|
+
self.importe = @importe_bruto
|
|
125
|
+
self.valor_unitario = @precio_bruto
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def add_info_to_traslado_iva
|
|
129
|
+
return if @tasa_iva.nil?
|
|
130
|
+
|
|
131
|
+
traslado_iva_node.importe = @iva
|
|
132
|
+
traslado_iva_node.base = @base_iva
|
|
133
|
+
traslado_iva_node.tasa_o_cuota = @tasa_iva
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def assign_objeto_imp
|
|
137
|
+
# 01 No objeto de impuesto.
|
|
138
|
+
# 02 Sí objeto de impuesto.
|
|
139
|
+
# 03 Sí objeto del impuesto y no obligado al desglose.
|
|
140
|
+
|
|
141
|
+
return if objeto_impuestos == "03"
|
|
142
|
+
|
|
143
|
+
self.objeto_impuestos = (!@tasa_iva.nil? || !@tasa_ieps.nil? ? "02" : "01")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def impuestos_node
|
|
147
|
+
return @impuestos_node if defined?(@impuestos_node)
|
|
148
|
+
return nil unless objeto_impuestos?
|
|
149
|
+
|
|
150
|
+
@impuestos_node = children_nodes.select { |child| child.is_a?(ConceptoImpuestos) }.first
|
|
151
|
+
return if @impuestos_node
|
|
152
|
+
|
|
153
|
+
@impuestos_node = ConceptoImpuestos.new
|
|
154
|
+
@impuestos_node.parent_node = self
|
|
155
|
+
children_nodes << @impuestos_node
|
|
156
|
+
@impuestos_node
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def add_inst_educativas
|
|
160
|
+
return unless inst_educativas_present?
|
|
161
|
+
|
|
162
|
+
inst_educativas_node.nombre_alumno = iedu_nombre_alumno
|
|
163
|
+
inst_educativas_node.curp = iedu_curp
|
|
164
|
+
inst_educativas_node.nivel_educativo = iedu_nivel_educativo
|
|
165
|
+
inst_educativas_node.aut_rvoe = iedu_aut_rvoe
|
|
166
|
+
inst_educativas_node.rfc_pago = iedu_rfc_pago
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def inst_educativas_present?
|
|
170
|
+
return false if iedu_nombre_alumno.nil?
|
|
171
|
+
return false if iedu_nivel_educativo.nil?
|
|
172
|
+
return false if iedu_aut_rvoe.nil?
|
|
173
|
+
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def concepto_complemento_node
|
|
178
|
+
return @complemento_concepto_node if defined?(@complemento_concepto_node)
|
|
179
|
+
|
|
180
|
+
@complemento_concepto_node = ComplementoConcepto.new
|
|
181
|
+
@complemento_concepto_node.parent_node = self
|
|
182
|
+
children_nodes << @complemento_concepto_node
|
|
183
|
+
@complemento_concepto_node
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def inst_educativas_node
|
|
187
|
+
return nil unless concepto_complemento_node.is_a?(ComplementoConcepto)
|
|
188
|
+
|
|
189
|
+
concepto_complemento_node.inst_educativas_node
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
data/lib/cfdi40/node.rb
CHANGED
|
@@ -48,6 +48,11 @@ module Cfdi40
|
|
|
48
48
|
|
|
49
49
|
def self.define_reader(accessor, format)
|
|
50
50
|
case format.to_s
|
|
51
|
+
when 'integer'
|
|
52
|
+
define_method("#{accessor}".to_sym) do
|
|
53
|
+
value = instance_variable_defined?("@#{accessor}".to_sym) ? instance_variable_get("@#{accessor}".to_sym) : 0
|
|
54
|
+
value.to_i
|
|
55
|
+
end
|
|
51
56
|
when 't_Importe', 'decimal'
|
|
52
57
|
define_method("#{accessor}".to_sym) do
|
|
53
58
|
value = instance_variable_defined?("@#{accessor}".to_sym) ? instance_variable_get("@#{accessor}".to_sym) : 0
|
data/lib/cfdi40/version.rb
CHANGED
data/lib/cfdi40/xml_loader.rb
CHANGED
|
@@ -34,6 +34,8 @@ module Cfdi40
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def load_conceptos
|
|
37
|
+
return load_concepto_rep if @cfdi.tipo_de_comprobante == 'P'
|
|
38
|
+
|
|
37
39
|
n_concepto = 0
|
|
38
40
|
xml_doc.xpath("//cfdi:Concepto").each do |node|
|
|
39
41
|
n_concepto += 1
|
|
@@ -44,6 +46,13 @@ module Cfdi40
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
49
|
+
def load_concepto_rep
|
|
50
|
+
node = xml_doc.xpath("//cfdi:Concepto").first
|
|
51
|
+
return if node.nil?
|
|
52
|
+
|
|
53
|
+
concepto = @cfdi.load_concepto_rep(node)
|
|
54
|
+
end
|
|
55
|
+
|
|
47
56
|
def load_emisor
|
|
48
57
|
ng_emisor_node = xml_doc.xpath("//cfdi:Emisor").first
|
|
49
58
|
return if ng_emisor_node.nil?
|
data/lib/cfdi40.rb
CHANGED
|
@@ -22,6 +22,7 @@ require_relative "cfdi40/traslado"
|
|
|
22
22
|
require_relative "cfdi40/complemento_concepto"
|
|
23
23
|
require_relative "cfdi40/inst_educativas"
|
|
24
24
|
require_relative "cfdi40/complemento"
|
|
25
|
+
require_relative "cfdi40/concepto_rep"
|
|
25
26
|
require_relative "cfdi40/pagos"
|
|
26
27
|
require_relative "cfdi40/pago"
|
|
27
28
|
require_relative "cfdi40/docto_relacionado"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cfdi40
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Israel Benítez
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -48,6 +48,7 @@ files:
|
|
|
48
48
|
- lib/cfdi40/comprobante.rb
|
|
49
49
|
- lib/cfdi40/concepto.rb
|
|
50
50
|
- lib/cfdi40/concepto_impuestos.rb
|
|
51
|
+
- lib/cfdi40/concepto_rep.rb
|
|
51
52
|
- lib/cfdi40/conceptos.rb
|
|
52
53
|
- lib/cfdi40/cp_totales.rb
|
|
53
54
|
- lib/cfdi40/docto_relacionado.rb
|