datev_types/
lib.rs

1use serde::{Serialize, Deserialize};
2use validator::Validate;
3use std::fmt::Display;
4use std::fmt::Formatter;
5use header::Header;
6use buchung::Buchung;
7use debkred::DebKred;
8#[macro_use]
9extern crate lazy_static;
10
11pub mod header;
12pub mod buchung;
13pub mod debkred;
14
15#[derive(Clone, Debug, PartialEq, Validate, Serialize, Deserialize)]
16pub struct Buchungsstapel {
17    pub header: Header,
18    pub buchungen: Vec<Buchung>,
19}
20
21impl Default for Buchungsstapel{
22    fn default() -> Self {
23        Buchungsstapel {
24            header: Header::default(),
25            buchungen: Vec::new(),
26        }
27    }
28}
29
30impl Display for Buchungsstapel {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        f.write_str(&format!("{}",self.header))?;
33        for buchung in &self.buchungen {
34            buchung.fmt(f)?;
35        }
36        Ok(())
37    }
38}
39
40impl TryFrom<&str> for Buchungsstapel {
41    type Error = &'static str;
42
43    fn try_from(value: &str) -> Result<Self, Self::Error> {
44        let split;
45        if value.contains("\r\n") {
46            split = value.split("\r\n");
47        }else{
48            split = value.split("\n");
49        }
50        let vec: Vec<&str> = split.collect();
51        
52        let header_str: &str = vec.get(0).unwrap();
53        let header = Header::try_from(header_str).unwrap();
54
55        let mut buchungen: Vec<Buchung> = Vec::new();
56        for input in vec.iter().skip(2) {
57            let input2: String = input.to_string();
58            let input3: &str = &input2.replace(',',".");
59            if !input3.is_empty() {
60                let buchung = Buchung::try_from(input3).unwrap();
61                buchungen.push(buchung);
62            }
63        }
64
65        let stapel = Buchungsstapel{
66            header,
67            buchungen,
68        };
69        Ok(stapel)
70    }
71}
72
73#[allow(non_camel_case_types)]
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
75pub struct DebKred_Stamm{
76    header: Header,
77    debitoren_kreditoren: Vec<DebKred>,
78}
79
80impl Default for DebKred_Stamm{
81    fn default() -> Self {
82        DebKred_Stamm {
83            header: Header::default(),
84            debitoren_kreditoren: Vec::new(),
85        }
86    }
87}
88
89impl Display for DebKred_Stamm {
90    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91        f.write_str(&format!("{}",self.header))?;
92        for debkred in &self.debitoren_kreditoren {
93            debkred.fmt(f)?;
94        }
95        Ok(())
96    }
97}
98
99impl TryFrom<&str> for DebKred_Stamm {
100    type Error = &'static str;
101
102    fn try_from(value: &str) -> Result<Self, Self::Error> {
103        let split;
104        if value.contains("\r\n") {
105            split = value.split("\r\n");
106        }else{
107            split = value.split("\n");
108        }
109        let vec: Vec<&str> = split.collect();
110        
111        let header_str: &str = vec.get(0).unwrap();
112        let header = Header::try_from(header_str).unwrap();
113
114        let mut debitoren_kreditoren: Vec<DebKred> = Vec::new();
115        for input in vec.iter().skip(2) {
116            let input2: String = input.to_string();
117            let input3: &str = &input2.replace(',',".");
118            if !input3.is_empty() {
119                let debkred = DebKred::try_from(input3).unwrap();
120                debitoren_kreditoren.push(debkred);
121            }
122        }
123
124        let stapel = DebKred_Stamm{
125            header,
126            debitoren_kreditoren,
127        };
128        Ok(stapel)
129    }
130}
131
132#[test]
133fn valid_header() {
134    use header::Kennzeichen;
135    let str = r#""EXTF";510;21;"Buchungsstapel";7;20211106165314647;;"";"";"";1000;1;20190101;4;20190101;20191231;"";"";;;;"";;"";;;"";;;"";"""#;
136    
137    let result = Header::try_from(str);
138    assert!(result.is_ok());
139    let header = result.unwrap();
140    header.validate().unwrap();
141    assert_eq!(header.kennzeichen, Kennzeichen::EXTF);
142    assert_eq!(header.versionsnummer, 510);
143    assert_eq!(header.format_kategorie, 21);
144    assert_eq!(header.format_name, "Buchungsstapel");
145    assert_eq!(header.format_version, 7);
146    assert_eq!(header.erzeugt_am, chrono::NaiveDateTime::parse_from_str("20211106165314647","%Y%m%d%H%M%S%3f").unwrap());
147    assert_eq!(header.beraternummer, 1000);
148    assert_eq!(header.mandantennummer, 1);
149    assert_eq!(header.wj_beginn, chrono::NaiveDate::from_ymd(2019,1,1));
150    assert_eq!(header.sachkontenlänge, 4);
151    assert_eq!(header.datum_von, chrono::NaiveDate::from_ymd(2019,1,1));
152    assert_eq!(header.datum_bis, chrono::NaiveDate::from_ymd(2019,12,31));
153    assert_eq!(header.buchungstyp, None);
154    assert_eq!(header.rechnungslegungszweck, None);
155    assert_eq!(header.festschreibung, None);
156}
157
158#[test]
159fn full_cycle_header() {
160    let header = Header{
161        format_name: "Buchungsstapel".to_string(),
162        erzeugt_am: chrono::Local::now().naive_local(),
163        beraternummer: 1000,
164        mandantennummer: 1,
165        wj_beginn: chrono::NaiveDate::from_ymd(2019,1,1),
166        sachkontenlänge: 4,
167        datum_von: chrono::NaiveDate::from_ymd(2019,1,1),
168        datum_bis: chrono::NaiveDate::from_ymd(2019,12,31),
169        ..Default::default()
170    };
171    let data = format!("{}",header);
172    println!("{}",data);
173    let mut header2 = Header::try_from(data.as_str()).unwrap();
174    //need to overwrite ezeugt_am because of the higher precision
175    header2.erzeugt_am = header.erzeugt_am.clone();
176    println!("{}",header2);
177    assert_eq!(header, header2);
178    assert_eq!(format!("{}",header),format!("{}",header2));
179}
180#[test]
181#[should_panic]
182fn invalid_header() {
183    let str = r#""INVALIDFORMAT";510;21;"Buchungsstapel";7;20211106165314647;;"";"";"";1000;1;20190101;4;20190101;20191231;"";"";;;;"";;"";;;"";;;"";"""#;
184    let result = Header::try_from(str);
185    assert!(result.is_ok());
186    let header = result.unwrap();
187    header.validate().unwrap();
188}
189
190#[test]
191fn einzelbuchung() {
192    use buchung::SollHabenKennzeichen;
193    use header::Kennzeichen;
194
195    let header = Header{
196        kennzeichen: Kennzeichen::EXTF,
197        versionsnummer: 700,
198        format_kategorie: 21,
199        format_name: "Buchungsstapel".to_string(),
200        format_version: 7,
201        erzeugt_am: chrono::Local::now().naive_local(),
202        beraternummer: 1000,
203        mandantennummer: 1,
204        wj_beginn: chrono::NaiveDate::from_ymd(2019,1,1),
205        sachkontenlänge: 4,
206        datum_von: chrono::NaiveDate::from_ymd(2019,1,1),
207        datum_bis: chrono::NaiveDate::from_ymd(2019,12,31),
208        ..Default::default()
209    };
210    let buchung = Buchung{
211        soll_haben_kennzeichen: SollHabenKennzeichen::Soll,
212        umsatz: 100.0,
213        beleg_datum: chrono::NaiveDate::from_ymd(2000, 2, 29),
214        konto: 1800,
215        gegenkonto: 1420,
216        buchungstext: Some("zahlung 123".to_string()),
217        ..Default::default()
218    };
219    let stapel = Buchungsstapel{
220        header: header,
221        buchungen: vec![buchung],
222    };
223    let _str = format!("{}", stapel);
224}
225
226#[test]
227fn test_extf_buchungstapel() {
228    use std::io::Read;
229    let mut f = std::fs::File::open("./test-data/EXTF_Buchungsstapel.csv").unwrap();
230    let mut buffer = Vec::new();
231    f.read_to_end(&mut buffer).unwrap();
232    //length in windows encoding
233    assert_eq!(buffer.len(), 21426);
234    let (cow, encoding_used, had_errors) = encoding_rs::WINDOWS_1252.decode(&buffer);
235    assert_eq!(had_errors, false);
236    assert_eq!(encoding_used, encoding_rs::WINDOWS_1252);
237    //length in utf-8 encoding
238    assert_eq!(cow.len(), 21447);
239    let str: String = cow.to_string();
240    println!("{}", str);
241    println!("done.");
242    let _stapel: Buchungsstapel = Buchungsstapel::try_from(str.as_str()).unwrap();
243}
244
245#[test]
246fn test_sage_export_buchungstapel() {
247    let input = r#""EXTF";510;21;"Buchungsstapel";7;20211106165344208;;"";"";"";1000;1;20170101;4;20170101;20171231;"";"";;;;"";;"";;;"";;;"";""
248Umsatz (ohne Soll/Haben-Kz);Soll/Haben-Kennzeichen;WKZ Umsatz;Kurs;Basis-Umsatz;WKZ Basis-Umsatz;Konto;Gegenkonto (ohne BU-Schlüssel);BU-Schlüssel;Belegdatum;Belegfeld 1;Belegfeld 2;Skonto;Buchungstext;Postensperre;Diverse Adressnummer;Geschäftspartnerbank;Sachverhalt;Zinssperre;Beleglink;Beleginfo - Art 1;Beleginfo - Inhalt 1;Beleginfo - Art 2;Beleginfo - Inhalt 2;Beleginfo - Art 3;Beleginfo - Inhalt 3;Beleginfo - Art 4;Beleginfo - Inhalt 4;Beleginfo - Art 5;Beleginfo - Inhalt 5;Beleginfo - Art 6;Beleginfo - Inhalt 6;Beleginfo - Art 7;Beleginfo - Inhalt 7;Beleginfo - Art 8;Beleginfo - Inhalt 8;KOST1 - Kostenstelle;KOST2 - Kostenstelle;Kost-Menge;EU-Land u. UStID;EU-Steuersatz;Abw. Versteuerungsart;Sachverhalt L+L;Funktionsergänzung L+L;BU 49 Hauptfunktionstyp;BU 49 Hauptfunktionsnummer;BU 49 Funktionsergänzung;Zusatzinformation - Art 1;Zusatzinformation- Inhalt 1;Zusatzinformation - Art 2;Zusatzinformation- Inhalt 2;Zusatzinformation - Art 3;Zusatzinformation- Inhalt 3;Zusatzinformation - Art 4;Zusatzinformation- Inhalt 4;Zusatzinformation - Art 5;Zusatzinformation- Inhalt 5;Zusatzinformation - Art 6;Zusatzinformation- Inhalt 6;Zusatzinformation - Art 7;Zusatzinformation- Inhalt 7;Zusatzinformation - Art 8;Zusatzinformation- Inhalt 8;Zusatzinformation - Art 9;Zusatzinformation- Inhalt 9;Zusatzinformation - Art 10;Zusatzinformation- Inhalt 10;Zusatzinformation - Art 11;Zusatzinformation- Inhalt 11;Zusatzinformation - Art 12;Zusatzinformation- Inhalt 12;Zusatzinformation - Art 13;Zusatzinformation- Inhalt 13;Zusatzinformation - Art 14;Zusatzinformation- Inhalt 14;Zusatzinformation - Art 15;Zusatzinformation- Inhalt 15;Zusatzinformation - Art 16;Zusatzinformation- Inhalt 16;Zusatzinformation - Art 17;Zusatzinformation- Inhalt 17;Zusatzinformation - Art 18;Zusatzinformation- Inhalt 18;Zusatzinformation - Art 19;Zusatzinformation- Inhalt 19;Zusatzinformation - Art 20;Zusatzinformation- Inhalt 20;Stück;Gewicht;Zahlweise;Forderungsart;Veranlagungsjahr;Zugeordnete Fälligkeit;Skontotyp;Auftragsnummer;Buchungstyp;USt-Schlüssel (Anzahlungen);EU-Land (Anzahlungen);Sachverhalt L+L (Anzahlungen);EU-Steuersatz (Anzahlungen);Erlöskonto (Anzahlungen);Herkunft-Kz;Buchungs GUID;KOST-Datum;SEPA-Mandatsreferenz;Skontosperre;Gesellschaftername;Beteiligtennummer;Identifikationsnummer;Zeichnernummer;Postensperre bis;Bezeichnung SoBil-Sachverhalt;Kennzeichen SoBil-Buchung;Festschreibung;Leistungsdatum;Datum Zuord. Steuerperiode
24925000,00;"S";"EUR";;;"";1800;1460;"";0610;"1";"";;"1";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;
25025000,00;"S";"EUR";;;"";1460;2900;"";1712;"2";"";;"2";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;
25125000,00;"S";"EUR";;;"";2900;1460;"";1712;"3";"";;"Storno von Journal";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;
25225000,00;"S";"EUR";;;"";1460;2900;"";0610;"4";"";;"2";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;
25325000,00;"S";"EUR";;;"";1810;1800;"";2410;"5";"";;"3";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;"#;
254    let stapel: Buchungsstapel = Buchungsstapel::try_from(input).unwrap();
255    println!("{:?}",stapel);
256    println!("{}", serde_json::to_string_pretty(&stapel).unwrap());
257    // panic!("x");
258}
259
260#[test]
261fn test_sage_export_buchung(){
262    let input = r#"389,92;"H";"EUR";;;"";70013;670;"";2912;"118";"280118";;"20171229_cyberport";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;"#;
263    let input2 = r#"74,08;"H";"EUR";;;"";70013;1406;"";2912;"118";"280118";;"20171229_cyberport";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;"#;
264    let input3 = r#"464,00;"H";"EUR";;;"";1810;70013;"";2912;"118";"";;"20171229_cyberport";;"";;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;;;"";"";;"";;"";;"";"";;"";;;;"#;    
265    let _b1 = Buchung::try_from(input).unwrap();
266    let _b2 = Buchung::try_from(input2).unwrap();
267    let _b3 = Buchung::try_from(input3).unwrap();
268}
269
270#[test]
271fn test_extf_debkred() {
272    use std::io::Read;
273    let mut f = std::fs::File::open("./test-data/EXTF_DebKred_Stamm.csv").unwrap();
274    let mut buffer = Vec::new();
275    f.read_to_end(&mut buffer).unwrap();
276    //length in windows encoding
277    assert_eq!(buffer.len(), 10294);
278    let (cow, encoding_used, had_errors) = encoding_rs::WINDOWS_1252.decode(&buffer);
279    assert_eq!(had_errors, false);
280    assert_eq!(encoding_used, encoding_rs::WINDOWS_1252);
281    //length in utf-8 encoding
282    assert_eq!(cow.len(), 10371);
283    let str: String = cow.to_string();
284    println!("{}", str);
285    println!("done.");
286    let stammdaten: DebKred_Stamm = DebKred_Stamm::try_from(str.as_str()).unwrap();
287    let first = stammdaten.debitoren_kreditoren.get(0).unwrap();
288    assert_eq!(first.konto, 10000);
289}