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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c0d18ec474ec0e93d596d77375b336b242d8de1b0ec45fe2eaa1f454bbf68aa
4
- data.tar.gz: 6003ead6bdc574401a68fbe6f9da3704b75dd832cfa863697dad82f4dd5691fd
3
+ metadata.gz: 3547682f502971438bc3e204ed001742c7695ba0b9f4b359b702c74494442b77
4
+ data.tar.gz: 22b21e8aaee290803d4e5d87e33275cb0e70389f9f5ec8112e1b0753a701d5e0
5
5
  SHA512:
6
- metadata.gz: d9f83884bb8377571543c315c81a6df477ad8f153eec0487a69d249a1a6b1fb3cf302209b09b3b95b3f2ce91ed8dab26454a52c2f981a28585434ee86deee28a
7
- data.tar.gz: 6412aa85572de8d791255f5232f9cc7351daf380ee4652eeaf1c44e7b65ba566714efbd73ab61fcbb1edaa43a5ce66816ed1f0faeb25ca301aff1e4289746d34
6
+ metadata.gz: 29fcd6e0b992bbbe6e8372585bd040fea651c945942d18015088d5633a316938b351d3d662cb44cf4de8acafc070ce0beba7e6b889082c7e0fa597fc8e6676cb
7
+ data.tar.gz: b07b1b62bfee3040b54bf1412f8446e92854cbee963b7ef45a4e95ed44e176c4b54de758362fb939db0ff43c43e768feea31c1ca0eea684fd725dc7bcd002172
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cfdi40 (0.4.5)
4
+ cfdi40 (0.4.7)
5
5
  nokogiri (>= 1.10.10)
6
6
 
7
7
  GEM
@@ -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 = Concepto.new
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cfdi40
4
- VERSION = "0.4.5"
4
+ VERSION = "0.4.7"
5
5
  end
@@ -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.5
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-12 00:00:00.000000000 Z
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