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,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module Nfcom
|
|
6
|
+
class Configuration
|
|
7
|
+
AMBIENTES = {
|
|
8
|
+
homologacao: 2,
|
|
9
|
+
producao: 1
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
ESTADOS = {
|
|
13
|
+
'AC' => '12', 'AL' => '27', 'AP' => '16', 'AM' => '13',
|
|
14
|
+
'BA' => '29', 'CE' => '23', 'DF' => '53', 'ES' => '32',
|
|
15
|
+
'GO' => '52', 'MA' => '21', 'MT' => '51', 'MS' => '50',
|
|
16
|
+
'MG' => '31', 'PA' => '15', 'PB' => '25', 'PR' => '41',
|
|
17
|
+
'PE' => '26', 'PI' => '22', 'RJ' => '33', 'RN' => '24',
|
|
18
|
+
'RS' => '43', 'RO' => '11', 'RR' => '14', 'SC' => '42',
|
|
19
|
+
'SP' => '35', 'SE' => '28', 'TO' => '17'
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# Configurações básicas
|
|
23
|
+
attr_accessor :ambiente, :estado, :timeout
|
|
24
|
+
|
|
25
|
+
# Certificado digital
|
|
26
|
+
attr_accessor :certificado_path, :certificado_senha
|
|
27
|
+
|
|
28
|
+
# Dados do emitente
|
|
29
|
+
attr_accessor :cnpj, :razao_social, :inscricao_estadual, :regime_tributario
|
|
30
|
+
|
|
31
|
+
# Configurações de série e numeração
|
|
32
|
+
attr_accessor :serie_padrao, :numero_inicial
|
|
33
|
+
|
|
34
|
+
# Logging
|
|
35
|
+
attr_accessor :logger
|
|
36
|
+
attr_reader :log_level
|
|
37
|
+
|
|
38
|
+
# Retry
|
|
39
|
+
attr_accessor :max_tentativas, :tempo_espera_retry
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@ambiente = :homologacao
|
|
43
|
+
@estado = 'PE'
|
|
44
|
+
@timeout = 30
|
|
45
|
+
@serie_padrao = 1
|
|
46
|
+
@numero_inicial = 1
|
|
47
|
+
@max_tentativas = 3
|
|
48
|
+
@tempo_espera_retry = 2
|
|
49
|
+
@log_level = :info
|
|
50
|
+
@logger = Logger.new($stdout)
|
|
51
|
+
@logger.level = Logger::INFO
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def ambiente_codigo
|
|
55
|
+
AMBIENTES[ambiente]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def codigo_uf
|
|
59
|
+
ESTADOS[estado]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def homologacao?
|
|
63
|
+
ambiente == :homologacao
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def producao?
|
|
67
|
+
ambiente == :producao
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def desabilitar_logs
|
|
71
|
+
@logger = Logger.new(IO::NULL)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def habilitar_logs(output = $stdout)
|
|
75
|
+
@logger = Logger.new(output)
|
|
76
|
+
atualizar_log_level
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Setter para log_level (atualiza também o logger)
|
|
80
|
+
def log_level=(level)
|
|
81
|
+
@log_level = level
|
|
82
|
+
atualizar_log_level
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def webservice_url(servico)
|
|
86
|
+
base_url = if homologacao?
|
|
87
|
+
webservices_homologacao[estado.to_sym]
|
|
88
|
+
else
|
|
89
|
+
webservices_producao[estado.to_sym]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
return nil unless base_url
|
|
93
|
+
|
|
94
|
+
base_url[servico]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def atualizar_log_level
|
|
100
|
+
return unless @logger
|
|
101
|
+
|
|
102
|
+
@logger.level = case @log_level
|
|
103
|
+
when :debug then Logger::DEBUG
|
|
104
|
+
when :info then Logger::INFO
|
|
105
|
+
when :warn then Logger::WARN
|
|
106
|
+
when :error then Logger::ERROR
|
|
107
|
+
when :fatal then Logger::FATAL
|
|
108
|
+
else Logger::INFO # rubocop:disable Lint/DuplicateBranch
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def webservices_homologacao
|
|
113
|
+
{
|
|
114
|
+
PE: {
|
|
115
|
+
recepcao: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComRecepcao/NFComRecepcao.asmx',
|
|
116
|
+
consulta: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComConsulta/NFComConsulta.asmx',
|
|
117
|
+
status: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComStatusServico/NFComStatusServico.asmx',
|
|
118
|
+
evento: 'https://nfcom-homologacao.svrs.rs.gov.br/WS/NFComRecepcaoEvento/NFComRecepcaoEvento.asmx'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def webservices_producao
|
|
124
|
+
{
|
|
125
|
+
PE: {
|
|
126
|
+
recepcao: 'https://nfcom.svrs.rs.gov.br/WS/NFComRecepcao/NFComRecepcao.asmx',
|
|
127
|
+
consulta: 'https://nfcom.svrs.rs.gov.br/WS/NFComConsulta/NFComConsulta.asmx',
|
|
128
|
+
status: 'https://nfcom.svrs.rs.gov.br/WS/NFComStatusServico/NFComStatusServico.asmx',
|
|
129
|
+
evento: 'https://nfcom.svrs.rs.gov.br/WS/NFComRecepcaoEvento/NFComRecepcaoEvento.asmx'
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/nfcom/errors.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Errors
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
class ConfigurationError < Error; end
|
|
8
|
+
class CertificateError < Error; end
|
|
9
|
+
class ValidationError < Error; end
|
|
10
|
+
class XmlError < Error; end
|
|
11
|
+
class SefazError < Error; end
|
|
12
|
+
class SefazIndisponivel < SefazError; end
|
|
13
|
+
|
|
14
|
+
class NotaRejeitada < SefazError
|
|
15
|
+
attr_reader :codigo, :motivo
|
|
16
|
+
|
|
17
|
+
def initialize(codigo, motivo)
|
|
18
|
+
@codigo = codigo
|
|
19
|
+
@motivo = motivo
|
|
20
|
+
super("Nota rejeitada [#{codigo}]: #{motivo}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class NotaDenegada < SefazError; end
|
|
25
|
+
class TimeoutError < Error; end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Helpers
|
|
5
|
+
module Consulta
|
|
6
|
+
# Returns the URL to confirm an already-authorized NFCom
|
|
7
|
+
#
|
|
8
|
+
# @param chave [String] Chave de acesso da NFCom (44 chars)
|
|
9
|
+
# @param ambiente [Symbol] :homologacao or :producao (optional, defaults to current config)
|
|
10
|
+
# @return [String] URL to confirm the nota
|
|
11
|
+
def self.url(chave:, ambiente: Nfcom.configuration.ambiente)
|
|
12
|
+
raise ArgumentError, 'Chave de acesso inválida' unless chave&.length == 44
|
|
13
|
+
|
|
14
|
+
tp_amb = (ambiente == :producao ? 1 : 2)
|
|
15
|
+
"https://dfe-portal.svrs.rs.gov.br/nfcom/qrcode?chNFCom=#{chave}&tpAmb=#{tp_amb}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Optional: parse the chave from a full XML string
|
|
19
|
+
#
|
|
20
|
+
# @param xml [String] NFCom XML (as stored in :xml_autorizado)
|
|
21
|
+
# @return [String] chave de acesso
|
|
22
|
+
def self.chave_from_xml(xml)
|
|
23
|
+
doc = Nokogiri::XML(xml)
|
|
24
|
+
doc.at_xpath('//NFCom/infNFCom/@Id')&.value&.sub(/^NFCom/, '')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
# Representa os dados do assinante do serviço de comunicação
|
|
6
|
+
class Assinante
|
|
7
|
+
include Utils::Helpers
|
|
8
|
+
|
|
9
|
+
attr_accessor :codigo, # iCodAssinante - Código único (1-30 chars)
|
|
10
|
+
:tipo, # tpAssinante - Tipo de assinante (1-8, 99)
|
|
11
|
+
:tipo_servico, # tpServUtil - Tipo de serviço (1-7)
|
|
12
|
+
:numero_contrato, # nContrato - Número do contrato (opcional)
|
|
13
|
+
:data_inicio_contrato, # dContratoIni - Data início (opcional)
|
|
14
|
+
:data_fim_contrato, # dContratoFim - Data fim (opcional)
|
|
15
|
+
:terminal_principal, # NroTermPrinc - Terminal principal (condicional)
|
|
16
|
+
:uf_terminal_principal, # cUFPrinc - UF do terminal (condicional)
|
|
17
|
+
:terminais_adicionais # Array de { numero:, uf: } (opcional)
|
|
18
|
+
|
|
19
|
+
# Tipos de assinante (tpAssinante) - Domain D18
|
|
20
|
+
TIPO_COMERCIAL = 1
|
|
21
|
+
TIPO_INDUSTRIAL = 2
|
|
22
|
+
TIPO_RESIDENCIAL = 3
|
|
23
|
+
TIPO_PRODUTOR_RURAL = 4
|
|
24
|
+
TIPO_ORGAO_PUBLICO = 5
|
|
25
|
+
TIPO_PRESTADOR_TELECOM = 6
|
|
26
|
+
TIPO_DIPLOMATICO = 7
|
|
27
|
+
TIPO_RELIGIOSO = 8
|
|
28
|
+
TIPO_OUTROS = 99
|
|
29
|
+
|
|
30
|
+
# Tipos de serviço (tpServUtil) - Domain D24
|
|
31
|
+
SERVICO_TELEFONIA = 1
|
|
32
|
+
SERVICO_DADOS = 2
|
|
33
|
+
SERVICO_TV = 3
|
|
34
|
+
SERVICO_INTERNET = 4
|
|
35
|
+
SERVICO_MULTIMIDIA = 5
|
|
36
|
+
SERVICO_OUTROS = 6
|
|
37
|
+
SERVICO_VARIOS = 7
|
|
38
|
+
|
|
39
|
+
def initialize(attrs = {})
|
|
40
|
+
@codigo = attrs[:codigo]
|
|
41
|
+
@tipo = attrs[:tipo]
|
|
42
|
+
@tipo_servico = attrs[:tipo_servico]
|
|
43
|
+
@numero_contrato = attrs[:numero_contrato]
|
|
44
|
+
@data_inicio_contrato = attrs[:data_inicio_contrato]
|
|
45
|
+
@data_fim_contrato = attrs[:data_fim_contrato]
|
|
46
|
+
@terminal_principal = attrs[:terminal_principal]
|
|
47
|
+
@uf_terminal_principal = attrs[:uf_terminal_principal]
|
|
48
|
+
@terminais_adicionais = attrs[:terminais_adicionais] || []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def valido?
|
|
52
|
+
erros.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def erros # rubocop:disable Metrics/MethodLength
|
|
56
|
+
errors = []
|
|
57
|
+
|
|
58
|
+
# Validações de campos obrigatórios
|
|
59
|
+
errors << 'Código do assinante é obrigatório' if codigo.to_s.strip.empty?
|
|
60
|
+
errors << 'Tipo de assinante é obrigatório' if tipo.nil?
|
|
61
|
+
errors << 'Tipo de serviço é obrigatório' if tipo_servico.nil?
|
|
62
|
+
|
|
63
|
+
# Validações declarativas de formato/schema
|
|
64
|
+
campos = {
|
|
65
|
+
codigo: { valor: codigo, validador: :er59, nome: 'Código do assinante' }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Só valida tipo se não for nil (já checado acima)
|
|
69
|
+
campos[:tipo] = { valor: tipo, validador: :d18, nome: 'Tipo de assinante' } unless tipo.nil?
|
|
70
|
+
unless tipo_servico.nil?
|
|
71
|
+
campos[:tipo_servico] =
|
|
72
|
+
{ valor: tipo_servico, validador: :d24, nome: 'Tipo de serviço' }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Adicionar campos opcionais apenas se informados
|
|
76
|
+
if numero_contrato && !numero_contrato.to_s.strip.empty?
|
|
77
|
+
campos[:numero_contrato] = { valor: numero_contrato, validador: :er60, nome: 'Número do contrato' }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if data_inicio_contrato && !data_inicio_contrato.to_s.strip.empty?
|
|
81
|
+
campos[:data_inicio] = { valor: data_inicio_contrato, validador: :er48, nome: 'Data de início' }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if data_fim_contrato && !data_fim_contrato.to_s.strip.empty?
|
|
85
|
+
campos[:data_fim] = { valor: data_fim_contrato, validador: :er48, nome: 'Data de fim' }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Executar validações declarativas
|
|
89
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos))
|
|
90
|
+
|
|
91
|
+
# Validação lógica: data fim >= data início
|
|
92
|
+
if data_inicio_contrato && data_fim_contrato &&
|
|
93
|
+
!data_inicio_contrato.to_s.strip.empty? && !data_fim_contrato.to_s.strip.empty?
|
|
94
|
+
begin
|
|
95
|
+
inicio = Date.parse(data_inicio_contrato.to_s)
|
|
96
|
+
fim = Date.parse(data_fim_contrato.to_s)
|
|
97
|
+
errors << 'Data de fim do contrato não pode ser anterior à data de início' if fim < inicio
|
|
98
|
+
rescue ArgumentError
|
|
99
|
+
# Erro de parsing já foi capturado pelas validações declarativas
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Validações de terminal principal (condicional)
|
|
104
|
+
if terminal_principal && !terminal_principal.to_s.strip.empty?
|
|
105
|
+
# Se informou terminal, UF é obrigatória
|
|
106
|
+
if uf_terminal_principal.to_s.strip.empty?
|
|
107
|
+
errors << 'UF do terminal principal é obrigatória quando o número do terminal é informado'
|
|
108
|
+
else
|
|
109
|
+
# Validar formato do terminal e UF
|
|
110
|
+
campos_terminal = {
|
|
111
|
+
terminal: { valor: terminal_principal, validador: :telefone, nome: 'Terminal principal' },
|
|
112
|
+
uf_terminal: { valor: uf_terminal_principal, validador: :d5, nome: 'UF do terminal' }
|
|
113
|
+
}
|
|
114
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos_terminal))
|
|
115
|
+
end
|
|
116
|
+
elsif uf_terminal_principal && !uf_terminal_principal.to_s.strip.empty?
|
|
117
|
+
# Se informou UF mas não informou terminal
|
|
118
|
+
errors << 'Número do terminal principal é obrigatório quando a UF é informada'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Validações de terminais adicionais
|
|
122
|
+
terminais_adicionais&.each_with_index do |terminal, index|
|
|
123
|
+
if terminal[:numero].to_s.strip.empty?
|
|
124
|
+
errors << "Terminal adicional #{index + 1}: número é obrigatório"
|
|
125
|
+
else
|
|
126
|
+
campos_adicional = {
|
|
127
|
+
numero: { valor: terminal[:numero], validador: :telefone, nome: "Terminal adicional #{index + 1}" }
|
|
128
|
+
}
|
|
129
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos_adicional))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
if terminal[:uf].to_s.strip.empty?
|
|
133
|
+
errors << "Terminal adicional #{index + 1}: UF é obrigatória"
|
|
134
|
+
else
|
|
135
|
+
campos_uf = {
|
|
136
|
+
uf: { valor: terminal[:uf], validador: :d5, nome: "UF do terminal adicional #{index + 1}" }
|
|
137
|
+
}
|
|
138
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos_uf))
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
errors
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
# Representa o destinatário (cliente) da NF-COM
|
|
6
|
+
#
|
|
7
|
+
# O destinatário é o tomador do serviço de comunicação/telecomunicação,
|
|
8
|
+
# podendo ser pessoa física (CPF) ou pessoa jurídica (CNPJ).
|
|
9
|
+
#
|
|
10
|
+
# @example Criar destinatário pessoa física
|
|
11
|
+
# destinatario = Nfcom::Models::Destinatario.new(
|
|
12
|
+
# cpf: '12345678901',
|
|
13
|
+
# razao_social: 'João da Silva',
|
|
14
|
+
# tipo_assinante: :residencial,
|
|
15
|
+
# email: 'joao@email.com',
|
|
16
|
+
# endereco: {
|
|
17
|
+
# logradouro: 'Rua das Flores',
|
|
18
|
+
# numero: '123',
|
|
19
|
+
# bairro: 'Centro',
|
|
20
|
+
# municipio: 'Recife',
|
|
21
|
+
# uf: 'PE',
|
|
22
|
+
# cep: '50000-000',
|
|
23
|
+
# codigo_municipio: '2611606'
|
|
24
|
+
# }
|
|
25
|
+
# )
|
|
26
|
+
#
|
|
27
|
+
# @example Criar destinatário pessoa jurídica
|
|
28
|
+
# destinatario = Nfcom::Models::Destinatario.new(
|
|
29
|
+
# cnpj: '12345678000100',
|
|
30
|
+
# razao_social: 'Empresa LTDA',
|
|
31
|
+
# tipo_assinante: :comercial,
|
|
32
|
+
# inscricao_estadual: '0123456789',
|
|
33
|
+
# email: 'contato@empresa.com',
|
|
34
|
+
# endereco: { ... }
|
|
35
|
+
# )
|
|
36
|
+
#
|
|
37
|
+
# @example Validar destinatário
|
|
38
|
+
# if destinatario.valido?
|
|
39
|
+
# puts "Destinatário válido"
|
|
40
|
+
# else
|
|
41
|
+
# puts "Erros: #{destinatario.erros.join(', ')}"
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# Tipos de assinante disponíveis:
|
|
45
|
+
# - :comercial (1) - Estabelecimentos comerciais
|
|
46
|
+
# - :industrial (2) - Indústrias
|
|
47
|
+
# - :residencial (3) - Residências (padrão para provedores)
|
|
48
|
+
# - :produtor_rural (4) - Produtores rurais
|
|
49
|
+
# - :orgao_publico (5) - Órgãos públicos
|
|
50
|
+
# - :prestador_servico (6) - Prestadores de serviço
|
|
51
|
+
# - :concessionaria (7) - Concessionárias
|
|
52
|
+
# - :outros (99) - Outros
|
|
53
|
+
#
|
|
54
|
+
# Atributos obrigatórios:
|
|
55
|
+
# - CNPJ ou CPF (pelo menos um)
|
|
56
|
+
# - razao_social (nome ou razão social)
|
|
57
|
+
# - endereco completo
|
|
58
|
+
#
|
|
59
|
+
# Atributos opcionais:
|
|
60
|
+
# - inscricao_estadual (apenas para PJ)
|
|
61
|
+
# - email (recomendado para envio da nota)
|
|
62
|
+
#
|
|
63
|
+
# Validações automáticas:
|
|
64
|
+
# - Validação de dígitos verificadores de CPF/CNPJ
|
|
65
|
+
# - Rejeita CPF/CNPJ com todos dígitos iguais
|
|
66
|
+
# - Valida campos obrigatórios do endereço
|
|
67
|
+
class Destinatario
|
|
68
|
+
include Utils::Helpers
|
|
69
|
+
|
|
70
|
+
attr_accessor :cnpj, :cpf, :razao_social, :inscricao_estadual,
|
|
71
|
+
:tipo_assinante, :endereco, :email
|
|
72
|
+
|
|
73
|
+
# Tipos de assinante
|
|
74
|
+
TIPO_ASSINANTE = {
|
|
75
|
+
comercial: 1,
|
|
76
|
+
industrial: 2,
|
|
77
|
+
residencial: 3,
|
|
78
|
+
produtor_rural: 4,
|
|
79
|
+
orgao_publico: 5,
|
|
80
|
+
prestador_servico: 6,
|
|
81
|
+
concessionaria: 7,
|
|
82
|
+
outros: 99
|
|
83
|
+
}.freeze
|
|
84
|
+
|
|
85
|
+
def initialize(attributes = {})
|
|
86
|
+
@endereco = Endereco.new
|
|
87
|
+
@tipo_assinante = :residencial # padrão para provedor de internet
|
|
88
|
+
|
|
89
|
+
attributes.each do |key, value|
|
|
90
|
+
if key == :endereco && value.is_a?(Hash)
|
|
91
|
+
@endereco = Endereco.new(value)
|
|
92
|
+
elsif respond_to?("#{key}=")
|
|
93
|
+
send("#{key}=", value)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def valido?
|
|
99
|
+
erros.empty?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def erros
|
|
103
|
+
errors = []
|
|
104
|
+
errors << 'CNPJ ou CPF é obrigatório' if cnpj.to_s.strip.empty? && cpf.to_s.strip.empty?
|
|
105
|
+
errors << 'CNPJ inválido' if !cnpj.to_s.strip.empty? && !cnpj_valido?(cnpj)
|
|
106
|
+
errors << 'CPF inválido' if !cpf.to_s.strip.empty? && !cpf_valido?(cpf)
|
|
107
|
+
errors << 'Razão social é obrigatória' if razao_social.to_s.strip.empty?
|
|
108
|
+
errors.concat(endereco.erros.map { |e| "Endereço: #{e}" }) unless endereco.valido?
|
|
109
|
+
|
|
110
|
+
campos = {}
|
|
111
|
+
|
|
112
|
+
campos[:razao_social] = {
|
|
113
|
+
valor: razao_social,
|
|
114
|
+
validador: :er47,
|
|
115
|
+
nome: 'Razão social',
|
|
116
|
+
max: 60
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
campos[:email] = { valor: email, validador: :er72, nome: 'Email' } if email && !email.to_s.strip.empty?
|
|
120
|
+
errors.concat(Validators::SchemaValidator.validar_campos(campos))
|
|
121
|
+
|
|
122
|
+
errors
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def tipo_assinante_codigo
|
|
126
|
+
TIPO_ASSINANTE[tipo_assinante] || TIPO_ASSINANTE[:residencial]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def pessoa_fisica?
|
|
130
|
+
!cpf.to_s.strip.empty?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def pessoa_juridica?
|
|
134
|
+
!cnpj.to_s.strip.empty?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfcom
|
|
4
|
+
module Models
|
|
5
|
+
# Representa o emitente da NF-COM
|
|
6
|
+
#
|
|
7
|
+
# O emitente é a empresa prestadora do serviço de comunicação que está
|
|
8
|
+
# emitindo a nota fiscal. Deve ser sempre pessoa jurídica (CNPJ).
|
|
9
|
+
#
|
|
10
|
+
# @example Criar emitente completo
|
|
11
|
+
# emitente = Nfcom::Models::Emitente.new(
|
|
12
|
+
# cnpj: '12345678000100',
|
|
13
|
+
# razao_social: 'Provedor Internet LTDA',
|
|
14
|
+
# nome_fantasia: 'Meu Provedor',
|
|
15
|
+
# inscricao_estadual: '0123456789',
|
|
16
|
+
# inscricao_municipal: '987654',
|
|
17
|
+
# cnae: '6190-6/01',
|
|
18
|
+
# regime_tributario: :simples_nacional,
|
|
19
|
+
# endereco: {
|
|
20
|
+
# logradouro: 'Av. Principal',
|
|
21
|
+
# numero: '1000',
|
|
22
|
+
# complemento: 'Sala 101',
|
|
23
|
+
# bairro: 'Centro',
|
|
24
|
+
# municipio: 'Recife',
|
|
25
|
+
# uf: 'PE',
|
|
26
|
+
# cep: '50000-000',
|
|
27
|
+
# codigo_municipio: '2611606'
|
|
28
|
+
# }
|
|
29
|
+
# )
|
|
30
|
+
#
|
|
31
|
+
# @example Validar emitente
|
|
32
|
+
# if emitente.valido?
|
|
33
|
+
# puts "Emitente válido"
|
|
34
|
+
# else
|
|
35
|
+
# puts "Erros: #{emitente.erros.join(', ')}"
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# Atributos obrigatórios:
|
|
39
|
+
# - cnpj (14 dígitos, com validação)
|
|
40
|
+
# - razao_social (razão social da empresa)
|
|
41
|
+
# - inscricao_estadual (IE do estado)
|
|
42
|
+
# - endereco completo
|
|
43
|
+
#
|
|
44
|
+
# Atributos opcionais:
|
|
45
|
+
# - nome_fantasia (nome de fantasia/comercial)
|
|
46
|
+
# - inscricao_municipal (IM do município)
|
|
47
|
+
# - cnae (classificação da atividade econômica)
|
|
48
|
+
# - regime_tributario (1=Simples Nacional, 2=Simples Excesso, 3=Normal)
|
|
49
|
+
#
|
|
50
|
+
# Validações automáticas:
|
|
51
|
+
# - Validação de dígitos verificadores do CNPJ
|
|
52
|
+
# - Rejeita CNPJ com todos dígitos iguais
|
|
53
|
+
# - Valida presença de campos obrigatórios
|
|
54
|
+
# - Valida campos obrigatórios do endereço
|
|
55
|
+
#
|
|
56
|
+
# @note O emitente deve estar cadastrado e credenciado na SEFAZ para
|
|
57
|
+
# emissão de NF-COM antes de usar esta gem.
|
|
58
|
+
class Emitente
|
|
59
|
+
include Utils::Helpers
|
|
60
|
+
|
|
61
|
+
attr_accessor :cnpj, :razao_social, :nome_fantasia, :inscricao_estadual,
|
|
62
|
+
:inscricao_municipal, :cnae, :regime_tributario, :endereco
|
|
63
|
+
|
|
64
|
+
# Códigos de Regime Tributário (CRT)
|
|
65
|
+
REGIME_TRIBUTARIO = {
|
|
66
|
+
simples_nacional: 1,
|
|
67
|
+
simples_excesso: 2,
|
|
68
|
+
normal: 3
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
def initialize(attributes = {})
|
|
72
|
+
@endereco = Endereco.new
|
|
73
|
+
@regime_tributario = :normal # Padrão: Regime Normal
|
|
74
|
+
|
|
75
|
+
attributes.each do |key, value|
|
|
76
|
+
if key == :endereco && value.is_a?(Hash)
|
|
77
|
+
@endereco = Endereco.new(value)
|
|
78
|
+
elsif respond_to?("#{key}=")
|
|
79
|
+
send("#{key}=", value)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Retorna o código do regime tributário para o XML
|
|
85
|
+
# @return [Integer] Código do regime tributário (1, 2 ou 3)
|
|
86
|
+
def regime_tributario_codigo
|
|
87
|
+
REGIME_TRIBUTARIO[regime_tributario] || REGIME_TRIBUTARIO[:normal]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def valido?
|
|
91
|
+
erros.empty?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def erros
|
|
95
|
+
errors = []
|
|
96
|
+
errors << 'CNPJ é obrigatório' if cnpj.to_s.strip.empty?
|
|
97
|
+
errors << 'CNPJ inválido' unless cnpj_valido?(cnpj)
|
|
98
|
+
errors << 'Razão social é obrigatória' if razao_social.to_s.strip.empty?
|
|
99
|
+
errors << 'Inscrição estadual é obrigatória' if inscricao_estadual.to_s.strip.empty?
|
|
100
|
+
errors.concat(endereco.erros.map { |e| "Endereço: #{e}" }) unless endereco.valido?
|
|
101
|
+
errors
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|