ecf-dgii 1.0.2 → 1.1.0
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/README.md +214 -60
- data/lib/ecf-dgii.rb +2 -1
- data/lib/ecf_dgii/client.rb +300 -152
- data/lib/ecf_dgii/exceptions.rb +29 -0
- data/lib/ecf_dgii/frontend_client.rb +217 -0
- data/lib/ecf_dgii/polling.rb +78 -21
- data/lib/ecf_dgii/railtie.rb +4 -1
- data/lib/ecf_dgii/version.rb +2 -1
- metadata +3 -1
data/lib/ecf_dgii/client.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
require "uri"
|
|
2
|
+
require_relative "exceptions"
|
|
3
|
+
require_relative "polling"
|
|
2
4
|
|
|
3
5
|
module EcfDgii
|
|
6
|
+
# High-level client for the ECF DGII API.
|
|
7
|
+
#
|
|
8
|
+
# Mirrors the TypeScript {EcfClient} 1:1 — same method names (snake_case),
|
|
9
|
+
# same validation, same error semantics.
|
|
4
10
|
class Client
|
|
5
11
|
ENVIRONMENT_URLS = {
|
|
6
12
|
test: "https://api.test.ecfx.ssd.com.do",
|
|
@@ -8,21 +14,39 @@ module EcfDgii
|
|
|
8
14
|
prod: "https://api.prod.ecfx.ssd.com.do"
|
|
9
15
|
}.freeze
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
ECF_TYPE_ROUTE_MAP = {
|
|
18
|
+
"FacturaDeCreditoFiscalElectronica" => "31",
|
|
19
|
+
"FacturaDeConsumoElectronica" => "32",
|
|
20
|
+
"NotaDeDebitoElectronica" => "33",
|
|
21
|
+
"NotaDeCreditoElectronica" => "34",
|
|
22
|
+
"ComprasElectronico" => "41",
|
|
23
|
+
"GastosMenoresElectronico" => "43",
|
|
24
|
+
"RegimenesEspecialesElectronico" => "44",
|
|
25
|
+
"GubernamentalElectronico" => "45",
|
|
26
|
+
"ComprobanteDeExportacionesElectronico" => "46",
|
|
27
|
+
"ComprobanteParaPagosAlExteriorElectronico" => "47"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# @return [EcfDgii::Generated::ApiClient] The underlying generated API client.
|
|
31
|
+
attr_reader :api_client
|
|
32
|
+
|
|
33
|
+
# @return [Symbol] The configured environment (:test, :cert, or :prod).
|
|
34
|
+
attr_reader :environment
|
|
12
35
|
|
|
13
36
|
def initialize(api_key: nil, base_url: nil, environment: :test, timeout: 30)
|
|
14
37
|
token = api_key || ENV["ECF_API_KEY"]
|
|
15
38
|
resolved_url = base_url || ENV["ECF_API_URL"] || ENVIRONMENT_URLS[environment.to_sym]
|
|
39
|
+
|
|
16
40
|
raise ArgumentError, "Se requiere un api_key o la variable de entorno ECF_API_KEY" if token.nil? || token.empty?
|
|
17
41
|
raise ArgumentError, "El entorno especificado o la URL base no son válidos" if resolved_url.nil? || resolved_url.empty?
|
|
18
42
|
|
|
19
43
|
config = EcfDgii::Generated::Configuration.new
|
|
20
44
|
uri = URI.parse(resolved_url)
|
|
21
|
-
|
|
45
|
+
|
|
22
46
|
config.scheme = uri.scheme
|
|
23
47
|
config.host = uri.host
|
|
24
48
|
config.base_path = uri.path.empty? ? "" : uri.path
|
|
25
|
-
|
|
49
|
+
|
|
26
50
|
config.access_token = token
|
|
27
51
|
config.timeout = timeout
|
|
28
52
|
|
|
@@ -30,9 +54,9 @@ module EcfDgii
|
|
|
30
54
|
@environment = environment.to_sym
|
|
31
55
|
end
|
|
32
56
|
|
|
33
|
-
#
|
|
34
|
-
# Base API
|
|
35
|
-
#
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Base API clients
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
36
60
|
|
|
37
61
|
def ecf_api
|
|
38
62
|
@ecf_api ||= EcfDgii::Generated::EcfApi.new(api_client)
|
|
@@ -58,9 +82,71 @@ module EcfDgii
|
|
|
58
82
|
@api_key_api ||= EcfDgii::Generated::ApiKeyApi.new(api_client)
|
|
59
83
|
end
|
|
60
84
|
|
|
61
|
-
#
|
|
62
|
-
# ECF send
|
|
63
|
-
#
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# ECF send + poll (mirrors TypeScript EcfClient.sendEcf)
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
# Send an ECF and poll until processing completes.
|
|
90
|
+
#
|
|
91
|
+
# Determines the correct endpoint from +ecf.encabezado.idDoc.tipoeCF+,
|
|
92
|
+
# posts the ECF, then polls until +progress+ is +Finished+ or +Error+.
|
|
93
|
+
#
|
|
94
|
+
# @param ecf [Object] Any ECF object (Ecf31ECF … Ecf47ECF) or Hash
|
|
95
|
+
# @param polling_options [PollingOptions, nil] Polling configuration
|
|
96
|
+
# @return [Object] The final EcfResponse when processing is complete
|
|
97
|
+
# @raise [ArgumentError] If required fields (tipoeCF, rncEmisor, encf) are missing
|
|
98
|
+
# @raise [EcfError] If the ECF type is unknown
|
|
99
|
+
# @raise [EcfError] If processing finishes with progress "Error"
|
|
100
|
+
# @raise [PollingTimeoutError] If total timeout is exceeded
|
|
101
|
+
# @raise [PollingMaxRetriesError] If max retries is exceeded
|
|
102
|
+
def send_ecf(ecf, polling_options = nil)
|
|
103
|
+
# 1. Extract tipoeCF
|
|
104
|
+
tipoe_cf = extract_tipoe_cf(ecf)
|
|
105
|
+
raise ArgumentError, "ECF must have encabezado.idDoc.tipoeCF" if tipoe_cf.nil? || tipoe_cf.to_s.empty?
|
|
106
|
+
|
|
107
|
+
# 2. Resolve route
|
|
108
|
+
route = ECF_TYPE_ROUTE_MAP[tipoe_cf.to_s]
|
|
109
|
+
raise ArgumentError, "Unknown tipoeCF: #{tipoe_cf}" if route.nil?
|
|
110
|
+
|
|
111
|
+
# 3. Extract rncEmisor (for polling)
|
|
112
|
+
rnc = extract_rncemisor(ecf)
|
|
113
|
+
raise ArgumentError, "ECF must have encabezado.emisor.rncEmisor" if rnc.nil? || rnc.to_s.empty?
|
|
114
|
+
|
|
115
|
+
# 4. Extract encf (for polling)
|
|
116
|
+
encf = extract_encf(ecf)
|
|
117
|
+
raise ArgumentError, "ECF must have encabezado.idDoc.encf" if encf.nil? || encf.to_s.empty?
|
|
118
|
+
|
|
119
|
+
# 5. POST to the correct endpoint
|
|
120
|
+
response = post_ecf(route, ecf)
|
|
121
|
+
|
|
122
|
+
# 6. Poll until complete
|
|
123
|
+
result = poll_until_complete(response, rnc, encf, polling_options)
|
|
124
|
+
|
|
125
|
+
# 7. Throw EcfError if progress is Error (matching TS behavior)
|
|
126
|
+
progress = extract_progress_value(result)
|
|
127
|
+
if progress == "Error"
|
|
128
|
+
error_msg = nil
|
|
129
|
+
if result.respond_to?(:errors)
|
|
130
|
+
error_msg = result.errors
|
|
131
|
+
elsif result.respond_to?(:mensaje)
|
|
132
|
+
error_msg = result.mensaje
|
|
133
|
+
end
|
|
134
|
+
error_msg ||= result[:errors] || result[:mensaje] || result["errors"] || result["mensaje"] || "ECF processing failed"
|
|
135
|
+
raise EcfError.new(error_msg, result)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
result
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Convenience alias matching older Ruby SDK API.
|
|
142
|
+
# @deprecated Use {#send_ecf} instead (which now includes polling 1:1 with TS).
|
|
143
|
+
def send_ecf_and_poll(ecf, options = nil)
|
|
144
|
+
send_ecf(ecf, options)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# Individual ECF type send methods (kept for backward compatibility)
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
64
150
|
|
|
65
151
|
def send_ecf31(ecf)
|
|
66
152
|
ecf_api.recepcion_ecf_31(ecf)
|
|
@@ -102,242 +188,304 @@ module EcfDgii
|
|
|
102
188
|
ecf_api.recepcion_ecf_47(ecf)
|
|
103
189
|
end
|
|
104
190
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
def send_ecf31_and_poll(ecf, options = nil)
|
|
110
|
-
_send_and_poll(send_ecf31(ecf), options)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def send_ecf32_and_poll(ecf, options = nil)
|
|
114
|
-
_send_and_poll(send_ecf32(ecf), options)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def send_ecf33_and_poll(ecf, options = nil)
|
|
118
|
-
_send_and_poll(send_ecf33(ecf), options)
|
|
119
|
-
end
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Company operations
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
120
194
|
|
|
121
|
-
|
|
122
|
-
|
|
195
|
+
# List companies with optional filters.
|
|
196
|
+
def get_companies(opts = {})
|
|
197
|
+
company_api.get_companies(opts)
|
|
123
198
|
end
|
|
124
199
|
|
|
125
|
-
|
|
126
|
-
|
|
200
|
+
# Get a company by RNC.
|
|
201
|
+
def get_company_by_rnc(rnc)
|
|
202
|
+
company_api.get_company_by_rnc(rnc)
|
|
127
203
|
end
|
|
128
204
|
|
|
129
|
-
|
|
130
|
-
|
|
205
|
+
# Create or update a company.
|
|
206
|
+
def upsert_company(body)
|
|
207
|
+
company_api.upsert_company(body)
|
|
131
208
|
end
|
|
132
209
|
|
|
133
|
-
|
|
134
|
-
|
|
210
|
+
# Delete a company by RNC.
|
|
211
|
+
def delete_company(rnc)
|
|
212
|
+
company_api.delete_company(rnc)
|
|
135
213
|
end
|
|
136
214
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
# Certificate operations
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
140
218
|
|
|
141
|
-
|
|
142
|
-
|
|
219
|
+
# Get the current certificate for a company.
|
|
220
|
+
def get_certificate(rnc)
|
|
221
|
+
company_api.get_current_certificate(rnc)
|
|
143
222
|
end
|
|
144
223
|
|
|
145
|
-
|
|
146
|
-
|
|
224
|
+
# Update a company's certificate.
|
|
225
|
+
#
|
|
226
|
+
# @param rnc [String] Company RNC
|
|
227
|
+
# @param certificate [String, File] Path to the .p12 file or a File object
|
|
228
|
+
# @param password [String] Certificate password
|
|
229
|
+
def update_certificate(rnc, certificate, password)
|
|
230
|
+
company_api.update_certificate_company(rnc, certificate, password)
|
|
147
231
|
end
|
|
148
232
|
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
# ------------------------------------------------------------------
|
|
152
|
-
|
|
153
|
-
ECF_TYPE_ROUTE_MAP = {
|
|
154
|
-
"FacturaDeCreditoFiscalElectronica" => "31",
|
|
155
|
-
"FacturaDeConsumoElectronica" => "32",
|
|
156
|
-
"NotaDeDebitoElectronica" => "33",
|
|
157
|
-
"NotaDeCreditoElectronica" => "34",
|
|
158
|
-
"ComprasElectronico" => "41",
|
|
159
|
-
"GastosMenoresElectronico" => "43",
|
|
160
|
-
"RegimenesEspecialesElectronico" => "44",
|
|
161
|
-
"GubernamentalElectronico" => "45",
|
|
162
|
-
"ComprobanteDeExportacionesElectronico" => "46",
|
|
163
|
-
"ComprobanteParaPagosAlExteriorElectronico" => "47"
|
|
164
|
-
}.freeze
|
|
165
|
-
|
|
166
|
-
def send_ecf(ecf)
|
|
167
|
-
tipoe_cf = nil
|
|
168
|
-
if ecf.respond_to?(:encabezado) && ecf.encabezado.respond_to?(:id_doc)
|
|
169
|
-
tipoe_cf = ecf.encabezado.id_doc.tipoe_cf
|
|
170
|
-
elsif ecf.is_a?(Hash)
|
|
171
|
-
tipoe_cf = ecf.dig(:encabezado, :id_doc, :tipoe_cf) || ecf.dig("encabezado", "idDoc", "tipoeCF")
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
raise ArgumentError, "El objeto ECF debe contener encabezado.id_doc.tipoe_cf" if tipoe_cf.nil? || tipoe_cf.to_s.empty?
|
|
175
|
-
|
|
176
|
-
route = ECF_TYPE_ROUTE_MAP[tipoe_cf.to_s]
|
|
177
|
-
raise ArgumentError, "Tipo de eCF desconocido: #{tipoe_cf}" if route.nil?
|
|
178
|
-
|
|
179
|
-
send("send_ecf#{route}", ecf)
|
|
180
|
-
end
|
|
233
|
+
# @deprecated Use {#get_certificate} instead.
|
|
234
|
+
alias get_current_certificate get_certificate
|
|
181
235
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
end
|
|
236
|
+
# @deprecated Use {#update_certificate} instead.
|
|
237
|
+
alias update_certificate_company update_certificate
|
|
185
238
|
|
|
186
|
-
#
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
187
240
|
# ECF query & search operations
|
|
188
|
-
#
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
189
242
|
|
|
243
|
+
# Query ECFs by RNC and eNCF.
|
|
190
244
|
def query_ecf(rnc, encf, opts = {})
|
|
191
245
|
ecf_api.query_ecf(rnc, encf, opts)
|
|
192
246
|
end
|
|
193
247
|
|
|
248
|
+
# Search ECFs for a specific RNC.
|
|
194
249
|
def search_ecfs(rnc, opts = {})
|
|
195
250
|
ecf_api.search_ecfs(rnc, opts)
|
|
196
251
|
end
|
|
197
252
|
|
|
253
|
+
# Search all ECFs across all companies.
|
|
198
254
|
def search_all_ecfs(opts = {})
|
|
199
255
|
ecf_api.search_all_ecfs(opts)
|
|
200
256
|
end
|
|
201
257
|
|
|
202
|
-
|
|
203
|
-
|
|
258
|
+
# Get a specific ECF by RNC and message ID.
|
|
259
|
+
def get_ecf_by_id(rnc, id)
|
|
260
|
+
ecf_api.get_ecf_by_id(rnc, id)
|
|
204
261
|
end
|
|
205
262
|
|
|
206
|
-
|
|
207
|
-
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Anulación rangos
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
# Request range annulment.
|
|
268
|
+
def anulacion_rangos(rnc, body)
|
|
269
|
+
ecf_api.anulacion_rangos(rnc, body)
|
|
208
270
|
end
|
|
209
271
|
|
|
272
|
+
# List annulments.
|
|
210
273
|
def list_anulaciones(opts = {})
|
|
211
274
|
ecf_api.list_anulaciones(opts)
|
|
212
275
|
end
|
|
213
276
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
# Firmar semilla
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
217
280
|
|
|
218
|
-
|
|
219
|
-
|
|
281
|
+
# Sign a seed for a company.
|
|
282
|
+
def firmar_semilla(rnc, body)
|
|
283
|
+
ecf_api.firmar_semilla(rnc, body)
|
|
220
284
|
end
|
|
221
285
|
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
286
|
+
# ---------------------------------------------------------------------------
|
|
287
|
+
# Reception operations
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
225
289
|
|
|
226
|
-
|
|
227
|
-
|
|
290
|
+
# Search ECF reception requests.
|
|
291
|
+
def search_ecf_reception_requests(opts = {})
|
|
292
|
+
recepcion_api.search_ecf_reception_requests(opts)
|
|
228
293
|
end
|
|
229
294
|
|
|
230
|
-
|
|
231
|
-
|
|
295
|
+
# Search ACECF reception requests.
|
|
296
|
+
def search_acecf_reception_requests(opts = {})
|
|
297
|
+
aprobacion_comercial_api.search_acecf_reception_requests(opts)
|
|
232
298
|
end
|
|
233
299
|
|
|
234
|
-
|
|
235
|
-
|
|
300
|
+
# Search ECF reception requests by RNC.
|
|
301
|
+
def search_ecf_reception_requests_by_rnc(rnc, opts = {})
|
|
302
|
+
recepcion_api.search_ecf_reception_requests_by_rnc(rnc, opts)
|
|
236
303
|
end
|
|
237
304
|
|
|
238
|
-
|
|
239
|
-
|
|
305
|
+
# Get a specific ECF reception request by RNC and messageId.
|
|
306
|
+
def get_ecf_reception_request(rnc, message_id)
|
|
307
|
+
recepcion_api.get_ecf_reception_request(rnc, message_id)
|
|
240
308
|
end
|
|
241
309
|
|
|
242
|
-
|
|
243
|
-
|
|
310
|
+
# Get a specific ACECF reception request by messageId.
|
|
311
|
+
def get_acecf_reception_request(message_id)
|
|
312
|
+
aprobacion_comercial_api.get_acecf_reception_request(message_id)
|
|
244
313
|
end
|
|
245
314
|
|
|
246
|
-
|
|
247
|
-
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
# Aprobación comercial
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
# Send aprobación comercial (ACECF) for a given ECF reception messageId.
|
|
320
|
+
def aprobacion_comercial(message_id, body)
|
|
321
|
+
recepcion_api.aprobacion_comercial(message_id, body)
|
|
248
322
|
end
|
|
249
323
|
|
|
250
|
-
#
|
|
251
|
-
|
|
252
|
-
# ------------------------------------------------------------------
|
|
324
|
+
# @deprecated Use {#aprobacion_comercial} instead.
|
|
325
|
+
alias send_aprobacion_comercial aprobacion_comercial
|
|
253
326
|
|
|
254
|
-
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
# ApiKey operations
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
# Create a new API key (read-only, scoped token for frontend use).
|
|
332
|
+
def create_api_key(body)
|
|
255
333
|
api_key_api.new_company_api_key(body)
|
|
256
334
|
end
|
|
257
335
|
|
|
258
|
-
#
|
|
259
|
-
|
|
260
|
-
# ------------------------------------------------------------------
|
|
336
|
+
# @deprecated Use {#create_api_key} instead.
|
|
337
|
+
alias new_company_api_key create_api_key
|
|
261
338
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
# DGII operations
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
265
342
|
|
|
266
|
-
|
|
267
|
-
|
|
343
|
+
# Consulta directorio — listado.
|
|
344
|
+
def consulta_directorio_listado(rnc)
|
|
345
|
+
dgii_api.consulta_directorio_listado(rnc)
|
|
268
346
|
end
|
|
269
347
|
|
|
270
|
-
|
|
271
|
-
|
|
348
|
+
# Consulta directorio — obtener directorio por RNC.
|
|
349
|
+
def consulta_directorio_por_rnc(rnc, target_rnc)
|
|
350
|
+
dgii_api.consulta_directorio_obtener_directorio_por_rnc(rnc, target_rnc)
|
|
272
351
|
end
|
|
273
352
|
|
|
274
|
-
|
|
275
|
-
|
|
353
|
+
# Consulta estado.
|
|
354
|
+
def consulta_estado(rnc, rnc_emisor, ncf_electronico, rnc_comprador, codigo_seguridad)
|
|
355
|
+
dgii_api.consulta_estado(rnc, rnc_emisor, ncf_electronico, rnc_comprador, codigo_seguridad)
|
|
276
356
|
end
|
|
277
357
|
|
|
358
|
+
# Consulta resultado.
|
|
278
359
|
def consulta_resultado(rnc, track_id)
|
|
279
360
|
dgii_api.consulta_resultado(rnc, track_id)
|
|
280
361
|
end
|
|
281
362
|
|
|
282
|
-
|
|
283
|
-
|
|
363
|
+
# Consulta RFCE.
|
|
364
|
+
def consulta_rfce(rnc, rnc_emisor, encf, codigo_seguridad)
|
|
365
|
+
dgii_api.consulta_rfce(rnc, rnc_emisor, encf, codigo_seguridad)
|
|
284
366
|
end
|
|
285
367
|
|
|
286
|
-
|
|
287
|
-
|
|
368
|
+
# Consulta timbre.
|
|
369
|
+
def consulta_timbre(rnc, rnc_emisor, ncf_electronico, rnc_comprador, codigo_seguridad, fecha_emision, monto_total, uid_timbre)
|
|
370
|
+
dgii_api.consulta_timbre(rnc, rnc_emisor, ncf_electronico, rnc_comprador, codigo_seguridad, fecha_emision, monto_total, uid_timbre)
|
|
288
371
|
end
|
|
289
372
|
|
|
290
|
-
|
|
291
|
-
|
|
373
|
+
# Consulta timbre FC.
|
|
374
|
+
def consulta_timbre_fc(rnc, rnc_emisor, ncf_electronico, rnc_comprador, fecha_emision, monto_total)
|
|
375
|
+
dgii_api.consulta_timbre_fc(rnc, rnc_emisor, ncf_electronico, rnc_comprador, fecha_emision, monto_total)
|
|
292
376
|
end
|
|
293
377
|
|
|
294
|
-
|
|
378
|
+
# Consulta track IDs.
|
|
379
|
+
def consulta_track_id(rnc, rnc_emisor, encf)
|
|
380
|
+
dgii_api.consulta_track_id(rnc, rnc_emisor, encf)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Estatus servicios — obtener estatus.
|
|
384
|
+
def estatus_servicios(rnc)
|
|
295
385
|
dgii_api.estatus_servicios_obtener_estatus(rnc)
|
|
296
386
|
end
|
|
297
387
|
|
|
388
|
+
# Estatus servicios — obtener ventanas de mantenimiento.
|
|
298
389
|
def ventanas_mantenimiento(rnc)
|
|
299
390
|
dgii_api.estatus_servicios_obtener_ventanas_mantenimiento(rnc)
|
|
300
391
|
end
|
|
301
392
|
|
|
302
|
-
#
|
|
303
|
-
|
|
304
|
-
# ------------------------------------------------------------------
|
|
305
|
-
|
|
306
|
-
def get_ecf_reception_request(message_id)
|
|
307
|
-
recepcion_api.get_ecf_reception_request(message_id)
|
|
308
|
-
end
|
|
393
|
+
# @deprecated Use {#estatus_servicios} instead.
|
|
394
|
+
alias estatus_servicio estatus_servicios
|
|
309
395
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
end
|
|
396
|
+
# @deprecated Use {#consulta_directorio_listado} instead.
|
|
397
|
+
alias consulta_directorio consulta_directorio_listado
|
|
313
398
|
|
|
314
|
-
|
|
315
|
-
recepcion_api.search_ecf_reception_requests(opts)
|
|
316
|
-
end
|
|
399
|
+
private
|
|
317
400
|
|
|
318
|
-
|
|
319
|
-
|
|
401
|
+
# Extract tipoeCF from ecf object or hash.
|
|
402
|
+
def extract_tipoe_cf(ecf)
|
|
403
|
+
if ecf.respond_to?(:encabezado) && ecf.encabezado.respond_to?(:id_doc)
|
|
404
|
+
id_doc = ecf.encabezado.id_doc
|
|
405
|
+
return id_doc.tipoe_cf if id_doc.respond_to?(:tipoe_cf)
|
|
406
|
+
return id_doc.tipoeCF if id_doc.respond_to?(:tipoeCF)
|
|
407
|
+
elsif ecf.is_a?(Hash)
|
|
408
|
+
return ecf.dig(:encabezado, :id_doc, :tipoe_cf) ||
|
|
409
|
+
ecf.dig(:encabezado, :idDoc, :tipoeCF) ||
|
|
410
|
+
ecf.dig("encabezado", "idDoc", "tipoeCF")
|
|
411
|
+
end
|
|
412
|
+
nil
|
|
320
413
|
end
|
|
321
414
|
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
415
|
+
# Extract rncEmisor from ecf object or hash.
|
|
416
|
+
def extract_rncemisor(ecf)
|
|
417
|
+
if ecf.respond_to?(:encabezado) && ecf.encabezado.respond_to?(:emisor)
|
|
418
|
+
emisor = ecf.encabezado.emisor
|
|
419
|
+
return emisor.rnc_emisor if emisor.respond_to?(:rnc_emisor)
|
|
420
|
+
return emisor.rncEmisor if emisor.respond_to?(:rncEmisor)
|
|
421
|
+
elsif ecf.is_a?(Hash)
|
|
422
|
+
return ecf.dig(:encabezado, :emisor, :rnc_emisor) ||
|
|
423
|
+
ecf.dig(:encabezado, :emisor, :rncEmisor) ||
|
|
424
|
+
ecf.dig("encabezado", "emisor", "rncEmisor")
|
|
425
|
+
end
|
|
426
|
+
nil
|
|
328
427
|
end
|
|
329
428
|
|
|
330
|
-
|
|
331
|
-
|
|
429
|
+
# Extract encf from ecf object or hash.
|
|
430
|
+
def extract_encf(ecf)
|
|
431
|
+
if ecf.respond_to?(:encabezado) && ecf.encabezado.respond_to?(:id_doc)
|
|
432
|
+
id_doc = ecf.encabezado.id_doc
|
|
433
|
+
return id_doc.encf if id_doc.respond_to?(:encf)
|
|
434
|
+
elsif ecf.is_a?(Hash)
|
|
435
|
+
return ecf.dig(:encabezado, :id_doc, :encf) ||
|
|
436
|
+
ecf.dig(:encabezado, :idDoc, :encf) ||
|
|
437
|
+
ecf.dig("encabezado", "idDoc", "encf")
|
|
438
|
+
end
|
|
439
|
+
nil
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Extract progress value from a response object.
|
|
443
|
+
def extract_progress_value(result)
|
|
444
|
+
if result.respond_to?(:progress)
|
|
445
|
+
p = result.progress
|
|
446
|
+
p = p.value if p.respond_to?(:value)
|
|
447
|
+
return p.to_s
|
|
448
|
+
elsif result.is_a?(Hash)
|
|
449
|
+
p = result[:progress] || result["progress"]
|
|
450
|
+
return p.to_s if p
|
|
451
|
+
end
|
|
452
|
+
""
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Internal: POST to the correct /ecf/{route} endpoint.
|
|
456
|
+
def post_ecf(route, body)
|
|
457
|
+
case route
|
|
458
|
+
when "31" then ecf_api.recepcion_ecf_31(body)
|
|
459
|
+
when "32" then ecf_api.recepcion_ecf_32(body)
|
|
460
|
+
when "33" then ecf_api.recepcion_ecf_33(body)
|
|
461
|
+
when "34" then ecf_api.recepcion_ecf_34(body)
|
|
462
|
+
when "41" then ecf_api.recepcion_ecf_41(body)
|
|
463
|
+
when "43" then ecf_api.recepcion_ecf_43(body)
|
|
464
|
+
when "44" then ecf_api.recepcion_ecf_44(body)
|
|
465
|
+
when "45" then ecf_api.recepcion_ecf_45(body)
|
|
466
|
+
when "46" then ecf_api.recepcion_ecf_46(body)
|
|
467
|
+
when "47" then ecf_api.recepcion_ecf_47(body)
|
|
468
|
+
else raise ArgumentError, "Unknown ECF route: #{route}"
|
|
469
|
+
end
|
|
332
470
|
end
|
|
333
471
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
472
|
+
# Internal: poll until the ECF reaches a terminal state.
|
|
473
|
+
#
|
|
474
|
+
# rubocop:disable Metrics/MethodLength
|
|
475
|
+
def poll_until_complete(initial_response, rnc, encf, polling_options)
|
|
476
|
+
opts = polling_options || EcfDgii::PollingOptions.new
|
|
477
|
+
|
|
478
|
+
EcfDgii::Polling.poll_until_complete(opts) do
|
|
479
|
+
results = query_ecf(rnc, encf, include_ecf_content: false)
|
|
480
|
+
if results.respond_to?(:first)
|
|
481
|
+
results.first
|
|
482
|
+
elsif results.is_a?(Array)
|
|
483
|
+
results.first
|
|
484
|
+
else
|
|
485
|
+
results
|
|
486
|
+
end
|
|
340
487
|
end
|
|
341
488
|
end
|
|
489
|
+
# rubocop:enable Metrics/MethodLength
|
|
342
490
|
end
|
|
343
491
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module EcfDgii
|
|
2
|
+
# Base error class for ECF SDK errors.
|
|
3
|
+
class EcfError < StandardError
|
|
4
|
+
# The full EcfResponse object containing details about the error.
|
|
5
|
+
attr_reader :response
|
|
6
|
+
|
|
7
|
+
def initialize(message, response = nil)
|
|
8
|
+
super(message)
|
|
9
|
+
@response = response
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Raised when polling exceeds the total timeout.
|
|
14
|
+
class PollingTimeoutError < EcfError
|
|
15
|
+
def initialize(message = "Polling timed out")
|
|
16
|
+
super(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Raised when polling exceeds the maximum number of retries.
|
|
21
|
+
class PollingMaxRetriesError < EcfError
|
|
22
|
+
def initialize(retries)
|
|
23
|
+
super("Polling exceeded maximum retries (#{retries})")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @deprecated Use {EcfError} instead. Kept for backward compatibility.
|
|
28
|
+
PollingError = EcfError
|
|
29
|
+
end
|