export_extf.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. from datetime import datetime
  2. import calendar
  3. import csv
  4. import pyodbc
  5. from pathlib import Path
  6. DSN = "dsn=GC_OPTIMA_64;uid=gaps;pwd=Gcbs12ma"
  7. class DatevConfig:
  8. data_path: str = "datev/data"
  9. export_path: str = "datev/export"
  10. translation_file: str = "datev/data/uebersetzungstabelle.csv"
  11. csv_date: datetime = datetime.now() # datetime(2023, 11, 20, 19, 2, 28, 714000)
  12. geschaeftsjahr_beginn: datetime = datetime(2024, 1, 1)
  13. periode: str = "202301"
  14. berater: int = 30612
  15. mandant: int = 10139
  16. konto_laenge: int = 5
  17. @property
  18. def datum_von(self):
  19. return datetime(int(self.periode[:4]), int(self.periode[4:]), 1)
  20. @property
  21. def datum_bis(self):
  22. year = int(self.periode[:4])
  23. month = int(self.periode[4:])
  24. end_of_month = calendar.monthrange(year, month)[1]
  25. return datetime(year, month, end_of_month)
  26. @property
  27. def header2(self):
  28. res = [
  29. "Umsatz (ohne Soll/Haben-Kz)",
  30. "Soll/Haben-Kennzeichen",
  31. "WKZ Umsatz",
  32. "Kurs",
  33. "Basis-Umsatz",
  34. "WKZ Basis-Umsatz",
  35. "Konto",
  36. "Gegenkonto (ohne BU-Schlüssel)",
  37. "BU-Schlüssel",
  38. "Belegdatum",
  39. "Belegfeld 1",
  40. "Belegfeld 2",
  41. "Skonto",
  42. "Buchungstext",
  43. "Postensperre",
  44. "Diverse Adressnummer",
  45. "Geschäftspartnerbank",
  46. "Sachverhalt",
  47. "Zinssperre",
  48. "Beleglink",
  49. "Beleginfo - Art 1",
  50. "Beleginfo - Inhalt 1",
  51. "Beleginfo - Art 2",
  52. "Beleginfo - Inhalt 2",
  53. "Beleginfo - Art 3",
  54. "Beleginfo - Inhalt 3",
  55. "Beleginfo - Art 4",
  56. "Beleginfo - Inhalt 4",
  57. "Beleginfo - Art 5",
  58. "Beleginfo - Inhalt 5",
  59. "Beleginfo - Art 6",
  60. "Beleginfo - Inhalt 6",
  61. "Beleginfo - Art 7",
  62. "Beleginfo - Inhalt 7",
  63. "Beleginfo - Art 8",
  64. "Beleginfo - Inhalt 8",
  65. "KOST1 - Kostenstelle",
  66. "KOST2 - Kostenstelle",
  67. "Kost-Menge",
  68. "EU-Land u. UStID",
  69. "EU-Steuersatz",
  70. "Abw. Versteuerungsart",
  71. "Sachverhalt L+L",
  72. "Funktionsergänzung L+L",
  73. "BU 49 Hauptfunktionstyp",
  74. "BU 49 Hauptfunktionsnummer",
  75. "BU 49 Funktionsergänzung",
  76. "Zusatzinformation - Art 1",
  77. "Zusatzinformation- Inhalt 1",
  78. "Zusatzinformation - Art 2",
  79. "Zusatzinformation- Inhalt 2",
  80. "Zusatzinformation - Art 3",
  81. "Zusatzinformation- Inhalt 3",
  82. "Zusatzinformation - Art 4",
  83. "Zusatzinformation- Inhalt 4",
  84. "Zusatzinformation - Art 5",
  85. "Zusatzinformation- Inhalt 5",
  86. "Zusatzinformation - Art 6",
  87. "Zusatzinformation- Inhalt 6",
  88. "Zusatzinformation - Art 7",
  89. "Zusatzinformation- Inhalt 7",
  90. "Zusatzinformation - Art 8",
  91. "Zusatzinformation- Inhalt 8",
  92. "Zusatzinformation - Art 9",
  93. "Zusatzinformation- Inhalt 9",
  94. "Zusatzinformation - Art 10",
  95. "Zusatzinformation- Inhalt 10",
  96. "Zusatzinformation - Art 11",
  97. "Zusatzinformation- Inhalt 11",
  98. "Zusatzinformation - Art 12",
  99. "Zusatzinformation- Inhalt 12",
  100. "Zusatzinformation - Art 13",
  101. "Zusatzinformation- Inhalt 13",
  102. "Zusatzinformation - Art 14",
  103. "Zusatzinformation- Inhalt 14",
  104. "Zusatzinformation - Art 15",
  105. "Zusatzinformation- Inhalt 15",
  106. "Zusatzinformation - Art 16",
  107. "Zusatzinformation- Inhalt 16",
  108. "Zusatzinformation - Art 17",
  109. "Zusatzinformation- Inhalt 17",
  110. "Zusatzinformation - Art 18",
  111. "Zusatzinformation- Inhalt 18",
  112. "Zusatzinformation - Art 19",
  113. "Zusatzinformation- Inhalt 19",
  114. "Zusatzinformation - Art 20",
  115. "Zusatzinformation- Inhalt 20",
  116. "Stück",
  117. "Gewicht",
  118. "Zahlweise",
  119. "Forderungsart",
  120. "Veranlagungsjahr",
  121. "Zugeordnete Fälligkeit",
  122. "Skontotyp",
  123. "Auftragsnummer",
  124. "Buchungstyp",
  125. "Ust-Schlüssel (Anzahlungen)",
  126. "EU-Land (Anzahlungen)",
  127. "Sachverhalt L+L (Anzahlungen)",
  128. "EU-Steuersatz (Anzahlungen)",
  129. "Erlöskonto (Anzahlungen)",
  130. "Herkunft-Kz",
  131. "Leerfeld",
  132. "KOST-Datum",
  133. "Mandatsreferenz",
  134. "Skontosperre",
  135. "Gesellschaftername",
  136. "Beteiligtennummer",
  137. "Identifikationsnummer",
  138. "Zeichnernummer",
  139. "Postensperre bis",
  140. "Bezeichnung SoBil-Sachverhalt",
  141. "Kennzeichen SoBil-Buchung",
  142. "Festschreibung",
  143. "Leistungsdatum",
  144. "Datum Zuord.Steuerperiode",
  145. ]
  146. return ";".join(res)
  147. row_template = (
  148. '{0};"{1}";"{2}";;;"";"{9}";"{4}";"";{5};"{6}";"";;"{7}";;"";;;;"";"";'
  149. + '"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"{10}";"";;"";;"";;;;;;"";"";"";"";"";"";"";"";"";"";"";"";"";'
  150. + '"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";;;;"";;;;"";"";;"";;'
  151. + ';;"";"";;"";;"";;"";"";;"";;{8};;'
  152. )
  153. # '592.80;H;EUR;"15800";90900;0101;6288;Opel Bank VoST 12/22 Lagerwag;1'
  154. @property
  155. def export_file(self):
  156. timestamp = self.csv_date.strftime("%Y%m%d_%H%M%S")
  157. period = self.datum_von.strftime("%Y%m")
  158. return f"{self.export_path}/EXTF_Buchungsstapel_30612_10139_{period}_{timestamp}.csv"
  159. @property
  160. def header(self):
  161. datev_header = {
  162. "Datev-Format-KZ": "EXTF",
  163. "Versionsnummer": 510,
  164. "Datenkategorie": 21,
  165. "Formatname": "Buchungsstapel",
  166. "Formatversion": 7,
  167. "Erzeugt_am": self.csv_date.strftime("%Y%m%d%H%M%S%f")[:-3],
  168. "Importiert_am": "",
  169. "Herkunftskennzeichen": "SV",
  170. "Exportiert_von": "dracar",
  171. "Importiert_von": "",
  172. "Berater": self.berater,
  173. "Mandant": self.mandant,
  174. "WJ-Beginn": self.geschaeftsjahr_beginn.strftime("%Y%m%d"),
  175. "Sachkontenlänge": self.konto_laenge,
  176. "Datum_von": self.datum_von.strftime("%Y%m%d"),
  177. "Datum_bis": self.datum_bis.strftime("%Y%m%d"),
  178. "Bezeichnung": "",
  179. "Diktatkürzel": "HE",
  180. "Buchungstyp": 1,
  181. "Rechnungslegungszweck": "",
  182. "Festschreibeinformation": 1,
  183. "WKZ": "",
  184. "reserviert_1": "",
  185. "Derivatskennzeichen": "",
  186. "reserviert_2": "",
  187. "reserviert_3": "",
  188. "SKR": "",
  189. "Branchenlösung-Id": "",
  190. "reserviert_4": "",
  191. "reserviert_5": "",
  192. "Anwendungsinformation": "",
  193. }
  194. template = (
  195. '"EXTF";{Versionsnummer};{Datenkategorie};"Buchungsstapel";{Formatversion};{Erzeugt_am};'
  196. + ';"SV";"dracar";"";{Berater};{Mandant};{WJ-Beginn};{Sachkontenlänge};{Datum_von};{Datum_bis};"";"HE";1;;1;"";;"";;;"";;;"";""'
  197. )
  198. return template.format(**datev_header)
  199. def get_translation(cfg: DatevConfig):
  200. translation = {}
  201. with Path(cfg.translation_file).open("r", encoding="latin-1") as frh:
  202. for line in csv.reader(frh, delimiter=";"):
  203. acct_no = line[0][:4] + "0"
  204. acct_details = "11" + line[0][11:].replace("-", "")
  205. translation[line[2]] = (acct_no, acct_details)
  206. return translation
  207. def from_database(period):
  208. with pyodbc.connect(DSN) as conn:
  209. cursor = conn.cursor()
  210. query = (
  211. "SELECT * FROM [import].[DATEV_Buchungsstapel] "
  212. + f"WHERE [BOOKKEEP_PERIOD] = '{period}' ORDER BY [BOOKKEEP_DATE], [UNIQUE_IDENT]"
  213. )
  214. cursor.execute(query)
  215. for row in cursor.fetchall():
  216. yield list(map(str, row[:9]))
  217. def from_csv(import_file):
  218. with import_file.open("r", encoding="latin-1") as frh:
  219. csv_reader = csv.reader(frh, delimiter=";")
  220. next(csv_reader) # ignore header
  221. for row in csv_reader:
  222. yield row[:9]
  223. def export_extf(period, import_method="csv"):
  224. cfg = DatevConfig()
  225. cfg.periode = period
  226. translation = get_translation(cfg)
  227. if import_method == "csv":
  228. import_file = Path(f"datev/data/{period}.csv")
  229. cfg.csv_date = datetime.fromtimestamp(import_file.stat().st_mtime)
  230. get_row = from_csv(import_file)
  231. else:
  232. get_row = from_database(cfg.periode)
  233. missing = []
  234. with Path(cfg.export_file).open("w", encoding="latin-1", newline="") as fwh:
  235. fwh.write(cfg.header + "\r\n")
  236. fwh.write(cfg.header2 + "\r\n")
  237. for row in get_row:
  238. row[0] = row[0].replace(".", ",")
  239. row.extend(translation.get(row[3], (row[3], "11000000")))
  240. if row[9] == row[3]:
  241. missing.append(row[3])
  242. fwh.write(cfg.row_template.format(*row) + "\r\n")
  243. print(set(missing))
  244. def export_all_periods():
  245. for period in range(202301, 202313):
  246. export_extf(str(period), "db")
  247. if __name__ == "__main__":
  248. # export_all_periods()
  249. export_extf("202401", "csv")