datev_types/
header.rs

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  /// 16 = Debitoren-/Kreditoren
19  /// 20 = Sachkontenbeschriftungen
20  /// 21 = Buchungsstapel
21  /// 46 = Zahlungsbedingungen
22  /// 48 = Diverse Adressen
23  /// 65 = Wiederkehrende Buchungen
24  pub format_kategorie: u16,
25  #[validate(regex = "FORMATNAME")]
26  pub format_name: String,
27  /// Debitoren-/Kreditoren = 5
28  /// Sachkontenbeschriftungen = 3
29  /// Buchungsstapel = 12
30  /// Zahlungsbedingungen = 2
31  /// Wiederkehrende Buchungen = 4
32  /// Diverse Adressen = 2
33  pub format_version: u16,
34  /// Zeitstempel:
35  /// YYYYMMDDHHMMSSFFF
36  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  /// Bereich 1001-9999999
46  pub beraternummer: u32,
47  /// Bereich 1-99999
48  pub mandantennummer: u32,
49  /// Wirtschaftsjahresbeginn
50  /// Format: YYYYMMDD
51  pub wj_beginn: NaiveDate,
52  /// Nummernlänge der Sachkonten.
53  /// Wert muss beim Import mit Konfiguration des Mandats in der DATEV App übereinstimmen.
54  pub sachkontenlänge: u32,
55  /// Beginn der Periode des Stapels
56  /// Format: YYYYMMDD
57  pub datum_von: NaiveDate,
58  /// Ende der Periode des Stapels
59  /// Format: YYYYMMDD
60  pub datum_bis: NaiveDate,
61  /// Bezeichnung des Stapels
62  /// z.B. „Rechnungsausgang 09/2019“
63  #[validate(length(min = 0, max = 30))]
64  #[serde(skip_serializing_if = "Option::is_none")]
65  pub bezeichnung: Option<String>,
66  /// Kürzel in Großbuchstaben des Bearbeiters
67  /// z.B. "MM" für Max Mustermann
68  #[validate(length(min = 0, max = 2))]
69  #[serde(skip_serializing_if = "Option::is_none")]
70  pub diktatkürzel: Option<String>,
71  /// 1 = Finanzbuchführung (default)
72  /// 2 = Jahresabschluss
73  #[serde(skip_serializing_if = "Option::is_none")]
74  pub buchungstyp: Option<BuchungsTyp>,
75  /// 0 = unabhängig (default)
76  /// 30 = Steuerrecht
77  /// 40 = Kalkulatorik
78  /// 50 = Handelsrecht
79  /// 64 = IFRS
80  #[serde(skip_serializing_if = "Option::is_none")]
81  pub rechnungslegungszweck: Option<u8>,
82  /// 0 = keine Festschreibung
83  /// 1 = Festschreibung (default)
84  // #[serde(default = "default_festschreibung")]
85  #[serde(skip_serializing_if = "Option::is_none")]
86  pub festschreibung: Option<Festschreibung>,
87  /// ISO-Code der Währung "EUR" = default
88  #[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  /// Sachkontenrahmen der für die Bewegungsdaten verwendet wurde
100  #[serde(skip_serializing_if = "Option::is_none")]
101  pub sachkontenrahmen: Option<String>,
102  /// Falls eine spezielle DATEV Branchenlösung genutzt wird.
103  #[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  /// Verarbeitungskennzeichen der abgebenden Anwendung
111  // z.B. „09/2019“
112  #[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      //manual way
273      let mut iter = rdr.records();
274      if let Some(result) = iter.next() {
275          let record = result.unwrap();
276          let mut header = Header::default();
277          //add values
278          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  /// externe Daten
459  EXTF,
460  /// datev Daten
461  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}