gchr.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import json
  2. import logging
  3. import os
  4. from datetime import datetime
  5. from pathlib import Path
  6. # import numpy as np
  7. import pandas as pd
  8. from gchr.gchr_bookings import GchrBookings
  9. from gchr.gchr_export import get_export_fn
  10. from gchr.gchr_model import ACCOUNT_INFO, GchrConfig, GchrExportConfig
  11. from gchr.gchr_translate import load_translation
  12. class GCHR:
  13. cfg: GchrConfig
  14. bookings: GchrBookings
  15. _df_translate: pd.DataFrame = None
  16. df_translate2: pd.DataFrame = None
  17. makes: dict[str, str] = None
  18. sites: dict[str, str] = None
  19. timestamp: str
  20. def __init__(self, base_dir: str) -> None:
  21. self.base_dir = base_dir
  22. cfg_file = f"{self.base_dir}\\config\\gchr2.json"
  23. if Path(cfg_file).exists():
  24. with open(cfg_file, "r") as frh:
  25. self.cfg = GchrConfig(**json.load(frh))
  26. else:
  27. os.makedirs(f"{self.base_dir}\\config", exist_ok=True)
  28. self.cfg = GchrConfig(
  29. first_month_of_financial_year="01",
  30. data_dir=f"{self.base_dir}\\data",
  31. gcstruct_dir=f"{self.base_dir}\\..\\GCStruct_Aufbereitung",
  32. export_dir=f"{self.base_dir}\\Export",
  33. export_format="SKR51",
  34. )
  35. with open(cfg_file, "w") as fwh:
  36. json.dump(self.cfg.__dict__, fwh, indent=2)
  37. os.makedirs(self.cfg.data_dir, exist_ok=True)
  38. os.makedirs(f"{self.cfg.export_dir}\\temp", exist_ok=True)
  39. os.makedirs(f"{self.base_dir}\\logs", exist_ok=True)
  40. self.account_translation = f"{self.cfg.data_dir}\\Kontenrahmen_uebersetzt.csv"
  41. self.bookings = GchrBookings(self.base_dir, self.cfg.first_month_of_financial_year)
  42. self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  43. pd.set_option("display.max_rows", 500)
  44. pd.set_option("display.float_format", lambda x: "%.2f" % x)
  45. @property
  46. def debug_file(self) -> str:
  47. return f"{self.logs_dir}\\debug_{self.timestamp}.csv"
  48. @property
  49. def account_ignored(self) -> str:
  50. return f"{self.export_info_dir}\\ignoriert_{self.bookings.period}.csv"
  51. # self.account_invalid = f"{self.export_info_dir}\\ungueltig_{self.period}.csv"
  52. def export_all_periods(self, overwrite=False, today=None) -> None:
  53. periods = GCHR.get_all_periods(today)
  54. for year, month in periods:
  55. filename = self.export_filename_for_period(year, month)
  56. if overwrite or not Path(filename).exists():
  57. os.makedirs(Path(filename).parent.joinpath("info"), exist_ok=True)
  58. self.export_period(year, month)
  59. @staticmethod
  60. def get_all_periods(today=None) -> list[tuple[str, str]]:
  61. dt = datetime.now()
  62. if today is not None:
  63. dt = datetime.fromisoformat(today)
  64. prev = str(dt.year - 1)
  65. periods = [(prev, str(x).zfill(2)) for x in range(dt.month, 13)] + [
  66. (str(dt.year), str(x).zfill(2)) for x in range(1, dt.month)
  67. ]
  68. return periods
  69. def export_period(self, year: str, month: str) -> str:
  70. export_fn = get_export_fn(self.cfg.export_format)
  71. # Kontensalden laden
  72. df_bookings = self.bookings.filter_bookings(year, month)
  73. all_periods = list(set(df_bookings["Bookkeep Period"].to_list()))
  74. bookkeep_period_date = datetime(int(year), int(month), 28)
  75. if (
  76. df_bookings.shape[0] == 0
  77. or (len(all_periods) <= 1 and all_periods[0][-2:] == "00")
  78. or self.bookings.booking_date < bookkeep_period_date
  79. ):
  80. logging.error("ABBRUCH!!! Keine Daten vorhanden!")
  81. return False
  82. filter_to = year + month
  83. period_no = list(self.bookings.bookkeep_filter.keys()).index(filter_to) + 1
  84. logging.info("df_bookings: " + str(df_bookings.shape))
  85. # Join auf Übersetzung
  86. df_combined = df_bookings.merge(self.df_translate, how="inner", on="Konto_Nr_Händler")
  87. logging.info(f"df_combined: {df_combined.shape}")
  88. df_pivot = df_combined.pivot_table(
  89. index=["Konto_Nr_SKR51"],
  90. columns=["period"],
  91. values="amount",
  92. aggfunc="sum",
  93. margins=True,
  94. margins_name="CumulatedYear",
  95. )
  96. df_pivot.drop(index="CumulatedYear", inplace=True)
  97. logging.info("df_pivot: " + str(df_pivot.shape))
  98. df = df_pivot.merge(self.df_translate2, how="inner", on="Konto_Nr_SKR51")
  99. makes_used = {}
  100. for m in sorted(list(set(df["Marke"].to_list()))):
  101. if m not in self.makes:
  102. continue
  103. makes_used[m] = self.makes[m]
  104. sites_used = {}
  105. for s in sorted(list(set((df["Marke"] + "-" + df["Standort"]).to_list()))):
  106. if s not in self.sites:
  107. continue
  108. sites_used[s] = self.sites[s]
  109. from_label = ["Marke", "Standort", "Konto_Nr", "Kostenstelle", "Absatzkanal", "Kostenträger", "KRM"]
  110. to_label = ["Make", "Site", "Account", "Origin", "SalesChannel", "CostCarrier", "CostAccountingString"]
  111. col_dict = dict(zip(from_label, to_label))
  112. df = df.rename(columns=col_dict)
  113. export_filename = self.export_filename_for_period(year, month)
  114. export_csv = export_filename[:-4] + ".csv"
  115. df.to_csv(export_csv, decimal=",", sep=";", encoding="latin-1", index=False)
  116. df = df[df["IsNumeric"] != False].groupby(ACCOUNT_INFO, as_index=False).aggregate("sum")
  117. if True:
  118. # Nur Konten mit Inhalt exportieren
  119. entries_with_content = pd.DataFrame(df["CumulatedYear"] != 0)
  120. period_columns = [p for p in df.columns if p.startswith("Period")]
  121. for p in period_columns:
  122. entries_with_content[p] = df[p] != 0
  123. df = df[entries_with_content.any(axis="columns")]
  124. # Infos ergänzen
  125. df["Decimals"] = 2
  126. # df.sort_values(by=["Konto_Nr_SKR51"], inplace=True)
  127. logging.info(df.shape)
  128. main_sites = [
  129. self.sites[s]
  130. for s in sites_used
  131. if s in self.sites and self.sites[s] != "0000" and "_" not in self.sites[s]
  132. ]
  133. for i, main_site in enumerate(main_sites):
  134. filename = export_filename
  135. if i > 0:
  136. filename = f"{filename[:-4]}_{main_site}.xml"
  137. export_cfg = GchrExportConfig(
  138. main_site=main_site,
  139. current_year=year,
  140. current_month=month,
  141. makes_used=makes_used,
  142. sites_used=sites_used,
  143. first_month=self.cfg.first_month_of_financial_year,
  144. period_no=period_no,
  145. bookkeep_filter=self.bookings.bookkeep_filter,
  146. extraction_date=self.bookings.booking_date,
  147. export_file=filename,
  148. bookkeep_records=df.to_dict(orient="records"),
  149. )
  150. export_fn(export_cfg)
  151. # Join auf Übersetzung - nicht zugeordnet
  152. df_ignored = df_bookings.merge(self.df_translate, how="left", on="Konto_Nr_Händler")
  153. df_ignored = df_ignored[df_ignored["Konto_Nr_SKR51"].isna()]
  154. if not df_ignored.empty:
  155. df_ignored = df_ignored.pivot_table(
  156. index=["Konto_Nr_Händler"],
  157. columns=["period"],
  158. values="amount",
  159. aggfunc="sum",
  160. margins=True,
  161. margins_name="CumulatedYear",
  162. )
  163. # df_ignored = df_ignored.merge(self.df_translate, how="inner", on="Konto_Nr_Händler")
  164. # df_ignored["Kontoart"] =
  165. df_ignored.to_csv(self.account_ignored, decimal=",", sep=";", encoding="latin-1")
  166. return export_filename
  167. @property
  168. def df_translate(self) -> pd.DataFrame:
  169. if self._df_translate is None:
  170. self.makes, self.sites, self._df_translate, self.df_translate2 = load_translation(
  171. self.account_translation, self.debug_file, self.export_invalid_filename
  172. )
  173. return self._df_translate
  174. @property
  175. def export_info_dir(self) -> str:
  176. return f"{self.cfg.export_dir}\\{self.bookings.current_year}\\info\\"
  177. @property
  178. def logs_dir(self) -> str:
  179. return f"{self.base_dir}\\Logs\\"
  180. @property
  181. def export_invalid_filename(self) -> str:
  182. return f"{self.cfg.export_dir}\\ungueltig.csv"
  183. def export_filename_for_period(self, year: str, month: str) -> str:
  184. return f"{self.cfg.export_dir}\\{year}\\export_{year}-{month}.xml"