use clap::{Parser, Subcommand};
use opensefaz_rs::{Config, NfeClient, Certificado, Ambiente};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "opensefaz")]
#[command(author = "OpenSefaz Contributors")]
#[command(version = "0.1.0")]
#[command(about = "CLI para manipulação de documentos fiscais eletrônicos", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(short, long, global = true)]
certificado: Option<PathBuf>,
#[arg(short, long, global = true)]
senha: Option<String>,
#[arg(long, global = true)]
cnpj: Option<String>,
#[arg(short, long, global = true, default_value = "SP")]
uf: String,
#[arg(short, long, global = true, default_value = "homologacao")]
ambiente: String,
#[arg(short, long, global = true, default_value = "info")]
log: String,
}
#[derive(Subcommand)]
enum Commands {
Consultar {
chave: String,
},
Status,
Validar {
arquivo: PathBuf,
},
Assinar {
arquivo: PathBuf,
#[arg(short, long)]
saida: Option<PathBuf>,
},
GerarChave {
#[arg(short, long, default_value = "42")]
estado: String,
#[arg(long)]
cnpj: Option<String>,
},
Certificado,
Formatar {
valor: String,
#[arg(short, long)]
tipo: Option<String>,
},
}
fn criar_cliente(args: &Cli) -> Result<NfeClient, Box<dyn std::error::Error>> {
let certificado_path = args.certificado.as_ref()
.ok_or("Certificado é obrigatório")?;
let senha = args.senha.as_ref()
.ok_or("Senha do certificado é obrigatória")?;
let cnpj = args.cnpj.as_ref()
.ok_or("CNPJ é obrigatório")?;
let ambiente = match args.ambiente.as_str() {
"producao" => Ambiente::Producao,
_ => Ambiente::Homologacao,
};
let config = Config::new(
cnpj.clone(),
args.uf.clone(),
ambiente,
);
let certificado = Certificado::from_pem_file(certificado_path, senha)?;
Ok(NfeClient::new(config, certificado)?)
}
fn formatar_valor(valor: &str, tipo: Option<&str>) -> String {
match tipo.unwrap_or("auto") {
"cnpj" => opensefaz_rs::utils::formatar_cnpj(valor),
"cpf" => opensefaz_rs::utils::formatar_cpf(valor),
"cep" => opensefaz_rs::utils::formatar_cep(valor),
"auto" => {
match valor.len() {
14 => opensefaz_rs::utils::formatar_cnpj(valor),
11 => opensefaz_rs::utils::formatar_cpf(valor),
8 => opensefaz_rs::utils::formatar_cep(valor),
_ => valor.to_string(),
}
}
_ => valor.to_string(),
}
}
#[tokio::main]
async fn main() {
let args = Cli::parse();
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or(&args.log)
).init();
let resultado = match args.command {
Commands::Consultar { ref chave } => {
match criar_cliente(&args) {
Ok(client) => {
println!("Consultando NF-e: {}", chave);
match client.consultar_por_chave(chave).await {
Ok(resultado) => {
println!("Status: {:?}", resultado.status);
println!("Protocolo: {:?}", resultado.protocolo);
println!("Motivo: {}", resultado.motivo);
Ok(())
}
Err(e) => Err(format!("Erro na consulta: {}", e)),
}
}
Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
}
}
Commands::Status => {
match criar_cliente(&args) {
Ok(client) => {
println!("Consultando status do serviço...");
match client.consultar_status_servico().await {
Ok(status) => {
if status {
println!("Serviço: Online");
} else {
println!("Serviço: Offline");
}
Ok(())
}
Err(e) => Err(format!("Erro na consulta: {}", e)),
}
}
Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
}
}
Commands::Validar { ref arquivo } => {
match std::fs::read_to_string(arquivo) {
Ok(conteudo) => {
match criar_cliente(&args) {
Ok(client) => {
match client.validar_xml(&conteudo) {
Ok(_) => {
println!("XML válido!");
Ok(())
}
Err(e) => Err(format!("XML inválido: {}", e)),
}
}
Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
}
}
Err(e) => Err(format!("Erro ao ler arquivo: {}", e)),
}
}
Commands::Assinar { ref arquivo, ref saida } => {
match std::fs::read_to_string(arquivo) {
Ok(conteudo) => {
match criar_cliente(&args) {
Ok(client) => {
match client.assinar_xml(&conteudo) {
Ok(xml_assinado) => {
let caminho_saida = saida.clone().unwrap_or_else(|| {
let mut path = arquivo.clone();
path.set_extension("assinado.xml");
path
});
match std::fs::write(&caminho_saida, &xml_assinado) {
Ok(_) => {
println!("XML assinado com sucesso!");
println!("Salvo em: {:?}", caminho_saida);
Ok(())
}
Err(e) => Err(format!("Erro ao salvar: {}", e)),
}
}
Err(e) => Err(format!("Erro ao assinar: {}", e)),
}
}
Err(e) => Err(format!("Erro ao criar cliente: {}", e)),
}
}
Err(e) => Err(format!("Erro ao ler arquivo: {}", e)),
}
}
Commands::GerarChave { estado, cnpj } => {
let cnpj = cnpj.unwrap_or_else(|| "99999999999999".to_string());
let data = chrono::Utc::now();
let ano_mes = data.format("%y%m").to_string();
let chave = format!(
"{}{}{}550010000000011{}",
estado,
ano_mes,
cnpj,
opensefaz_rs::utils::gerar_numero_aleatorio()
);
let dv = opensefaz_rs::utils::calcular_digito_verificador(&chave);
let chave_completa = format!("{}{}", chave, dv);
println!("Chave de acesso gerada:");
println!("{}", opensefaz_rs::types::ChaveAcesso::formatar(&chave_completa));
Ok(())
}
Commands::Certificado => {
match criar_cliente(&args) {
Ok(_) => {
println!("Certificado carregado com sucesso!");
Ok(())
}
Err(e) => Err(format!("Erro ao carregar certificado: {}", e)),
}
}
Commands::Formatar { valor, tipo } => {
let formatado = formatar_valor(&valor, tipo.as_deref());
println!("Original: {}", valor);
println!("Formatado: {}", formatado);
Ok(())
}
};
if let Err(e) = resultado {
eprintln!("Erro: {}", e);
std::process::exit(1);
}
}