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 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 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 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 }
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 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 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}