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_produtos: total_produtos,
352                valor_desconto: total_desconto,
353                valor_total,
354                ..Default::default()
355            },
356            transporte: Transporte {
357                modalidade: self.modalidade_frete.unwrap_or(ModalidadeFrete::SemTransporte),
358            },
359            informacao_complementar: self.informacao_complementar,
360        })
361    }
362}
363
364impl ItemBuilder {
365    /// Cria um novo item
366    pub fn new(codigo: &str, descricao: &str, ncm: &str, cfop: &str) -> Self {
367        Self {
368            codigo: codigo.to_string(),
369            descricao: descricao.to_string(),
370            ncm: ncm.to_string(),
371            cfop: cfop.to_string(),
372            unidade: "UN".to_string(),
373            quantidade: 1.0,
374            valor_unitario: 0.0,
375            gtin: None,
376            valor_desconto: None,
377        }
378    }
379
380    /// Define a unidade
381    pub fn unidade(mut self, unidade: &str) -> Self {
382        self.unidade = unidade.to_string();
383        self
384    }
385
386    /// Define a quantidade
387    pub fn quantidade(mut self, qtd: f32) -> Self {
388        self.quantidade = qtd;
389        self
390    }
391
392    /// Define o valor unitário
393    pub fn valor_unitario(mut self, valor: f32) -> Self {
394        self.valor_unitario = valor;
395        self
396    }
397
398    /// Define o GTIN/EAN
399    pub fn gtin(mut self, gtin: &str) -> Self {
400        self.gtin = Some(gtin.to_string());
401        self
402    }
403
404    /// Define o valor do desconto
405    pub fn desconto(mut self, valor: f32) -> Self {
406        self.valor_desconto = Some(valor);
407        self
408    }
409}
410
411/// Calcula o dígito verificador da chave de acesso (módulo 11)
412fn calcular_dv(chave: &str) -> u8 {
413    let pesos = [2, 3, 4, 5, 6, 7, 8, 9];
414    let mut soma = 0u32;
415
416    for (i, c) in chave.chars().rev().enumerate() {
417        if let Some(digito) = c.to_digit(10) {
418            soma += digito * pesos[i % 8];
419        }
420    }
421
422    let resto = soma % 11;
423    if resto < 2 { 0 } else { (11 - resto) as u8 }
424}
425
426/// Gera um número pseudo-aleatório simples
427fn rand_u32() -> u32 {
428    use std::time::{SystemTime, UNIX_EPOCH};
429    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
430    (duration.as_nanos() % u32::MAX as u128) as u32
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_builder_basico() {
439        let nfe = NfeBuilder::new()
440            .codigo_uf(35)
441            .numero(1)
442            .serie(1)
443            .natureza_operacao("VENDA DE MERCADORIA")
444            .codigo_municipio(3550308)
445            .emit_cnpj("12.345.678/0001-90")
446            .emit_razao_social("EMPRESA TESTE LTDA")
447            .emit_ie("123456789")
448            .add_item(
449                ItemBuilder::new("PROD001", "Produto Teste", "12345678", "5102")
450                    .quantidade(10.0)
451                    .valor_unitario(100.0)
452            )
453            .build();
454
455        assert!(nfe.is_ok());
456        let nfe = nfe.unwrap();
457        assert_eq!(nfe.ide.numero, 1);
458        assert_eq!(nfe.itens.len(), 1);
459        assert_eq!(nfe.totais.valor_produtos, 1000.0);
460    }
461
462    #[test]
463    fn test_calculo_dv() {
464        // Exemplo de chave sem DV
465        let chave = "35150312345678901234550010000000011000000011";
466        let dv = calcular_dv(&chave[..43]);
467        assert!(dv < 10);
468    }
469}