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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/COMPLIANCE.md +149 -0
  3. data/LICENSE +21 -0
  4. data/README.md +404 -0
  5. data/doc/FUENTES.md +505 -0
  6. data/lib/generators/verifactu/install/install_generator.rb +61 -0
  7. data/lib/generators/verifactu/install/templates/instalar_verifactu.rb.tt +19 -0
  8. data/lib/generators/verifactu/install/templates/verifactu.rb.tt +64 -0
  9. data/lib/verifactu-rails.rb +31 -0
  10. data/lib/verifactu_rails/certificado.rb +122 -0
  11. data/lib/verifactu_rails/consulta.rb +279 -0
  12. data/lib/verifactu_rails/desglose.rb +396 -0
  13. data/lib/verifactu_rails/envio.rb +153 -0
  14. data/lib/verifactu_rails/error.rb +31 -0
  15. data/lib/verifactu_rails/formato.rb +199 -0
  16. data/lib/verifactu_rails/huella.rb +130 -0
  17. data/lib/verifactu_rails/importe.rb +80 -0
  18. data/lib/verifactu_rails/libro/autochequeo.rb +67 -0
  19. data/lib/verifactu_rails/libro/cadena.rb +110 -0
  20. data/lib/verifactu_rails/libro/migracion.rb +92 -0
  21. data/lib/verifactu_rails/libro/reconciliacion.rb +224 -0
  22. data/lib/verifactu_rails/libro/registro.rb +74 -0
  23. data/lib/verifactu_rails/libro/remesa.rb +120 -0
  24. data/lib/verifactu_rails/libro.rb +89 -0
  25. data/lib/verifactu_rails/qr.rb +56 -0
  26. data/lib/verifactu_rails/railtie.rb +30 -0
  27. data/lib/verifactu_rails/registro.rb +833 -0
  28. data/lib/verifactu_rails/respuesta.rb +154 -0
  29. data/lib/verifactu_rails/schemas/ConsultaLR.xsd +54 -0
  30. data/lib/verifactu_rails/schemas/EventosSIF.xsd +823 -0
  31. data/lib/verifactu_rails/schemas/PROCEDENCIA.md +64 -0
  32. data/lib/verifactu_rails/schemas/RespuestaConsultaLR.xsd +201 -0
  33. data/lib/verifactu_rails/schemas/RespuestaSuministro.xsd +139 -0
  34. data/lib/verifactu_rails/schemas/RespuestaValRegistNoVeriFactu.xsd +103 -0
  35. data/lib/verifactu_rails/schemas/SuministroInformacion.xsd +1390 -0
  36. data/lib/verifactu_rails/schemas/SuministroLR.xsd +25 -0
  37. data/lib/verifactu_rails/schemas/catalog.xml +5 -0
  38. data/lib/verifactu_rails/schemas/xmldsig-core-schema.xsd +318 -0
  39. data/lib/verifactu_rails/sistema_informatico.rb +88 -0
  40. data/lib/verifactu_rails/transporte.rb +127 -0
  41. data/lib/verifactu_rails/version.rb +5 -0
  42. 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>