Some helper scripts for the day to day work with Ubuntu in WSL2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
7.2 KiB

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())