import click from datetime import datetime import pathlib import sys import shutil import pandas as pd import warnings import re from PyPDFForm import FormWrapper, PdfWrapper warnings.filterwarnings("ignore") Pathlike = pathlib.Path | str WINHOME = pathlib.Path("/mnt/c/Users/Holgi/") DESKTOP = WINHOME / "Desktop" TODAY = datetime.now().strftime("%Y-%m-%d") def _iso_date_to_german(date: str) -> str: return ".".join(reversed(date.split("-"))) def _search_files( folder: Pathlike, partial_name: str, suffix: str ) -> list[pathlib.Path]: parent = pathlib.Path(folder) if not suffix.startswith("."): suffix = f".{suffix}" files = (item for item in parent.iterdir() if item.is_file()) non_hidden = (item for item in files if not item.stem.startswith(".")) non_tempfile = (item for item in files if not item.stem.startswith("~")) types = ( item for item in non_tempfile if item.suffix.lower() == suffix.lower() ) return [item for item in types if partial_name.lower() in item.stem.lower()] def _get_latest_file( folder: Pathlike, partial_name: str, suffix: str ) -> pathlib.Path | None: results = _search_files(folder, partial_name, suffix) if not results: return None creation_times = [item.stat().st_ctime for item in results] by_creation_time = sorted(zip(creation_times, results)) newest_with_time = by_creation_time[-1] # latest entry return newest_with_time[1] # the path entry of the tuple def _get_form_path(partial_name: str) -> pathlib.Path: own_parent = pathlib.Path(__file__).parent matches = _search_files(own_parent, partial_name, ".pdf") if len(matches) == 1: return matches[0] counts = len(matches) msg = f"Found {counts} matching pdf forms for '{partial_name}'" raise IOError(msg) def _get_unique(data: pd.DataFrame, column: str) -> str | int | float: uniques = data[column].unique() if len(uniques) != 1: msg = f"Found multiple unique values for '{column}'" raise ValueError(msg) return uniques[0] def _extract_travel_number(data: pd.DataFrame, column: str) -> str: belege = data[column] travel_nr = belege.apply( lambda x: re.search("(5\d{7,8})", x.replace(" ", "")) ) match_result = travel_nr[travel_nr.first_valid_index()] return match_result[0] @click.command() @click.argument( "form", type=click.Path(exists=True, file_okay=True, dir_okay=False) ) @click.option( "-o", "--output", default=None, help="Output file path, defaults to desktop folder", ) def inspect(form, output): form = pathlib.Path(form) if not output: new_name = f"inspected {form.stem}{form.suffix}" output = DESKTOP / new_name preview_stream = PdfWrapper(str(form)).preview with output.open("wb+") as output_stream: output_stream.write(preview_stream) def payments(): downloads = WINHOME / "Downloads" forms = DESKTOP / "Formulare" latest_export = _get_latest_file(downloads, "Buchungen_SAP", ".xlsx") if not latest_export: sys.exit("Could not find an SuperX export file, aborting.") fields = ["BelegNr", "VorgängerBelegNr", "Kostenstelle", "Fonds", "Projekt"] converters = {field: str for field in fields} data = pd.read_excel(latest_export, skiprows=3, converters=converters) travel_nr = _extract_travel_number(data, "BelegNr") mask = data["Werttyp"] == "Zahlung" payments = data[mask].copy() summary = ( payments.groupby("BelegNr") .agg({"Betrag": "sum", "BuDat": "first"}) .reset_index() .sort_values("BelegNr") ) try: cost_center = _get_unique(payments, "Kostenstelle") fonds = _get_unique(payments, "Fonds") project = _get_unique(payments, "Projekt") if not project or len(project) <= 4: project = "" except ValueError as e: sys.exit(str(e)) form_data = { "Projekt": project, "Kostenstelle": cost_center, "Mittelbindung": travel_nr, "Fonds": fonds, } print(f"Projekt: {project}") print(f"Kostenstelle: {cost_center}") print(f"Mittelbindung: {travel_nr}") print(f"Fonds: {fonds}") print("") print(f" Datum Betrag SuperX") print("") for i, row in summary.iterrows(): index = i + 1 form_data.update( { f"Datum{index}": row["BuDat"], f"BelegNr aus SuberX{index}": row["BelegNr"], # no field "Euro{index}" # the automatic form calculation would not work. } ) betrag = f"{row['Betrag']:0.2f}".replace(".", ",") print(f" {row['BuDat']} {betrag:>7} {row['BelegNr']}") source_path = forms / "Vorlage UK-Abschlag, v2023-01.pdf" destination_path = DESKTOP / f"{TODAY} UK-Abschlag.pdf" form = FormWrapper(str(source_path)) filled = form.fill(form_data, flatten=False) destination_path.write_bytes(filled.read()) @click.command() @click.option("-l", "--search_last_name", prompt=True, required=True) @click.option("-d", "--iso_date", prompt=True, required=True) @click.option("-p", "--place", prompt=True, required=True) def prepare_payments(search_last_name: str, iso_date: str, place: str): forms_path = DESKTOP / "Formulare" templates_path = forms_path / "vorbereitet UK-As" templates = _search_files(templates_path, f" {search_last_name}", ".pdf") if len(templates) == 0: sys.exit( f"Could not find a UK-A template for search '{search_last_name}'" ) if len(templates) > 1: sys.exit( f"Found multiple UK-A templates for search '{search_last_name}'" ) template = templates[0] rest, first_name = template.stem.rsplit(",", maxsplit=1) *_, last_name = rest.split() first_name = first_name.strip() last_name = last_name.strip() first_last = f"{first_name} {last_name}" last_first = f"{last_name}, {first_name}" travel_short = f"{last_first}, {place}" travel_name = f"{iso_date} {travel_short}" date = _iso_date_to_german(iso_date) folder = DESKTOP / f"{travel_name} (abgerechnet)" folder.mkdir() rk_path = folder / f" {travel_short}, Reisekostenabrechnung.txt" rk_path.write_text(rk_path.stem) uka_hint_path = ( folder / f" UK-A {last_first}, Dienstreise {place}, Schlusszahlung.txt" ) content = "\t".join( [ _iso_date_to_german(TODAY), first_last, f"Schlusszahlung Dienstreise {place}, {date}", ] ) uka_hint_path.write_text(content) source_path = forms_path / "Vorlage UK-Abschlag, v2023-01.pdf" destination_path = folder / f"{TODAY} {travel_short}, UK-Abschlag.pdf" shutil.copy(source_path, destination_path) form = FormWrapper(str(template)) form_data = { "Verwendungszweck": f"Schlusszahlung Dienstreise nach {place}", "Begründung": f"Schlusszahlung Dienstreise {first_last} nach {place} am {date}", "Datum_Feststellung": _iso_date_to_german(TODAY), "Datum_Anordnung": _iso_date_to_german(TODAY), } filled = form.fill(form_data, flatten=False) uka_path = folder / f"{TODAY} {travel_short}, UK-A Schlusszahlung.pdf" uka_path.write_bytes(filled.read())