sefaz 0.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +115 -0
- data/Gemfile.lock +31 -17
- data/README.md +449 -17
- data/Rakefile +1 -0
- data/docs/CFe-SAT/Especificacao_SAT_v_ER_2_30_03.pdf +0 -0
- data/docs/CFe-SAT/Manual_Orientacao_SAT_v_MO_2_19_04.pdf +0 -0
- data/docs/CFe-SAT/Manual_do_Emulador_SAT-CF-e_Offline_2015_09_10.pdf +0 -0
- data/docs/CFe-SAT/_CANCEL_dataset.md +14 -0
- data/docs/CFe-SAT/_SALE_dataset.md +171 -0
- data/docs/CFe-SAT/emulador_off_line_v2_9_4.zip +0 -0
- data/docs/NFe-NFCe/ANEXO I - Leiaute e Regra de Valida/303/247/303/243o - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/ANEXO II -Manual Especifica/303/247/303/265esT/303/251cnicas - Danfe-C/303/263digo-Barras.pdf +0 -0
- data/docs/NFe-NFCe/Anexo III - Manual de Conting/303/252ncia - NF-e.pdf +0 -0
- data/docs/NFe-NFCe/Anexo IV - Manual de Conting/303/252ncia - NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/Manual de Orienta/303/247/303/243o ao Contribuinte - MOC - vers/303/243o 7.0 - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/_dataset.md +819 -0
- data/lib/sefaz/base.rb +11 -0
- data/lib/sefaz/configuration.rb +58 -0
- data/lib/sefaz/exception.rb +14 -0
- data/lib/sefaz/refinement.rb +91 -0
- data/lib/sefaz/utils/connection.rb +35 -0
- data/lib/sefaz/utils/prawn_helper.rb +47 -0
- data/lib/sefaz/utils/signer.rb +122 -0
- data/lib/sefaz/version.rb +1 -1
- data/lib/sefaz/webservice/base.rb +13 -0
- data/lib/sefaz/webservice/nfe/auditor.rb +35 -0
- data/lib/sefaz/webservice/nfe/client.rb +426 -0
- data/lib/sefaz/webservice/nfe/connection.rb +17 -0
- data/lib/sefaz/webservice/nfe/dataset.rb +451 -0
- data/lib/sefaz/webservice/nfe/validator.rb +63 -0
- data/lib/sefaz/webservice/nfe/wsdl.rb +117 -0
- data/lib/sefaz/webservice/sat/client.rb +44 -0
- data/lib/sefaz/webservice/sat/dataset/cancel.rb +34 -0
- data/lib/sefaz/webservice/sat/dataset/sale.rb +176 -0
- data/lib/sefaz/webservice/sat/templates/base.rb +54 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_55mm.rb +307 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_80mm.rb +307 -0
- data/lib/sefaz.rb +45 -3
- data/sefaz.gemspec +5 -3
- metadata +80 -6
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
# Principal classe de integração com o módulo CFe-SAT
|
7
|
+
class Client < SEFAZ::Webservice::Base
|
8
|
+
|
9
|
+
SERVICES = %i[ exportarCF ]
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
end
|
13
|
+
|
14
|
+
# Exportar CFe (Cupom Fiscal Eletrônico) no formato PDF
|
15
|
+
# Para gerar o CFe de Cancelamento, é obrigatório informar o @canc (CFe de Cancelmaneto) junto com o @aut (CFe de Movimento)
|
16
|
+
#
|
17
|
+
# Exemplo de CFe de Movimento:
|
18
|
+
# @pdf = @client.exportarCF(80, aut: @aut, infAdic: "Participe da Promoção de Natal.")
|
19
|
+
#
|
20
|
+
# Exemplo de CFe de Cancelmaneto:
|
21
|
+
# @pdf = @client.exportarCF(80, aut: @aut, canc: @canc, infAdic: "Participe da Promoção de Natal.")
|
22
|
+
#
|
23
|
+
# @mm(Integer) = Milímetro da bobina termica para gerar o documento PDF (opções=55,80)
|
24
|
+
# @aut(Hash ou String) = XML ou Hash - Autorizado
|
25
|
+
# @canc(Hash ou String) = XML ou Hash - Cancelamento (OPCIONAL)
|
26
|
+
# @summary(Boolean) = Exibir Extrato de Movimento no formato resumido (OPCIONAL)
|
27
|
+
# @infAdic(String) = Informações adicionais de rodapé do CFe (OPCIONAL)
|
28
|
+
def exportarCF(mm, aut:, canc: nil, summary: false, infAdic: nil)
|
29
|
+
aut = (aut.is_a?(Hash) ? aut : aut.to_hash!)
|
30
|
+
canc = (canc.is_a?(Hash) ? canc : canc.to_hash!) if canc
|
31
|
+
case mm
|
32
|
+
when 55
|
33
|
+
SEFAZ::Webservice::SAT::Templates::CupomFiscal55mm.new.render(aut, canc, summary, infAdic)
|
34
|
+
when 80
|
35
|
+
SEFAZ::Webservice::SAT::Templates::CupomFiscal80mm.new.render(aut, canc, summary, infAdic)
|
36
|
+
else
|
37
|
+
SEFAZ::Webservice::SAT::Templates::Base.new.render(aut, canc, summary, infAdic)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Dataset
|
7
|
+
# Principal classe de elaboração do XML de Cancelamento para o módulo CFe-SAT
|
8
|
+
class Cancel
|
9
|
+
|
10
|
+
attr_accessor :ide
|
11
|
+
|
12
|
+
IDE = Struct.new("IDE", :CNPJ, :signAC, :numeroCaixa)
|
13
|
+
|
14
|
+
def initialize(chCanc)
|
15
|
+
@chCanc = chCanc
|
16
|
+
@ide = IDE.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def gerarCF
|
20
|
+
hash = { CFeCanc: { infCFe: { :@chCanc => ("CFe" + @chCanc.to_s) } } }
|
21
|
+
hash[:CFeCanc][:infCFe][:ide] = @ide.to_h
|
22
|
+
hash[:CFeCanc][:infCFe][:emit] = {}
|
23
|
+
hash[:CFeCanc][:infCFe][:dest] = {}
|
24
|
+
hash[:CFeCanc][:infCFe][:total] = {}
|
25
|
+
hash[:CFeCanc][:infCFe][:infAdic] = {}
|
26
|
+
|
27
|
+
return [hash.to_xml!, hash]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Dataset
|
7
|
+
# Principal classe de elaboração do XML de Venda para o módulo CFe-SAT
|
8
|
+
class Sale
|
9
|
+
|
10
|
+
attr_accessor :ide, :emit, :dest, :entrega, :det, :obsfiscodet, :total, :pgto, :mp, :infAdic
|
11
|
+
|
12
|
+
IDE = Struct.new("IDE", :CNPJ, :signAC, :numeroCaixa)
|
13
|
+
EMIT = Struct.new("EMIT", :CNPJ, :IE, :IM, :cRegTribISSQN, :indRatISSQN)
|
14
|
+
DEST = Struct.new("DEST", :CNPJ, :CPF, :xNome)
|
15
|
+
ENTREGA = Struct.new("ENTREGA", :xLgr, :nro, :xCpl, :xBairro, :xMun, :UF)
|
16
|
+
DET = Struct.new("DET", :@nItem, :prod, :imposto, :infAdProd)
|
17
|
+
PROD = Struct.new("PROD", :cProd, :cEAN, :xProd, :NCM, :CEST, :CFOP, :uCom, :qCom, :vUnCom, :indRegra, :vDesc, :vOutro, :obsFiscoDet, :cANP)
|
18
|
+
OBSFISCODET = Struct.new("OBSFISCODET", :xCampoDet, :xTextoDet)
|
19
|
+
IMPOSTO = Struct.new("IMPOSTO", :vItem12741, :ICMS, :PIS, :PISST, :COFINS, :COFINSST, :ISSQN)
|
20
|
+
ICMS = Struct.new("ICMS", :ICMS00, :ICMS40, :ICMSSN102, :ICMSSN900)
|
21
|
+
ICMS00 = Struct.new("ICMS00", :Orig, :CST, :pICMS)
|
22
|
+
ICMS40 = Struct.new("ICMS40", :Orig, :CST)
|
23
|
+
ICMSSN102 = Struct.new("ICMSSN102", :Orig, :CSOSN)
|
24
|
+
ICMSSN900 = Struct.new("ICMSSN900", :Orig, :CSOSN, :pICMS)
|
25
|
+
PIS = Struct.new("PIS", :PISAliq, :PISQtde, :PISNT, :PISSN, :PISOutr)
|
26
|
+
PISAliq = Struct.new("PISAliq", :CST, :vBC, :pPIS)
|
27
|
+
PISQtde = Struct.new("PISQtde", :CST, :qBCProd, :vAliqProd)
|
28
|
+
PISNT = Struct.new("PISNT", :CST)
|
29
|
+
PISSN = Struct.new("PISSN", :CST)
|
30
|
+
PISOutr = Struct.new("PISOutr", :CST, :vBC, :pPIS, :qBCProd, :vAliqProd)
|
31
|
+
PISST = Struct.new("PISST", :vBC, :pPIS, :qBCProd, :vAliqProd)
|
32
|
+
COFINS = Struct.new("COFINS", :COFINSAliq, :COFINSQtde, :COFINSNT, :COFINSSN, :COFINSOutr)
|
33
|
+
COFINSAliq = Struct.new("COFINSAliq", :CST, :vBC, :pCOFINS)
|
34
|
+
COFINSQtde = Struct.new("COFINSQtde", :CST, :qBCProd, :vAliqProd)
|
35
|
+
COFINSNT = Struct.new("COFINSNT", :CST)
|
36
|
+
COFINSSN = Struct.new("COFINSSN", :CST)
|
37
|
+
COFINSOutr = Struct.new("COFINSOutr", :CST, :vBC, :pCOFINS, :qBCProd, :vAliqProd)
|
38
|
+
COFINSST = Struct.new("COFINSST", :vBC, :pCOFINS, :qBCProd, :vAliqProd)
|
39
|
+
ISSQN = Struct.new("ISSQN", :vDeducISSQN, :vAliq, :cMunFG, :cListServ, :cServTribMun, :cNatOp, :indIncFisc)
|
40
|
+
TOTAL = Struct.new("TOTAL", :DescAcrEntr, :vCFeLei12741)
|
41
|
+
DescAcrEntr = Struct.new("DescAcrEntr", :vDescSubtot, :vAcresSubtot)
|
42
|
+
PGTO = Struct.new("PGTO", :MP)
|
43
|
+
MP = Struct.new("MP", :cMP, :vMP, :cAdmC, :cAut)
|
44
|
+
INFADIC = Struct.new("INFADIC", :infCpl)
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@versaoDadosEnt = "0.08"
|
48
|
+
|
49
|
+
@ide = IDE.new
|
50
|
+
@emit = EMIT.new
|
51
|
+
@dest = DEST.new
|
52
|
+
@entrega = ENTREGA.new
|
53
|
+
@total = TOTAL.new
|
54
|
+
@total.DescAcrEntr = DescAcrEntr.new
|
55
|
+
@pgto = PGTO.new
|
56
|
+
@pgto.MP = []
|
57
|
+
@infAdic = INFADIC.new
|
58
|
+
|
59
|
+
@listas = {}
|
60
|
+
@listas[:det] = []
|
61
|
+
end
|
62
|
+
|
63
|
+
def gerarCF
|
64
|
+
# Raiz = CFe
|
65
|
+
# A01 = infCFe
|
66
|
+
# H01 = det
|
67
|
+
# I01 = prod
|
68
|
+
# M01 = imposto
|
69
|
+
# N01 = ICMS
|
70
|
+
hash = { CFe: { infCFe: { :@versaoDadosEnt => @versaoDadosEnt } } }
|
71
|
+
hash[:CFe][:infCFe][:ide] = @ide.to_h
|
72
|
+
hash[:CFe][:infCFe][:emit] = @emit.to_h
|
73
|
+
hash[:CFe][:infCFe][:dest] = @dest.to_h
|
74
|
+
hash[:CFe][:infCFe][:entrega] = @entrega.to_h
|
75
|
+
hash[:CFe][:infCFe][:det] = @listas[:det]
|
76
|
+
hash[:CFe][:infCFe][:total] = @total.to_h
|
77
|
+
hash[:CFe][:infCFe][:total][:DescAcrEntr] = @total.DescAcrEntr.to_h
|
78
|
+
hash[:CFe][:infCFe][:pgto] = @pgto.to_h
|
79
|
+
hash[:CFe][:infCFe][:infAdic] = @infAdic.to_h
|
80
|
+
compressed = hash.compress!
|
81
|
+
|
82
|
+
return [compressed.to_xml!, compressed]
|
83
|
+
end
|
84
|
+
|
85
|
+
def add(part_name)
|
86
|
+
part_object = case part_name
|
87
|
+
when "DET"
|
88
|
+
@det = DET.new
|
89
|
+
@det.prod = PROD.new
|
90
|
+
@det.prod.obsFiscoDet = []
|
91
|
+
@det.imposto = IMPOSTO.new
|
92
|
+
@det.imposto.ICMS = ICMS.new
|
93
|
+
@det.imposto.ICMS.ICMS00 = ICMS00.new
|
94
|
+
@det.imposto.ICMS.ICMS40 = ICMS40.new
|
95
|
+
@det.imposto.ICMS.ICMSSN102 = ICMSSN102.new
|
96
|
+
@det.imposto.ICMS.ICMSSN900 = ICMSSN900.new
|
97
|
+
@det.imposto.PIS = PIS.new
|
98
|
+
@det.imposto.PIS.PISAliq = PISAliq.new
|
99
|
+
@det.imposto.PIS.PISQtde = PISQtde.new
|
100
|
+
@det.imposto.PIS.PISNT = PISNT.new
|
101
|
+
@det.imposto.PIS.PISSN = PISSN.new
|
102
|
+
@det.imposto.PIS.PISOutr = PISOutr.new
|
103
|
+
@det.imposto.PISST = PISST.new
|
104
|
+
@det.imposto.COFINS = COFINS.new
|
105
|
+
@det.imposto.COFINS.COFINSAliq = COFINSAliq.new
|
106
|
+
@det.imposto.COFINS.COFINSQtde = COFINSQtde.new
|
107
|
+
@det.imposto.COFINS.COFINSNT = COFINSNT.new
|
108
|
+
@det.imposto.COFINS.COFINSSN = COFINSSN.new
|
109
|
+
@det.imposto.COFINS.COFINSOutr = COFINSOutr.new
|
110
|
+
@det.imposto.COFINSST = COFINSST.new
|
111
|
+
@det.imposto.ISSQN = ISSQN.new
|
112
|
+
@det
|
113
|
+
when "OBSFISCODET"
|
114
|
+
@obsfiscodet = OBSFISCODET.new
|
115
|
+
@obsfiscodet
|
116
|
+
when "MP"
|
117
|
+
@mp = MP.new
|
118
|
+
@mp
|
119
|
+
end
|
120
|
+
|
121
|
+
if block_given?
|
122
|
+
yield(part_object)
|
123
|
+
save(part_name)
|
124
|
+
end
|
125
|
+
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def save(part_name)
|
130
|
+
case part_name
|
131
|
+
when "DET"
|
132
|
+
if @det.is_a?(Struct::DET)
|
133
|
+
item = @det
|
134
|
+
item.prod = item.prod.to_h
|
135
|
+
item.imposto.ICMS.ICMS00 = item.imposto.ICMS.ICMS00.to_h
|
136
|
+
item.imposto.ICMS.ICMS40 = item.imposto.ICMS.ICMS40.to_h
|
137
|
+
item.imposto.ICMS.ICMSSN102 = item.imposto.ICMS.ICMSSN102.to_h
|
138
|
+
item.imposto.ICMS.ICMSSN900 = item.imposto.ICMS.ICMSSN900.to_h
|
139
|
+
item.imposto.ICMS = item.imposto.ICMS.to_h
|
140
|
+
item.imposto.PIS.PISAliq = item.imposto.PIS.PISAliq.to_h
|
141
|
+
item.imposto.PIS.PISQtde = item.imposto.PIS.PISQtde.to_h
|
142
|
+
item.imposto.PIS.PISNT = item.imposto.PIS.PISNT.to_h
|
143
|
+
item.imposto.PIS.PISSN = item.imposto.PIS.PISSN.to_h
|
144
|
+
item.imposto.PIS.PISOutr = item.imposto.PIS.PISOutr.to_h
|
145
|
+
item.imposto.PIS = item.imposto.PIS.to_h
|
146
|
+
item.imposto.PISST = item.imposto.PISST.to_h
|
147
|
+
item.imposto.COFINS.COFINSAliq = item.imposto.COFINS.COFINSAliq.to_h
|
148
|
+
item.imposto.COFINS.COFINSQtde = item.imposto.COFINS.COFINSQtde.to_h
|
149
|
+
item.imposto.COFINS.COFINSNT = item.imposto.COFINS.COFINSNT.to_h
|
150
|
+
item.imposto.COFINS.COFINSSN = item.imposto.COFINS.COFINSSN.to_h
|
151
|
+
item.imposto.COFINS.COFINSOutr = item.imposto.COFINS.COFINSOutr.to_h
|
152
|
+
item.imposto.COFINS = item.imposto.COFINS.to_h
|
153
|
+
item.imposto.COFINSST = item.imposto.COFINSST.to_h
|
154
|
+
item.imposto.ISSQN = item.imposto.ISSQN.to_h
|
155
|
+
item.imposto = item.imposto.to_h
|
156
|
+
item[:@nItem] = (@listas[:det].length + 1)
|
157
|
+
@listas[:det].push(item.to_h)
|
158
|
+
end
|
159
|
+
when "OBSFISCODET"
|
160
|
+
if @det.is_a?(Struct::DET) && @obsfiscodet.is_a?(Struct::OBSFISCODET)
|
161
|
+
@det.prod.obsFiscoDet.push(@obsfiscodet.to_h)
|
162
|
+
end
|
163
|
+
when "MP"
|
164
|
+
if @pgto.is_a?(Struct::PGTO) && @mp.is_a?(Struct::MP)
|
165
|
+
@pgto.MP.push(@mp.to_h)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Templates
|
7
|
+
# Classe base dos templates para CFe-SAT
|
8
|
+
class Base
|
9
|
+
|
10
|
+
include SEFAZ::Utils::PrawnHelper
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@page_width = 0
|
14
|
+
@margin = 0
|
15
|
+
@page_break = 80.m
|
16
|
+
@page_layout = :portrait
|
17
|
+
end
|
18
|
+
|
19
|
+
def build(doc, aut, canc, summary, infAdic)
|
20
|
+
raise SEFAZ::NotImplemented, "Você deve implementar o método 'build' em uma subclasse."
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(aut, canc, summary, infAdic)
|
24
|
+
validation(aut, canc)
|
25
|
+
|
26
|
+
Prawn::Document.new(page_size: [@page_width, @page_break], margin: @margin, page_layout: @page_layout) do |doc|
|
27
|
+
build(doc, aut, canc, summary, infAdic)
|
28
|
+
doc.page.dictionary.data[:MediaBox] = [0, (doc.y - @margin), @page_width, @page_break]
|
29
|
+
end.render
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def validation(aut, canc)
|
35
|
+
unless aut.is_a?(Hash) && aut.dig(:CFe, :Signature) && aut.dig(:CFe, :infCFe, :@Id) &&
|
36
|
+
aut.dig(:CFe, :infCFe, :ide) && aut.dig(:CFe, :infCFe, :emit) && aut.dig(:CFe, :infCFe, :emit, :enderEmit) &&
|
37
|
+
aut.dig(:CFe, :infCFe, :det) && aut.dig(:CFe, :infCFe, :total) && aut.dig(:CFe, :infCFe, :pgto)
|
38
|
+
raise SEFAZ::ValidationError, "Layout do arquivo CFe-SAT inválido (Tipo de Arquivo: AUTORIZADO)."
|
39
|
+
end
|
40
|
+
|
41
|
+
if canc
|
42
|
+
unless canc.is_a?(Hash) && canc.dig(:CFeCanc, :Signature) && canc.dig(:CFeCanc, :infCFe, :@Id) && canc.dig(:CFeCanc, :infCFe, :@chCanc) &&
|
43
|
+
canc.dig(:CFeCanc, :infCFe, :ide) && canc.dig(:CFeCanc, :infCFe, :emit) && canc.dig(:CFeCanc, :infCFe, :emit, :enderEmit) &&
|
44
|
+
canc.dig(:CFeCanc, :infCFe, :total) && (aut.dig(:CFe, :infCFe, :@Id) == canc.dig(:CFeCanc, :infCFe, :@chCanc))
|
45
|
+
raise SEFAZ::ValidationError, "Layout do arquivo CFe-SAT inválido (Tipo de Arquivo: CANCELAMENTO)."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module SAT
|
6
|
+
module Templates
|
7
|
+
# Template para CFe-SAT 55mm em PDF
|
8
|
+
class CupomFiscal55mm < SEFAZ::Webservice::SAT::Templates::Base
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@page_width = Configuration.default.cfe_page_width_55mm
|
13
|
+
@margin = Configuration.default.cfe_margin_55mm
|
14
|
+
end
|
15
|
+
|
16
|
+
def build(doc, aut, canc, summary, infAdic)
|
17
|
+
doc.font "Courier", style: :normal
|
18
|
+
doc.font_size 5
|
19
|
+
|
20
|
+
infCFe = (canc ? canc.dig(:CFeCanc, :infCFe) : aut.dig(:CFe, :infCFe))
|
21
|
+
header(doc, aut)
|
22
|
+
body(doc, aut, summary, infCFe.dig(:@chCanc))
|
23
|
+
footer(doc, infCFe, infAdic)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# CABEÇALHO (Aut)
|
28
|
+
#
|
29
|
+
def header(doc, aut)
|
30
|
+
emit = aut.dig(:CFe, :infCFe, :emit)
|
31
|
+
enderEmit = aut.dig(:CFe, :infCFe, :emit, :enderEmit)
|
32
|
+
|
33
|
+
# Dados do emitente
|
34
|
+
doc.text emit.dig(:xFant).to_s.strip.upcase, align: :center
|
35
|
+
doc.text emit.dig(:xNome).to_s.strip.upcase, align: :center
|
36
|
+
doc.text "#{enderEmit.dig(:xLgr)} #{enderEmit.dig(:nro)} #{enderEmit.dig(:xCpl)} #{enderEmit.dig(:xBairro)} #{enderEmit.dig(:xMun)} #{enderEmit.dig(:CEP)}".to_s.strip.upcase, align: :center
|
37
|
+
doc.text "CNPJ #{emit.dig(:CNPJ).to_s.mask!("##.###.###/####-##")} IE #{emit.dig(:IE).to_s.mask!("###.###.###.###")}".to_s.strip.upcase, align: :center
|
38
|
+
doc.text "IM #{emit.dig(:IM).to_s.mask!("####.###.###.###")}".to_s.strip.upcase, align: :center if emit.dig(:IM)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# CORPO DO EXTRATO (Aut)
|
43
|
+
#
|
44
|
+
# Exibe os dados de forma dinâmica:
|
45
|
+
# Quando CFe Movimento: exibe lista dos itens a partir do XML AUTORIZADO (Resumido ou Não Resumido).
|
46
|
+
# Quando CFe Cancelamento: exibe os dados do XML AUTORIZADO em modo cancelado.
|
47
|
+
#
|
48
|
+
def body(doc, aut, summary, chCanc)
|
49
|
+
tpAmb = aut.dig(:CFe, :infCFe, :ide, :tpAmb)
|
50
|
+
nCFe = (tpAmb == 1) ? aut.dig(:CFe, :infCFe, :ide, :nCFe) : "000000"
|
51
|
+
|
52
|
+
# (I) - Título
|
53
|
+
doc.text "Extrato #{nCFe} do", style: :bold, align: :center
|
54
|
+
doc.text "CUPOM FISCAL ELETRÔNICO - SAT", style: :bold, align: :center
|
55
|
+
|
56
|
+
# (A) - Título - Cancelamento
|
57
|
+
if chCanc
|
58
|
+
doc.move_down 1.mm
|
59
|
+
doc.text "CANCELAMENTO", style: :bold, align: :center
|
60
|
+
end
|
61
|
+
|
62
|
+
# (TESTE) Marca de Teste
|
63
|
+
if tpAmb == "2"
|
64
|
+
doc.move_down doc.font_size
|
65
|
+
|
66
|
+
doc.text "= TESTE =", style: :italic, align: :center
|
67
|
+
doc.move_down doc.font_size
|
68
|
+
3.times do
|
69
|
+
doc.text_box (">" * 100), at: [0, doc.cursor], height: doc.font_size, width: doc.bounds.width
|
70
|
+
doc.move_down doc.font_size
|
71
|
+
doc.move_down doc.font_size
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Exibe o extrato no modo cancelado caso exista Chave de Cancelamento, caso contrário exibe no modo movimento
|
76
|
+
if chCanc
|
77
|
+
body_canceled_mode(doc, aut)
|
78
|
+
else
|
79
|
+
body_movement_mode(doc, aut, summary)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# CORPO DO EXTRATO > MODO MOVIMENTO (Aut)
|
85
|
+
#
|
86
|
+
# Exibe lista dos itens.
|
87
|
+
#
|
88
|
+
def body_movement_mode(doc, aut, summary)
|
89
|
+
# (II) - Legenda
|
90
|
+
doc.text "#|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*|VL ITEM R$"
|
91
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
92
|
+
|
93
|
+
# (III) - Registros de item
|
94
|
+
unless summary
|
95
|
+
det = aut.dig(:CFe, :infCFe, :det)
|
96
|
+
det = det.is_a?(Array) ? det : [det]
|
97
|
+
det.each do |i|
|
98
|
+
# DADOS: #|COD|DESC|QTD|UN|VL UN R$|(VL TR R$)*
|
99
|
+
doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 10.mm)) do
|
100
|
+
doc.text "#{i.dig(:@nItem)} #{i.dig(:prod, :cProd)} #{i.dig(:prod, :xProd)} #{i.dig(:prod, :qCom)} #{i.dig(:prod, :uCom)} X #{i.dig(:prod, :vUnCom).to_f.to_currency} (#{i.dig(:imposto, :vItem12741).to_f.to_currency})"
|
101
|
+
end
|
102
|
+
|
103
|
+
# DADOS: VL ITEM R$
|
104
|
+
doc.move_down -doc.font_size
|
105
|
+
doc.text i.dig(:prod, :vProd).to_f.to_currency, align: :right
|
106
|
+
|
107
|
+
# DADOS: DESCONTO
|
108
|
+
if i.dig(:prod, :vDesc).to_f > 0
|
109
|
+
doc.float { doc.text "-#{i.dig(:prod, :vDesc).to_f.to_currency}", align: :right }
|
110
|
+
doc.text "desconto sobre item"
|
111
|
+
end
|
112
|
+
|
113
|
+
# DADOS: ACRÉSCIMO
|
114
|
+
if i.dig(:prod, :vOutro).to_f > 0
|
115
|
+
doc.float { doc.text "+#{i.dig(:prod, :vOutro).to_f.to_currency}", align: :right }
|
116
|
+
doc.text "acréscimo sobre item"
|
117
|
+
end
|
118
|
+
|
119
|
+
# DADOS: RATEIO DESCONTO
|
120
|
+
if i.dig(:prod, :vRatDesc).to_f > 0
|
121
|
+
doc.float { doc.text "-#{i.dig(:prod, :vRatDesc).to_f.to_currency}", align: :right }
|
122
|
+
doc.text "rateio de desconto sobre subtotal"
|
123
|
+
end
|
124
|
+
|
125
|
+
# DADOS: RATEIO ACRÉSCIMO
|
126
|
+
if i.dig(:prod, :vRatAcr).to_f > 0
|
127
|
+
doc.float { doc.text "+#{i.dig(:prod, :vRatAcr).to_f.to_currency}", align: :right }
|
128
|
+
doc.text "rateio de acréscimo sobre subtotal"
|
129
|
+
end
|
130
|
+
|
131
|
+
# DADOS: ISSQN
|
132
|
+
if i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f > 0
|
133
|
+
doc.float { doc.text "-#{i.dig(:imposto, :ISSQN, :vDeducISSQN).to_f.to_currency}", align: :right }
|
134
|
+
doc.text "dedução para ISSQN"
|
135
|
+
doc.float { doc.text i.dig(:imposto, :ISSQN, :vBC).to_f.to_currency, align: :right }
|
136
|
+
doc.text "base de cálculo ISSQN"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# (IV) - Total do Cupom
|
142
|
+
total = aut.dig(:CFe, :infCFe, :total)
|
143
|
+
|
144
|
+
# DADOS: VALOR BRUTO
|
145
|
+
if (total.dig(:DescAcrEntr, :vDescSubtot).to_f > 0) || (total.dig(:DescAcrEntr, :vAcresSubtot).to_f > 0) || (total.dig(:ICMSTot, :vDesc).to_f > 0) || (total.dig(:ICMSTot, :vOutro).to_f > 0)
|
146
|
+
doc.float { doc.text total.dig(:ICMSTot, :vProd).to_f.to_currency, align: :right }
|
147
|
+
doc.text "Total bruto de itens"
|
148
|
+
end
|
149
|
+
|
150
|
+
# DADOS: TOTAL DESC/ACRES SOBRE ITENS
|
151
|
+
if (total.dig(:ICMSTot, :vDesc).to_f > 0) || (total.dig(:ICMSTot, :vOutro).to_f > 0)
|
152
|
+
valor11 = total.dig(:ICMSTot, :vOutro).to_f - total.dig(:ICMSTot, :vDesc).to_f
|
153
|
+
doc.text "Total de descontos/acréscimos sobre item"
|
154
|
+
doc.text "#{ valor11 > 0 ? '+' : '-' }#{valor11.abs.to_currency}", align: :right
|
155
|
+
end
|
156
|
+
|
157
|
+
# DADOS: DESCONTO SOBRE TOTAL
|
158
|
+
if total.dig(:DescAcrEntr, :vDescSubtot).to_f > 0
|
159
|
+
doc.float { doc.text "-#{total.dig(:DescAcrEntr, :vDescSubtot).to_f.to_currency}", align: :right }
|
160
|
+
doc.text "Desconto sobre subtotal"
|
161
|
+
end
|
162
|
+
|
163
|
+
# DADOS: ACRÉSCIMO SOBRE TOTAL
|
164
|
+
if total.dig(:DescAcrEntr, :vAcresSubtot).to_f > 0
|
165
|
+
doc.float { doc.text "+#{total.dig(:DescAcrEntr, :vAcresSubtot).to_f.to_currency}", align: :right }
|
166
|
+
doc.text "Acréscimo sobre subtotal"
|
167
|
+
end
|
168
|
+
|
169
|
+
# DADOS: TOTAL FINAL
|
170
|
+
doc.float { doc.text total.dig(:vCFe).to_f.to_currency, style: :bold, align: :right }
|
171
|
+
doc.text "Total R$", style: :bold
|
172
|
+
|
173
|
+
# (V) - Meio de Pagamento
|
174
|
+
pgto = aut.dig(:CFe, :infCFe, :pgto)
|
175
|
+
mp = aut.dig(:CFe, :infCFe, :pgto, :MP)
|
176
|
+
mp = mp.is_a?(Array) ? mp : [mp]
|
177
|
+
mp.each do |i|
|
178
|
+
# DADOS: DESCRIÇÃO MEIO DE PAGAMENTO
|
179
|
+
doc.bounding_box([0, doc.cursor], width: (doc.bounds.width - 10.mm)) do
|
180
|
+
doc.text Configuration.default.cfe_cMP_cod_desc[i.dig(:cMP)]
|
181
|
+
end
|
182
|
+
|
183
|
+
# DADOS: VALOR PAGAMENTO
|
184
|
+
doc.move_down -doc.font_size
|
185
|
+
doc.text i.dig(:vMP).to_f.to_currency, align: :right
|
186
|
+
end
|
187
|
+
|
188
|
+
# DADOS: TROCO
|
189
|
+
doc.float { doc.text pgto.dig(:vTroco).to_f.to_currency, align: :right }
|
190
|
+
doc.text "Troco R$"
|
191
|
+
|
192
|
+
# (VI) - Observações do Fisco
|
193
|
+
obsFisco = aut.dig(:CFe, :infCFe, :obsFisco)
|
194
|
+
if obsFisco
|
195
|
+
obsFisco = obsFisco.is_a?(Array) ? obsFisco : [obsFisco]
|
196
|
+
obsFisco.each do |i|
|
197
|
+
doc.text "#{i.dig(:xCampo)}: #{i.dig(:xTexto)}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# (VII) - Dados para Entrega
|
202
|
+
entrega = aut.dig(:CFe, :infCFe, :entrega)
|
203
|
+
doc.text "ENDEREÇO DE ENTREGA: #{entrega.dig(:xLgr)} #{entrega.dig(:nro)} #{entrega.dig(:xCpl)} #{entrega.dig(:xBairro)} #{entrega.dig(:xMun)} #{entrega.dig(:UF)}".to_s.strip.upcase if entrega
|
204
|
+
|
205
|
+
# (VIII) - Observações do Contribuinte
|
206
|
+
infCFe = aut.dig(:CFe, :infCFe)
|
207
|
+
doc.text "OBSERVAÇÕES DO CONTRIBUINTE"
|
208
|
+
doc.text infCFe.dig(:infCpl)
|
209
|
+
doc.text "Valor aproximado dos tributos deste cupom R$ #{total.dig(:vCFeLei12741).to_f.to_currency}"
|
210
|
+
doc.text "(conforme Lei Fed. 12.741/2012)"
|
211
|
+
doc.move_down doc.font_size
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# CORPO DO EXTRATO > MODO CANCELADO (Aut)
|
216
|
+
#
|
217
|
+
# Exibe os dados do XML AUTORIZADO em modo cancelado.
|
218
|
+
# É renderizado o rodapé do XML AUTORIZADO no final do corpo do extrato.
|
219
|
+
#
|
220
|
+
def body_canceled_mode(doc, aut)
|
221
|
+
total = aut.dig(:CFe, :infCFe, :total)
|
222
|
+
infCFe = aut.dig(:CFe, :infCFe)
|
223
|
+
|
224
|
+
# (B) - Dados do Cupom Fiscal Cancelado
|
225
|
+
doc.move_down 1.mm
|
226
|
+
doc.text "DADOS DO CUPOM ELETRÔNICO CANCELADO", style: :bold
|
227
|
+
doc.text "TOTAL R$ #{total.dig(:vCFe).to_f.to_currency}"
|
228
|
+
|
229
|
+
# (RODAPÉ DE MOVIMENTO) Exibe o rodapé do Extrato de Movimento no corpo do Extrato de Cancelamento
|
230
|
+
footer(doc, infCFe, vl_aprox_trib: false)
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# RODAPÉ (Aut/Canc)
|
235
|
+
#
|
236
|
+
# Exibe os elementos de rodapé (QR Code, código de barras, número da chave, mensagens fixas, etc).
|
237
|
+
# O método permite exibir o rodapé no modo movimento ou cancelado.
|
238
|
+
#
|
239
|
+
def footer(doc, infCFe, infAdic = nil, vl_aprox_trib: true)
|
240
|
+
id = infCFe.dig(:@Id)
|
241
|
+
chCanc = infCFe.dig(:@chCanc)
|
242
|
+
ide = infCFe.dig(:ide)
|
243
|
+
total = infCFe.dig(:total)
|
244
|
+
|
245
|
+
# Título do cancelamento
|
246
|
+
if chCanc
|
247
|
+
doc.move_down 2
|
248
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
249
|
+
doc.text "DADOS DO CUPOM FISCAL \nELETRÔNICO DE CANCELAMENTO", align: :center, style: :bold
|
250
|
+
end
|
251
|
+
|
252
|
+
# Número da chave do Cupom Fiscal (dividos em 11 blocos com 4 números cada)
|
253
|
+
doc.move_down 2
|
254
|
+
doc.text id.to_s.delete("^0-9").scan(/.{4}/).join(" "), align: :center, style: :bold
|
255
|
+
doc.move_down doc.font_size
|
256
|
+
|
257
|
+
# Código de barra CODE 128-C (divido em duas partes)
|
258
|
+
id.to_s.delete("^0-9").scan(/.{22}/).each do |id_22|
|
259
|
+
barcode_128c(doc, value: id_22, x: 0.5.mm, y: doc.cursor, xdim: 0.8)
|
260
|
+
doc.move_down 3.mm
|
261
|
+
end
|
262
|
+
|
263
|
+
# Informação do consumidor e SAT
|
264
|
+
doc.float do
|
265
|
+
doc.move_down 5.mm
|
266
|
+
doc.bounding_box([75, doc.cursor], width: 55) do
|
267
|
+
# Informação do consumidor (CNPJ/CPF e NOME)
|
268
|
+
if !chCanc && (infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))
|
269
|
+
doc.text "Consumidor:", align: :right, style: :bold
|
270
|
+
doc.text infCFe.dig(:dest, :CNPJ).to_s.mask!("##.###.###/####-##"), align: :right if infCFe.dig(:dest, :CNPJ)
|
271
|
+
doc.text infCFe.dig(:dest, :CPF).to_s.mask!("###.###.###-##"), align: :right if infCFe.dig(:dest, :CPF)
|
272
|
+
doc.text infCFe.dig(:dest, :xNome), align: :right if infCFe.dig(:dest, :xNome)
|
273
|
+
end
|
274
|
+
|
275
|
+
# Informação do SAT (Nº Série SAT)
|
276
|
+
doc.text "No. Serie do SAT:", align: :right, style: :bold
|
277
|
+
doc.text ide.dig(:nserieSAT), align: :right
|
278
|
+
doc.text DateTime.parse("#{ide.dig(:dEmi)}T#{ide.dig(:hEmi)}").strftime("%d/%m/%Y\n%H:%M:%S"), align: :right
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# QR Code do Cupom Fiscal = Id|(dEmi)(hEmi)|vCFe|(CNPJ/CPF)|assinaturaQRCODE
|
283
|
+
doc.move_down 2.mm
|
284
|
+
codigo_qr_code = "#{id}|#{ide.dig(:dEmi)}#{ide.dig(:hEmi)}|#{total.dig(:vCFe)}|#{(infCFe.dig(:dest, :CNPJ) || infCFe.dig(:dest, :CPF))}|#{ide.dig(:assinaturaQRCODE)}"
|
285
|
+
qrcode(doc, value: codigo_qr_code, x: 0, y: doc.cursor)
|
286
|
+
doc.move_down 5.mm
|
287
|
+
|
288
|
+
# Mensagens fixas do Cupom Fiscal
|
289
|
+
if !chCanc
|
290
|
+
doc.text "Consulte o QR Code pelo aplicativo \"#{Configuration.default.cfe_qr_code_aplicativo}\",\ndisponível na AppStore (Apple) e PlayStore (Android)", align: :center, size: 4
|
291
|
+
doc.move_down doc.font_size
|
292
|
+
doc.text "*valor aproximado dos tributos do item", align: :center, size: 4 if vl_aprox_trib
|
293
|
+
end
|
294
|
+
|
295
|
+
# Informações adicionais do Cupom Fiscal (customizado pelo contribuinte)
|
296
|
+
if infAdic
|
297
|
+
doc.move_down 2
|
298
|
+
dash(doc, gap: 2, space: 0.5, line_width: 0.1, x1: 0, x2: doc.bounds.width, y: doc.cursor)
|
299
|
+
doc.text infAdic, align: :justify
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|