Skip to main content

nfe_parser/
builder.rs

1//! Builder para criação de NF-e
2//!
3//! Este módulo fornece uma API fluente para construir uma NF-e do zero.
4
5use crate::base::dest::{Destinatario, IndicadorContribuicaoIe};
6use crate::base::emit::Emitente;
7use crate::base::endereco::Endereco;
8use crate::base::ide::*;
9use crate::base::item::{Item, Produto, Imposto};
10use crate::base::totais::Totalizacao;
11use crate::base::transporte::{Transporte, ModalidadeFrete};
12use crate::base::{Nfe, VersaoLayout};
13use chrono::{DateTime, Utc};
14
15/// Builder para construção de NF-e
16#[derive(Debug, Default)]
17pub struct NfeBuilder {
18    // Identificação
19    codigo_uf: Option<u8>,
20    numero: Option<u32>,
21    serie: Option<u16>,
22    modelo: Option<ModeloDocumentoFiscal>,
23    natureza_operacao: Option<String>,
24    tipo_operacao: Option<TipoOperacao>,
25    destino_operacao: Option<DestinoOperacao>,
26    finalidade: Option<FinalidadeEmissao>,
27    ambiente: Option<TipoAmbiente>,
28    codigo_municipio: Option<u32>,
29
30    // Emitente
31    emit_cnpj: Option<String>,
32    emit_razao_social: Option<String>,
33    emit_nome_fantasia: Option<String>,
34    emit_ie: Option<String>,
35    emit_endereco: Option<Endereco>,
36
37    // Destinatário
38    dest_cnpj: Option<String>,
39    dest_razao_social: Option<String>,
40    dest_indicador_ie: Option<IndicadorContribuicaoIe>,
41    dest_endereco: Option<Endereco>,
42
43    // Itens
44    itens: Vec<ItemBuilder>,
45
46    // Transporte
47    modalidade_frete: Option<ModalidadeFrete>,
48
49    // Informações adicionais
50    informacao_complementar: Option<String>,
51}
52
53/// Builder para itens da NF-e
54#[derive(Debug, Clone)]
55pub struct ItemBuilder {
56    pub codigo: String,
57    pub descricao: String,
58    pub ncm: String,
59    pub cfop: String,
60    pub unidade: String,
61    pub quantidade: f32,
62    pub valor_unitario: f32,
63    pub gtin: Option<String>,
64    pub valor_desconto: Option<f32>,
65}
66
67impl NfeBuilder {
68    /// Cria um novo builder
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    // === Identificação ===
74
75    /// Define o código da UF (ex: 35 para SP)
76    pub fn codigo_uf(mut self, uf: u8) -> Self {
77        self.codigo_uf = Some(uf);
78        self
79    }
80
81    /// Define o número da NF-e
82    pub fn numero(mut self, numero: u32) -> Self {
83        self.numero = Some(numero);
84        self
85    }
86
87    /// Define a série da NF-e
88    pub fn serie(mut self, serie: u16) -> Self {
89        self.serie = Some(serie);
90        self
91    }
92
93    /// Define o modelo (55 = NF-e, 65 = NFC-e)
94    pub fn modelo(mut self, modelo: ModeloDocumentoFiscal) -> Self {
95        self.modelo = Some(modelo);
96        self
97    }
98
99    /// Define a natureza da operação
100    pub fn natureza_operacao(mut self, natureza: &str) -> Self {
101        self.natureza_operacao = Some(natureza.to_string());
102        self
103    }
104
105    /// Define o tipo de operação (Entrada/Saída)
106    pub fn tipo_operacao(mut self, tipo: TipoOperacao) -> Self {
107        self.tipo_operacao = Some(tipo);
108        self
109    }
110
111    /// Define o destino da operação
112    pub fn destino_operacao(mut self, destino: DestinoOperacao) -> Self {
113        self.destino_operacao = Some(destino);
114        self
115    }
116
117    /// Define a finalidade da emissão
118    pub fn finalidade(mut self, finalidade: FinalidadeEmissao) -> Self {
119        self.finalidade = Some(finalidade);
120        self
121    }
122
123    /// Define o ambiente (Produção/Homologação)
124    pub fn ambiente(mut self, ambiente: TipoAmbiente) -> Self {
125        self.ambiente = Some(ambiente);
126        self
127    }
128
129    /// Define o código do município
130    pub fn codigo_municipio(mut self, codigo: u32) -> Self {
131        self.codigo_municipio = Some(codigo);
132        self
133    }
134
135    // === Emitente ===
136
137    /// Define o CNPJ do emitente
138    pub fn emit_cnpj(mut self, cnpj: &str) -> Self {
139        self.emit_cnpj = Some(cnpj.replace(&['.', '/', '-'][..], ""));
140        self
141    }
142
143    /// Define a razão social do emitente
144    pub fn emit_razao_social(mut self, razao: &str) -> Self {
145        self.emit_razao_social = Some(razao.to_string());
146        self
147    }
148
149    /// Define o nome fantasia do emitente
150    pub fn emit_nome_fantasia(mut self, fantasia: &str) -> Self {
151        self.emit_nome_fantasia = Some(fantasia.to_string());
152        self
153    }
154
155    /// Define a IE do emitente
156    pub fn emit_ie(mut self, ie: &str) -> Self {
157        self.emit_ie = Some(ie.to_string());
158        self
159    }
160
161    /// Define o endereço do emitente
162    pub fn emit_endereco(mut self, endereco: Endereco) -> Self {
163        self.emit_endereco = Some(endereco);
164        self
165    }
166
167    // === Destinatário ===
168
169    /// Define o CNPJ do destinatário
170    pub fn dest_cnpj(mut self, cnpj: &str) -> Self {
171        self.dest_cnpj = Some(cnpj.replace(&['.', '/', '-'][..], ""));
172        self
173    }
174
175    /// Define a razão social do destinatário
176    pub fn dest_razao_social(mut self, razao: &str) -> Self {
177        self.dest_razao_social = Some(razao.to_string());
178        self
179    }
180
181    /// Define o indicador de IE do destinatário
182    pub fn dest_indicador_ie(mut self, indicador: IndicadorContribuicaoIe) -> Self {
183        self.dest_indicador_ie = Some(indicador);
184        self
185    }
186
187    /// Define o endereço do destinatário
188    pub fn dest_endereco(mut self, endereco: Endereco) -> Self {
189        self.dest_endereco = Some(endereco);
190        self
191    }
192
193    // === Itens ===
194
195    /// Adiciona um item à NF-e
196    pub fn add_item(mut self, item: ItemBuilder) -> Self {
197        self.itens.push(item);
198        self
199    }
200
201    // === Transporte ===
202
203    /// Define a modalidade do frete
204    pub fn modalidade_frete(mut self, modalidade: ModalidadeFrete) -> Self {
205        self.modalidade_frete = Some(modalidade);
206        self
207    }
208
209    // === Informações Adicionais ===
210
211    /// Define informações complementares
212    pub fn informacao_complementar(mut self, info: &str) -> Self {
213        self.informacao_complementar = Some(info.to_string());
214        self
215    }
216
217    /// Constrói a NF-e
218    pub fn build(self) -> Result<Nfe, String> {
219        // Validações básicas
220        let codigo_uf = self.codigo_uf.ok_or("Código UF é obrigatório")?;
221        let numero = self.numero.ok_or("Número é obrigatório")?;
222        let serie = self.serie.unwrap_or(1);
223        let modelo = self.modelo.unwrap_or(ModeloDocumentoFiscal::Nfe);
224        let natureza = self.natureza_operacao.ok_or("Natureza da operação é obrigatória")?;
225        let tipo_op = self.tipo_operacao.unwrap_or(TipoOperacao::Saida);
226        let destino = self.destino_operacao.unwrap_or(DestinoOperacao::Interna);
227        let finalidade = self.finalidade.unwrap_or(FinalidadeEmissao::Normal);
228        let ambiente = self.ambiente.unwrap_or(TipoAmbiente::Homologacao);
229        let codigo_mun = self.codigo_municipio.ok_or("Código do município é obrigatório")?;
230
231        if self.itens.is_empty() {
232            return Err("Pelo menos um item é obrigatório".to_string());
233        }
234
235        // Gerar código numérico aleatório (8 dígitos)
236        let codigo_numerico = format!("{:08}", rand_u32() % 100000000);
237
238        // Data/hora atual
239        let agora: DateTime<Utc> = Utc::now();
240
241        // Construir itens
242        let mut itens_nfe = Vec::new();
243        let mut total_produtos = 0.0f32;
244        let mut total_desconto = 0.0f32;
245
246        for (idx, item) in self.itens.iter().enumerate() {
247            let valor_bruto = item.quantidade * item.valor_unitario;
248            total_produtos += valor_bruto;
249            if let Some(desc) = item.valor_desconto {
250                total_desconto += desc;
251            }
252
253            let produto = Produto::new(
254                item.codigo.clone(),
255                item.descricao.clone(),
256                item.ncm.clone(),
257                item.cfop.clone(),
258                item.unidade.clone(),
259                item.quantidade,
260                item.valor_unitario,
261                valor_bruto,
262            );
263
264            // Criar imposto básico (ICMS 00, PIS e COFINS)
265            let imposto = Imposto::default();
266
267            itens_nfe.push(Item {
268                numero: (idx + 1) as u8,
269                produto,
270                imposto,
271            });
272        }
273
274        // Calcular totais
275        let valor_total = total_produtos - total_desconto;
276
277        // Gerar chave de acesso (44 dígitos)
278        let aamm = agora.format("%y%m").to_string();
279        let cnpj_emit = self.emit_cnpj.clone().unwrap_or_default();
280        let chave_sem_dv = format!(
281            "{:02}{}{:014}{:02}{:03}{:09}{:01}{:08}",
282            codigo_uf,
283            aamm,
284            cnpj_emit,
285            modelo as u8,
286            serie,
287            numero,
288            1, // tpEmis
289            codigo_numerico
290        );
291        let dv = calcular_dv(&chave_sem_dv);
292        let chave_acesso = format!("{}{}", chave_sem_dv, dv);
293
294        // Construir endereço do emitente
295        let emit_endereco = self.emit_endereco.unwrap_or_else(|| Endereco::default());
296
297        // Construir NF-e
298        Ok(Nfe {
299            versao: VersaoLayout::V4_00,
300            chave_acesso,
301            ide: Identificacao {
302                codigo_uf,
303                chave: ComposicaoChaveAcesso {
304                    codigo: codigo_numerico,
305                    digito_verificador: dv,
306                },
307                numero,
308                serie,
309                modelo,
310                emissao: Emissao {
311                    horario: agora,
312                    tipo: TipoEmissao::Normal,
313                    finalidade,
314                    processo: TipoProcessoEmissao::ViaAplicativoDoContribuinte,
315                    versao_processo: "1.0.0".to_string(),
316                },
317                operacao: Operacao {
318                    horario: None,
319                    tipo: tipo_op,
320                    destino,
321                    natureza,
322                    consumidor: TipoConsumidor::Normal,
323                    presenca: TipoPresencaComprador::Presencial,
324                    intermediador: None,
325                },
326                codigo_municipio: codigo_mun,
327                formato_danfe: FormatoImpressaoDanfe::NormalRetrato,
328                ambiente,
329            },
330            emit: Emitente {
331                cnpj: self.emit_cnpj,
332                razao_social: self.emit_razao_social,
333                nome_fantasia: self.emit_nome_fantasia,
334                ie: self.emit_ie,
335                iest: None,
336                endereco: emit_endereco,
337            },
338            dest: if self.dest_cnpj.is_some() {
339                Some(Destinatario {
340                    cnpj: self.dest_cnpj.unwrap_or_default(),
341                    razao_social: self.dest_razao_social,
342                    indicador_ie: self.dest_indicador_ie.unwrap_or(IndicadorContribuicaoIe::NaoContribuinteIe),
343                    ie: None,
344                    endereco: self.dest_endereco,
345                })
346            } else {
347                None
348            },
349            itens: itens_nfe,
350            totais: Totalizacao {
351                valor_base_calculo: 0.0,
352                valor_icms: 0.0,
353                valor_produtos: total_produtos,
354                valor_frete: 0.0,
355                valor_seguro: 0.0,
356                valor_desconto: total_desconto,
357                valor_outros: 0.0,
358                valor_pis: 0.0,
359                valor_cofins: 0.0,
360                valor_total,
361                valor_aproximado_tributos: 0.0,
362            },
363            transporte: Transporte {
364                modalidade: self.modalidade_frete.unwrap_or(ModalidadeFrete::SemTransporte),
365            },
366            informacao_complementar: self.informacao_complementar,
367        })
368    }
369}
370
371impl ItemBuilder {
372    /// Cria um novo item
373    pub fn new(codigo: &str, descricao: &str, ncm: &str, cfop: &str) -> Self {
374        Self {
375            codigo: codigo.to_string(),
376            descricao: descricao.to_string(),
377            ncm: ncm.to_string(),
378            cfop: cfop.to_string(),
379            unidade: "UN".to_string(),
380            quantidade: 1.0,
381            valor_unitario: 0.0,
382            gtin: None,
383            valor_desconto: None,
384        }
385    }
386
387    /// Define a unidade
388    pub fn unidade(mut self, unidade: &str) -> Self {
389        self.unidade = unidade.to_string();
390        self
391    }
392
393    /// Define a quantidade
394    pub fn quantidade(mut self, qtd: f32) -> Self {
395        self.quantidade = qtd;
396        self
397    }
398
399    /// Define o valor unitário
400    pub fn valor_unitario(mut self, valor: f32) -> Self {
401        self.valor_unitario = valor;
402        self
403    }
404
405    /// Define o GTIN/EAN
406    pub fn gtin(mut self, gtin: &str) -> Self {
407        self.gtin = Some(gtin.to_string());
408        self
409    }
410
411    /// Define o valor do desconto
412    pub fn desconto(mut self, valor: f32) -> Self {
413        self.valor_desconto = Some(valor);
414        self
415    }
416}
417
418/// Calcula o dígito verificador da chave de acesso (módulo 11)
419fn calcular_dv(chave: &str) -> u8 {
420    let pesos = [2, 3, 4, 5, 6, 7, 8, 9];
421    let mut soma = 0u32;
422
423    for (i, c) in chave.chars().rev().enumerate() {
424        if let Some(digito) = c.to_digit(10) {
425            soma += digito * pesos[i % 8];
426        }
427    }
428
429    let resto = soma % 11;
430    if resto < 2 { 0 } else { (11 - resto) as u8 }
431}
432
433/// Gera um número pseudo-aleatório simples
434fn rand_u32() -> u32 {
435    use std::time::{SystemTime, UNIX_EPOCH};
436    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
437    (duration.as_nanos() % u32::MAX as u128) as u32
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_builder_basico() {
446        let nfe = NfeBuilder::new()
447            .codigo_uf(35)
448            .numero(1)
449            .serie(1)
450            .natureza_operacao("VENDA DE MERCADORIA")
451            .codigo_municipio(3550308)
452            .emit_cnpj("12.345.678/0001-90")
453            .emit_razao_social("EMPRESA TESTE LTDA")
454            .emit_ie("123456789")
455            .add_item(
456                ItemBuilder::new("PROD001", "Produto Teste", "12345678", "5102")
457                    .quantidade(10.0)
458                    .valor_unitario(100.0)
459            )
460            .build();
461
462        assert!(nfe.is_ok());
463        let nfe = nfe.unwrap();
464        assert_eq!(nfe.ide.numero, 1);
465        assert_eq!(nfe.itens.len(), 1);
466        assert_eq!(nfe.totais.valor_produtos, 1000.0);
467    }
468
469    #[test]
470    fn test_calculo_dv() {
471        // Exemplo de chave sem DV
472        let chave = "35150312345678901234550010000000011000000011";
473        let dv = calcular_dv(&chave[..43]);
474        assert!(dv < 10);
475    }
476}