gchr.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. import csv
  2. import logging
  3. import os
  4. import xml.etree.ElementTree as ET
  5. from dataclasses import dataclass
  6. from datetime import datetime
  7. from pathlib import Path
  8. from xml.dom import minidom
  9. import numpy as np
  10. import pandas as pd
  11. ACCOUNT_INFO = [
  12. "Account",
  13. "Make",
  14. "Site",
  15. "Origin",
  16. "SalesChannel",
  17. "CostCarrier",
  18. "CostAccountingString",
  19. ]
  20. TRANSLATE = [
  21. "Konto_Nr_Händler",
  22. "Konto_Nr_SKR51",
  23. "Marke",
  24. "Standort",
  25. "Konto_Nr",
  26. "Kostenstelle",
  27. "Absatzkanal",
  28. "Kostenträger",
  29. "Kontoart",
  30. "Konto_1",
  31. "KRM",
  32. "IsNumeric",
  33. ]
  34. @dataclass
  35. class GchrConfig:
  36. first_month_of_financial_year: str
  37. data_path: str
  38. gcstruct_path: str
  39. export_path: str
  40. @dataclass
  41. class GchrExportConfig:
  42. current_year: str
  43. current_month: str
  44. class GCHR:
  45. booking_date: datetime
  46. df_bookings: pd.DataFrame = None
  47. df_translate: pd.DataFrame = None
  48. df_translate2: pd.DataFrame = None
  49. makes: dict[str, str] = None
  50. sites: dict[str, str] = None
  51. current_year: str
  52. current_month: str
  53. def __init__(self, base_dir: str) -> None:
  54. self.base_dir = base_dir
  55. self.account_translation = f"{self.base_dir}/data/Kontenrahmen_uebersetzt.csv"
  56. self.account_bookings = list(Path(self.base_dir).joinpath("data").glob("GuV_Bilanz_Salden*.csv"))
  57. self.first_month_of_financial_year = "10"
  58. pd.set_option("display.max_rows", 500)
  59. pd.set_option("display.float_format", lambda x: "%.2f" % x)
  60. def set_bookkeep_period(self, year: str, month: str) -> None:
  61. self.current_year = year
  62. self.current_month = month
  63. period = f"{year}-{month}"
  64. prot_file = f"{self.export_info_dir}/protokoll_{period}.log"
  65. logging.basicConfig(
  66. filename=prot_file,
  67. filemode="w",
  68. encoding="utf-8",
  69. level=logging.DEBUG,
  70. force=True,
  71. )
  72. self.debug_file = f"{self.export_info_dir}/debug_{period}.csv"
  73. self.account_ignored = f"{self.export_info_dir}/ignoriert_{period}.csv"
  74. # self.account_invalid = f"{self.export_info_dir}/ungueltig_{period}.csv"
  75. self.last_year = str(int(self.current_year) - 1)
  76. self.last_year2 = str(int(self.current_year) - 2)
  77. self.next_year = str(int(self.current_year) + 1)
  78. def header(self, makes_used: list[str], sites_used: list[str], main_site: str) -> dict[str, str]:
  79. return {
  80. "Country": "DE",
  81. "MainBmCode": main_site,
  82. "Month": self.current_month,
  83. "Year": self.current_year,
  84. "Currency": "EUR",
  85. "NumberOfMakes": len(makes_used),
  86. "NumberOfSites": len(sites_used),
  87. "ExtractionDate": self.booking_date.strftime("%d.%m.%Y"),
  88. "ExtractionTime": self.booking_date.strftime("%H:%M:%S"),
  89. "BeginFiscalYear": self.first_month_of_financial_year,
  90. }
  91. @property
  92. def bookkeep_filter(self) -> dict[str, str]:
  93. period = [self.current_year + str(i).zfill(2) for i in range(1, 13)]
  94. if self.first_month_of_financial_year != "01":
  95. if self.first_month_of_financial_year > self.current_month:
  96. period = [self.last_year + str(i).zfill(2) for i in range(1, 13)] + period
  97. else:
  98. period = period + [self.next_year + str(i).zfill(2) for i in range(1, 13)]
  99. fm = int(self.first_month_of_financial_year)
  100. period = period[fm - 1 : fm + 12]
  101. period = [self.current_year + "00"] + period
  102. rename_to = ["OpeningBalance"] + ["Period" + str(i).zfill(2) for i in range(1, 13)]
  103. return dict(zip(period, rename_to))
  104. def extract_acct_info(self, df: pd.DataFrame) -> pd.DataFrame:
  105. acct_info = [
  106. "Marke",
  107. "Standort",
  108. "Konto_Nr",
  109. "Kostenstelle",
  110. "Absatzkanal",
  111. "Kostenträger",
  112. ]
  113. df["HasFiveDashes"] = df["Konto_Nr_SKR51"].str.count("-") == 5
  114. df["Invalid"] = "XX-XX-XXXX-XX-XX-XX"
  115. df["Konto_Nr_SKR51"] = np.where(
  116. df["HasFiveDashes"],
  117. df["Konto_Nr_SKR51"],
  118. df["Invalid"],
  119. )
  120. df[acct_info] = df["Konto_Nr_SKR51"].str.split(pat="-", n=6, expand=True)
  121. return df
  122. def export_all_periods(self, overwrite=False, today=None) -> None:
  123. dt = datetime.now()
  124. if today is not None:
  125. dt = datetime.fromisoformat(today)
  126. prev = str(dt.year - 1)
  127. periods = [(prev, str(x).zfill(2)) for x in range(dt.month, 13)] + [
  128. (str(dt.year), str(x).zfill(2)) for x in range(1, dt.month)
  129. ]
  130. for year, month in periods:
  131. filename = self.export_filename_for_period(year, month)
  132. if overwrite or not Path(filename).exists():
  133. os.makedirs(Path(filename).parent.joinpath("info"), exist_ok=True)
  134. self.export_period(year, month)
  135. def export_period(self, year: str, month: str) -> str:
  136. self.set_bookkeep_period(year, month)
  137. # Übersetzungstabelle laden
  138. self.get_translation()
  139. # Kontensalden laden
  140. df_bookings = self.filter_bookings()
  141. all_periods = set(df_bookings["Bookkeep Period"].to_list())
  142. bookkeep_period_date = datetime(int(year), int(month), 28)
  143. if df_bookings.shape[0] == 0 or len(all_periods) <= 1 or self.booking_date < bookkeep_period_date:
  144. logging.error("ABBRUCH!!! Keine Daten vorhanden!")
  145. return False
  146. filter_to = self.current_year + self.current_month
  147. period_no = list(self.bookkeep_filter.keys()).index(filter_to) + 1
  148. logging.info("df_bookings: " + str(df_bookings.shape))
  149. # Join auf Übersetzung
  150. df_combined = df_bookings.merge(self.df_translate, how="inner", on="Konto_Nr_Händler")
  151. logging.info(f"df_combined: {df_combined.shape}")
  152. df_pivot = df_combined.pivot_table(
  153. index=["Konto_Nr_SKR51"],
  154. columns=["period"],
  155. values="amount",
  156. aggfunc="sum",
  157. margins=True,
  158. margins_name="CumulatedYear",
  159. )
  160. df_pivot.drop(index="CumulatedYear", inplace=True)
  161. logging.info("df_pivot: " + str(df_pivot.shape))
  162. df = df_pivot.merge(self.df_translate2, how="inner", on="Konto_Nr_SKR51")
  163. makes_used = sorted(list(set(df["Marke"].to_list())))
  164. sites_used = sorted(list(set((df["Marke"] + "-" + df["Standort"]).to_list())))
  165. from_label = ["Marke", "Standort", "Konto_Nr", "Kostenstelle", "Absatzkanal", "Kostenträger", "KRM"]
  166. to_label = ["Make", "Site", "Account", "Origin", "SalesChannel", "CostCarrier", "CostAccountingString"]
  167. col_dict = dict(zip(from_label, to_label))
  168. df = df.rename(columns=col_dict)
  169. export_csv = self.export_filename[:-4] + ".csv"
  170. df.to_csv(export_csv, decimal=",", sep=";", encoding="latin-1", index=False)
  171. df = df[df["IsNumeric"] != False].groupby(ACCOUNT_INFO, as_index=False).aggregate("sum")
  172. # Infos ergänzen
  173. df["Decimals"] = 2
  174. # df.sort_values(by=["Konto_Nr_SKR51"], inplace=True)
  175. logging.info(df.shape)
  176. main_sites = [self.sites[s] for s in sites_used if s in self.sites and self.sites[s] != "0000"]
  177. for i, main_site in enumerate(main_sites):
  178. filename = self.export_filename
  179. if i > 0:
  180. filename = f"{filename[:-4]}_{main_site}.xml"
  181. self.export_skr51_xml(
  182. df.to_dict(orient="records"),
  183. self.bookkeep_filter,
  184. period_no,
  185. makes_used,
  186. sites_used,
  187. main_site,
  188. filename,
  189. )
  190. # Join auf Übersetzung - nicht zugeordnet
  191. df_ignored = df_bookings.merge(self.df_translate, how="left", on="Konto_Nr_Händler")
  192. df_ignored = df_ignored[df_ignored["Konto_Nr_SKR51"].isna()]
  193. if not df_ignored.empty:
  194. df_ignored = df_ignored.pivot_table(
  195. index=["Konto_Nr_Händler"],
  196. columns=["period"],
  197. values="amount",
  198. aggfunc="sum",
  199. margins=True,
  200. margins_name="CumulatedYear",
  201. )
  202. df_ignored.to_csv(self.account_ignored, decimal=",", sep=";", encoding="latin-1")
  203. return self.export_filename
  204. def get_translation(self) -> pd.DataFrame:
  205. if self.df_translate is None:
  206. df_translate_import = pd.read_csv(
  207. self.account_translation,
  208. decimal=",",
  209. sep=";",
  210. encoding="latin-1",
  211. converters={i: str for i in range(0, 200)},
  212. ).reset_index()
  213. df_makes = df_translate_import[["Marke", "Marke_HBV"]].copy().drop_duplicates()
  214. df_makes = df_makes[df_makes["Marke_HBV"] != "0000"]
  215. self.makes = dict([(e["Marke"], e["Marke_HBV"]) for e in df_makes.to_dict(orient="records")])
  216. self.makes["99"] = "0000"
  217. df_sites = df_translate_import[["Marke", "Standort", "Standort_HBV"]].copy().drop_duplicates()
  218. df_sites["Standort_HBV"] = np.where(
  219. df_sites["Standort_HBV"].str.len() != 6, "0000", df_sites["Standort_HBV"]
  220. )
  221. self.sites = dict(
  222. [(e["Marke"] + "-" + e["Standort"], e["Standort_HBV"]) for e in df_sites.to_dict(orient="records")]
  223. )
  224. df_prepared = self.prepare_translation(df_translate_import)
  225. self.df_translate = self.special_translation(df_prepared)
  226. self.df_translate2 = (
  227. self.df_translate.drop(columns=["Konto_Nr_Händler"])
  228. .copy()
  229. .drop_duplicates()
  230. .set_index("Konto_Nr_SKR51")
  231. )
  232. return self.df_translate
  233. def prepare_translation(self, df_translate_import: pd.DataFrame):
  234. df_translate = df_translate_import[
  235. [
  236. "Konto_Nr_Händler",
  237. "Konto_Nr_SKR51",
  238. ]
  239. ].drop_duplicates()
  240. logging.info(df_translate.shape)
  241. row = {
  242. "Konto_Nr_Händler": "01-01-0861-00-00-00",
  243. "Konto_Nr_SKR51": "01-01-0861-00-00-00",
  244. }
  245. df_translate = pd.concat([df_translate, pd.DataFrame.from_records([row])])
  246. df_translate.set_index("Konto_Nr_Händler")
  247. return df_translate
  248. def special_translation(self, df: pd.DataFrame) -> pd.DataFrame:
  249. df["Konto_Nr_Händler"] = df["Konto_Nr_Händler"].str.upper()
  250. df["Konto_Nr_SKR51"] = df["Konto_Nr_SKR51"].str.upper()
  251. df = self.extract_acct_info(df)
  252. df["Konto_Nr"] = df["Konto_Nr"].str.upper()
  253. logging.info(df.shape)
  254. logging.info(df.columns)
  255. logging.info(df.head())
  256. logging.info("df: " + str(df.shape))
  257. df["Bilanz"] = df["Konto_Nr"].str.match(r"^[013]")
  258. df["Kontoart"] = np.where(df["Bilanz"], "1", "2")
  259. df["Kontoart"] = np.where(df["Konto_Nr"].str.contains("_STK"), "3", df["Kontoart"])
  260. df["Kontoart"] = np.where(df["Konto_Nr"].str.match(r"^[9]"), "3", df["Kontoart"])
  261. df["Konto_1"] = df["Konto_Nr"].str.slice(0, 1)
  262. # fehlende Marken- und Standortzuordnung
  263. df["Marke"] = np.where(df["Marke"].isin(self.makes.keys()), df["Marke"], "99")
  264. df["Marke_Standort"] = df["Marke"] + "-" + df["Standort"]
  265. df["Standort"] = np.where(df["Marke_Standort"].isin(self.sites.keys()), df["Standort"], "01")
  266. df_debug = df.drop(columns=["Bilanz"])
  267. logging.info(df_debug.groupby(["Kontoart"]).aggregate("sum"))
  268. logging.info(df_debug.groupby(["Kontoart", "Konto_1"]).aggregate("sum"))
  269. logging.info(df_debug.groupby(["Konto_Nr"]).aggregate("sum"))
  270. df_debug.groupby(["Konto_Nr"]).aggregate("sum").to_csv(
  271. self.debug_file, decimal=",", sep=";", encoding="latin-1"
  272. )
  273. # Bereinigung GW-Kostenträger
  274. df["NW_Verkauf_1"] = (df["Konto_Nr"].str.match(r"^[78]0")) & (df["Kostenstelle"].str.match(r"^[^1]\d"))
  275. df["Kostenstelle"] = np.where(df["NW_Verkauf_1"] == True, "11", df["Kostenstelle"])
  276. df["Konto_7010"] = df["Konto_Nr"].str.match(r"^[78]01[01]")
  277. df["Kostenstelle"] = np.where(df["Konto_7010"] == True, "14", df["Kostenstelle"])
  278. df["GW_Verkauf_2"] = (df["Konto_Nr"].str.match(r"^[78]1")) & (df["Kostenstelle"].str.match(r"^[^2]\d"))
  279. df["Kostenstelle"] = np.where(df["GW_Verkauf_2"] == True, "21", df["Kostenstelle"])
  280. df["GW_Verkauf_3"] = (df["Konto_Nr"].str.match(r"^[78]3")) & (df["Kostenstelle"].str.match(r"^[^3]\d"))
  281. df["Kostenstelle"] = np.where(df["GW_Verkauf_3"] == True, "31", df["Kostenstelle"])
  282. df["GW_Verkauf_4"] = (df["Konto_Nr"].str.match(r"^[78]4")) & (df["Kostenstelle"].str.match(r"^[^4]\d"))
  283. df["Kostenstelle"] = np.where(df["GW_Verkauf_4"] == True, "41", df["Kostenstelle"])
  284. df["GW_Verkauf_x420"] = df["Konto_Nr"].str.match(r"^[78]420")
  285. df["Kostenstelle"] = np.where(df["GW_Verkauf_x420"] == True, "42", df["Kostenstelle"])
  286. df["GW_Verkauf_5"] = (df["Konto_Nr"].str.match(r"^[78]5")) & (df["Kostenstelle"].str.match(r"^[^5]\d"))
  287. df["Kostenstelle"] = np.where(df["GW_Verkauf_5"] == True, "51", df["Kostenstelle"])
  288. df["GW_Verkauf_50"] = (df["Konto_Nr"].str.match(r"^[78]")) & (df["Kostenstelle"].str.match(r"^2"))
  289. df["Kostenträger"] = np.where(df["GW_Verkauf_50"] == True, "52", df["Kostenträger"])
  290. df["Kostenträger"] = np.where(
  291. (df["GW_Verkauf_50"] == True) & (df["Marke"] == "01"),
  292. "50",
  293. df["Kostenträger"],
  294. )
  295. df["NW_Verkauf_00"] = (
  296. (df["Konto_Nr"].str.match(r"^[78]2"))
  297. & (df["Kostenstelle"].str.match(r"^1"))
  298. & (df["Kostenträger"].str.match(r"^[^01234]"))
  299. )
  300. df["Kostenträger"] = np.where(df["NW_Verkauf_00"] == True, "00", df["Kostenträger"])
  301. df["GW_Stk_50"] = (df["Konto_Nr"].str.match(r"^9130")) & (df["Kostenstelle"].str.match(r"^2"))
  302. df["Kostenträger"] = np.where(df["GW_Stk_50"] == True, "52", df["Kostenträger"])
  303. df["Kostenträger"] = np.where((df["GW_Stk_50"] == True) & (df["Marke"] == "01"), "50", df["Kostenträger"])
  304. df["Kostenträger"] = np.where(df["Bilanz"] == True, "00", df["Kostenträger"])
  305. df["Konto_5er"] = (df["Konto_Nr"].str.match("^5")) | (df["Konto_Nr"].str.match("^9143"))
  306. df["Absatzkanal"] = np.where(df["Konto_5er"] == True, "99", df["Absatzkanal"])
  307. df["Konto_5005"] = (df["Konto_Nr"].str.match("^5005")) & (df["Kostenstelle"].str.match(r"^[^12]"))
  308. df["Kostenstelle"] = np.where(df["Konto_5005"] == True, "20", df["Kostenstelle"])
  309. df["Kostenträger"] = np.where(df["Konto_5005"] == True, "50", df["Kostenträger"])
  310. df["Konto_5007"] = (df["Konto_Nr"].str.match("^5007")) & (df["Kostenstelle"].str.match(r"^([^4]|42)"))
  311. df["Kostenstelle"] = np.where(df["Konto_5007"] == True, "41", df["Kostenstelle"])
  312. df["Kostenträger"] = np.where(df["Konto_5007"] == True, "70", df["Kostenträger"])
  313. df["Konto_914er"] = (df["Konto_Nr"].str.match("^914[34]")) & (df["Kostenträger"].str.match(r"^[^7]"))
  314. df["Kostenträger"] = np.where(df["Konto_914er"] == True, "70", df["Kostenträger"])
  315. df["Teile_30_60"] = (
  316. (df["Konto_Nr"].str.match(r"^[578]"))
  317. & (df["Kostenstelle"].str.match(r"^[3]"))
  318. & (df["Kostenträger"].str.match(r"^[^6]"))
  319. )
  320. df["Kostenträger"] = np.where(df["Teile_30_60"] == True, "60", df["Kostenträger"])
  321. df["Service_40_70"] = (
  322. (df["Konto_Nr"].str.match(r"^[578]"))
  323. & (df["Kostenstelle"].str.match(r"^[4]"))
  324. & (df["Kostenträger"].str.match(r"^[^7]"))
  325. )
  326. df["Kostenträger"] = np.where(df["Service_40_70"] == True, "70", df["Kostenträger"])
  327. df["KRM"] = df["Marke"] + df["Standort"] + df["Kostenstelle"] + df["Absatzkanal"] + df["Kostenträger"]
  328. df["Konto_Nr_SKR51"] = (
  329. (df["Marke"] + "-" + df["Standort"] + "-" + df["Konto_Nr"])
  330. + "-"
  331. + (df["Kostenstelle"] + "-" + df["Absatzkanal"] + "-" + df["Kostenträger"])
  332. )
  333. df["IsNumeric"] = (
  334. (df["KRM"].str.isdigit())
  335. & (df["Konto_Nr"].str.isdigit())
  336. & (df["Konto_Nr"].str.len() == 4)
  337. # & (df["Konto_Nr_SKR51"].str.len() == 19)
  338. )
  339. df_invalid = df[df["IsNumeric"] == False]
  340. df_invalid.to_csv(self.export_invalid_filename, decimal=",", sep=";", encoding="latin-1", index=False)
  341. return df[df["IsNumeric"] == True][TRANSLATE]
  342. def load_bookings_from_file(self) -> None:
  343. df2 = []
  344. timestamps = []
  345. for csv_file in self.account_bookings:
  346. df2.append(
  347. pd.read_csv(
  348. csv_file,
  349. decimal=",",
  350. sep=";",
  351. encoding="latin-1",
  352. converters={0: str, 1: str},
  353. )
  354. )
  355. timestamps.append(Path(csv_file).stat().st_mtime)
  356. self.booking_date = datetime.fromtimestamp(max(timestamps))
  357. self.df_bookings = pd.concat(df2)
  358. self.df_bookings["amount"] = (self.df_bookings["Debit Amount"] + self.df_bookings["Credit Amount"]).round(2)
  359. def filter_bookings(self) -> pd.DataFrame:
  360. if self.df_bookings is None:
  361. self.load_bookings_from_file()
  362. # Kontensalden auf gegebenen Monat filtern
  363. filter_from = self.current_year + self.first_month_of_financial_year
  364. filter_prev = self.last_year + self.first_month_of_financial_year
  365. if self.first_month_of_financial_year > self.current_month:
  366. filter_from = self.last_year + self.first_month_of_financial_year
  367. filter_prev = self.last_year2 + self.first_month_of_financial_year
  368. filter_to = self.current_year + self.current_month
  369. filter_opening = self.current_year + "00"
  370. filter_prev_opening = self.last_year + "00"
  371. prev_year_closed = True
  372. df_opening_balance = self.df_bookings[(self.df_bookings["Bookkeep Period"] == filter_opening)]
  373. if df_opening_balance.shape[0] == 0:
  374. df_opening_balance = self.df_bookings[
  375. (self.df_bookings["Bookkeep Period"] == filter_prev_opening)
  376. | (
  377. (self.df_bookings["Bookkeep Period"] >= filter_prev)
  378. & (self.df_bookings["Bookkeep Period"] < filter_from)
  379. )
  380. ].copy()
  381. df_opening_balance["Bookkeep Period"] = filter_opening
  382. prev_year_closed = False
  383. df_opening_balance = df_opening_balance[(df_opening_balance["Konto_Nr_Händler"].str.contains(r"-[013]\d\d+-"))]
  384. opening_balance = df_opening_balance["amount"].aggregate("sum").round(2)
  385. logging.info("Gewinn/Verlustvortrag")
  386. logging.info(opening_balance)
  387. if not prev_year_closed:
  388. row = {
  389. "Konto_Nr_Händler": "01-01-0861-00-00-00",
  390. "Bookkeep Period": filter_opening,
  391. "Debit Amount": opening_balance * -1,
  392. "Credit Amount": 0,
  393. "Debit Quantity": 0,
  394. "Credit Quantity": 0,
  395. "amount": opening_balance * -1,
  396. }
  397. df_opening_balance = pd.concat([df_opening_balance, pd.DataFrame.from_records([row])])
  398. df_filtered = self.df_bookings[
  399. (self.df_bookings["Bookkeep Period"] >= filter_from) & (self.df_bookings["Bookkeep Period"] <= filter_to)
  400. ]
  401. # Buchungen kopieren und als Statistikkonten anhängen
  402. df_stats = df_filtered.copy()
  403. # df_stats = df_stats[df_stats['Konto_Nr_Händler'].str.match(r'-[24578]\d\d\d-')]
  404. df_stats["Konto_Nr_Händler"] = df_stats["Konto_Nr_Händler"].str.replace(r"-(\d\d\d+)-", r"-\1_STK-", regex=True)
  405. df_stats["amount"] = (df_filtered["Debit Quantity"] + df_filtered["Credit Quantity"]).round(2)
  406. df_combined = pd.concat([df_opening_balance, df_filtered, df_stats])
  407. # Spalten konvertieren
  408. df_combined["period"] = df_combined["Bookkeep Period"].apply(lambda x: self.bookkeep_filter[x])
  409. return df_combined[df_combined["amount"] != 0.00]
  410. @property
  411. def export_filename(self) -> str:
  412. return self.export_filename_for_period(self.current_year, self.current_month)
  413. @property
  414. def export_info_dir(self) -> str:
  415. return f"{self.base_dir}/Export/{self.current_year}/info/"
  416. @property
  417. def export_invalid_filename(self) -> str:
  418. return f"{self.base_dir}/Export/ungueltig.csv"
  419. def export_filename_for_period(self, year: str, month: str) -> str:
  420. return f"{self.base_dir}/Export/{year}/export_{year}-{month}.xml"
  421. def export_skr51_xml(
  422. self,
  423. records: dict[str, list[str]],
  424. bk_filter: dict[str, str],
  425. period_no: str,
  426. makes_used: list[str],
  427. sites_used: list[str],
  428. main_site: str,
  429. filename: str,
  430. ):
  431. record_elements = ACCOUNT_INFO + ["Decimals"] + list(bk_filter.values())[:period_no] + ["CumulatedYear"]
  432. root = ET.Element("HbvData")
  433. h = ET.SubElement(root, "Header")
  434. for k, v in self.header(makes_used, sites_used, main_site).items():
  435. ET.SubElement(h, k).text = str(v)
  436. make_list = ET.SubElement(root, "MakeList")
  437. for make in makes_used:
  438. if make not in self.makes:
  439. continue
  440. e = ET.SubElement(make_list, "MakeListEntry")
  441. ET.SubElement(e, "Make").text = make
  442. ET.SubElement(e, "MakeCode").text = self.makes[make]
  443. bm_code_list = ET.SubElement(root, "BmCodeList")
  444. for s in sites_used:
  445. make, site = s.split("-")
  446. if s not in self.sites:
  447. continue
  448. e = ET.SubElement(bm_code_list, "BmCodeEntry")
  449. ET.SubElement(e, "Make").text = make
  450. ET.SubElement(e, "Site").text = site
  451. ET.SubElement(e, "BmCode").text = self.sites[s]
  452. record_list = ET.SubElement(root, "RecordList")
  453. for row in records:
  454. record = ET.SubElement(record_list, "Record")
  455. for e in record_elements:
  456. child = ET.SubElement(record, e)
  457. field = row.get(e, 0.0)
  458. if str(field) == "nan":
  459. field = "0"
  460. elif type(field) is float:
  461. field = "{:.0f}".format(field * 100)
  462. child.text = str(field)
  463. with open(filename, "w", encoding="utf-8") as fwh:
  464. fwh.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
  465. def convert_to_row(self, node: list[ET.Element]) -> list[str]:
  466. return [child.text for child in node]
  467. def convert_xml_to_csv(self, xmlfile: str, csvfile: str) -> bool:
  468. with open(xmlfile) as frh:
  469. record_list = ET.parse(frh).getroot().find("RecordList")
  470. header = [child.tag for child in record_list.find("Record")]
  471. bookings = [self.convert_to_row(node) for node in record_list.findall("Record")]
  472. with open(csvfile, "w") as fwh:
  473. cwh = csv.writer(fwh, delimiter=";")
  474. cwh.writerow(header)
  475. cwh.writerows(bookings)
  476. return True
  477. def convert_csv_to_xml(self, csvfile: str, xmlfile: str) -> None:
  478. self.makes = {"01": "1844"}
  479. self.sites = {"01-01": "1844"}
  480. with open(csvfile, "r", encoding="latin-1") as frh:
  481. csv_reader = csv.DictReader(frh, delimiter=";")
  482. self.export_skr51_xml(csv_reader, self.bookkeep_filter(), 1, list(self.sites.values())[0], xmlfile)
  483. def gchr_local() -> None:
  484. base_dir = os.getcwd() + "/../GCHR2_Testdaten/Kunden"
  485. for path in Path(base_dir).glob("*"):
  486. if path.is_dir():
  487. print(path.name)
  488. gchr_export(str(path))
  489. def gchr_export(base_dir: str) -> None:
  490. gchr = GCHR(base_dir)
  491. # gchr.export_all_periods(overwrite=True, today="2022-08-01")
  492. gchr.export_all_periods()
  493. if __name__ == "__main__":
  494. gchr_local()
  495. # import cProfile
  496. # cProfile.run(
  497. # "gchr_local()",
  498. # "gchr_local.prof",
  499. # )