focus_nfe 1.0.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.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/.git-hooks/pre_push/steep.rb +18 -0
  3. data/.git-hooks/pre_push/yard_doc.rb +18 -0
  4. data/.gitattributes +1 -0
  5. data/.overcommit.yml +69 -0
  6. data/.rspec +3 -0
  7. data/.yardopts +11 -0
  8. data/CHANGELOG.md +77 -0
  9. data/CLAUDE.md +118 -0
  10. data/CODE_OF_CONDUCT.md +10 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +348 -0
  13. data/Rakefile +105 -0
  14. data/data/schemas/schema_cte.json +2793 -0
  15. data/data/schemas/schema_cte_os.json +1335 -0
  16. data/data/schemas/schema_cte_os_transporte_rodoviario.json +109 -0
  17. data/data/schemas/schema_cte_transporte_aereo.json +115 -0
  18. data/data/schemas/schema_cte_transporte_aquaviario.json +174 -0
  19. data/data/schemas/schema_cte_transporte_dutoviario.json +65 -0
  20. data/data/schemas/schema_cte_transporte_ferroviario.json +144 -0
  21. data/data/schemas/schema_cte_transporte_multimodal.json +45 -0
  22. data/data/schemas/schema_cte_transporte_rodoviario.json +78 -0
  23. data/data/schemas/schema_dce.json +549 -0
  24. data/data/schemas/schema_mdfe.json +1102 -0
  25. data/data/schemas/schema_mdfe_transporte_aereo.json +44 -0
  26. data/data/schemas/schema_mdfe_transporte_aquaviario.json +209 -0
  27. data/data/schemas/schema_mdfe_transporte_ferroviario.json +99 -0
  28. data/data/schemas/schema_mdfe_transporte_rodoviario.json +628 -0
  29. data/data/schemas/schema_nfcom.json +1859 -0
  30. data/data/schemas/schema_nfe.json +4750 -0
  31. data/data/schemas/schema_nfe_forma_pagamento.json +97 -0
  32. data/data/schemas/schema_nfe_item.json +2574 -0
  33. data/data/schemas/schema_nfgas.json +2316 -0
  34. data/data/schemas/schema_nfse_nacional.json +1847 -0
  35. data/data/schemas/schema_nfse_recebida.json +548 -0
  36. data/lib/focus_nfe/client.rb +162 -0
  37. data/lib/focus_nfe/configuration.rb +104 -0
  38. data/lib/focus_nfe/errors.rb +123 -0
  39. data/lib/focus_nfe/esquemas/campo.rb +171 -0
  40. data/lib/focus_nfe/esquemas/catalogo.rb +34 -0
  41. data/lib/focus_nfe/esquemas/decimal.rb +66 -0
  42. data/lib/focus_nfe/esquemas/esquema.rb +72 -0
  43. data/lib/focus_nfe/esquemas/validador.rb +87 -0
  44. data/lib/focus_nfe/http/adapter.rb +25 -0
  45. data/lib/focus_nfe/http/adapters/net_http.rb +99 -0
  46. data/lib/focus_nfe/http/authentication.rb +23 -0
  47. data/lib/focus_nfe/http/connection.rb +118 -0
  48. data/lib/focus_nfe/http/logging.rb +100 -0
  49. data/lib/focus_nfe/http/response.rb +75 -0
  50. data/lib/focus_nfe/modelos/documento.rb +113 -0
  51. data/lib/focus_nfe/modelos/inutilizacao.rb +75 -0
  52. data/lib/focus_nfe/modelos/pagina.rb +54 -0
  53. data/lib/focus_nfe/recursos/backups.rb +17 -0
  54. data/lib/focus_nfe/recursos/base.rb +91 -0
  55. data/lib/focus_nfe/recursos/ceps.rb +16 -0
  56. data/lib/focus_nfe/recursos/cfops.rb +16 -0
  57. data/lib/focus_nfe/recursos/cnaes.rb +16 -0
  58. data/lib/focus_nfe/recursos/cnpjs.rb +13 -0
  59. data/lib/focus_nfe/recursos/concerns/baixavel.rb +41 -0
  60. data/lib/focus_nfe/recursos/concerns/baixavel_eventos.rb +34 -0
  61. data/lib/focus_nfe/recursos/concerns/cancelavel.rb +25 -0
  62. data/lib/focus_nfe/recursos/concerns/conciliavel.rb +66 -0
  63. data/lib/focus_nfe/recursos/concerns/consultavel.rb +26 -0
  64. data/lib/focus_nfe/recursos/concerns/corrigivel.rb +45 -0
  65. data/lib/focus_nfe/recursos/concerns/corrigivel_cte.rb +60 -0
  66. data/lib/focus_nfe/recursos/concerns/emitivel.rb +51 -0
  67. data/lib/focus_nfe/recursos/concerns/enviavel.rb +40 -0
  68. data/lib/focus_nfe/recursos/concerns/eventavel.rb +46 -0
  69. data/lib/focus_nfe/recursos/concerns/inutilizavel.rb +96 -0
  70. data/lib/focus_nfe/recursos/concerns/listavel.rb +22 -0
  71. data/lib/focus_nfe/recursos/concerns/localizavel.rb +22 -0
  72. data/lib/focus_nfe/recursos/concerns/notificavel.rb +23 -0
  73. data/lib/focus_nfe/recursos/concerns/removivel.rb +20 -0
  74. data/lib/focus_nfe/recursos/concerns/visualizavel.rb +28 -0
  75. data/lib/focus_nfe/recursos/cte.rb +35 -0
  76. data/lib/focus_nfe/recursos/cte_os.rb +29 -0
  77. data/lib/focus_nfe/recursos/ctes_recebidas.rb +38 -0
  78. data/lib/focus_nfe/recursos/dce.rb +16 -0
  79. data/lib/focus_nfe/recursos/emails_bloqueados.rb +31 -0
  80. data/lib/focus_nfe/recursos/empresas.rb +35 -0
  81. data/lib/focus_nfe/recursos/mdfe.rb +78 -0
  82. data/lib/focus_nfe/recursos/municipios.rb +60 -0
  83. data/lib/focus_nfe/recursos/ncms.rb +16 -0
  84. data/lib/focus_nfe/recursos/nfce.rb +19 -0
  85. data/lib/focus_nfe/recursos/nfcom.rb +16 -0
  86. data/lib/focus_nfe/recursos/nfe.rb +106 -0
  87. data/lib/focus_nfe/recursos/nfes_recebidas.rb +56 -0
  88. data/lib/focus_nfe/recursos/nfgas.rb +16 -0
  89. data/lib/focus_nfe/recursos/nfse.rb +18 -0
  90. data/lib/focus_nfe/recursos/nfse_nacional.rb +16 -0
  91. data/lib/focus_nfe/recursos/nfses_nacionais_recebidas.rb +21 -0
  92. data/lib/focus_nfe/recursos/webhooks.rb +22 -0
  93. data/lib/focus_nfe/version.rb +6 -0
  94. data/lib/focus_nfe/webhook.rb +68 -0
  95. data/lib/focus_nfe.rb +124 -0
  96. data/sig/focus_nfe/client.rbs +38 -0
  97. data/sig/focus_nfe/configuration.rbs +29 -0
  98. data/sig/focus_nfe/errors.rbs +59 -0
  99. data/sig/focus_nfe/esquemas/campo.rbs +47 -0
  100. data/sig/focus_nfe/esquemas/catalogo.rbs +8 -0
  101. data/sig/focus_nfe/esquemas/decimal.rbs +25 -0
  102. data/sig/focus_nfe/esquemas/esquema.rbs +30 -0
  103. data/sig/focus_nfe/esquemas/validador.rbs +20 -0
  104. data/sig/focus_nfe/http/adapter.rbs +7 -0
  105. data/sig/focus_nfe/http/adapters/net_http.rbs +25 -0
  106. data/sig/focus_nfe/http/authentication.rbs +9 -0
  107. data/sig/focus_nfe/http/connection.rbs +32 -0
  108. data/sig/focus_nfe/http/logging.rbs +30 -0
  109. data/sig/focus_nfe/http/response.rbs +28 -0
  110. data/sig/focus_nfe/modelos/documento.rbs +34 -0
  111. data/sig/focus_nfe/modelos/inutilizacao.rbs +24 -0
  112. data/sig/focus_nfe/modelos/pagina.rbs +21 -0
  113. data/sig/focus_nfe/recursos/backups.rbs +7 -0
  114. data/sig/focus_nfe/recursos/base.rbs +25 -0
  115. data/sig/focus_nfe/recursos/ceps.rbs +10 -0
  116. data/sig/focus_nfe/recursos/cfops.rbs +10 -0
  117. data/sig/focus_nfe/recursos/cnaes.rbs +10 -0
  118. data/sig/focus_nfe/recursos/cnpjs.rbs +7 -0
  119. data/sig/focus_nfe/recursos/concerns/baixavel.rbs +12 -0
  120. data/sig/focus_nfe/recursos/concerns/baixavel_eventos.rbs +14 -0
  121. data/sig/focus_nfe/recursos/concerns/cancelavel.rbs +9 -0
  122. data/sig/focus_nfe/recursos/concerns/conciliavel.rbs +15 -0
  123. data/sig/focus_nfe/recursos/concerns/consultavel.rbs +9 -0
  124. data/sig/focus_nfe/recursos/concerns/corrigivel.rbs +15 -0
  125. data/sig/focus_nfe/recursos/concerns/corrigivel_cte.rbs +17 -0
  126. data/sig/focus_nfe/recursos/concerns/emitivel.rbs +14 -0
  127. data/sig/focus_nfe/recursos/concerns/enviavel.rbs +15 -0
  128. data/sig/focus_nfe/recursos/concerns/eventavel.rbs +12 -0
  129. data/sig/focus_nfe/recursos/concerns/inutilizavel.rbs +20 -0
  130. data/sig/focus_nfe/recursos/concerns/listavel.rbs +9 -0
  131. data/sig/focus_nfe/recursos/concerns/localizavel.rbs +9 -0
  132. data/sig/focus_nfe/recursos/concerns/notificavel.rbs +9 -0
  133. data/sig/focus_nfe/recursos/concerns/removivel.rbs +9 -0
  134. data/sig/focus_nfe/recursos/concerns/visualizavel.rbs +9 -0
  135. data/sig/focus_nfe/recursos/cte.rbs +17 -0
  136. data/sig/focus_nfe/recursos/cte_os.rbs +17 -0
  137. data/sig/focus_nfe/recursos/ctes_recebidas.rbs +14 -0
  138. data/sig/focus_nfe/recursos/dce.rbs +10 -0
  139. data/sig/focus_nfe/recursos/emails_bloqueados.rbs +12 -0
  140. data/sig/focus_nfe/recursos/empresas.rbs +12 -0
  141. data/sig/focus_nfe/recursos/mdfe.rbs +21 -0
  142. data/sig/focus_nfe/recursos/municipios.rbs +19 -0
  143. data/sig/focus_nfe/recursos/ncms.rbs +10 -0
  144. data/sig/focus_nfe/recursos/nfce.rbs +12 -0
  145. data/sig/focus_nfe/recursos/nfcom.rbs +10 -0
  146. data/sig/focus_nfe/recursos/nfe.rbs +23 -0
  147. data/sig/focus_nfe/recursos/nfes_recebidas.rbs +16 -0
  148. data/sig/focus_nfe/recursos/nfgas.rbs +10 -0
  149. data/sig/focus_nfe/recursos/nfse.rbs +11 -0
  150. data/sig/focus_nfe/recursos/nfse_nacional.rbs +10 -0
  151. data/sig/focus_nfe/recursos/nfses_nacionais_recebidas.rbs +11 -0
  152. data/sig/focus_nfe/recursos/webhooks.rbs +11 -0
  153. data/sig/focus_nfe/webhook.rbs +11 -0
  154. data/sig/focus_nfe.rbs +10 -0
  155. data/spec/focus_nfe/client_spec.rb +208 -0
  156. data/spec/focus_nfe/configuration_spec.rb +121 -0
  157. data/spec/focus_nfe/errors_mapping_spec.rb +68 -0
  158. data/spec/focus_nfe/errors_spec.rb +107 -0
  159. data/spec/focus_nfe/esquemas/campo_spec.rb +291 -0
  160. data/spec/focus_nfe/esquemas/decimal_spec.rb +54 -0
  161. data/spec/focus_nfe/esquemas/esquema_spec.rb +73 -0
  162. data/spec/focus_nfe/esquemas/validador_spec.rb +167 -0
  163. data/spec/focus_nfe/esquemas_spec.rb +42 -0
  164. data/spec/focus_nfe/http/adapter_spec.rb +8 -0
  165. data/spec/focus_nfe/http/adapters/net_http_spec.rb +181 -0
  166. data/spec/focus_nfe/http/authentication_spec.rb +24 -0
  167. data/spec/focus_nfe/http/connection_spec.rb +255 -0
  168. data/spec/focus_nfe/http/logging_spec.rb +83 -0
  169. data/spec/focus_nfe/http/response_spec.rb +161 -0
  170. data/spec/focus_nfe/modelos/documento_spec.rb +150 -0
  171. data/spec/focus_nfe/modelos/inutilizacao_spec.rb +91 -0
  172. data/spec/focus_nfe/modelos/pagina_spec.rb +77 -0
  173. data/spec/focus_nfe/recursos/backups_spec.rb +29 -0
  174. data/spec/focus_nfe/recursos/base_spec.rb +56 -0
  175. data/spec/focus_nfe/recursos/ceps_spec.rb +16 -0
  176. data/spec/focus_nfe/recursos/cfops_spec.rb +16 -0
  177. data/spec/focus_nfe/recursos/cnaes_spec.rb +20 -0
  178. data/spec/focus_nfe/recursos/cnpjs_spec.rb +11 -0
  179. data/spec/focus_nfe/recursos/concerns/emitivel_validacao_spec.rb +158 -0
  180. data/spec/focus_nfe/recursos/cte_os_spec.rb +9 -0
  181. data/spec/focus_nfe/recursos/cte_spec.rb +9 -0
  182. data/spec/focus_nfe/recursos/ctes_recebidas_spec.rb +56 -0
  183. data/spec/focus_nfe/recursos/dce_spec.rb +8 -0
  184. data/spec/focus_nfe/recursos/emails_bloqueados_spec.rb +29 -0
  185. data/spec/focus_nfe/recursos/empresas_spec.rb +45 -0
  186. data/spec/focus_nfe/recursos/mdfe_spec.rb +100 -0
  187. data/spec/focus_nfe/recursos/municipios_spec.rb +58 -0
  188. data/spec/focus_nfe/recursos/ncms_spec.rb +16 -0
  189. data/spec/focus_nfe/recursos/nfce_spec.rb +10 -0
  190. data/spec/focus_nfe/recursos/nfcom_spec.rb +8 -0
  191. data/spec/focus_nfe/recursos/nfe_spec.rb +262 -0
  192. data/spec/focus_nfe/recursos/nfes_recebidas_spec.rb +87 -0
  193. data/spec/focus_nfe/recursos/nfgas_spec.rb +8 -0
  194. data/spec/focus_nfe/recursos/nfse_nacional_spec.rb +8 -0
  195. data/spec/focus_nfe/recursos/nfse_spec.rb +9 -0
  196. data/spec/focus_nfe/recursos/nfses_nacionais_recebidas_spec.rb +17 -0
  197. data/spec/focus_nfe/recursos/webhooks_spec.rb +22 -0
  198. data/spec/focus_nfe/webhook_spec.rb +66 -0
  199. data/spec/focus_nfe_global_configuration_spec.rb +70 -0
  200. data/spec/focus_nfe_require_spec.rb +87 -0
  201. data/spec/focus_nfe_spec.rb +11 -0
  202. data/spec/spec_helper.rb +58 -0
  203. data/spec/support/shared_examples/recurso_fiscal.rb +445 -0
  204. data/spec/support/shared_examples/recurso_leitura.rb +217 -0
  205. data/tools/pull_fields.rb +62 -0
  206. metadata +420 -0
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Mdfe do
4
+ let(:ref) { "manifesto-7" }
5
+
6
+ it_behaves_like "um recurso emitível", "mdfe"
7
+ it_behaves_like "um recurso consultável", "mdfe"
8
+ it_behaves_like "um recurso cancelável", "mdfe"
9
+ it_behaves_like "um recurso notificável", "mdfe"
10
+
11
+ include_context "com recurso conectado"
12
+
13
+ describe "#encerrar" do
14
+ it "faz POST em /v2/mdfe/{ref}/encerrar com data, UF e município", :aggregate_failures do
15
+ stub = stub_envio(:post, "mdfe/#{ref}/encerrar",
16
+ body: { data: "2026-06-13", sigla_uf: "SP", nome_municipio: "São Paulo" })
17
+
18
+ doc = recurso.encerrar(ref, data: "2026-06-13", sigla_uf: "SP", nome_municipio: "São Paulo")
19
+
20
+ expect(stub).to have_been_requested
21
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
22
+ end
23
+
24
+ it "rejeita ref inválida sem requisição" do
25
+ expect { recurso.encerrar("manifesto 7", data: "2026-06-13", sigla_uf: "SP", nome_municipio: "São Paulo") }
26
+ .to raise_error(ArgumentError)
27
+ end
28
+
29
+ context "quando o ambiente é produção" do
30
+ let(:client) { FocusNfe::Client.new(token_empresa: "tok", environment: :producao) }
31
+
32
+ it "usa o host de produção" do
33
+ stub = stub_request(:post, "https://api.focusnfe.com.br/v2/mdfe/#{ref}/encerrar")
34
+ .to_return(status: 200, body: "{}", headers: json)
35
+
36
+ recurso.encerrar(ref, data: "2026-06-13", sigla_uf: "SP", nome_municipio: "São Paulo")
37
+
38
+ expect(stub).to have_been_requested
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#incluir_condutor" do
44
+ it "faz POST em /v2/mdfe/{ref}/inclusao_condutor com nome e CPF", :aggregate_failures do
45
+ stub = stub_envio(:post, "mdfe/#{ref}/inclusao_condutor", body: { nome: "João", cpf: "12345678912" })
46
+
47
+ doc = recurso.incluir_condutor(ref, nome: "João", cpf: "12345678912")
48
+
49
+ expect(stub).to have_been_requested
50
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
51
+ end
52
+ end
53
+
54
+ describe "#incluir_dfe" do
55
+ let(:documentos) do
56
+ [{ "chave_nfe" => "3" * 44, "codigo_municipio_descarregamento" => "3550308" }]
57
+ end
58
+
59
+ it "faz POST em /v2/mdfe/{ref}/inclusao_dfe com protocolo, município e documentos", :aggregate_failures do
60
+ stub = stub_envio(:post, "mdfe/#{ref}/inclusao_dfe",
61
+ body: { protocolo: "141250000012345",
62
+ codigo_municipio_carregamento: "3550308",
63
+ documentos: documentos })
64
+
65
+ doc = recurso.incluir_dfe(ref, protocolo: "141250000012345",
66
+ codigo_municipio_carregamento: "3550308",
67
+ documentos: documentos)
68
+
69
+ expect(stub).to have_been_requested
70
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
71
+ end
72
+
73
+ it "inclui nome_municipio_carregamento no corpo quando informado" do
74
+ stub = stub_envio(:post, "mdfe/#{ref}/inclusao_dfe",
75
+ body: { protocolo: "141250000012345",
76
+ codigo_municipio_carregamento: "3550308",
77
+ documentos: documentos,
78
+ nome_municipio_carregamento: "São Paulo" })
79
+
80
+ recurso.incluir_dfe(ref, protocolo: "141250000012345",
81
+ codigo_municipio_carregamento: "3550308",
82
+ documentos: documentos,
83
+ nome_municipio_carregamento: "São Paulo")
84
+
85
+ expect(stub).to have_been_requested
86
+ end
87
+
88
+ it "omite nome_municipio_carregamento do corpo quando não informado" do
89
+ stub_envio(:post, "mdfe/#{ref}/inclusao_dfe")
90
+
91
+ recurso.incluir_dfe(ref, protocolo: "141250000012345",
92
+ codigo_municipio_carregamento: "3550308",
93
+ documentos: documentos)
94
+
95
+ requisicao = a_request(:post, "#{homologacao}/v2/mdfe/#{ref}/inclusao_dfe")
96
+ .with { |req| !req.body.include?("nome_municipio_carregamento") }
97
+ expect(requisicao).to have_been_made
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Municipios do
4
+ include_context "com recurso conectado"
5
+
6
+ let(:codigo) { "1302603" }
7
+
8
+ it_behaves_like "um recurso listável", "municipios"
9
+ it_behaves_like "um recurso localizável", "municipios"
10
+
11
+ describe "#listar_codigos_tributarios" do
12
+ it "faz GET no sub-recurso de códigos tributários e devolve uma Pagina" do
13
+ stub_get("municipios/#{codigo}/codigos_tributarios_municipio", body: "[]")
14
+
15
+ expect(recurso.listar_codigos_tributarios(codigo)).to be_a(FocusNfe::Modelos::Pagina)
16
+ end
17
+
18
+ it "repassa os filtros como query string" do
19
+ stub = stub_get("municipios/#{codigo}/codigos_tributarios_municipio", query: { offset: 50 })
20
+
21
+ recurso.listar_codigos_tributarios(codigo, offset: 50)
22
+
23
+ expect(stub).to have_been_requested
24
+ end
25
+ end
26
+
27
+ describe "#listar_itens_lista_servico" do
28
+ it "faz GET no sub-recurso de itens da lista de serviço e devolve uma Pagina" do
29
+ stub_get("municipios/#{codigo}/itens_lista_servico", body: "[]")
30
+
31
+ expect(recurso.listar_itens_lista_servico(codigo)).to be_a(FocusNfe::Modelos::Pagina)
32
+ end
33
+ end
34
+
35
+ describe "#consultar_codigo_tributario" do
36
+ it "faz GET no código tributário por código e devolve o corpo cru" do
37
+ stub_get("municipios/#{codigo}/codigos_tributarios_municipio/1.06", body: '{"codigo":"1.06"}')
38
+
39
+ expect(recurso.consultar_codigo_tributario(codigo, "1.06")).to eq("codigo" => "1.06")
40
+ end
41
+ end
42
+
43
+ describe "#consultar_item_lista_servico" do
44
+ it "faz GET no item da lista de serviço por código e devolve o corpo cru" do
45
+ stub_get("municipios/#{codigo}/itens_lista_servico/1.06", body: '{"codigo":"1.06"}')
46
+
47
+ expect(recurso.consultar_item_lista_servico(codigo, "1.06")).to eq("codigo" => "1.06")
48
+ end
49
+ end
50
+
51
+ describe "#consultar_json" do
52
+ it "faz GET no JSON de exemplo do município e devolve o corpo cru" do
53
+ stub_get("municipios/#{codigo}/json", body: '{"prestador":{}}')
54
+
55
+ expect(recurso.consultar_json(codigo)).to eq("prestador" => {})
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Ncms do
4
+ it_behaves_like "um recurso listável", "ncms"
5
+ it_behaves_like "um recurso localizável", "ncms"
6
+
7
+ include_context "com recurso conectado"
8
+
9
+ describe "#buscar" do
10
+ it "é um apelido de #listar" do
11
+ stub_get("ncms", query: { capitulo: "85" }, body: "[]")
12
+
13
+ expect(recurso.buscar(capitulo: "85")).to be_a(FocusNfe::Modelos::Pagina)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Nfce do
4
+ it_behaves_like "um recurso emitível", "nfce"
5
+ it_behaves_like "um recurso consultável", "nfce"
6
+ it_behaves_like "um recurso cancelável", "nfce"
7
+ it_behaves_like "um recurso inutilizável", "nfce"
8
+ it_behaves_like "um recurso enviável por email", "nfce"
9
+ it_behaves_like "um recurso conciliável", "nfce"
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Nfcom do
4
+ it_behaves_like "um recurso emitível", "nfcom"
5
+ it_behaves_like "um recurso consultável", "nfcom"
6
+ it_behaves_like "um recurso cancelável", "nfcom"
7
+ it_behaves_like "um recurso notificável", "nfcom"
8
+ end
@@ -0,0 +1,262 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Nfe do
4
+ subject(:nfe) { described_class.new(client.connection) }
5
+
6
+ let(:client) { FocusNfe::Client.new(token_empresa: "tok", environment: environment) }
7
+ let(:environment) { :homologacao }
8
+ let(:json) { { "Content-Type" => "application/json" } }
9
+ let(:dados) { { "natureza_operacao" => "Venda" } }
10
+ let(:processando) { '{"status":"processando_autorizacao"}' }
11
+
12
+ def homologacao = "https://homologacao.focusnfe.com.br"
13
+ def producao = "https://api.focusnfe.com.br"
14
+
15
+ def stub_nfe(verb, path, host: homologacao, status: 200, body: "{}")
16
+ stub_request(verb, "#{host}/v2/#{path}").to_return(status: status, body: body, headers: json)
17
+ end
18
+
19
+ it_behaves_like "um recurso corrigível", "nfe"
20
+ it_behaves_like "um recurso inutilizável", "nfe"
21
+ it_behaves_like "um recurso visualizável", "nfe/danfe"
22
+ it_behaves_like "um recurso notificável", "nfe"
23
+ it_behaves_like "um recurso enviável por email", "nfe"
24
+ it_behaves_like "um recurso conciliável", "nfe"
25
+
26
+ describe "#emitir" do
27
+ before { stub_nfe(:post, "nfe?ref=pedido-42", status: 202, body: processando) }
28
+
29
+ it "envia POST em /v2/nfe?ref= com o JSON dos dados" do
30
+ nfe.emitir(ref: "pedido-42", dados: dados)
31
+
32
+ expect(a_request(:post, "#{homologacao}/v2/nfe?ref=pedido-42").with(body: JSON.generate(dados))).to have_been_made
33
+ end
34
+
35
+ it "devolve um Documento processando com a ref", :aggregate_failures do
36
+ doc = nfe.emitir(ref: "pedido-42", dados: dados)
37
+
38
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
39
+ expect(doc).to be_processando
40
+ expect(doc.ref).to eq("pedido-42")
41
+ end
42
+
43
+ context "quando o ambiente é produção" do
44
+ let(:environment) { :producao }
45
+
46
+ it "usa o host de produção" do
47
+ stub = stub_nfe(:post, "nfe?ref=pedido-42", host: producao, status: 202, body: processando)
48
+
49
+ nfe.emitir(ref: "pedido-42", dados: dados)
50
+
51
+ expect(stub).to have_been_requested
52
+ end
53
+ end
54
+
55
+ it "propaga erro tipado da API (422)" do
56
+ stub_nfe(:post, "nfe?ref=pedido-42", status: 422, body: '{"erros":[]}')
57
+
58
+ expect { nfe.emitir(ref: "pedido-42", dados: dados) }.to raise_error(FocusNfe::Errors::ValidationError)
59
+ end
60
+
61
+ it "rejeita ref inválida sem requisição", :aggregate_failures do
62
+ expect { nfe.emitir(ref: "pedido 42", dados: dados) }.to raise_error(ArgumentError)
63
+ expect(a_request(:post, "#{homologacao}/v2/nfe")).not_to have_been_made
64
+ end
65
+ end
66
+
67
+ describe "#consultar" do
68
+ it "faz GET em /v2/nfe/{ref} e devolve o Documento autorizado", :aggregate_failures do
69
+ stub_nfe(:get, "nfe/pedido-42", body: '{"status":"autorizado","chave_nfe":"3520"}')
70
+ doc = nfe.consultar("pedido-42")
71
+
72
+ expect(doc).to be_autorizado
73
+ expect(doc.chave_nfe).to eq("3520")
74
+ end
75
+
76
+ it "envia ?completa=1 quando completa: true" do
77
+ stub = stub_nfe(:get, "nfe/pedido-42?completa=1", body: '{"status":"autorizado"}')
78
+
79
+ nfe.consultar("pedido-42", completa: true)
80
+
81
+ expect(stub).to have_been_requested
82
+ end
83
+
84
+ it "não envia completa por padrão" do
85
+ stub = stub_nfe(:get, "nfe/pedido-42", body: '{"status":"autorizado"}')
86
+
87
+ nfe.consultar("pedido-42")
88
+
89
+ expect(stub).to have_been_requested
90
+ end
91
+
92
+ it "rejeita ref inválida sem requisição" do
93
+ expect { nfe.consultar("pedido 42") }.to raise_error(ArgumentError)
94
+ end
95
+ end
96
+
97
+ describe "#cancelar" do
98
+ it "faz DELETE em /v2/nfe/{ref} com a justificativa no corpo", :aggregate_failures do
99
+ stub_nfe(:delete, "nfe/pedido-42", body: '{"status":"cancelado"}')
100
+ doc = nfe.cancelar("pedido-42", justificativa: "erro")
101
+ enviado = a_request(:delete, "#{homologacao}/v2/nfe/pedido-42").with(body: '{"justificativa":"erro"}')
102
+
103
+ expect(doc).to be_cancelado
104
+ expect(enviado).to have_been_made
105
+ end
106
+
107
+ it "rejeita ref inválida sem requisição" do
108
+ expect { nfe.cancelar("pedido 42", justificativa: "x") }.to raise_error(ArgumentError)
109
+ end
110
+ end
111
+
112
+ describe "#importar" do
113
+ let(:xml) { "<nfeProc><NFe/></nfeProc>" }
114
+
115
+ it "faz POST em /v2/nfe/importacao com o XML cru e Content-Type application/xml" do
116
+ stub_nfe(:post, "nfe/importacao", body: '{"status":"processando_autorizacao"}')
117
+ nfe.importar(xml)
118
+
119
+ enviado = a_request(:post, "#{homologacao}/v2/nfe/importacao")
120
+ .with(body: xml, headers: { "Content-Type" => "application/xml" })
121
+ expect(enviado).to have_been_made
122
+ end
123
+
124
+ it "envia ?ref= quando a referência é informada", :aggregate_failures do
125
+ stub = stub_nfe(:post, "nfe/importacao?ref=pedido-42", body: '{"status":"processando_autorizacao"}')
126
+
127
+ doc = nfe.importar(xml, ref: "pedido-42")
128
+
129
+ expect(stub).to have_been_requested
130
+ expect(doc.ref).to eq("pedido-42")
131
+ end
132
+
133
+ it "omite ref por padrão" do
134
+ stub = stub_nfe(:post, "nfe/importacao", body: "{}")
135
+
136
+ nfe.importar(xml)
137
+
138
+ expect(stub).to have_been_requested
139
+ end
140
+
141
+ context "quando o ambiente é produção" do
142
+ let(:environment) { :producao }
143
+
144
+ it "usa o host de produção" do
145
+ stub = stub_nfe(:post, "nfe/importacao", host: producao, body: "{}")
146
+
147
+ nfe.importar(xml)
148
+
149
+ expect(stub).to have_been_requested
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#emitir_evento" do
155
+ let(:corpo) do
156
+ { tipo_evento: "prorrogacao_suspensao_icms",
157
+ itens_prorrogacao_suspensao_icms: [{ numero_item: "1", quantidade_item: 1 }] }
158
+ end
159
+
160
+ it "faz POST em /v2/nfe/{ref}/evento com tipo_evento e itens", :aggregate_failures do
161
+ stub_nfe(:post, "nfe/pedido-42/evento")
162
+
163
+ doc = nfe.emitir_evento("pedido-42", **corpo)
164
+
165
+ enviado = a_request(:post, "#{homologacao}/v2/nfe/pedido-42/evento").with(body: JSON.generate(corpo))
166
+ expect(enviado).to have_been_made
167
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
168
+ end
169
+
170
+ it "rejeita ref inválida sem requisição" do
171
+ expect { nfe.emitir_evento("pedido 42", tipo_evento: "x") }.to raise_error(ArgumentError)
172
+ end
173
+ end
174
+
175
+ describe "#cancelar_evento" do
176
+ it "faz DELETE em /v2/nfe/{ref}/evento com tipo_evento no corpo", :aggregate_failures do
177
+ stub_nfe(:delete, "nfe/pedido-42/evento")
178
+
179
+ nfe.cancelar_evento("pedido-42", tipo_evento: "prorrogacao_suspensao_icms")
180
+
181
+ enviado = a_request(:delete, "#{homologacao}/v2/nfe/pedido-42/evento")
182
+ .with(body: JSON.generate(tipo_evento: "prorrogacao_suspensao_icms"))
183
+ expect(enviado).to have_been_made
184
+ end
185
+
186
+ it "rejeita ref inválida sem requisição" do
187
+ expect { nfe.cancelar_evento("pedido 42", tipo_evento: "x") }.to raise_error(ArgumentError)
188
+ end
189
+ end
190
+
191
+ describe "#registrar_ator_interessado" do
192
+ it "faz POST em /v2/nfe/{ref}/ator_interessado com o CNPJ e a autorização", :aggregate_failures do
193
+ stub_nfe(:post, "nfe/pedido-42/ator_interessado")
194
+
195
+ nfe.registrar_ator_interessado("pedido-42", permite_autorizacao_terceiros: true, cnpj: "12345678000190")
196
+
197
+ corpo = JSON.generate(permite_autorizacao_terceiros: true, cnpj: "12345678000190")
198
+ expect(a_request(:post, "#{homologacao}/v2/nfe/pedido-42/ator_interessado").with(body: corpo))
199
+ .to have_been_made
200
+ end
201
+
202
+ it "omite cpf e cnpj quando não informados" do
203
+ stub_nfe(:post, "nfe/pedido-42/ator_interessado")
204
+
205
+ nfe.registrar_ator_interessado("pedido-42", permite_autorizacao_terceiros: false)
206
+
207
+ corpo = JSON.generate(permite_autorizacao_terceiros: false)
208
+ expect(a_request(:post, "#{homologacao}/v2/nfe/pedido-42/ator_interessado").with(body: corpo))
209
+ .to have_been_made
210
+ end
211
+
212
+ it "rejeita ref inválida sem requisição" do
213
+ expect { nfe.registrar_ator_interessado("pedido 42", permite_autorizacao_terceiros: true) }
214
+ .to raise_error(ArgumentError)
215
+ end
216
+ end
217
+
218
+ describe "#registrar_insucesso_entrega" do
219
+ let(:insucesso) do
220
+ { data_tentativa_entrega: "2024-07-24T10:30:56-03:00", motivo_insucesso: 4,
221
+ hash_tentativa_entrega: "yzmPGyT1YM5KqilP56w+oPlVkx8=", justificativa_insucesso: "endereço incorreto" }
222
+ end
223
+
224
+ it "faz POST em /v2/nfe/{ref}/insucesso_entrega com os campos obrigatórios e opcionais" do
225
+ stub_nfe(:post, "nfe/pedido-42/insucesso_entrega")
226
+ nfe.registrar_insucesso_entrega("pedido-42", **insucesso)
227
+
228
+ enviado = a_request(:post, "#{homologacao}/v2/nfe/pedido-42/insucesso_entrega")
229
+ .with(body: JSON.generate(insucesso))
230
+ expect(enviado).to have_been_made
231
+ end
232
+
233
+ it "rejeita ref inválida sem requisição" do
234
+ expect { nfe.registrar_insucesso_entrega("pedido 42", **insucesso) }.to raise_error(ArgumentError)
235
+ end
236
+ end
237
+
238
+ describe "#cancelar_insucesso_entrega" do
239
+ it "faz DELETE em /v2/nfe/{ref}/insucesso_entrega sem corpo", :aggregate_failures do
240
+ stub_nfe(:delete, "nfe/pedido-42/insucesso_entrega")
241
+
242
+ doc = nfe.cancelar_insucesso_entrega("pedido-42")
243
+
244
+ expect(a_request(:delete, "#{homologacao}/v2/nfe/pedido-42/insucesso_entrega").with(body: nil))
245
+ .to have_been_made
246
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
247
+ end
248
+
249
+ it "rejeita ref inválida sem requisição" do
250
+ expect { nfe.cancelar_insucesso_entrega("pedido 42") }.to raise_error(ArgumentError)
251
+ end
252
+ end
253
+
254
+ describe "#previa com validar: true" do
255
+ it "levanta ErroDeValidacao sem fazer requisição quando faltam obrigatórios", :aggregate_failures do
256
+ stub = stub_nfe(:post, "nfe/danfe", body: "%PDF-1.4")
257
+
258
+ expect { nfe.previa(dados: {}, validar: true) }.to raise_error(FocusNfe::Esquemas::ErroDeValidacao)
259
+ expect(stub).not_to have_been_requested
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::NfesRecebidas do
4
+ include_context "com recurso conectado"
5
+
6
+ let(:chave) { "35200114200166000187550010000000201234567890" }
7
+
8
+ it_behaves_like "um recurso listável", "nfes_recebidas"
9
+ it_behaves_like "um recurso baixável", "nfes_recebidas"
10
+ it_behaves_like "um recurso notificável", "nfes_recebidas"
11
+
12
+ describe "#consultar" do
13
+ it "faz GET em /v2/nfes_recebidas/{chave} e devolve o corpo cru" do
14
+ stub_get("nfes_recebidas/#{chave}", body: '{"situacao":"autorizada"}')
15
+
16
+ expect(recurso.consultar(chave)).to eq("situacao" => "autorizada")
17
+ end
18
+
19
+ it "envia ?completa=1 quando completa: true" do
20
+ stub = stub_get("nfes_recebidas/#{chave}", query: { completa: 1 })
21
+
22
+ recurso.consultar(chave, completa: true)
23
+
24
+ expect(stub).to have_been_requested
25
+ end
26
+ end
27
+
28
+ describe "#manifestar" do
29
+ it "faz POST em /{chave}/manifesto com tipo e justificativa" do
30
+ corpo = '{"tipo":"ciencia","justificativa":"Ciente da operação."}'
31
+ stub = stub_envio(:post, "nfes_recebidas/#{chave}/manifesto", body: corpo)
32
+
33
+ recurso.manifestar(chave, tipo: "ciencia", justificativa: "Ciente da operação.")
34
+
35
+ expect(stub).to have_been_requested
36
+ end
37
+
38
+ it "omite a justificativa quando não informada" do
39
+ stub = stub_envio(:post, "nfes_recebidas/#{chave}/manifesto", body: '{"tipo":"confirmacao"}')
40
+
41
+ recurso.manifestar(chave, tipo: "confirmacao")
42
+
43
+ expect(stub).to have_been_requested
44
+ end
45
+ end
46
+
47
+ describe "#emitir_evento" do
48
+ it "faz POST em /{chave}/evento e devolve um Documento", :aggregate_failures do
49
+ corpo = '{"tipo_evento":"imobilizacao_item","item":1}'
50
+ stub = stub_envio(:post, "nfes_recebidas/#{chave}/evento", body: corpo)
51
+
52
+ doc = recurso.emitir_evento(chave, tipo_evento: "imobilizacao_item", item: 1)
53
+
54
+ expect(stub).to have_been_requested
55
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
56
+ end
57
+ end
58
+
59
+ describe "#cancelar_evento" do
60
+ it "faz DELETE em /{chave}/evento e devolve um Documento", :aggregate_failures do
61
+ stub = stub_envio(:delete, "nfes_recebidas/#{chave}/evento")
62
+
63
+ doc = recurso.cancelar_evento(chave)
64
+
65
+ expect(stub).to have_been_requested
66
+ expect(doc).to be_a(FocusNfe::Modelos::Documento)
67
+ end
68
+ end
69
+
70
+ describe "#download_xml_cancelamento" do
71
+ it "faz GET em /{chave}/cancelamento.xml e devolve o XML cru" do
72
+ stub_get("nfes_recebidas/#{chave}/cancelamento.xml",
73
+ body: "<procEventoNFe/>", headers: { "Content-Type" => "application/xml" })
74
+
75
+ expect(recurso.download_xml_cancelamento(chave)).to eq("<procEventoNFe/>")
76
+ end
77
+ end
78
+
79
+ describe "#download_xml_carta_correcao" do
80
+ it "faz GET em /{chave}/carta_correcao.xml e devolve o XML cru" do
81
+ stub_get("nfes_recebidas/#{chave}/carta_correcao.xml",
82
+ body: "<procEventoNFe/>", headers: { "Content-Type" => "application/xml" })
83
+
84
+ expect(recurso.download_xml_carta_correcao(chave)).to eq("<procEventoNFe/>")
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Nfgas do
4
+ it_behaves_like "um recurso emitível", "nfgas"
5
+ it_behaves_like "um recurso consultável", "nfgas"
6
+ it_behaves_like "um recurso cancelável", "nfgas"
7
+ it_behaves_like "um recurso notificável", "nfgas"
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::NfseNacional do
4
+ it_behaves_like "um recurso emitível", "nfse_nacional"
5
+ it_behaves_like "um recurso consultável", "nfse_nacional"
6
+ it_behaves_like "um recurso cancelável", "nfse_nacional"
7
+ it_behaves_like "um recurso notificável", "nfse_nacional"
8
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Nfse do
4
+ it_behaves_like "um recurso emitível", "nfse"
5
+ it_behaves_like "um recurso consultável", "nfse"
6
+ it_behaves_like "um recurso cancelável", "nfse"
7
+ it_behaves_like "um recurso notificável", "nfse"
8
+ it_behaves_like "um recurso enviável por email", "nfse"
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::NfsesNacionaisRecebidas do
4
+ it_behaves_like "um recurso listável", "nfsens_recebidas"
5
+ it_behaves_like "um recurso baixável", "nfsens_recebidas"
6
+ it_behaves_like "um recurso notificável", "nfsens_recebidas"
7
+
8
+ include_context "com recurso conectado"
9
+
10
+ describe "#download_html" do
11
+ it "baixa o DANFSe em HTML cru" do
12
+ stub_get("nfsens_recebidas/CHAVE.html", body: "<html></html>", headers: { "Content-Type" => "text/html" })
13
+
14
+ expect(recurso.download_html("CHAVE")).to eq("<html></html>")
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe FocusNfe::Recursos::Webhooks do
4
+ include_context "com recurso conectado"
5
+
6
+ it_behaves_like "um recurso listável", "hooks"
7
+ it_behaves_like "um recurso localizável", "hooks"
8
+ it_behaves_like "um recurso removível", "hooks"
9
+
10
+ it "usa o caminho_base 'hooks'" do
11
+ expect(recurso.caminho_base).to eq("hooks")
12
+ end
13
+
14
+ describe "#criar" do
15
+ it "faz POST em /v2/hooks com os dados e devolve o corpo cru" do
16
+ dados = { "event" => "nfe", "url" => "https://meu.app/hooks/nfe" }
17
+ stub_envio(:post, "hooks", body: JSON.generate(dados), resposta: '{"id":9}')
18
+
19
+ expect(recurso.criar(dados: dados)).to eq("id" => 9)
20
+ end
21
+ end
22
+ end