gchr.py 7.4 KB

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