1use serde::{Serialize, Deserialize};
2use validator::Validate;
3use regex::Regex;
4use std::fmt::Display;
5use std::fmt::Formatter;
6use chrono::NaiveDate;
7use chrono::NaiveDateTime;
8
9lazy_static! {
10 static ref FORMATNAME: Regex = Regex::new(r#"^(Buchungsstapel|Wiederkehrende Buchungen|Debitoren/Kreditoren|Sachkontenbeschriftungen|Zahlungsbedingungen|Diverse Adressen)$"#).unwrap();
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, Validate, Deserialize, Serialize)]
14#[serde(rename_all = "PascalCase")]
15pub struct Header {
16 pub kennzeichen: Kennzeichen,
17 pub versionsnummer: u32,
18 pub format_kategorie: u16,
25 #[validate(regex = "FORMATNAME")]
26 pub format_name: String,
27 pub format_version: u16,
34 pub erzeugt_am: NaiveDateTime,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub leerfeld1: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub leerfeld2: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub leerfeld3: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub leerfeld4: Option<String>,
45 pub beraternummer: u32,
47 pub mandantennummer: u32,
49 pub wj_beginn: NaiveDate,
52 pub sachkontenlänge: u32,
55 pub datum_von: NaiveDate,
58 pub datum_bis: NaiveDate,
61 #[validate(length(min = 0, max = 30))]
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub bezeichnung: Option<String>,
66 #[validate(length(min = 0, max = 2))]
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub diktatkürzel: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
74 pub buchungstyp: Option<BuchungsTyp>,
75 #[serde(skip_serializing_if = "Option::is_none")]
81 pub rechnungslegungszweck: Option<u8>,
82 #[serde(skip_serializing_if = "Option::is_none")]
86 pub festschreibung: Option<Festschreibung>,
87 #[validate(length(min = 0, max = 3))]
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub wkz: Option<String>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub leerfeld5: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub derivatskennzeichen: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub leerfeld6: Option<String>,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub leerfeld7: Option<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub sachkontenrahmen: Option<String>,
102 #[validate(length(min = 0, max = 4))]
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub id_der_branchenlösung: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub leerfeld8: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub leerfeld9: Option<String>,
110 #[validate(length(min = 0, max = 16))]
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub anwendungsinformation: Option<String>,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
118#[serde(rename_all = "PascalCase")]
119pub enum BuchungsTyp {
120 Finanzbuchführung,
121 Jahresabschluss,
122}
123
124impl Default for Header {
125 fn default() -> Self {
126 Header{
127 kennzeichen: Kennzeichen::EXTF,
128 versionsnummer: 700,
129 format_kategorie: 21,
130 format_name: String::from("Buchungsstapel"),
131 format_version: 12,
132 erzeugt_am: chrono::Local::now().naive_local(),
133 leerfeld1: None,
134 leerfeld2: None,
135 leerfeld3: None,
136 leerfeld4: None,
137 beraternummer: 0,
138 mandantennummer: 0,
139 wj_beginn: NaiveDate::from_ymd(2000, 1, 1),
140 sachkontenlänge: 0,
141 datum_von: NaiveDate::from_ymd(2000, 1, 1),
142 datum_bis: NaiveDate::from_ymd(2000, 12, 31),
143 bezeichnung: None,
144 diktatkürzel: None,
145 buchungstyp: None,
146 rechnungslegungszweck: None,
147 festschreibung: None,
148 wkz: None,
149 leerfeld5: None,
150 derivatskennzeichen: None,
151 leerfeld6: None,
152 leerfeld7: None,
153 sachkontenrahmen: None,
154 id_der_branchenlösung: None,
155 leerfeld8: None,
156 leerfeld9: None,
157 anwendungsinformation: None,
158 }
159 }
160}
161
162impl Display for Header {
163 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
164 write!(f, r#""{kennzeichen}";{versionsnummer};{format_kategorie};"{format_name}";{format_version};{erzeugt_am};{leerfeld1};{leerfeld2};{leerfeld3};{leerfeld4};{beraternummer};{mandantennummer};{wj_beginn};{sachkontenlänge};{datum_von};{datum_bis};{bezeichnung};{diktatkürzel};{buchungstyp};{rechnungslegungszweck};{festschreibung};{wkz};{leerfeld5};{derivatskennzeichen};{leerfeld6};{leerfeld7};{sachkontenrahmen};{id_der_branchenlösung};{leerfeld8};{leerfeld9};{anwendungsinformation}{newline}"#,
165 kennzeichen=self.kennzeichen,
166 versionsnummer=self.versionsnummer,
167 format_kategorie=self.format_kategorie,
168 format_name=self.format_name,
169 format_version=self.format_version,
170 erzeugt_am=self.erzeugt_am.format("%Y%m%d%H%M%S%3f"),
171 leerfeld1=match &self.leerfeld1{
172 Some(x) => format!("{}", x),
173 None => String::from(""),
174 },
175 leerfeld2=match &self.leerfeld2{
176 Some(x) => format!("{}", x),
177 None => String::from(""),
178 },
179 leerfeld3=match &self.leerfeld3{
180 Some(x) => format!("{}", x),
181 None => String::from(""),
182 },
183 leerfeld4=match &self.leerfeld4{
184 Some(x) => format!("{}", x),
185 None => String::from(""),
186 },
187 beraternummer=self.beraternummer,
188 mandantennummer=self.mandantennummer,
189 wj_beginn=self.wj_beginn.format("%Y%m%d").to_string(),
190 sachkontenlänge=self.sachkontenlänge,
191 datum_von=self.datum_von.format("%Y%m%d").to_string(),
192 datum_bis=self.datum_bis.format("%Y%m%d").to_string(),
193 bezeichnung=match &self.bezeichnung{
194 Some(x) => format!("{}", x),
195 None => String::from(""),
196 },
197 diktatkürzel=match &self.diktatkürzel{
198 Some(x) => format!("{}", x),
199 None => String::from(""),
200 },
201 buchungstyp = match self.buchungstyp {
202 Some(BuchungsTyp::Finanzbuchführung) => "1",
203 Some(BuchungsTyp::Jahresabschluss) => "2",
204 _ => "",
205 },
206 rechnungslegungszweck = match self.rechnungslegungszweck {
207 Some(0) => "0",
208 Some(30) => "30",
209 Some(40) => "40",
210 Some(50) => "50",
211 Some(64) => "64",
212 None => "",
213 _ => "",
214 },
215 festschreibung= match self.festschreibung {
216 Some(Festschreibung::KeineFestschreibung) => "0",
217 Some(Festschreibung::Festschreibung) => "1",
218 None => "",
219 },
220 wkz = match &self.wkz {
221 Some(val) => val.to_string(),
222 None => "".to_string(),
223 },
224 leerfeld5=match &self.leerfeld5{
225 Some(x) => format!("{}", x),
226 None => String::from(""),
227 },
228 derivatskennzeichen=match &self.derivatskennzeichen{
229 Some(x) => format!("{}", x),
230 None => String::from(""),
231 },
232 leerfeld6=match &self.leerfeld6{
233 Some(x) => format!("{}", x),
234 None => String::from(""),
235 },
236 leerfeld7=match &self.leerfeld7{
237 Some(x) => format!("{}", x),
238 None => String::from(""),
239 },
240 sachkontenrahmen=match &self.sachkontenrahmen{
241 Some(x) => format!("{}", x),
242 None => String::from(""),
243 },
244 id_der_branchenlösung=match &self.id_der_branchenlösung{
245 Some(x) => format!("{}", x),
246 None => String::from(""),
247 },
248 leerfeld8=match &self.leerfeld8{
249 Some(x) => format!("{}", x),
250 None => String::from(""),
251 },
252 leerfeld9=match &self.leerfeld9{
253 Some(x) => format!("{}", x),
254 None => String::from(""),
255 },
256 anwendungsinformation=match &self.anwendungsinformation{
257 Some(x) => format!("{}", x),
258 None => String::from(""),
259 },
260 newline = "\n",
261 )
262 }
263}
264
265impl TryFrom<&str> for Header {
266 type Error = &'static str;
267
268 fn try_from(value: &str) -> Result<Self, Self::Error> {
269 let mut rdr = csv::ReaderBuilder::new().delimiter(b';')
270 .has_headers(false).from_reader(value.as_bytes());
271
272 let mut iter = rdr.records();
274 if let Some(result) = iter.next() {
275 let record = result.unwrap();
276 let mut header = Header::default();
277 if let Some(val) = record.get(0) {
279 if val.len() > 0 {
280 header.kennzeichen = match val {
281 "EXTF" => Kennzeichen::EXTF,
282 "DTVF" => Kennzeichen::DTVF,
283 _ => return Err("Kennzeichen ist weder EXTF noch DTVF"),
284 };
285 }
286 }
287 if let Some(val) = record.get(1) {
288 header.versionsnummer = val.parse::<u32>().unwrap();
289 }
290 if let Some(val) = record.get(2) {
291 header.format_kategorie = val.parse::<u16>().unwrap();
292 }
293 if let Some(val) = record.get(3) {
294 header.format_name = val.to_string();
295 }
296 if let Some(val) = record.get(4) {
297 header.format_version = val.parse::<u16>().unwrap();
298 }
299 if let Some(val) = record.get(5) {
300 header.erzeugt_am = NaiveDateTime::parse_from_str(val, "%Y%m%d%H%M%S%3f").unwrap();
301 }
302 if let Some(val) = record.get(6) {
303 if val.len() > 0 {
304 header.leerfeld1 = Some(val.to_string());
305 }
306 }
307 if let Some(val) = record.get(7) {
308 if val.len() > 0 {
309 header.leerfeld2 = Some(val.to_string());
310 }
311 }
312 if let Some(val) = record.get(8) {
313 if val.len() > 0 {
314 header.leerfeld3 = Some(val.to_string());
315 }
316 }
317 if let Some(val) = record.get(9) {
318 if val.len() > 0 {
319 header.leerfeld4 = Some(val.to_string());
320 }
321 }
322 if let Some(val) = record.get(10) {
323 header.beraternummer = val.parse::<u32>().unwrap();
324 }
325 if let Some(val) = record.get(11) {
326 header.mandantennummer = val.parse::<u32>().unwrap();
327 }
328 if let Some(val) = record.get(12) {
329 header.wj_beginn = NaiveDate::parse_from_str(val, "%Y%m%d").unwrap();
330 }
331 if let Some(val) = record.get(13) {
332 header.sachkontenlänge = val.parse::<u32>().unwrap();
333 }
334 if let Some(val) = record.get(14) {
335 if val.len() > 0 {
336 header.datum_von = NaiveDate::parse_from_str(val, "%Y%m%d").unwrap();
337 }
338 }
339 if let Some(val) = record.get(15) {
340 if val.len() > 0 {
341 header.datum_bis = NaiveDate::parse_from_str(val, "%Y%m%d").unwrap();
342 }
343 }
344 if let Some(val) = record.get(16) {
345 if val.len() > 0 {
346 header.bezeichnung = Some(val.to_string());
347 }
348 }
349 if let Some(val) = record.get(17) {
350 if val.len() > 0 {
351 header.diktatkürzel = Some(val.to_string());
352 }
353 }
354 if let Some(val) = record.get(18) {
355 header.buchungstyp = match val {
356 "1" => Some(BuchungsTyp::Finanzbuchführung),
357 "2" => Some(BuchungsTyp::Jahresabschluss),
358 _ => None,
359 };
360 }
361 if let Some(val) = record.get(19) {
362 header.rechnungslegungszweck = match val {
363 "0" => Some(0),
364 "30" => Some(30),
365 "40" => Some(40),
366 "50" => Some(50),
367 "64" => Some(64),
368 _ => None,
369 };
370 }
371 if let Some(val) = record.get(20) {
372 header.festschreibung = match val {
373 "0" => Some(Festschreibung::KeineFestschreibung),
374 "1" => Some(Festschreibung::Festschreibung),
375 _ => None,
376 };
377 }
378 if let Some(val) = record.get(21) {
379 if !val.is_empty() {
380 header.wkz = Some(val.to_string());
381 } else {
382 header.wkz = None;
383 }
384 }
385 if let Some(val) = record.get(22) {
386 if val.len() > 0 {
387 header.leerfeld5 = Some(val.to_string());
388 }
389 }
390 if let Some(val) = record.get(23) {
391 if val.len() > 0 {
392 header.derivatskennzeichen = Some(val.to_string());
393 }
394 }
395 if let Some(val) = record.get(24) {
396 if val.len() > 0 {
397 header.leerfeld6 = Some(val.to_string());
398 }
399 }
400 if let Some(val) = record.get(25) {
401 if val.len() > 0 {
402 header.leerfeld7 = Some(val.to_string());
403 }
404 }
405 if let Some(val) = record.get(26) {
406 if val.len() > 0 {
407 header.sachkontenrahmen = Some(val.to_string());
408 }
409 }
410 if let Some(val) = record.get(27) {
411 if val.len() > 0 {
412 header.id_der_branchenlösung = Some(val.to_string());
413 }
414 }
415 if let Some(val) = record.get(28) {
416 if val.len() > 0 {
417 header.leerfeld8 = Some(val.to_string());
418 }
419 }
420 if let Some(val) = record.get(29) {
421 if val.len() > 0 {
422 header.leerfeld9 = Some(val.to_string());
423 }
424 }
425 if let Some(val) = record.get(30) {
426 if val.len() > 0 {
427 header.anwendungsinformation = Some(val.to_string());
428 }
429 }
430 Ok(header)
431 } else {
432 Err("No header found")
433 }
434 }
435}
436
437#[derive(Clone, PartialEq, Debug, Eq, Serialize, Deserialize)]
438pub enum Festschreibung{
439 KeineFestschreibung,
440 Festschreibung,
441}
442impl Default for Festschreibung {
443 fn default() -> Self {
444 Festschreibung::Festschreibung
445 }
446}
447impl Display for Festschreibung {
448 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
449 write!(f, "{}", match self {
450 Festschreibung::KeineFestschreibung => "0",
451 Festschreibung::Festschreibung => "1",
452 })
453 }
454}
455
456#[derive(Clone, PartialEq, Debug, Eq, Serialize, Deserialize)]
457pub enum Kennzeichen{
458 EXTF,
460 DTVF,
462}
463
464impl Default for Kennzeichen {
465 fn default() -> Self {
466 Kennzeichen::EXTF
467 }
468}
469
470impl Display for Kennzeichen {
471 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
472 write!(f, "{}", match self {
473 Kennzeichen::EXTF => "EXTF",
474 Kennzeichen::DTVF => "DTVF",
475 })
476 }
477}