verifactu-rails 0.1.0.pre
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 +7 -0
- data/COMPLIANCE.md +149 -0
- data/LICENSE +21 -0
- data/README.md +404 -0
- data/doc/FUENTES.md +505 -0
- data/lib/generators/verifactu/install/install_generator.rb +61 -0
- data/lib/generators/verifactu/install/templates/instalar_verifactu.rb.tt +19 -0
- data/lib/generators/verifactu/install/templates/verifactu.rb.tt +64 -0
- data/lib/verifactu-rails.rb +31 -0
- data/lib/verifactu_rails/certificado.rb +122 -0
- data/lib/verifactu_rails/consulta.rb +279 -0
- data/lib/verifactu_rails/desglose.rb +396 -0
- data/lib/verifactu_rails/envio.rb +153 -0
- data/lib/verifactu_rails/error.rb +31 -0
- data/lib/verifactu_rails/formato.rb +199 -0
- data/lib/verifactu_rails/huella.rb +130 -0
- data/lib/verifactu_rails/importe.rb +80 -0
- data/lib/verifactu_rails/libro/autochequeo.rb +67 -0
- data/lib/verifactu_rails/libro/cadena.rb +110 -0
- data/lib/verifactu_rails/libro/migracion.rb +92 -0
- data/lib/verifactu_rails/libro/reconciliacion.rb +224 -0
- data/lib/verifactu_rails/libro/registro.rb +74 -0
- data/lib/verifactu_rails/libro/remesa.rb +120 -0
- data/lib/verifactu_rails/libro.rb +89 -0
- data/lib/verifactu_rails/qr.rb +56 -0
- data/lib/verifactu_rails/railtie.rb +30 -0
- data/lib/verifactu_rails/registro.rb +833 -0
- data/lib/verifactu_rails/respuesta.rb +154 -0
- data/lib/verifactu_rails/schemas/ConsultaLR.xsd +54 -0
- data/lib/verifactu_rails/schemas/EventosSIF.xsd +823 -0
- data/lib/verifactu_rails/schemas/PROCEDENCIA.md +64 -0
- data/lib/verifactu_rails/schemas/RespuestaConsultaLR.xsd +201 -0
- data/lib/verifactu_rails/schemas/RespuestaSuministro.xsd +139 -0
- data/lib/verifactu_rails/schemas/RespuestaValRegistNoVeriFactu.xsd +103 -0
- data/lib/verifactu_rails/schemas/SuministroInformacion.xsd +1390 -0
- data/lib/verifactu_rails/schemas/SuministroLR.xsd +25 -0
- data/lib/verifactu_rails/schemas/catalog.xml +5 -0
- data/lib/verifactu_rails/schemas/xmldsig-core-schema.xsd +318 -0
- data/lib/verifactu_rails/sistema_informatico.rb +88 -0
- data/lib/verifactu_rails/transporte.rb +127 -0
- data/lib/verifactu_rails/version.rb +5 -0
- metadata +124 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
require_relative 'error'
|
|
5
|
+
|
|
6
|
+
module VerifactuRails
|
|
7
|
+
# Respuesta de la AEAT a un envío de registros (RespuestaSuministro.xsd).
|
|
8
|
+
#
|
|
9
|
+
# Solo lee: no valida contra el esquema ni impone reglas de negocio. Lo que
|
|
10
|
+
# llega de la AEAT es la verdad, y una respuesta que no encaje con lo que
|
|
11
|
+
# esperamos no debe impedir leer el resto.
|
|
12
|
+
#
|
|
13
|
+
# El punto que más importa entender: `EstadoRegistro` tiene TRES valores, no
|
|
14
|
+
# dos. "AceptadoConErrores" significa que el registro SÍ quedó anotado en la
|
|
15
|
+
# AEAT pero arrastra un error que hay que subsanar. Tratarlo como fallo lleva
|
|
16
|
+
# a reenviar un registro que ya existe (y a un rechazo por duplicado);
|
|
17
|
+
# tratarlo como éxito deja una obligación pendiente sin que nadie se entere.
|
|
18
|
+
class Respuesta
|
|
19
|
+
NS = 'https://www2.agenciatributaria.gob.es/static_files/common/internet/' \
|
|
20
|
+
'dep/aplicaciones/es/aeat/tike/cont/ws/RespuestaSuministro.xsd'
|
|
21
|
+
NS_SF = 'https://www2.agenciatributaria.gob.es/static_files/common/internet/' \
|
|
22
|
+
'dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd'
|
|
23
|
+
NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'
|
|
24
|
+
|
|
25
|
+
ESTADOS_ENVIO = %w[Correcto ParcialmenteCorrecto Incorrecto].freeze
|
|
26
|
+
ESTADOS_REGISTRO = %w[Correcto AceptadoConErrores Incorrecto].freeze
|
|
27
|
+
|
|
28
|
+
# Una línea de la respuesta: el veredicto de un registro concreto.
|
|
29
|
+
Linea = Struct.new(:num_serie, :id_emisor, :fecha_expedicion, :estado,
|
|
30
|
+
:codigo_error, :descripcion_error, :duplicado,
|
|
31
|
+
keyword_init: true) do
|
|
32
|
+
def correcto? = estado == 'Correcto'
|
|
33
|
+
|
|
34
|
+
# Anotado en la AEAT, pero con un error que obliga a subsanar. NO se
|
|
35
|
+
# reenvía: se manda después un alta con subsanacion: 'S'.
|
|
36
|
+
def aceptado_con_errores? = estado == 'AceptadoConErrores'
|
|
37
|
+
|
|
38
|
+
# Rechazado: no consta en la AEAT. Este sí hay que corregir y reenviar.
|
|
39
|
+
def incorrecto? = estado == 'Incorrecto'
|
|
40
|
+
|
|
41
|
+
# Quedó anotado, con o sin errores.
|
|
42
|
+
def anotado? = correcto? || aceptado_con_errores?
|
|
43
|
+
|
|
44
|
+
def duplicado? = !duplicado.nil?
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
base = "#{num_serie}: #{estado}"
|
|
48
|
+
codigo_error ? "#{base} [#{codigo_error}] #{descripcion_error}" : base
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Estado del registro que ya existía, cuando el rechazo es por duplicado.
|
|
53
|
+
Duplicado = Struct.new(:id_peticion, :estado, :codigo_error,
|
|
54
|
+
:descripcion_error, keyword_init: true) do
|
|
55
|
+
def anulado? = estado == 'Anulada'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attr_reader :xml, :csv, :estado_envio, :tiempo_espera, :lineas,
|
|
59
|
+
:nif_presentador, :timestamp_presentacion
|
|
60
|
+
|
|
61
|
+
# @param xml [String] cuerpo devuelto por Transporte#enviar
|
|
62
|
+
def initialize(xml)
|
|
63
|
+
@xml = xml
|
|
64
|
+
doc = Nokogiri::XML(xml)
|
|
65
|
+
raiz = doc.at_xpath('//r:RespuestaRegFactuSistemaFacturacion', 'r' => NS)
|
|
66
|
+
|
|
67
|
+
if raiz.nil?
|
|
68
|
+
raise RespuestaError,
|
|
69
|
+
"La respuesta no contiene RespuestaRegFactuSistemaFacturacion. " \
|
|
70
|
+
"#{resumen_de_fallo(doc)}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@csv = texto(raiz, 'r:CSV')
|
|
74
|
+
@estado_envio = texto(raiz, 'r:EstadoEnvio')
|
|
75
|
+
@tiempo_espera = texto(raiz, 'r:TiempoEsperaEnvio')&.to_i
|
|
76
|
+
@nif_presentador = texto(raiz, 'r:DatosPresentacion/sf:NIFPresentador')
|
|
77
|
+
@timestamp_presentacion = texto(raiz, 'r:DatosPresentacion/sf:TimestampPresentacion')
|
|
78
|
+
@lineas = raiz.xpath('r:RespuestaLinea', 'r' => NS).map { |n| leer_linea(n) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def correcto? = estado_envio == 'Correcto'
|
|
82
|
+
def parcialmente_correcto? = estado_envio == 'ParcialmenteCorrecto'
|
|
83
|
+
def incorrecto? = estado_envio == 'Incorrecto'
|
|
84
|
+
|
|
85
|
+
def anotadas = lineas.select(&:anotado?)
|
|
86
|
+
def rechazadas = lineas.select(&:incorrecto?)
|
|
87
|
+
|
|
88
|
+
# Las que quedaron anotadas pero obligan a subsanar. Es la lista que hay que
|
|
89
|
+
# mirar cuando el envío sale "Correcto" y uno se relaja: un envío puede ser
|
|
90
|
+
# globalmente correcto y aun así contener registros con esta marca.
|
|
91
|
+
def a_subsanar = lineas.select(&:aceptado_con_errores?)
|
|
92
|
+
|
|
93
|
+
# Segundos que hay que esperar antes del siguiente envío, o acumular hasta
|
|
94
|
+
# el límite de lote, lo que ocurra primero.
|
|
95
|
+
def esperar_hasta(desde = nil)
|
|
96
|
+
return nil if tiempo_espera.nil?
|
|
97
|
+
|
|
98
|
+
(desde || Time.now) + tiempo_espera
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# El recuento es de LOS REGISTROS DE ESTE ENVÍO, no de la cadena: la AEAT
|
|
102
|
+
# responde sobre lo que le mandaste en esta petición. Una cadena de tres
|
|
103
|
+
# eslabones puede haberse construido en tres envíos de un registro cada uno.
|
|
104
|
+
def to_s
|
|
105
|
+
registros = lineas.size == 1 ? '1 registro en este envío' : "#{lineas.size} registros en este envío"
|
|
106
|
+
"#{estado_envio} (#{registros}: #{anotadas.size} anotados, " \
|
|
107
|
+
"#{a_subsanar.size} a subsanar, #{rechazadas.size} rechazados)"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def leer_linea(nodo)
|
|
113
|
+
Linea.new(
|
|
114
|
+
num_serie: texto(nodo, 'r:IDFactura/sf:NumSerieFactura'),
|
|
115
|
+
id_emisor: texto(nodo, 'r:IDFactura/sf:IDEmisorFactura'),
|
|
116
|
+
fecha_expedicion: texto(nodo, 'r:IDFactura/sf:FechaExpedicionFactura'),
|
|
117
|
+
estado: texto(nodo, 'r:EstadoRegistro'),
|
|
118
|
+
codigo_error: texto(nodo, 'r:CodigoErrorRegistro'),
|
|
119
|
+
descripcion_error: texto(nodo, 'r:DescripcionErrorRegistro'),
|
|
120
|
+
duplicado: leer_duplicado(nodo.at_xpath('r:RegistroDuplicado', 'r' => NS))
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def leer_duplicado(nodo)
|
|
125
|
+
return nil if nodo.nil?
|
|
126
|
+
|
|
127
|
+
Duplicado.new(
|
|
128
|
+
id_peticion: texto(nodo, 'sf:IdPeticionRegistroDuplicado'),
|
|
129
|
+
estado: texto(nodo, 'sf:EstadoRegistroDuplicado'),
|
|
130
|
+
codigo_error: texto(nodo, 'sf:CodigoErrorRegistro'),
|
|
131
|
+
descripcion_error: texto(nodo, 'sf:DescripcionErrorRegistro')
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def texto(nodo, ruta)
|
|
136
|
+
nodo.at_xpath(ruta, 'r' => NS, 'sf' => NS_SF)&.text
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Un fallo de servicio llega como SOAP Fault, no como RespuestaSuministro.
|
|
140
|
+
# Sin esto el usuario solo vería "no contiene RespuestaRegFactu...".
|
|
141
|
+
def resumen_de_fallo(doc)
|
|
142
|
+
fault = doc.at_xpath('//soap:Fault', 'soap' => NS_SOAP)
|
|
143
|
+
return "Cuerpo recibido: #{xml.to_s[0, 300]}" if fault.nil?
|
|
144
|
+
|
|
145
|
+
codigo = fault.at_xpath('faultcode')&.text
|
|
146
|
+
motivo = fault.at_xpath('faultstring')&.text
|
|
147
|
+
"La AEAT devolvió un SOAP Fault #{codigo}: #{motivo}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class RespuestaError < StandardError
|
|
152
|
+
include Error
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por AEAT (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
|
|
3
|
+
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
|
|
4
|
+
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfLRC="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/ConsultaLR.xsd" elementFormDefault="qualified">
|
|
5
|
+
<import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
|
|
6
|
+
<!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
|
|
7
|
+
<element name="ConsultaFactuSistemaFacturacion" type="sfLRC:ConsultaFactuSistemaFacturacionType">
|
|
8
|
+
<annotation>
|
|
9
|
+
<documentation>Servicio de consulta Registros Facturacion</documentation>
|
|
10
|
+
</annotation>
|
|
11
|
+
</element>
|
|
12
|
+
<complexType name="ConsultaFactuSistemaFacturacionType">
|
|
13
|
+
<sequence>
|
|
14
|
+
<element name="Cabecera" type="sf:CabeceraConsultaSf"/>
|
|
15
|
+
<element name="FiltroConsulta" type="sfLRC:LRFiltroRegFacturacionType"/>
|
|
16
|
+
<element name="DatosAdicionalesRespuesta" type="sfLRC:DatosAdicionalesRespuestaType" minOccurs="0"/>
|
|
17
|
+
</sequence>
|
|
18
|
+
</complexType>
|
|
19
|
+
<complexType name="LRFiltroRegFacturacionType">
|
|
20
|
+
<sequence>
|
|
21
|
+
<element name="PeriodoImputacion" type="sf:PeriodoImputacionType"/>
|
|
22
|
+
<element name="NumSerieFactura" type="sf:TextoIDFacturaType" minOccurs="0">
|
|
23
|
+
<annotation>
|
|
24
|
+
<documentation xml:lang="es"> Nº Serie+Nº Factura de la Factura del Emisor.</documentation>
|
|
25
|
+
</annotation>
|
|
26
|
+
</element>
|
|
27
|
+
<element name="Contraparte" type="sf:ContraparteConsultaType" minOccurs="0">
|
|
28
|
+
<annotation>
|
|
29
|
+
<documentation xml:lang="es">Contraparte del NIF de la cabecera que realiza la consulta.
|
|
30
|
+
Obligado si la cosulta la realiza el Destinatario de los registros de facturacion.
|
|
31
|
+
Destinatario si la cosulta la realiza el Obligado dde los registros de facturacion.</documentation>
|
|
32
|
+
</annotation>
|
|
33
|
+
</element>
|
|
34
|
+
<element name="FechaExpedicionFactura" type="sf:FechaExpedicionConsultaType" minOccurs="0"/>
|
|
35
|
+
<element name="SistemaInformatico" type="sf:SistemaInformaticoConsultaType" minOccurs="0"/>
|
|
36
|
+
<element name="RefExterna" type="sf:TextMax60Type" minOccurs="0"/>
|
|
37
|
+
<element name="ClavePaginacion" type="sf:IDFacturaExpedidaBCType" minOccurs="0"/>
|
|
38
|
+
</sequence>
|
|
39
|
+
</complexType>
|
|
40
|
+
<complexType name="DatosAdicionalesRespuestaType">
|
|
41
|
+
<sequence>
|
|
42
|
+
<element name="MostrarNombreRazonEmisor" type="sf:MostrarNombreRazonEmisorType" minOccurs="0">
|
|
43
|
+
<annotation>
|
|
44
|
+
<documentation xml:lang="es">Indicador que especifica si se quiere obtener en la respuesta el campo NombreRazonEmisor en la información del registro se facturacion. Si el Valor es S aumenta el tiempo de respuesta en la cosulta por detinatario</documentation>
|
|
45
|
+
</annotation>
|
|
46
|
+
</element>
|
|
47
|
+
<element name="MostrarSistemaInformatico" type="sf:MostrarSistemaInformaticoType" minOccurs="0">
|
|
48
|
+
<annotation>
|
|
49
|
+
<documentation xml:lang="es">Indicador que especifica si se quiere obtener en la respuesta el bloque SistemaInformatico en la información del registro se facturacion. Si el Valor es S aumenta el tiempo de respuesta en la cosulta. Si se consulta por Destinatario el valor del campo MostrarSistemaInformatico debe ser 'N' o no estar cumplimentado</documentation>
|
|
50
|
+
</annotation>
|
|
51
|
+
</element>
|
|
52
|
+
</sequence>
|
|
53
|
+
</complexType>
|
|
54
|
+
</schema>
|