fiscal_core/xml_builder/
access_key.rs1use crate::FiscalError;
12use crate::types::AccessKeyParams;
13
14pub fn build_access_key(params: &AccessKeyParams) -> Result<String, FiscalError> {
24 let base = format!(
25 "{cuf:0>2}{aamm}{cnpj:0>14}{model:0>2}{serie:0>3}{nnf:0>9}{tp_emis}{cnf:0>8}",
26 cuf = params.state_code,
27 aamm = params.year_month,
28 cnpj = params.tax_id,
29 model = params.model.as_str(),
30 serie = params.series,
31 nnf = params.number,
32 tp_emis = params.emission_type.as_str(),
33 cnf = params.numeric_code,
34 );
35
36 if base.len() != 43 {
37 return Err(FiscalError::XmlGeneration(format!(
38 "Access key base must be 43 digits, got {} (\"{}\")",
39 base.len(),
40 base
41 )));
42 }
43
44 let check_digit = calculate_mod11(&base);
45 Ok(format!("{base}{check_digit}"))
46}
47
48pub fn calculate_mod11(digits: &str) -> u8 {
53 let mut sum: u32 = 0;
54 let mut weight: u32 = 2;
55
56 for ch in digits.bytes().rev() {
57 let val = (ch - b'0') as u32;
58 sum += val * weight;
59 weight = if weight >= 9 { 2 } else { weight + 1 };
60 }
61
62 let remainder = sum % 11;
63 if remainder < 2 {
64 0
65 } else {
66 (11 - remainder) as u8
67 }
68}
69
70pub fn generate_numeric_code() -> String {
72 use std::time::{SystemTime, UNIX_EPOCH};
73 let nanos = SystemTime::now()
74 .duration_since(UNIX_EPOCH)
75 .unwrap_or_default()
76 .subsec_nanos();
77 let code = (nanos ^ (nanos >> 16)) % 100_000_000;
78 format!("{code:08}")
79}
80
81pub fn format_year_month(dt: &chrono::DateTime<chrono::FixedOffset>) -> String {
83 format!("{}{:02}", &dt.format("%y"), dt.format("%m"))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn mod11_known_values() {
92 let base = "4325030412345678901255001000000001100000001";
93 let dv = calculate_mod11(base);
94 let full = format!("{base}{dv}");
95 assert_eq!(full.len(), 44);
96 }
97
98 #[test]
99 fn mod11_all_zeros() {
100 let dv = calculate_mod11("0000000000000000000000000000000000000000000");
101 assert_eq!(dv, 0);
102 }
103}