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
data/lib/cfdi40/node.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Node
|
3
|
+
# Nokigir XML Document for the xml_node
|
4
|
+
attr_accessor :xml_document, :xml_parent, :children_nodes, :parent_node
|
5
|
+
attr_writer :element_name
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.class.verify_class_variables
|
9
|
+
@children_nodes = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Use class variables to define attributes used to create nodes
|
13
|
+
# Class variables are the same for children classes, so are organized by
|
14
|
+
# the name of the class.
|
15
|
+
def self.verify_class_variables
|
16
|
+
@@attributes ||= {}
|
17
|
+
@@attributes[name] ||= {}
|
18
|
+
@@namespaces ||= {}
|
19
|
+
@@namespaces[name] ||= {}
|
20
|
+
@@default_values ||= {}
|
21
|
+
@@default_values[name] ||= {}
|
22
|
+
@@formats ||= {}
|
23
|
+
@@formats[name] ||= {}
|
24
|
+
@@element_names ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.define_attribute(accessor, xml_attribute:, default: nil, format: nil, readonly: false)
|
28
|
+
verify_class_variables
|
29
|
+
if readonly
|
30
|
+
attr_reader accessor.to_sym
|
31
|
+
else
|
32
|
+
attr_accessor accessor.to_sym
|
33
|
+
end
|
34
|
+
@@attributes[name][accessor.to_sym] = xml_attribute
|
35
|
+
if default
|
36
|
+
@@default_values[name][accessor.to_sym] = default
|
37
|
+
end
|
38
|
+
if format
|
39
|
+
@@formats[name][accessor.to_sym] = format
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.define_namespace(namespace, value)
|
44
|
+
verify_class_variables
|
45
|
+
@@namespaces[name][namespace] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.define_element_name(value)
|
49
|
+
verify_class_variables
|
50
|
+
@@element_names[name] = value.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.namespaces
|
54
|
+
@@namespaces[name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.attributes
|
58
|
+
@@attributes[name]
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.default_values
|
62
|
+
@@default_values[name]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.formats
|
66
|
+
@@formats[name]
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.element_name
|
70
|
+
verify_class_variables
|
71
|
+
@@element_names[name]
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_defaults
|
75
|
+
return if self.class.default_values.nil?
|
76
|
+
|
77
|
+
self.class.default_values.each do |accessor, value|
|
78
|
+
next unless attibute_is_null?(accessor)
|
79
|
+
|
80
|
+
instance_variable_set "@#{accessor}".to_sym, value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def attibute_is_null?(accessor)
|
85
|
+
return true unless instance_variable_defined?("@#{accessor}".to_sym)
|
86
|
+
|
87
|
+
instance_variable_get("@#{accessor}".to_sym).nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
def current_namespace
|
91
|
+
return unless self.class.respond_to?(:namespaces)
|
92
|
+
if self.class.namespaces.empty?
|
93
|
+
return parent_node.current_namespace unless parent_node.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
self.class.namespaces.keys.last
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_xml_node
|
100
|
+
set_defaults
|
101
|
+
if self.respond_to?(:before_add, true)
|
102
|
+
self.before_add
|
103
|
+
end
|
104
|
+
xml_node = xml_document.create_element(expanded_element_name)
|
105
|
+
add_namespaces_to(xml_node)
|
106
|
+
add_attributes_to(xml_node)
|
107
|
+
add_children_to(xml_node)
|
108
|
+
xml_parent.add_child xml_node
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns setted @element_name or use class_name
|
112
|
+
def element_name
|
113
|
+
return self.class.element_name unless self.class.element_name.nil? || self.class.element_name == ''
|
114
|
+
|
115
|
+
self.class.name.split('::').last
|
116
|
+
end
|
117
|
+
|
118
|
+
def expanded_element_name
|
119
|
+
return element_name unless current_namespace
|
120
|
+
"#{current_namespace}:#{element_name}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_namespaces_to(xml_node)
|
124
|
+
self.class.namespaces.each do |namespace, value|
|
125
|
+
xml_node.add_namespace namespace, value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def add_attributes_to(node)
|
130
|
+
self.class.attributes.each do |object_accessor, xml_attribute|
|
131
|
+
next unless respond_to?(object_accessor)
|
132
|
+
next if public_send(object_accessor).nil?
|
133
|
+
|
134
|
+
node[xml_attribute] = formated_value(object_accessor)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_children_to(xml_node)
|
139
|
+
children_nodes.each do |node|
|
140
|
+
node.xml_document = xml_document
|
141
|
+
node.xml_parent = xml_node
|
142
|
+
node.create_xml_node
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def formated_value(accessor)
|
147
|
+
case self.class.formats[accessor]
|
148
|
+
when :t_Importe
|
149
|
+
sprintf("%0.6f", public_send(accessor).to_f)
|
150
|
+
else
|
151
|
+
public_send(accessor)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Receptor < Node
|
3
|
+
define_attribute :rfc, xml_attribute: 'Rfc'
|
4
|
+
define_attribute :nombre, xml_attribute: 'Nombre'
|
5
|
+
define_attribute :domicilio_fiscal, xml_attribute: 'DomicilioFiscalReceptor'
|
6
|
+
define_attribute :residencia_fiscal, xml_attribute: 'ResidenciaFiscal'
|
7
|
+
define_attribute :num_reg_id_trib, xml_attribute: 'NumRegIdTrib'
|
8
|
+
define_attribute :regimen_fiscal, xml_attribute: 'RegimenFiscalReceptor'
|
9
|
+
define_attribute :uso_cfdi, xml_attribute: 'UsoCFDI'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class SatCsd
|
3
|
+
attr_reader :x509_cert
|
4
|
+
attr_reader :private_key
|
5
|
+
|
6
|
+
def cert_path=(path)
|
7
|
+
@cert_path = path
|
8
|
+
@x509_cert = OpenSSL::X509::Certificate.new(File.read(path))
|
9
|
+
end
|
10
|
+
|
11
|
+
def cert_der=(data)
|
12
|
+
@x509_cert = OpenSSL::X509::Certificate.new(data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_private_key(key_path, key_pass)
|
16
|
+
key_pem = key_to_pem(File.read(key_path))
|
17
|
+
@private_key = OpenSSL::PKey::RSA.new(key_pem, key_pass)
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_private_key(key_data, key_pass = nil)
|
21
|
+
key_pem = (pem_format?(key_data) ? key_data : key_to_pem(key_data))
|
22
|
+
@private_key = OpenSSL::PKey::RSA.new(key_pem, key_pass)
|
23
|
+
end
|
24
|
+
|
25
|
+
def rfc
|
26
|
+
return unless subject_data
|
27
|
+
|
28
|
+
unique_identifier = subject_data.select { |data| data[0] == "x500UniqueIdentifier" }.first
|
29
|
+
return unless unique_identifier
|
30
|
+
|
31
|
+
unique_identifier[1].split(" / ").first
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
return unless subject_data
|
36
|
+
|
37
|
+
subject_name = subject_data.select { |data| data[0] == "name" }.first
|
38
|
+
return unless subject_name
|
39
|
+
|
40
|
+
subject_name[1]
|
41
|
+
end
|
42
|
+
|
43
|
+
def no_certificado
|
44
|
+
return unless x509_cert
|
45
|
+
|
46
|
+
s = ''
|
47
|
+
x509_cert.serial.to_s(16).split('').each_with_index do |c, i|
|
48
|
+
next if i.even?
|
49
|
+
|
50
|
+
s += c
|
51
|
+
end
|
52
|
+
s
|
53
|
+
end
|
54
|
+
|
55
|
+
def cert64
|
56
|
+
return unless x509_cert
|
57
|
+
|
58
|
+
Base64.strict_encode64 x509_cert.to_der
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid_pair?
|
62
|
+
return false unless x509_cert && private_key
|
63
|
+
|
64
|
+
x509_cert.check_private_key private_key
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def subject_data
|
70
|
+
return unless x509_cert
|
71
|
+
|
72
|
+
x509_cert.subject.to_a
|
73
|
+
end
|
74
|
+
|
75
|
+
def key_to_pem(key_der)
|
76
|
+
array_key_pem = []
|
77
|
+
array_key_pem << '-----BEGIN ENCRYPTED PRIVATE KEY-----'
|
78
|
+
array_key_pem += Base64.strict_encode64(key_der).scan(/.{1,64}/)
|
79
|
+
array_key_pem << '-----END ENCRYPTED PRIVATE KEY-----'
|
80
|
+
array_key_pem.join("\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
def pem_format?(data)
|
84
|
+
return false unless data.valid_encoding?
|
85
|
+
|
86
|
+
data.match?(/BEGIN/)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Validates Schema Using xsd files
|
2
|
+
module Cfdi40
|
3
|
+
class SchemaValidator
|
4
|
+
# options = Nokogiri::XML::ParseOptions.new.nononet
|
5
|
+
# schema = Nokogiri::XML::Schema(Net::HTTP.get('www.sat.gob.mx', '/sitio_internet/cfd/4/cfdv40.xsd'), options)
|
6
|
+
# schema = Nokogiri::XML::Schema(File.open("/home/israel/git/cfdi40/lib/xsd/cfdv40.xsd"), options)
|
7
|
+
|
8
|
+
attr_reader :errors
|
9
|
+
LOCAL_XSD_PATH = File.join(File.dirname(__FILE__), '..', 'xsd', 'cfdv40.xsd')
|
10
|
+
|
11
|
+
# Param xml is xml string
|
12
|
+
def initialize(xml)
|
13
|
+
@xml_doc = Nokogiri::XML(xml)
|
14
|
+
@schema = Nokogiri::XML::Schema(File.open(LOCAL_XSD_PATH))
|
15
|
+
end
|
16
|
+
|
17
|
+
def valid?
|
18
|
+
validate unless defined?(@errors)
|
19
|
+
|
20
|
+
@errors.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate
|
26
|
+
@errors = @schema.validate(@xml_doc)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Traslado < Node
|
3
|
+
define_attribute :base, xml_attribute: 'Base', format: :t_Importe
|
4
|
+
define_attribute :impuesto, xml_attribute: 'Impuesto'
|
5
|
+
define_attribute :tipo_factor, xml_attribute: 'TipoFactor', default: 'Tasa'
|
6
|
+
define_attribute :tasa_o_cuota, xml_attribute: 'TasaOCuota', format: :t_Importe
|
7
|
+
define_attribute :importe, xml_attribute: 'Importe', format: :t_Importe
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cfdi40
|
2
|
+
class Traslados < Node
|
3
|
+
def traslado_iva
|
4
|
+
return @traslado_iva if defined?(@traslado_iva)
|
5
|
+
|
6
|
+
@traslado_iva = Traslado.new
|
7
|
+
# TODO: FIX magic number
|
8
|
+
@traslado_iva.impuesto = '002'
|
9
|
+
@traslado_iva.parent_node = self
|
10
|
+
self.children_nodes << @traslado_iva
|
11
|
+
@traslado_iva
|
12
|
+
end
|
13
|
+
|
14
|
+
def traslado_nodes
|
15
|
+
children_nodes
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/cfdi40/version.rb
CHANGED
data/lib/cfdi40.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "nokogiri"
|
4
|
+
require "base64"
|
5
|
+
require "openssl"
|
4
6
|
require_relative "cfdi40/version"
|
7
|
+
require_relative "cfdi40/schema_validator"
|
8
|
+
require_relative "cfdi40/sat_csd"
|
9
|
+
require_relative "cfdi40/node"
|
5
10
|
require_relative "cfdi40/comprobante"
|
11
|
+
require_relative "cfdi40/emisor"
|
12
|
+
require_relative "cfdi40/receptor"
|
13
|
+
require_relative "cfdi40/conceptos"
|
14
|
+
require_relative "cfdi40/concepto"
|
15
|
+
require_relative "cfdi40/impuestos"
|
16
|
+
require_relative "cfdi40/traslados"
|
17
|
+
require_relative "cfdi40/traslado"
|
18
|
+
require_relative "cfdi40/complemento_concepto"
|
19
|
+
require_relative "cfdi40/inst_educativas"
|
6
20
|
|
7
21
|
# Leading module and entry point for all features and classes
|
8
22
|
#
|
data/lib/xsd/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Local XSD files
|
2
|
+
|
3
|
+
## Why?
|
4
|
+
|
5
|
+
In order to avoid innecesary and redundant reading from SAT site the
|
6
|
+
file `cfdv40.xsd` referenced in cfdi standard, has been
|
7
|
+
prepared for local access.
|
8
|
+
|
9
|
+
The `cfdv40.xsd` has been modified to reference the local files:
|
10
|
+
|
11
|
+
* `tdCFDI.xsd`,
|
12
|
+
* `catCFDI.xsd` this file has 5.8MB
|
13
|
+
* `iedu.xsd`
|
14
|
+
|
15
|
+
Local files are imported schemas.
|
16
|
+
|
17
|
+
## Use external files
|
18
|
+
|
19
|
+
If you want to use the schema in the original location must enable the
|
20
|
+
external references for Nokogiri:
|
21
|
+
|
22
|
+
require 'net/http'
|
23
|
+
|
24
|
+
xml_doc = Nokogiri::XML(xml_string)
|
25
|
+
|
26
|
+
options = Nokogiri::XML::ParseOptions.new.nononet
|
27
|
+
schema = Nokogiri::XML::Schema(Net::HTTP.get('www.sat.gob.mx', '/sitio_internet/cfd/4/cfdv40.xsd'), options)
|
28
|
+
|
29
|
+
schema.validate(xml_doc)
|
30
|
+
|
31
|
+
References:
|
32
|
+
|
33
|
+
https://nokogiri.org/rdoc/Nokogiri/XML/Schema.html
|
34
|
+
https://nokogiri.org/rdoc/Nokogiri/XML/ParseOptions.html
|