nfcom 0.1.2
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/.rubocop.yml +115 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +66 -0
- data/LICENSE +21 -0
- data/README.md +280 -0
- data/Rakefile +22 -0
- data/examples/.env.example +41 -0
- data/examples/emitir_nota.rb +91 -0
- data/examples/rails_initializer.rb +72 -0
- data/lib/nfcom/builder/danfe_com.rb +564 -0
- data/lib/nfcom/builder/qrcode.rb +68 -0
- data/lib/nfcom/builder/signature.rb +156 -0
- data/lib/nfcom/builder/xml_builder.rb +362 -0
- data/lib/nfcom/client.rb +106 -0
- data/lib/nfcom/configuration.rb +134 -0
- data/lib/nfcom/errors.rb +27 -0
- data/lib/nfcom/helpers/consulta.rb +28 -0
- data/lib/nfcom/models/assinante.rb +146 -0
- data/lib/nfcom/models/destinatario.rb +138 -0
- data/lib/nfcom/models/emitente.rb +105 -0
- data/lib/nfcom/models/endereco.rb +123 -0
- data/lib/nfcom/models/fatura/codigo_de_barras/formato_44.rb +52 -0
- data/lib/nfcom/models/fatura/codigo_de_barras.rb +57 -0
- data/lib/nfcom/models/fatura.rb +172 -0
- data/lib/nfcom/models/item.rb +353 -0
- data/lib/nfcom/models/nota.rb +398 -0
- data/lib/nfcom/models/total.rb +60 -0
- data/lib/nfcom/parsers/autorizacao.rb +28 -0
- data/lib/nfcom/parsers/base.rb +30 -0
- data/lib/nfcom/parsers/consulta.rb +34 -0
- data/lib/nfcom/parsers/inutilizacao.rb +23 -0
- data/lib/nfcom/parsers/status.rb +23 -0
- data/lib/nfcom/utils/certificate.rb +109 -0
- data/lib/nfcom/utils/compressor.rb +47 -0
- data/lib/nfcom/utils/helpers.rb +141 -0
- data/lib/nfcom/utils/response_decompressor.rb +47 -0
- data/lib/nfcom/utils/xml_authorized.rb +29 -0
- data/lib/nfcom/utils/xml_cleaner.rb +68 -0
- data/lib/nfcom/validators/business_rules.rb +45 -0
- data/lib/nfcom/validators/schema_validator.rb +316 -0
- data/lib/nfcom/validators/xml_validator.rb +29 -0
- data/lib/nfcom/version.rb +5 -0
- data/lib/nfcom/webservices/autorizacao.rb +36 -0
- data/lib/nfcom/webservices/base.rb +96 -0
- data/lib/nfcom/webservices/consulta.rb +59 -0
- data/lib/nfcom/webservices/inutilizacao.rb +71 -0
- data/lib/nfcom/webservices/status.rb +64 -0
- data/lib/nfcom.rb +98 -0
- data/nfcom.gemspec +42 -0
- metadata +242 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
# Representa o endereço do emitente ou destinatário da NF-COM
|
|
6
|
+
#
|
|
7
|
+
# Esta classe é utilizada tanto pelo emitente quanto pelo destinatário
|
|
8
|
+
# para armazenar as informações completas de endereço.
|
|
9
|
+
#
|
|
10
|
+
# @example Criar endereço completo
|
|
11
|
+
# endereco = Nfcom::Models::Endereco.new(
|
|
12
|
+
# logradouro: 'Rua das Flores',
|
|
13
|
+
# numero: '123',
|
|
14
|
+
# complemento: 'Sala 101',
|
|
15
|
+
# bairro: 'Centro',
|
|
16
|
+
# municipio: 'Recife',
|
|
17
|
+
# codigo_municipio: '2611606',
|
|
18
|
+
# uf: 'PE',
|
|
19
|
+
# cep: '50000-000',
|
|
20
|
+
# telefone: '(81) 3333-4444'
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @example Validar endereço
|
|
24
|
+
# if endereco.valido?
|
|
25
|
+
# puts "Endereço válido"
|
|
26
|
+
# else
|
|
27
|
+
# puts "Erros: #{endereco.erros.join(', ')}"
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# Atributos obrigatórios:
|
|
31
|
+
# - logradouro (rua, avenida, etc) - 2-60 caracteres (ER47)
|
|
32
|
+
# - numero (número do imóvel) - 1-60 caracteres (ER47)
|
|
33
|
+
# - bairro (bairro/distrito) - 2-60 caracteres (ER47)
|
|
34
|
+
# - municipio (nome do município) - 2-60 caracteres (ER47)
|
|
35
|
+
# - codigo_municipio (código IBGE do município - 7 dígitos) (ER2)
|
|
36
|
+
# - uf (sigla do estado - 2 letras) (D5)
|
|
37
|
+
# - cep (8 dígitos) (ER67)
|
|
38
|
+
#
|
|
39
|
+
# Atributos opcionais:
|
|
40
|
+
# - complemento (apartamento, sala, bloco, etc) - 1-60 caracteres (ER47)
|
|
41
|
+
# - codigo_pais (padrão: 1058 para Brasil)
|
|
42
|
+
# - pais (padrão: 'Brasil')
|
|
43
|
+
# - telefone (7-12 dígitos) (ER61)
|
|
44
|
+
# - email (ER72)
|
|
45
|
+
#
|
|
46
|
+
# @note O código do município (IBGE) pode ser consultado em:
|
|
47
|
+
# https://www.ibge.gov.br/explica/codigos-dos-municipios.php
|
|
48
|
+
class Endereco
|
|
49
|
+
include Utils::Helpers
|
|
50
|
+
|
|
51
|
+
attr_accessor :logradouro, :numero, :complemento, :bairro,
|
|
52
|
+
:codigo_municipio, :municipio, :uf, :cep,
|
|
53
|
+
:codigo_pais, :pais, :telefone, :email
|
|
54
|
+
|
|
55
|
+
def initialize(attributes = {})
|
|
56
|
+
attributes.each do |key, value|
|
|
57
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def valido?
|
|
62
|
+
erros.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def erros # rubocop:disable Metrics/MethodLength
|
|
66
|
+
errors = []
|
|
67
|
+
|
|
68
|
+
# Validações de campos obrigatórios
|
|
69
|
+
errors << 'Logradouro é obrigatório' if logradouro.to_s.strip.empty?
|
|
70
|
+
errors << 'Número é obrigatório' if numero.to_s.strip.empty?
|
|
71
|
+
errors << 'Bairro é obrigatório' if bairro.to_s.strip.empty?
|
|
72
|
+
errors << 'Município é obrigatório' if municipio.to_s.strip.empty?
|
|
73
|
+
errors << 'Código do município é obrigatório' if codigo_municipio.to_s.strip.empty?
|
|
74
|
+
errors << 'UF é obrigatório' if uf.to_s.strip.empty?
|
|
75
|
+
errors << 'CEP é obrigatório' if cep.to_s.strip.empty?
|
|
76
|
+
|
|
77
|
+
# Validações declarativas de formato/schema
|
|
78
|
+
campos = {}
|
|
79
|
+
|
|
80
|
+
# Campos obrigatórios - validar formato apenas se não estiverem vazios
|
|
81
|
+
unless logradouro.to_s.strip.empty?
|
|
82
|
+
campos[:logradouro] = { valor: logradouro, validador: :er47, nome: 'Logradouro', max: 60 }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
campos[:numero] = { valor: numero, validador: :er47, nome: 'Número', max: 60 } unless numero.to_s.strip.empty?
|
|
86
|
+
|
|
87
|
+
campos[:bairro] = { valor: bairro, validador: :er47, nome: 'Bairro', max: 60 } unless bairro.to_s.strip.empty?
|
|
88
|
+
|
|
89
|
+
unless municipio.to_s.strip.empty?
|
|
90
|
+
campos[:municipio] = { valor: municipio, validador: :er47, nome: 'Município', max: 60 }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
unless codigo_municipio.to_s.strip.empty?
|
|
94
|
+
campos[:codigo_municipio] = { valor: codigo_municipio, validador: :er2, nome: 'Código do município' }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
campos[:uf] = { valor: uf, validador: :d5, nome: 'UF' } unless uf.to_s.strip.empty?
|
|
98
|
+
|
|
99
|
+
unless cep.to_s.strip.empty?
|
|
100
|
+
cep_limpo = apenas_numeros(cep)
|
|
101
|
+
campos[:cep] = { valor: cep_limpo, validador: :er67, nome: 'CEP' }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Campos opcionais - validar formato apenas se informados
|
|
105
|
+
if complemento && !complemento.to_s.strip.empty?
|
|
106
|
+
campos[:complemento] = { valor: complemento, validador: :er47, nome: 'Complemento', max: 60 }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if telefone && !telefone.to_s.strip.empty?
|
|
110
|
+
telefone_limpo = apenas_numeros(telefone)
|
|
111
|
+
campos[:telefone] = { valor: telefone_limpo, validador: :er61, nome: 'Telefone' }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
campos[:email] = { valor: email, validador: :er72, nome: 'Email' } if email && !email.to_s.strip.empty?
|
|
115
|
+
|
|
116
|
+
# Executar validações declarativas
|
|
117
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos))
|
|
118
|
+
|
|
119
|
+
errors
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
class Fatura
|
|
6
|
+
class CodigoDeBarras
|
|
7
|
+
class Formato44
|
|
8
|
+
def initialize(valor)
|
|
9
|
+
@valor = valor
|
|
10
|
+
validar!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def linha_digitavel
|
|
14
|
+
[
|
|
15
|
+
campo(@valor[0, 4] + @valor[19, 5]),
|
|
16
|
+
campo(@valor[24, 10]),
|
|
17
|
+
campo(@valor[34, 10]),
|
|
18
|
+
@valor[4],
|
|
19
|
+
@valor[5, 14]
|
|
20
|
+
].join(' ')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def campo(valor)
|
|
26
|
+
"#{valor[0, 5]}.#{valor[5, 5]}#{modulo10(valor)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def modulo10(numero)
|
|
30
|
+
soma = 0
|
|
31
|
+
multiplicador = 2
|
|
32
|
+
|
|
33
|
+
numero.reverse.each_char do |char|
|
|
34
|
+
v = char.to_i * multiplicador
|
|
35
|
+
v -= 9 if v > 9
|
|
36
|
+
soma += v
|
|
37
|
+
multiplicador = multiplicador == 2 ? 1 : 2
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
(10 - (soma % 10)) % 10
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validar!
|
|
44
|
+
return if @valor.match?(/\A\d{44}\z/)
|
|
45
|
+
|
|
46
|
+
raise ArgumentError, 'Código de barras (44) inválido'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
class Fatura
|
|
6
|
+
class CodigoDeBarras
|
|
7
|
+
attr_reader :valor
|
|
8
|
+
|
|
9
|
+
def initialize(valor)
|
|
10
|
+
@valor = valor.to_s.gsub(/\D/, '')
|
|
11
|
+
validar!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def tamanho
|
|
15
|
+
valor.length
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def formato
|
|
19
|
+
case tamanho
|
|
20
|
+
when 44 then :formato_44
|
|
21
|
+
when 48 then :formato_48
|
|
22
|
+
else :desconhecido
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def linha_digitavel
|
|
27
|
+
handler.linha_digitavel
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def valido?
|
|
31
|
+
true
|
|
32
|
+
rescue ArgumentError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def handler
|
|
39
|
+
@handler ||= case formato
|
|
40
|
+
when :formato_44
|
|
41
|
+
CodigoDeBarras::Formato44.new(valor)
|
|
42
|
+
when :formato_48
|
|
43
|
+
CodigoDeBarras::Formato48.new(valor)
|
|
44
|
+
else
|
|
45
|
+
raise ArgumentError, 'Formato de código de barras não suportado'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validar!
|
|
50
|
+
return if valor.match?(/\A\d+\z/)
|
|
51
|
+
|
|
52
|
+
raise ArgumentError, 'Código de barras deve conter apenas números'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
# Representa as informações de faturamento da NF-COM (grupo gFat)
|
|
6
|
+
#
|
|
7
|
+
# Este grupo contém as informações sobre o período de faturamento,
|
|
8
|
+
# vencimento e valores da fatura.
|
|
9
|
+
#
|
|
10
|
+
# @example Criar fatura básica para ISP
|
|
11
|
+
# fatura = Nfcom::Models::Fatura.new(
|
|
12
|
+
# competencia: '2026-01', # Aceita YYYY-MM ou AAAAMM
|
|
13
|
+
# data_vencimento: '2026-02-15', # Data de vencimento
|
|
14
|
+
# codigo_barras: '23793381286000000099901234567890123456789012',
|
|
15
|
+
# valor_fatura: 99.90
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# @example Fatura com período de uso
|
|
19
|
+
# fatura = Nfcom::Models::Fatura.new(
|
|
20
|
+
# competencia: '202601',
|
|
21
|
+
# data_vencimento: '2026-02-15',
|
|
22
|
+
# codigo_barras: '23793381286000000099901234567890123456789012',
|
|
23
|
+
# periodo_uso_inicio: '2026-01-01',
|
|
24
|
+
# periodo_uso_fim: '2026-01-31',
|
|
25
|
+
# valor_fatura: 99.90
|
|
26
|
+
# )
|
|
27
|
+
#
|
|
28
|
+
# Atributos obrigatórios:
|
|
29
|
+
# - competencia (formato AAAAMM, ex: '202601' ou aceita '2026-01')
|
|
30
|
+
# - data_vencimento (formato YYYY-MM-DD, ex: '2026-02-15')
|
|
31
|
+
# - codigo_barras (linha digitável do boleto, 1-48 caracteres)
|
|
32
|
+
# - valor_fatura (valor total da fatura)
|
|
33
|
+
#
|
|
34
|
+
# Atributos opcionais:
|
|
35
|
+
# - valor_liquido (valor líquido após descontos)
|
|
36
|
+
# - periodo_uso_inicio (início do período de uso - YYYY-MM-DD)
|
|
37
|
+
# - periodo_uso_fim (fim do período de uso - YYYY-MM-DD)
|
|
38
|
+
# - codigo_debito_automatico (código de autorização débito em conta)
|
|
39
|
+
# - codigo_banco (número do banco - se houver débito automático)
|
|
40
|
+
# - codigo_agencia (número da agência - se houver débito automático)
|
|
41
|
+
class Fatura
|
|
42
|
+
include Utils::Helpers
|
|
43
|
+
|
|
44
|
+
attr_accessor :data_vencimento, # dVencFat - YYYY-MM-DD
|
|
45
|
+
:codigo_barras, # codBarras - OBRIGATÓRIO
|
|
46
|
+
:valor_fatura, # vFat - Valor total
|
|
47
|
+
:valor_liquido, # vLiqFat - Valor líquido (opcional)
|
|
48
|
+
:periodo_uso_inicio, # dPerUsoIni (opcional)
|
|
49
|
+
:periodo_uso_fim, # dPerUsoFim (opcional)
|
|
50
|
+
:codigo_debito_automatico, # codDebAuto (opcional)
|
|
51
|
+
:codigo_banco, # codBanco (opcional)
|
|
52
|
+
:codigo_agencia # codAgencia (opcional)
|
|
53
|
+
|
|
54
|
+
attr_reader :competencia # CompetFat - AAAAMM
|
|
55
|
+
|
|
56
|
+
def initialize(attributes = {})
|
|
57
|
+
attributes.each do |key, value|
|
|
58
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Se valor_liquido não foi informado, usa o mesmo da fatura
|
|
62
|
+
@valor_liquido ||= @valor_fatura
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Define a competência, aceitando tanto YYYY-MM quanto AAAAMM
|
|
66
|
+
# @param value [String] Competência no formato YYYY-MM ou AAAAMM
|
|
67
|
+
def competencia=(value)
|
|
68
|
+
return if value.nil?
|
|
69
|
+
|
|
70
|
+
# Se vier no formato YYYY-MM, converter para AAAAMM
|
|
71
|
+
@competencia = if value.to_s.include?('-')
|
|
72
|
+
value.to_s.gsub('-', '')
|
|
73
|
+
else
|
|
74
|
+
value.to_s
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def valido?
|
|
79
|
+
erros.empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def erros # rubocop:disable Metrics/MethodLength
|
|
83
|
+
errors = []
|
|
84
|
+
|
|
85
|
+
# Validar competência
|
|
86
|
+
if competencia.to_s.strip.empty?
|
|
87
|
+
errors << 'Competência é obrigatória'
|
|
88
|
+
elsif !competencia_valida?
|
|
89
|
+
errors << 'Competência deve estar no formato AAAAMM (ex: 202601)'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Validar data de vencimento
|
|
93
|
+
if data_vencimento.to_s.strip.empty?
|
|
94
|
+
errors << 'Data de vencimento é obrigatória'
|
|
95
|
+
elsif !data_vencimento_valida?
|
|
96
|
+
errors << 'Data de vencimento deve estar no formato YYYY-MM-DD (ex: 2026-02-15)'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Validar código de barras (OBRIGATÓRIO)
|
|
100
|
+
if codigo_barras.to_s.strip.empty?
|
|
101
|
+
errors << 'Código de barras é obrigatório'
|
|
102
|
+
elsif codigo_barras.to_s.length > 48
|
|
103
|
+
errors << 'Código de barras deve ter no máximo 48 caracteres'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Validar valor da fatura
|
|
107
|
+
if valor_fatura.nil?
|
|
108
|
+
errors << 'Valor da fatura é obrigatório'
|
|
109
|
+
elsif valor_fatura.to_f <= 0
|
|
110
|
+
errors << 'Valor da fatura deve ser maior que zero'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Validar períodos de uso (se informados)
|
|
114
|
+
if periodo_uso_inicio && periodo_uso_fim
|
|
115
|
+
inicio = safe_to_date(periodo_uso_inicio)
|
|
116
|
+
fim = safe_to_date(periodo_uso_fim)
|
|
117
|
+
|
|
118
|
+
if inicio.nil? || fim.nil?
|
|
119
|
+
errors << 'Período de uso: datas inválidas'
|
|
120
|
+
elsif inicio > fim
|
|
121
|
+
errors << 'Período de uso: data inicial não pode ser posterior à data final'
|
|
122
|
+
end
|
|
123
|
+
elsif periodo_uso_inicio || periodo_uso_fim
|
|
124
|
+
errors << 'Período de uso: ambas as datas (início e fim) devem ser informadas'
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validar débito automático (se informado)
|
|
128
|
+
if codigo_debito_automatico
|
|
129
|
+
errors << 'Código do banco é obrigatório quando há débito automático' if codigo_banco.to_s.strip.empty?
|
|
130
|
+
errors << 'Código da agência é obrigatório quando há débito automático' if codigo_agencia.to_s.strip.empty?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
errors
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def linha_digitavel
|
|
137
|
+
CodigoDeBarras.new(codigo_barras).linha_digitavel
|
|
138
|
+
rescue StandardError
|
|
139
|
+
''
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def competencia_valida?
|
|
145
|
+
return false unless competencia
|
|
146
|
+
|
|
147
|
+
# Formato: AAAAMM (6 dígitos)
|
|
148
|
+
return false unless competencia.to_s.match?(/^\d{6}$/)
|
|
149
|
+
|
|
150
|
+
# Validar se o mês é válido (01-12)
|
|
151
|
+
mes = competencia[-2..].to_i
|
|
152
|
+
mes.between?(1, 12)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def data_vencimento_valida?
|
|
156
|
+
return false unless data_vencimento
|
|
157
|
+
|
|
158
|
+
# Se já é um Date, valida diretamente
|
|
159
|
+
return true if data_vencimento.is_a?(Date)
|
|
160
|
+
|
|
161
|
+
# Se é String, valida formato YYYY-MM-DD
|
|
162
|
+
return false unless data_vencimento.to_s.match?(/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/)
|
|
163
|
+
|
|
164
|
+
# Tentar fazer parse para validar se é uma data real
|
|
165
|
+
Date.parse(data_vencimento.to_s)
|
|
166
|
+
true
|
|
167
|
+
rescue ArgumentError
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|