1use super::types::*;
4use crate::sefaz;
5use async_graphql::{Context, Object, Result as GqlResult};
6
7pub struct QueryRoot;
9
10#[Object]
11impl QueryRoot {
12 async fn nfe(&self, chave_acesso: String) -> GqlResult<Option<NfeType>> {
14 let info = sefaz::validar_chave_acesso(&chave_acesso)
16 .map_err(|e| async_graphql::Error::new(e))?;
17
18 Ok(Some(NfeType {
20 id: chave_acesso.clone(),
21 chave_acesso: chave_acesso.clone(),
22 numero: info.numero as i32,
23 serie: info.serie as i32,
24 tipo: TipoDocumento::Nfe,
25 ambiente: Ambiente::Producao,
26 status: StatusNfe::Pendente,
27 data_emissao: format!("20{}-{}-01", &info.ano_mes[0..2], &info.ano_mes[2..4]),
28 data_autorizacao: None,
29 protocolo: None,
30 emitente: EmitenteType {
31 cnpj: info.cnpj,
32 razao_social: "Consulte SEFAZ para dados completos".to_string(),
33 nome_fantasia: None,
34 inscricao_estadual: None,
35 endereco: EnderecoType {
36 logradouro: String::new(),
37 numero: String::new(),
38 complemento: None,
39 bairro: String::new(),
40 municipio: String::new(),
41 uf: info.uf,
42 cep: String::new(),
43 pais: Some("Brasil".to_string()),
44 },
45 },
46 destinatario: None,
47 itens: vec![],
48 totais: TotaisType {
49 base_calculo_icms: 0.0,
50 valor_icms: 0.0,
51 valor_produtos: 0.0,
52 valor_frete: 0.0,
53 valor_desconto: 0.0,
54 valor_total: 0.0,
55 },
56 xml: None,
57 }))
58 }
59
60 async fn nfes(
62 &self,
63 filter: Option<NfeFilter>,
64 pagination: Option<Pagination>,
65 ) -> GqlResult<Vec<NfeType>> {
66 let _filter = filter.unwrap_or_default();
67 let _pagination = pagination.unwrap_or_default();
68
69 Ok(vec![])
71 }
72
73 async fn consultar_sefaz(&self, chave_acesso: String) -> GqlResult<ConsultaSefazResult> {
75 let info = sefaz::validar_chave_acesso(&chave_acesso)
76 .map_err(|e| async_graphql::Error::new(e))?;
77
78 let url = sefaz::gerar_url_consulta_portal(&chave_acesso);
79
80 Ok(ConsultaSefazResult {
81 sucesso: true,
82 codigo_status: "100".to_string(),
83 motivo: "Chave válida - acesse URL para consulta completa".to_string(),
84 chave_acesso: Some(info.chave),
85 protocolo: None,
86 data_recebimento: None,
87 situacao: Some(format!("URL: {}", url)),
88 })
89 }
90
91 async fn validar_chave(&self, chave: String) -> GqlResult<bool> {
93 match sefaz::validar_chave_acesso(&chave) {
94 Ok(_) => Ok(true),
95 Err(_) => Ok(false),
96 }
97 }
98
99 async fn certificado(&self, ctx: &Context<'_>) -> GqlResult<Option<CertificadoInfoType>> {
101 if let Some(cert_info) = ctx.data_opt::<crate::certificado::CertificadoInfo>() {
103 Ok(Some(CertificadoInfoType {
104 cnpj: cert_info.cnpj.clone(),
105 razao_social: cert_info.razao_social.clone(),
106 valido: cert_info.valido,
107 data_validade: cert_info.not_after.clone(),
108 dias_para_expirar: cert_info.dias_para_expirar as i32,
109 }))
110 } else {
111 Ok(None)
112 }
113 }
114
115 async fn health(&self) -> GqlResult<String> {
117 Ok("OK".to_string())
118 }
119}
120
121pub struct MutationRoot;
123
124#[Object]
125impl MutationRoot {
126 async fn emitir_nfe(&self, input: NfeInput) -> GqlResult<EmissaoResult> {
128 if input.itens.is_empty() {
130 return Err(async_graphql::Error::new("NF-e deve ter pelo menos um item"));
131 }
132
133 Ok(EmissaoResult {
140 sucesso: false,
141 codigo_status: "999".to_string(),
142 motivo: "Emissão requer certificado digital configurado".to_string(),
143 chave_acesso: None,
144 protocolo: None,
145 xml_autorizado: None,
146 })
147 }
148
149 async fn cancelar_nfe(&self, input: CancelamentoInput) -> GqlResult<CancelamentoResult> {
151 let _info = sefaz::validar_chave_acesso(&input.chave_acesso)
153 .map_err(|e| async_graphql::Error::new(e))?;
154
155 if input.justificativa.len() < 15 {
157 return Err(async_graphql::Error::new("Justificativa deve ter no mínimo 15 caracteres"));
158 }
159
160 Ok(CancelamentoResult {
167 sucesso: false,
168 codigo_status: "999".to_string(),
169 motivo: "Cancelamento requer certificado digital configurado".to_string(),
170 protocolo: None,
171 data_cancelamento: None,
172 })
173 }
174
175 async fn carta_correcao(&self, input: CartaCorrecaoInput) -> GqlResult<EmissaoResult> {
177 let _info = sefaz::validar_chave_acesso(&input.chave_acesso)
179 .map_err(|e| async_graphql::Error::new(e))?;
180
181 if input.correcao.len() < 15 {
183 return Err(async_graphql::Error::new("Correção deve ter no mínimo 15 caracteres"));
184 }
185
186 Ok(EmissaoResult {
187 sucesso: false,
188 codigo_status: "999".to_string(),
189 motivo: "Carta de correção requer certificado digital configurado".to_string(),
190 chave_acesso: None,
191 protocolo: None,
192 xml_autorizado: None,
193 })
194 }
195
196 async fn carregar_certificado(
198 &self,
199 pfx_base64: String,
200 senha: String,
201 ) -> GqlResult<CertificadoInfoType> {
202 use base64::Engine;
203
204 let pfx_bytes = base64::engine::general_purpose::STANDARD
205 .decode(&pfx_base64)
206 .map_err(|e| async_graphql::Error::new(format!("Erro ao decodificar certificado: {}", e)))?;
207
208 let cert = crate::certificado::CertificadoA1::from_bytes(&pfx_bytes, &senha)
209 .map_err(|e| async_graphql::Error::new(e))?;
210
211 Ok(CertificadoInfoType {
212 cnpj: cert.info.cnpj,
213 razao_social: cert.info.razao_social,
214 valido: cert.info.valido,
215 data_validade: cert.info.not_after,
216 dias_para_expirar: cert.info.dias_para_expirar as i32,
217 })
218 }
219
220 async fn parse_xml(&self, xml: String) -> GqlResult<NfeType> {
222 let xml_clean = xml.replace("xmlns=\"http://www.portalfiscal.inf.br/nfe\"", "");
224
225 let nfe: nfe_parser::Nfe = xml_clean.parse()
226 .map_err(|e| async_graphql::Error::new(format!("Erro ao parsear XML: {}", e)))?;
227
228 Ok(NfeType {
229 id: nfe.chave_acesso.clone(),
230 chave_acesso: nfe.chave_acesso,
231 numero: nfe.ide.numero as i32,
232 serie: nfe.ide.serie as i32,
233 tipo: TipoDocumento::Nfe,
234 ambiente: if nfe.ide.ambiente == nfe_parser::TipoAmbiente::Producao {
235 Ambiente::Producao
236 } else {
237 Ambiente::Homologacao
238 },
239 status: StatusNfe::Pendente,
240 data_emissao: nfe.ide.emissao.horario.format("%Y-%m-%dT%H:%M:%S").to_string(),
241 data_autorizacao: None,
242 protocolo: None,
243 emitente: EmitenteType {
244 cnpj: nfe.emit.cnpj.clone().unwrap_or_default(),
245 razao_social: nfe.emit.razao_social.clone().unwrap_or_default(),
246 nome_fantasia: nfe.emit.nome_fantasia.clone(),
247 inscricao_estadual: nfe.emit.ie.clone(),
248 endereco: EnderecoType {
249 logradouro: nfe.emit.endereco.logradouro.clone(),
250 numero: nfe.emit.endereco.numero.clone(),
251 complemento: nfe.emit.endereco.complemento.clone(),
252 bairro: nfe.emit.endereco.bairro.clone(),
253 municipio: nfe.emit.endereco.nome_municipio.clone(),
254 uf: nfe.emit.endereco.sigla_uf.clone(),
255 cep: nfe.emit.endereco.cep.clone(),
256 pais: Some("Brasil".to_string()),
257 },
258 },
259 destinatario: nfe.dest.as_ref().map(|d| DestinatarioType {
260 cnpj: Some(d.cnpj.clone()),
261 cpf: None,
262 razao_social: d.razao_social.clone().unwrap_or_default(),
263 inscricao_estadual: None,
264 endereco: d.endereco.as_ref().map(|e| EnderecoType {
265 logradouro: e.logradouro.clone(),
266 numero: e.numero.clone(),
267 complemento: e.complemento.clone(),
268 bairro: e.bairro.clone(),
269 municipio: e.nome_municipio.clone(),
270 uf: e.sigla_uf.clone(),
271 cep: e.cep.clone(),
272 pais: Some("Brasil".to_string()),
273 }),
274 }),
275 itens: nfe.itens.iter().map(|item| ItemType {
276 numero: item.numero as i32,
277 codigo: item.produto.codigo.clone(),
278 descricao: item.produto.descricao.clone(),
279 ncm: item.produto.ncm.clone(),
280 cfop: item.produto.tributacao.cfop.clone(),
281 unidade: item.produto.unidade.clone(),
282 quantidade: item.produto.quantidade as f64,
283 valor_unitario: item.produto.valor_unitario as f64,
284 valor_total: item.produto.valor_bruto as f64,
285 }).collect(),
286 totais: TotaisType {
287 base_calculo_icms: nfe.totais.valor_base_calculo as f64,
288 valor_icms: nfe.totais.valor_icms as f64,
289 valor_produtos: nfe.totais.valor_produtos as f64,
290 valor_frete: nfe.totais.valor_frete as f64,
291 valor_desconto: nfe.totais.valor_desconto as f64,
292 valor_total: nfe.totais.valor_total as f64,
293 },
294 xml: Some(xml),
295 })
296 }
297}