From dc140754ae57ed576b223045cff36cd667611b17 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Sat, 20 Jan 2024 21:29:12 +0100 Subject: [PATCH 01/34] Added Django ORM --- requirements.txt | 2 +- {{ cookiecutter.app_name }}/pyproject.toml | 8 ++++ .../db/__init__.py | 0 .../db/manage.py | 31 +++++++++++++ .../db/settings.py | 45 +++++++++++++++++++ .../{{ cookiecutter.module_name }}/models.py | 0 .../services.py | 0 .../{{ cookiecutter.module_name }}/utils.py | 0 8 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/__init__.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py diff --git a/requirements.txt b/requirements.txt index 8c2ccff..46563d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ flake8 == 6.1.0 pre-commit == 3.5.0 pytest == 7.4.3 toml == 0.10.2 -tox == 4.11.3 +tox == 4.11.3 \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/pyproject.toml b/{{ cookiecutter.app_name }}/pyproject.toml index 22d1a8c..f0a3837 100644 --- a/{{ cookiecutter.app_name }}/pyproject.toml +++ b/{{ cookiecutter.app_name }}/pyproject.toml @@ -21,6 +21,14 @@ sources = [ test_sources = [ "tests", ] + +requires = [ + "python-dotenv", + "toml", + "django", + +] + {{- cookiecutter.pyproject_table_briefcase_app_extra_content }} {% if cookiecutter.pyproject_table_macOS %} diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/__init__.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py new file mode 100644 index 0000000..66dc72f --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + import sys + + sys.dont_write_bytecode = True + + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + + import django + + django.setup() + + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py new file mode 100644 index 0000000..04e053f --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py @@ -0,0 +1,45 @@ +import os +from pathlib import Path +import django +from dotenv import load_dotenv + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = Path(__file__).resolve().parent.parent +ENV_FILE = Path(__file__).resolve() / ".env" + +# Load the .env file into the environment. This is done before the settings are loaded. +# This is done to make sure that the environment variables are available when the settings are loaded. +# Was required to make the environment variables available in the test_services.py file. +load_dotenv(dotenv_path=ENV_FILE) + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "changeme") + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db", "db.sqlite3"), + } +} + + +""" +To connect to an existing postgres database, first: +pip install psycopg2 +then overwrite the settings above with: + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'YOURDB', + 'USER': 'postgres', + 'PASSWORD': 'password', + 'HOST': 'localhost', + 'PORT': '', + } +} +""" + +INSTALLED_APPS = ("db",) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py new file mode 100644 index 0000000..e69de29 From e47623065823eef365b02075edfd0171e1c69081 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:27:38 +0100 Subject: [PATCH 02/34] Added a bunch of bolderplate and features from NadooLaw Update messaging, Error Componete, DropdownMenue, --- .gitignore | 4 + requirements.txt | 3 +- tests/test_app_template.py | 194 +++++++++- .../CONSTANTS.py | 4 + .../components/DropDownMenuComponent.py | 365 ++++++++++++++++++ .../components/Fehlermeldung.py | 73 ++++ .../components/LicenseWindow.py | 37 ++ .../components/UpdateWindow.py | 77 ++++ .../db/manage.py | 31 -- .../db/settings.py | 45 --- .../resources/help.pdf | Bin 0 -> 7215 bytes .../kundendaten/placeholder.txt} | 0 .../resources/lizenzen.txt | 0 .../resources/update.pdf | Bin 0 -> 7215 bytes .../resources/updates/update.json | 25 ++ .../services.py | 176 +++++++++ .../{{ cookiecutter.module_name }}/styling.py | 77 ++++ .../{{ cookiecutter.module_name }}/utils.py | 189 +++++++++ 18 files changed, 1222 insertions(+), 78 deletions(-) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/DropDownMenuComponent.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Fehlermeldung.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/LicenseWindow.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py delete mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py delete mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/help.pdf rename {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/{db/__init__.py => resources/kundendaten/placeholder.txt} (100%) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/lizenzen.txt create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/update.pdf create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/updates/update.json create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/styling.py diff --git a/.gitignore b/.gitignore index c003e02..93e57e0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ .vscode .idea __pycache__ + + +{{ cookiecutter.app_name|lower|replace('-', '_') }}/src/{{ cookiecutter.app_name|lower|replace('-', '_') }}/resources/kundendaten/vorlagen/*.docx +{{ cookiecutter.app_name|lower|replace('-', '_') }}/src/{{ cookiecutter.app_name|lower|replace('-', '_') }}/resources/kundendaten/*.json \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 46563d4..d63bff8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ flake8 == 6.1.0 pre-commit == 3.5.0 pytest == 7.4.3 toml == 0.10.2 -tox == 4.11.3 \ No newline at end of file +tox == 4.11.3 +screeninfo == 1.5.0 \ No newline at end of file diff --git a/tests/test_app_template.py b/tests/test_app_template.py index a7a3078..44ef4ae 100644 --- a/tests/test_app_template.py +++ b/tests/test_app_template.py @@ -32,11 +32,203 @@ """ APP_SOURCE = """\ +import os +import sys +import subprocess from datetime import datetime +import toga +from toga.style.pack import COLUMN +from toga.style import Pack +from toga import Command, Group + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen +from screeninfo import get_monitors + +class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): + + # Define the action handlers for the commands + def show_license(self, widget): + # Instantiate the LicenseWindow + license_window = LicenseWindow(title="Lizenzinformationen") + + # Show the license window + license_window.show() + + def show_updates(self, *argv): + # Instantiate the UpdateWindow + update_window = UpdateWindow(title="Updateinformationen") + + # Show the update window + update_window.show() + + def show_help(self, widget): + help_file_path = get_help_file_path(self.app) + if os.path.exists(help_file_path): + open_file(help_file_path) + + def base_ordner_offnen(self, widget): + # Retrieve the template folder path + base_folder_path = get_base_dir() + + # Open the template folder in the system's file explorer + if sys.platform == "win32": + os.startfile(base_folder_path) + elif sys.platform == "darwin": + subprocess.Popen(["open", base_folder_path]) + else: # 'linux', 'linux2', 'cygwin', 'os2', 'os2emx', 'riscos', 'atheos' + subprocess.Popen(["xdg-open", base_folder_path]) + + def aktualisierung_anzeigen(self): + self.show_updates() + + def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): + #beim start ausführen + update_daten = update_daten_laden_user() + neues_update_wurde_installiert = update_daten.get("neues_update_wurde_installiert") + if neues_update_wurde_installiert: + return True + else: + return False + + def neues_update_existiert(self): + updates_user = update_daten_laden_user()['Updates'] + update_keys_user = list(updates_user.keys()) # Extrahiere alle Schlüssel + anzahl_update_user = len(update_keys_user) + updates_app = update_daten_laden_app(self)['Updates'] + update_keys_app = list(updates_app.keys()) # Extrahiere alle Schlüssel + anzahl_update_app = len(update_keys_app) + if anzahl_update_app > anzahl_update_user: + return True + else: + return False + + def startup(self): + + # Erstellen von Vorraussetung damit die App funktioniert. + setup_folders() + + # Menü + help_group = Group("Help", order=100) + settings_group = Group("Settings", order=50) + + # Define commands + license_command = Command( + action=self.show_license, + text="Lizenzinformationen", + tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", + group=help_group, + ) + open_help_file_command = Command( + action=self.show_help, + text="Hilfe öffnen", + tooltip="Öffent die Hilfe PDF", + group=help_group, + ) + update_command = Command( + action=self.show_updates, + text="Updateinformationen", + tooltip="Zeigt die Veränderung in der Software an", + group=help_group, + ) + basis_ordner_öffnen_command = Command( + action=self.base_ordner_offnen, + text="Basis Ordner öffnen", + tooltip="Öffnet den Programmordner", + group=settings_group, + ) + + # Menü + help_group = Group("Help", order=100) + settings_group = Group("Settings", order=50) + + # Define commands + license_command = Command( + action=self.show_license, + text="Lizenzinformationen", + tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", + group=help_group, + ) + + open_help_file_command = Command( + action=self.show_help, + text="Hilfe öffnen", + tooltip="Öffent die Hilfe PDF", + group=help_group, + ) + + update_command = Command( + action=self.show_updates, + text="Updateinformationen", + tooltip="Zeigt die Veränderung in der Software an", + group=help_group, + ) + + basis_ordner_öffnen_command = Command( + action=self.base_ordner_offnen, + text="Basis Ordner öffnen", + tooltip="Öffnet den Programmordner", + group=settings_group, + ) + + # Add commands to the app + self.commands.add( + license_command, + open_help_file_command, + basis_ordner_öffnen_command, + update_command, + ) + + # Get the size of the primary monitor + monitor = get_monitors()[0] + screen_width, screen_height = monitor.width, monitor.height + + # Calculate half screen width and use full screen height + half_screen_width = screen_width // 2 + + # Main layout Box + self.main_box = toga.Box( + style=Pack( + direction=COLUMN, + padding_left=30, + padding_right=60, + #width=half_screen_width * 0.95, + flex=1, + ) + ) + + # ScrollContainer to allow infinite scrolling + self.scroll_container = toga.ScrollContainer( + content=self.main_box, style=Pack(direction=COLUMN, flex=1), vertical=True + ) + self.main_window = toga.MainWindow( + title=self.formal_name, size=(half_screen_width, screen_height * 80 / 100) + ) + self.main_window.content = ( + self.scroll_container + ) # Set the ScrollContainer as the main content + + # Set the window position to the right side of the screen + self.main_window.position = (half_screen_width, 0) + + self.main_window.show() + + if not os.path.isfile(get_updates_datei_user()) or self.neues_update_existiert(): + update_in_updates_ordner_uebertragen(self.app) + self.aktualisierung_anzeigen() + + def main(self): + pass + + def exit(self): + pass + def main(): - print(f"hello world - it's {datetime.now()}") + return {{ cookiecutter.app_name|lower|replace('-', '_') }}() """ APP_START_SOURCE = """\ diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py new file mode 100644 index 0000000..525d2f8 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py @@ -0,0 +1,4 @@ +UPDATE_ORDNER_NAME_APP = "updates" +UPDATE_ORDNER_NAME_USER = "Updates" +BASE_DIR = "{{ cookiecutter.app_name|lower|replace('-', '_') }}" +ARCHIV_ORDNER = "Archive" \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/DropDownMenuComponent.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/DropDownMenuComponent.py new file mode 100644 index 0000000..bddf40d --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/DropDownMenuComponent.py @@ -0,0 +1,365 @@ +import uuid +import toga +from toga.style import Pack +from toga.style.pack import COLUMN +from toga.widgets.button import OnPressHandler +from toga import Window +from nadoo_law.services import get_lawyer_data, set_lawyer_data +from nadoo_law.styling import StandardStyling +class LawyerSelectionComponent(toga.Box): + def __init__( + self, + app, + id: str | None = None, + ): + style = Pack(direction=COLUMN) + super().__init__(id=id, style=style) + self.app = app + + # Setup and load data + self.setup_data() + + self.name_input = toga.TextInput( + placeholder="Name", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("name", ""), + ) + self.email_input = toga.TextInput( + placeholder="Email", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("email", ""), + ) + self.phone_input = toga.TextInput( + placeholder="Phone", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("phone", ""), + ) + self.fax_input = toga.TextInput( + placeholder="Fax", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("fax", ""), + ) + self.title_input = toga.TextInput( + placeholder="Title", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("title", ""), + ) + self.specialty_input = toga.TextInput( + placeholder="Specialty", + style=StandardStyling.standard_input_style(), + value=self.lawyer_details[self.selected_lawyer_id].get("specialty", ""), + ) + + current_names = self.get_lawyer_names() + + self.lawyer_select = toga.Selection( + items=current_names, + value=self.lawyer_details[self.selected_lawyer_id]["name"], + on_change=self.on_lawyer_select, + style=StandardStyling.standard_selection_style(), + ) + + self.new_button = toga.Button( + "Neu", on_press=NewLawyerHandler(self), style=StandardStyling.standard_button_style() + ) + self.delete_button = toga.Button( + "Löschen", + on_press=DeleteLawyerHandler(self), + style=StandardStyling.standard_button_style(), + ) + self.save_lawyer_button = toga.Button( + "Speichern", + on_press=SaveLawyerHandler(self), + style=StandardStyling.standard_button_style(), + ) + + self.add(self.lawyer_select) + self.add(self.name_input) + self.add(self.email_input) + self.add(self.phone_input) + self.add(self.fax_input) + self.add(self.title_input) + self.add(self.specialty_input) + self.add(self.new_button) + self.add(self.save_lawyer_button) + self.add(self.delete_button) + + def get_dummy_lawyer(self): + dummy_id = str(uuid.uuid4()) + return { + dummy_id: { + "name": "Dummy Lawyer", + "email": "dummy@lawfirm.com", + "phone": "123-456-7890", + "fax": "098-765-4321", + "title": "Lawyer", + "specialty": "General", + } + } + + def setup_data(self): + # Use the service function to get lawyer details + lawyer_data = get_lawyer_data() + self.lawyer_details = lawyer_data.get("lawyer_details", {}) + self.selected_lawyer_id = lawyer_data.get("selected_lawyer_id") + + # If there's no valid selected lawyer ID, or it's not in the details, use the first lawyer ID + if ( + not self.selected_lawyer_id + or self.selected_lawyer_id not in self.lawyer_details + ): + print( + "No valid selected lawyer ID found. Creating a new one with a dummy lawyer." + ) + dummy_lawyer = self.get_dummy_lawyer() + self.lawyer_details.update(dummy_lawyer) + self.selected_lawyer_id = next(iter(dummy_lawyer)) + + # Save the lawyer details with the newly added dummy lawyer + try: + data = { + "selected_lawyer_id": self.selected_lawyer_id, + "lawyer_details": self.lawyer_details, + } + set_lawyer_data(data) + except Exception as e: + print(f"Error saving lawyer details: {e}") + + print(f"The last lawyer id that was selected {self.selected_lawyer_id} ") + + def update_dropdown_items(self): + # Get the current names, including placeholders + current_names = self.get_lawyer_names() + + lawyer_selected_before_list_update = self.selected_lawyer_id + + # Update the dropdown items + self.lawyer_select.items = current_names + + self.lawyer_select.value = self.get_lawyer_details_for_id( + lawyer_selected_before_list_update + )["name"] + + def reset_input_fields(self): + self.name_input.value = "" + self.email_input.value = "" + self.phone_input.value = "" + self.fax_input.value = "" + self.title_input.value = "" + self.specialty_input.value = "" + + def clean_empty_lawyer_details(self): + self.lawyer_details = { + id: details + for id, details in self.lawyer_details.items() + if any(details.values()) + } + + def get_lawyer_names(self): + return [details["name"] for details in self.lawyer_details.values()] + + def on_lawyer_select(self, widget, **kwargs): + try: + print("Lawyer was selected") + + selected_name = widget.value + print(f"Selected lawyer: {selected_name}") + + if selected_name == self.get_lawyer_details_for_id( + self.selected_lawyer_id + ).get("name"): + print( + "Selection was the same as the selected lawyer. Skipping loading the lawyer details" + ) + return + + if selected_name: + print(self.lawyer_details.items()) + + for lawyer_id, details in self.lawyer_details.items(): + print(lawyer_id) + print(details) + + if details["name"] == selected_name: + self.selected_lawyer_id = lawyer_id + + # Set the currently selected lawyer id in the lawyer_details + try: + data = { + "selected_lawyer_id": self.selected_lawyer_id, + "lawyer_details": self.lawyer_details, + } + set_lawyer_data(data) + except Exception as e: + print(f"Error saving lawyer details: {e}") + + self.set_input_fields_for_id(lawyer_id) + break + except Exception as e: + print(f"Error during lawyer selection: {e}") + + def set_input_fields_for_id(self, lawyer_id): + try: + details = self.get_lawyer_details_for_id(lawyer_id) + self.name_input.value = details.get("name", "") + self.email_input.value = details.get("email", "") + self.phone_input.value = details.get("phone", "") + self.fax_input.value = details.get("fax", "") + self.title_input.value = details.get("title", "") + self.specialty_input.value = details.get("specialty", "") + except Exception as e: + print(f"Error during lawyer selection: {e}") + + def get_lawyer_details_for_id(self, lawyer_id): + return self.lawyer_details.get(lawyer_id, {}) + + def get_selected_lawyer_details(self): + if self.selected_lawyer_id and self.selected_lawyer_id in self.lawyer_details: + return self.lawyer_details[self.selected_lawyer_id] + return None + + +class DeleteLawyerHandler(OnPressHandler): + def __init__(self, component: LawyerSelectionComponent): + self.component = component + + def __call__(self, widget, **kwargs): + # Show confirmation dialog with the result handler + self.component.app.main_window.confirm_dialog( + title="Bestätigung", + message="Wollen Sie wirklich den Eintrag löschen?", + on_result=self.on_confirm_dialog_result, + ) + + def on_confirm_dialog_result(self, window: Window, result: bool): + # Proceed based on confirmation result + if result: + self.delete_lawyer() + + def delete_lawyer(self): + # Retrieve the current lawyer details + lawyer_data = get_lawyer_data() + lawyer_details = lawyer_data.get("lawyer_details", {}) + selected_lawyer_id = lawyer_data.get("selected_lawyer_id") + + if selected_lawyer_id and selected_lawyer_id in lawyer_details: + # Remove the selected lawyer from the lawyer_details dictionary + del lawyer_details[selected_lawyer_id] + + # Determine the next selected lawyer or create a dummy if none are left + if not lawyer_details: + dummy_lawyer = self.component.get_dummy_lawyer() + lawyer_details.update(dummy_lawyer) + selected_lawyer_id = next(iter(dummy_lawyer)) + else: + # Select the first lawyer from the remaining ones + selected_lawyer_id = next(iter(lawyer_details)) + + # Save the updated lawyer details + set_lawyer_data( + { + "selected_lawyer_id": selected_lawyer_id, + "lawyer_details": lawyer_details, + } + ) + + # Update the component state + self.component.lawyer_details = lawyer_details + self.component.selected_lawyer_id = selected_lawyer_id + + # Update the UI components + self.component.update_dropdown_items() + self.component.set_input_fields_for_id(selected_lawyer_id) + + print("Lawyer successfully deleted.") + else: + print("No valid lawyer selected for deletion.") + + +class NewLawyerHandler(OnPressHandler): + def __init__(self, component: LawyerSelectionComponent): + self.component = component + + def __call__(self, widget, **kwargs): + print("New Lawyer Handler Called") + new_id = str(uuid.uuid4()) + + # Construct the new lawyer details + new_lawyer_details = { + new_id: { + "name": "New Lawyer " + + new_id[:8], # Shorten the UUID for display purposes + "email": "new.lawyer@lawfirm.com", + "phone": "+49 (221) 99999 9999", + "fax": "+49 (221) 99999 9998", + "title": "Rechtsanwalt", + "specialty": "Fachanwalt für Spezialgebiet", + } + } + + # Retrieve the current lawyer details and update them with the new lawyer + lawyer_data = get_lawyer_data() + lawyer_data["lawyer_details"].update(new_lawyer_details) + lawyer_data["selected_lawyer_id"] = new_id # Update the selected lawyer ID + + # Save the updated lawyer details + set_lawyer_data(lawyer_data) + + # Update the component state with the new details + self.component.lawyer_details = lawyer_data["lawyer_details"] + self.component.selected_lawyer_id = new_id + + # Refresh the UI components + self.component.update_dropdown_items() + self.component.set_input_fields_for_id(new_id) + self.component.lawyer_select.value = new_lawyer_details[new_id]["name"] + + print(f"New lawyer created with name: {new_lawyer_details[new_id]['name']}") + + +class SaveLawyerHandler(OnPressHandler): + def __init__(self, component: LawyerSelectionComponent): + self.component = component + + def __call__(self, widget, **kwargs): + print("Save Lawyer Handler Called") + # Gather the updated details from the input fields + updated_details = { + "name": self.component.name_input.value.strip(), + "email": self.component.email_input.value.strip(), + "phone": self.component.phone_input.value.strip(), + "fax": self.component.fax_input.value.strip(), + "title": self.component.title_input.value.strip(), + "specialty": self.component.specialty_input.value.strip(), + } + + # Check for uniqueness of the new name + if any( + lawyer["name"] == updated_details["name"] + and lawyer_id != self.component.selected_lawyer_id + for lawyer_id, lawyer in self.component.lawyer_details.items() + ): + print(f"A lawyer with the name '{updated_details['name']}' already exists.") + self.component.app.main_window.info_dialog( + "Doppelter Name", + f"Ein Anwalt mit dem Namen '{updated_details['name']}' existiert bereits. Bitte wählen Sie einen anderen Namen.", + ) + return + + # Update the details of the selected lawyer directly in the component + self.component.lawyer_details[self.component.selected_lawyer_id] = ( + updated_details + ) + + # Prepare data to save + lawyer_data = { + "selected_lawyer_id": self.component.selected_lawyer_id, + "lawyer_details": self.component.lawyer_details, + } + + # Save the updated lawyer details + set_lawyer_data(lawyer_data) + + # Refresh the UI components + self.component.update_dropdown_items() + print(f"Lawyer details for '{updated_details['name']}' have been updated.") diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Fehlermeldung.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Fehlermeldung.py new file mode 100644 index 0000000..cdb919a --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Fehlermeldung.py @@ -0,0 +1,73 @@ +import toga +from toga.style.pack import COLUMN +from toga.style import Pack + +class Fehlermeldung(toga.Box): + def __init__(self, app, id: str | None = None, style=None, fehlermeldung=None, retry_function=None): + style = style or Pack(direction=COLUMN) + super().__init__(id=id, style=style) + + self.app = app + self.fehlermeldung = fehlermeldung or "" + self.retry_function = retry_function + + self.content = self.create_content() + self.add(self.content) + + def create_content(self): + # Get the width of the main window + main_window_width, _ = self.app.main_window.size + + # Calculate the width of the content_box widget to be 20% smaller than the main window + error_text_width = main_window_width * 0.8 + + content_box = toga.Box(style=Pack(direction=COLUMN, flex=1)) + + # Calculate the height of the error_text widget based on the number of newline characters + # TODO find a way for dynamic height + #num_lines = self.fehlermeldung.count('\n') + 1 + + error_text = toga.MultilineTextInput(value=self.fehlermeldung, style=Pack(padding=10, width=error_text_width, height=200), readonly=True) + content_box.add(error_text) + + contact_label = toga.TextInput(value="Für Support kontaktieren Sie bitte:\nName: Support Team\nEmail: support@nadooit.de Telefon: 02065 - 7098429", style=Pack(padding=10, height=100)) + content_box.add(contact_label) + + # Add a button to send the error code to support + # send_button = toga.Button("Senden", on_press=self.send_error_email) + # content_box.add(send_button) + + # Add a retry button + retry_button = toga.Button("Retry", on_press=self.retry) + content_box.add(retry_button) + + return content_box + + """ + def send_error_email(self, widget): + # Set up the email message + msg = email.message.EmailMessage() + msg.set_content("Error code: {}\n\nRegards,\nThe Nadoo Law Team".format(12345)) + msg["Subject"] = "Error Report" + msg["From"] = "nadoo_law@example.com" + msg["To"] = "support@example.com" + + # Send the email + try: + server = smtplib.SMTP("smtp.example.com", 587) + server.starttls() + server.login("nadoo_law@example.com", "password") + server.send_message(msg) + server.quit() + + # Display a success message + toga.Message.info("Die Fehlermeldung wurde erfolgreich gesendet.", title="Gesendet") + + except Exception as e: + # Display an error message + toga.Message.error("Fehler beim Senden der Fehlermeldung: {}\nBitte kontaktieren Sie das Support-Team manuell.".format(str(e)), title="Fehler") + """ + def retry(self, widget): + # Call the retry function + if self.retry_function: + self.retry_function() diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/LicenseWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/LicenseWindow.py new file mode 100644 index 0000000..222c838 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/LicenseWindow.py @@ -0,0 +1,37 @@ +import os +import toga +from toga.style import Pack +from toga.style.pack import COLUMN + + +class LicenseWindow(toga.Window): + def __init__(self, title, width=400, height=400): + super().__init__(title, size=(width, height)) + self.content = self.build() + + def build(self): + # Create the main content box + box = toga.Box(style=Pack(direction=COLUMN, padding=10, flex=1)) + + license_text="" + license_path = os.path.join(self.app.paths.app , "resources" , "lizenzen.txt") + + with open(license_path, "r") as f: + license_text = f.read() + + # Create a label to display the license text + license_textfield = toga.MultilineTextInput(value=license_text, style=Pack(padding_bottom=10, height=700, width=600), readonly=True) + + # Add the label to the box + box.add(license_textfield) + + # Optionally, add a close button + close_button = toga.Button('Close', on_press=self.close_window, style=Pack(padding_top=10)) + box.add(close_button) + + return box + + def close_window(self, widget): + self.close() + + diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py new file mode 100644 index 0000000..22cb671 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py @@ -0,0 +1,77 @@ +import json +import os +import shutil + +import toga +from toga.style import Pack +from toga.style.pack import COLUMN + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_updates_datei_user, get_updates_pdf_path, open_file, update_daten_laden_user, update_in_updates_ordner_uebertragen +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import UPDATE_ORDNER_NAME_APP, UPDATE_ORDNER_NAME_USER +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import ensure_folder_exists +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.styling import StandardStyling + +class UpdateWindow(toga.Window): + def __init__(self, title, size=(400, 400)): + super().__init__(title, size=size) + self.update_keys = [] + self.current_update_index = 0 + self.content = self.build() + self.update_keys_berechnen() + self.daten_in_felder_übertragen() + + def build(self): + box = toga.Box(style=Pack(direction=COLUMN, padding=10, flex=1)) + self.patchnotes_inhalt = toga.MultilineTextInput(style=Pack(padding_bottom=10, height=100, flex=1), readonly=True) + box.add(self.patchnotes_inhalt) + pdf_oeffnen_button = toga.Button('Patchnote-PDF öffnen', style=StandardStyling.standard_button_style(), on_press=self.open_update_pdf) + box.add(pdf_oeffnen_button) + vorheriges_update_button = toga.Button('Vorheriges Update', style=StandardStyling.standard_button_style(), on_press=self.vorheriges_update) + box.add(vorheriges_update_button) + nächstes_update_button = toga.Button('Nächstes Update', style=StandardStyling.standard_button_style(), on_press=self.nächstes_update) + box.add(nächstes_update_button) + fenster_schliessen_button = toga.Button('Fenster schließen', style=StandardStyling.standard_button_style(), on_press=self.close) + box.add(fenster_schliessen_button) + + return box + + + + + def update_keys_berechnen(self): + updates = update_daten_laden_user()['Updates'] + self.update_keys = list(updates.keys()) # Extrahiere alle Schlüssel + self.current_update_index = len(self.update_keys) - 2 + + def daten_in_felder_übertragen(self): + if self.update_keys: + update_id = self.update_keys[self.current_update_index] + if update_id == "template": # Überspringe das Template-Update + return + update_data = update_daten_laden_user() + changes_data = update_data['Updates'][update_id]['changes'] + + formatted_changes = "Bugfixes:\n- " + "\n- ".join(changes_data['bugfixes']) + "\n\n" + formatted_changes += "Features:\n- " + "\n- ".join(changes_data['features']) + "\n\n" + formatted_changes += "General Updates:\n- " + "\n- ".join(changes_data['general_updates']) + + self.patchnotes_inhalt.value = formatted_changes + + + def nächstes_update(self, widget): + if self.current_update_index < len(self.update_keys) - 1: + self.current_update_index += 1 + self.daten_in_felder_übertragen() + + def vorheriges_update(self, widget): + if self.current_update_index > 0: + self.current_update_index -= 1 + self.daten_in_felder_übertragen() + + def open_update_pdf(self, widget): + update_file_path = get_updates_pdf_path(self.app) + if os.path.exists(update_file_path): + open_file(update_file_path) + + def fenster_schließen(self, widget): + self.close() diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py deleted file mode 100644 index 66dc72f..0000000 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/manage.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys - - -def main(): - """Run administrative tasks.""" - import sys - - sys.dont_write_bytecode = True - - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") - - import django - - django.setup() - - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py deleted file mode 100644 index 04e053f..0000000 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/settings.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -from pathlib import Path -import django -from dotenv import load_dotenv - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = Path(__file__).resolve().parent.parent -ENV_FILE = Path(__file__).resolve() / ".env" - -# Load the .env file into the environment. This is done before the settings are loaded. -# This is done to make sure that the environment variables are available when the settings are loaded. -# Was required to make the environment variables available in the test_services.py file. -load_dotenv(dotenv_path=ENV_FILE) - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "changeme") - -DEFAULT_AUTO_FIELD = "django.db.models.AutoField" - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db", "db.sqlite3"), - } -} - - -""" -To connect to an existing postgres database, first: -pip install psycopg2 -then overwrite the settings above with: - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'YOURDB', - 'USER': 'postgres', - 'PASSWORD': 'password', - 'HOST': 'localhost', - 'PORT': '', - } -} -""" - -INSTALLED_APPS = ("db",) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/help.pdf b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/help.pdf new file mode 100644 index 0000000000000000000000000000000000000000..80b80726175bf1ef33cd133ee900c4d59833adc8 GIT binary patch literal 7215 zcmai32{@E(_pd072t~FSN@8O6G1hEj$r`e6gUN1~8Cg@=lI$f4A%u{v>`RtND3x8v zo~5$y>Hkc<@AuZ*_g(+Fu9>-?Gw1%D?RTGNuJZ`#DyxXW#U(&OO_@zKO=V4~AOsi& zCfYcGWMrV41UGw%0~iTN^q?w^E))U@s^WsD5R?hFL_30<9EeOI5%8`c?_{xvT2LeG zn99b^vC)N?56zic0<;U!X1QKhPZX2E z(V+7uUrW3ZJr-}@>4(8b*v|GE#2Z+7tao||naCm?iI=V^|q?rZiE4Cj^W z>DFVk9tgx>RAajQR@CIi8JfRFYG(0QDRaDH`7S;_b$cmB^94L`^H$h#owg%FyrBi3 zMg!Ncw@ivm)xZ9BCSvm(&9N-DB?GC$t;hEndLjb^b5CtQ;PPYinEsA>N55B;$Jrng z%J^{q-Z`t5+q(OQTMS`JfuqFg<^=q~97}4=xoyvmaa8H_gt`j$k8H=O?E+hE>(91j!hn{ zD)YlB)U*!?KJnQKv5l1p+z<$RezgT-IGm9K{R!Hvei4jUTYLDAK8=>pQVRgrA^C;LxUw#b)8AvJm#;rtJdaw z4M!mb9{rGZ4Y6s05H954qg|N{g=6&H7vgDU9~*0F)Rs)fPJY>zn#+x#^!E9jBI;%# zE-vAXA)a33xKnv3wVr32F$pn2k9mYzzs69UhsE|(DK6xM-VHrtE4tDU96g6X=92`r zQoC==jr;Mn_VfAs9K`|Eus_qDTg`da;q>+6 z!_*No@taQhWooD$E^DcGJ&vF7*1N{p z-s-$ib!K8m6k}~tgf+(B2#OzTPUdN0xqzezV9JJF@;mt9$>23nS}uFJ)?7|}vSw6i z4&R-k@>-?6e5af*3>tj-n+ztM5n~kDm)s{!x(TU6yn1E%cgiXbnBTQAkR3a7G19qM zE$5lR{+ZBRJ2769^~&`MCnY*(jdq7y!Iu(GL{Boz)gxoiqvVUbhMx_B5AR&`5O$C)~HBL0gRW zk_Pu;{h?0A1bYd+p^!`c^w+gdyL^5wVm9Bl-8Au7)`4-Njwd@HNy(G0sA+#&A#Hy@ zKEz3UXw#i`Gym|8-;KsR)~e8Jp7_mBpDUtlM%7%J&$!J`89$eMq+}kai9y z?=%|ag{~hAZVx;-V}PmnJd~T-4;GHZ=b*bYD08I4^_@Afk@~7$uTA-s-fF9sce1)W z7+p!Z=JHrth3P27$tx*5Duiq`nc*!z+E25k-`$o!Ar{ooSEIs8V?T($B#9UCdSKgqD>_)}ag2d53+~zq$N^TVz|YqQ{9v>9J6 z>-(^e>owZ_K&uj&7Tb9zgO$FMfxGjeq-pz!N(K2Sg^3FCyF;1xP^O{pH_?pwc3%`T zh9rZ3eoc9AORe*NsnKgK=Lg!9d_G|e(4m$+Wv zdE2e+p>l4Kk?J!HGuqMLXE@@(iy* ztu66Z0>^Ga438zmn_Rh$&?xo*>$;>A3Ocd-)P*NM5xgfFmm}C9I-Vz}i@W@>$pXjy zn7%;pt|%#`JzZD4_(eY7^UP~m28YCCP6n$nUJB+ad8&rZicV$*BIayL(?o9iGwRm>dV(CJbiJ8<7 zy!@wTEO$#8?FSM*E;h1{$#!_k(thAMu{8zJNaJ zu3tWoCyYpw&hv7M$&NxZJ1S4Q5?FSL!xtG{J7w5^c5Yeb@a5r0(^I|EkFd4K zpp~cNXC*(ZF68*^*jxA2E$!8-^JeoOcjDwQE%I2{78p=`ApE@r9if{sbnJQHs2m;3X6$A;nB>NOQ*3a!`cqKa zq~!??Fn|4flrb$&0FzVEenQlI5$pNIq>Di-i$hy}QprbDm!u7Yw7efkdRFYa8+-YL z_$m1K`!PXdp_<~&REF$D#uMV#wH~V9NN6};9FHSfTNyxaZOp^=JF$2gUn3k+zk%y_ zap=a=ywdzs#A?XR*Ec4UgV@bwo@vSsy(Q%mxYoibWxg*jf`86EeKB_FoM#1nS>nk1 zF&nI|Zf29vqP4>7K19FqBJjsw@O~8E#YWWGYyx+Ip{>SNBOp#1aA$+Nc z1`et+_ripyC@4NM$K?+qy~=cNmCKW#WXKlBltQH*Ih0V;)KyyP;&0BXOI9M;^=$A$ z>hrTHY+mDj@pje*9*lIaaL}Me~EOO54r)NHF?z+c&xAo!ewWZH(aUE+f;a1q$zyO!)^0>Y+{d^$F1W0cGQ z^~s4PWA2L+2Fy3$9{GkcDA^a=!36D@z}re~*k!icB<=#utmIgVRB_Lgl>cUtey`cl z#@E=lCG+#%vnLsFUsdBw?%iK#T5mO;f8=x3giT5I%F}b#*OqMD4UXqzRJOTLFmvKX z5pR{HrZjQFDNB>Vd80#gDJ&rj=8op73fEz}=7@2GqCl9^fhhSn4P4Cmr6U&fM{ppm zu6>aeXDI0gyj!<(bkb?YPB|4qkfcDLCcsokintLK9w&bW@PF9*`qK0lx>zWp(?q+?0QZ7#WXiHC+^MnPuzRm5J z*agd3)J&0Dw)g6d7!K9i!aO>hWQZ3ugg3+%?-oD6r*Kn1d}`u;KAYngv6igRuj9r1 zvQZfUVO|JfuJN(~Wrw>H^s1}(<-*pqm+2eLp$TRn*{hW_Ua64+e8Yz~DwE!GJs7XK z+^p|wY1$d;bIc>N#FV#1Epe3o2EpQLDn9xpX<4eRX3jJ_J0@wmL?BaV{0-}s1TFkUl$GtaoK%MwLHUh$ z_)u06UFwZ3gJz#mn<#WmYx(1cot+hO*>~novqY_&C3)u0)Z*`*kVjd53PK*U6`|lEz~mI;GSU(SyG*^z?gY- zr%ZqATEC6yoP}Q0o?eTb_hkqW%~~^lw7D0y$e{V$(7%|1r!2I#&A87#VVL!6$!%TO zJ~v-n!B!Kfv7t(W-xY}{)*1a){K7(_*6#bX6g=2om;W4;ZF|>y?bTc9vqcuc0GcJd*5%pJ^qZc%zHX!G@5U_U;62K%l9*(lmzFp=jG&xGxBP|n$Kmeg%$dh zRPezI!G_~4**rdwIcYQg;T|3^_|L0ON6E1_)FD?J#P21SD03AZguQR zuO!SAzDYt5lLM4C=ld)!ugtIa9oz}oV{8=3yBgOkGug^t!(}=+O`mV1IBR((&GsTkBL~ zPc$S7$jI)rf`_#D@mVsFRrPz~v6SMJT-lYydq?dfZnh$#Qvx(QWPGaXeWI7RqXC1k5fm6)4aUi7_a#f-Md}+ap~W7xEFS+oEA1v36Y#|JPqhR zuDbv9`Yn&8JKh&MB$&1zFVIwo?>yO|EiX*#cm7hoQN8`5GNj!ttM7X6WueQ2%Qt05 zxc$8PII1h#N2ca{tLG=4I9vE+SWHUpu07bWTn%?4Mz+td8%oyXRk@z*oeq!Hv*9a7 zC8^Q|M!JQvCipa*P3+8=w!QTBuq)UuzgcbiqX9i^3ih|FZmDh$@;Jj8f`q*QVKv&jd>Hp9z$Kk2?XXLUf}j6UeqCM|TPl7_^ z2D-F5LRDDU4#=%43v5(ozebG+@hjBc+++%7kp3Db9`>qAA<$6{KG8o%UQMEkW^1-4i(&{s!m zsMeU(d~alM4Gv`3&rSErCvML~`(^2Tms6aU`4PJ<9&amI#dx2}ix;#f7P1<_pTqJ+ zK>IsCpXN0ErjsY3dM-zY%b_H*?IE@&&^>;sNo35nCAu|uo~cjr>le*7$F()eEn_7B&hPEl;4>{1-OB;s0dqKb42-d)iQ_G%z4}5`IV(@nixu_y3y! zjwCWg$pKHI7FHAg*BT&%k)s{Ofoui?V}Jso08SvWl3;{{1lZzdHUD85m`XxD5Y&Kp z(an)M(*^%1>F-7UWg@Hs2mkQ?cZI*_t1>b_@d5h%gw-VxZS@HhGbk`-h8hsODHgx6 z=U-{i#oH60x_A9>2(G|c z0C4bbc6gE<2&xN4QC$NT_F!rqft3Onn3d}Q74RW?QlPpncw2%iU<0b?LbP?J1`O2z z{QWI-Dh9P702_bQ`gd!}L|acP0@WJ)4|5u3RA2SLza#$8`xDZDL?Gx9iBzZ`iKSkH z;XeRCP(7jxK@0B=IPrIUP~E@${bBXrCVt70wx=uE3I=29 zWWe6P$_XUK(T;5PGmlhqDiJ+_+QWZAg8v&z2@fezEQw8 zntBb80yrFYp0u9@gD-*q zlA$HgKw#7t*zIo_3>qe&I2LFYzvH1%l7GrDm_PSQ!2a+B1C#v27YrQesQo1hD2aON^&NeIbBG0wAQ2$^k8THoQb`7e|GdjW)s=z6C4+=vQIZlcv=SDERz#_w zRg_^WDsTiEts()#!4Z;jp#NWmda{6)qU1oZbtZedf}x5S3=$4xT?vkogrgPFI22q3 isfbiT!vO&jhousmOu>^VKPZC1pb;P;A)Jm1=>Gt%KQea! literal 0 HcmV?d00001 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/__init__.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/kundendaten/placeholder.txt similarity index 100% rename from {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/db/__init__.py rename to {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/kundendaten/placeholder.txt diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/lizenzen.txt b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/lizenzen.txt new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/update.pdf b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/update.pdf new file mode 100644 index 0000000000000000000000000000000000000000..80b80726175bf1ef33cd133ee900c4d59833adc8 GIT binary patch literal 7215 zcmai32{@E(_pd072t~FSN@8O6G1hEj$r`e6gUN1~8Cg@=lI$f4A%u{v>`RtND3x8v zo~5$y>Hkc<@AuZ*_g(+Fu9>-?Gw1%D?RTGNuJZ`#DyxXW#U(&OO_@zKO=V4~AOsi& zCfYcGWMrV41UGw%0~iTN^q?w^E))U@s^WsD5R?hFL_30<9EeOI5%8`c?_{xvT2LeG zn99b^vC)N?56zic0<;U!X1QKhPZX2E z(V+7uUrW3ZJr-}@>4(8b*v|GE#2Z+7tao||naCm?iI=V^|q?rZiE4Cj^W z>DFVk9tgx>RAajQR@CIi8JfRFYG(0QDRaDH`7S;_b$cmB^94L`^H$h#owg%FyrBi3 zMg!Ncw@ivm)xZ9BCSvm(&9N-DB?GC$t;hEndLjb^b5CtQ;PPYinEsA>N55B;$Jrng z%J^{q-Z`t5+q(OQTMS`JfuqFg<^=q~97}4=xoyvmaa8H_gt`j$k8H=O?E+hE>(91j!hn{ zD)YlB)U*!?KJnQKv5l1p+z<$RezgT-IGm9K{R!Hvei4jUTYLDAK8=>pQVRgrA^C;LxUw#b)8AvJm#;rtJdaw z4M!mb9{rGZ4Y6s05H954qg|N{g=6&H7vgDU9~*0F)Rs)fPJY>zn#+x#^!E9jBI;%# zE-vAXA)a33xKnv3wVr32F$pn2k9mYzzs69UhsE|(DK6xM-VHrtE4tDU96g6X=92`r zQoC==jr;Mn_VfAs9K`|Eus_qDTg`da;q>+6 z!_*No@taQhWooD$E^DcGJ&vF7*1N{p z-s-$ib!K8m6k}~tgf+(B2#OzTPUdN0xqzezV9JJF@;mt9$>23nS}uFJ)?7|}vSw6i z4&R-k@>-?6e5af*3>tj-n+ztM5n~kDm)s{!x(TU6yn1E%cgiXbnBTQAkR3a7G19qM zE$5lR{+ZBRJ2769^~&`MCnY*(jdq7y!Iu(GL{Boz)gxoiqvVUbhMx_B5AR&`5O$C)~HBL0gRW zk_Pu;{h?0A1bYd+p^!`c^w+gdyL^5wVm9Bl-8Au7)`4-Njwd@HNy(G0sA+#&A#Hy@ zKEz3UXw#i`Gym|8-;KsR)~e8Jp7_mBpDUtlM%7%J&$!J`89$eMq+}kai9y z?=%|ag{~hAZVx;-V}PmnJd~T-4;GHZ=b*bYD08I4^_@Afk@~7$uTA-s-fF9sce1)W z7+p!Z=JHrth3P27$tx*5Duiq`nc*!z+E25k-`$o!Ar{ooSEIs8V?T($B#9UCdSKgqD>_)}ag2d53+~zq$N^TVz|YqQ{9v>9J6 z>-(^e>owZ_K&uj&7Tb9zgO$FMfxGjeq-pz!N(K2Sg^3FCyF;1xP^O{pH_?pwc3%`T zh9rZ3eoc9AORe*NsnKgK=Lg!9d_G|e(4m$+Wv zdE2e+p>l4Kk?J!HGuqMLXE@@(iy* ztu66Z0>^Ga438zmn_Rh$&?xo*>$;>A3Ocd-)P*NM5xgfFmm}C9I-Vz}i@W@>$pXjy zn7%;pt|%#`JzZD4_(eY7^UP~m28YCCP6n$nUJB+ad8&rZicV$*BIayL(?o9iGwRm>dV(CJbiJ8<7 zy!@wTEO$#8?FSM*E;h1{$#!_k(thAMu{8zJNaJ zu3tWoCyYpw&hv7M$&NxZJ1S4Q5?FSL!xtG{J7w5^c5Yeb@a5r0(^I|EkFd4K zpp~cNXC*(ZF68*^*jxA2E$!8-^JeoOcjDwQE%I2{78p=`ApE@r9if{sbnJQHs2m;3X6$A;nB>NOQ*3a!`cqKa zq~!??Fn|4flrb$&0FzVEenQlI5$pNIq>Di-i$hy}QprbDm!u7Yw7efkdRFYa8+-YL z_$m1K`!PXdp_<~&REF$D#uMV#wH~V9NN6};9FHSfTNyxaZOp^=JF$2gUn3k+zk%y_ zap=a=ywdzs#A?XR*Ec4UgV@bwo@vSsy(Q%mxYoibWxg*jf`86EeKB_FoM#1nS>nk1 zF&nI|Zf29vqP4>7K19FqBJjsw@O~8E#YWWGYyx+Ip{>SNBOp#1aA$+Nc z1`et+_ripyC@4NM$K?+qy~=cNmCKW#WXKlBltQH*Ih0V;)KyyP;&0BXOI9M;^=$A$ z>hrTHY+mDj@pje*9*lIaaL}Me~EOO54r)NHF?z+c&xAo!ewWZH(aUE+f;a1q$zyO!)^0>Y+{d^$F1W0cGQ z^~s4PWA2L+2Fy3$9{GkcDA^a=!36D@z}re~*k!icB<=#utmIgVRB_Lgl>cUtey`cl z#@E=lCG+#%vnLsFUsdBw?%iK#T5mO;f8=x3giT5I%F}b#*OqMD4UXqzRJOTLFmvKX z5pR{HrZjQFDNB>Vd80#gDJ&rj=8op73fEz}=7@2GqCl9^fhhSn4P4Cmr6U&fM{ppm zu6>aeXDI0gyj!<(bkb?YPB|4qkfcDLCcsokintLK9w&bW@PF9*`qK0lx>zWp(?q+?0QZ7#WXiHC+^MnPuzRm5J z*agd3)J&0Dw)g6d7!K9i!aO>hWQZ3ugg3+%?-oD6r*Kn1d}`u;KAYngv6igRuj9r1 zvQZfUVO|JfuJN(~Wrw>H^s1}(<-*pqm+2eLp$TRn*{hW_Ua64+e8Yz~DwE!GJs7XK z+^p|wY1$d;bIc>N#FV#1Epe3o2EpQLDn9xpX<4eRX3jJ_J0@wmL?BaV{0-}s1TFkUl$GtaoK%MwLHUh$ z_)u06UFwZ3gJz#mn<#WmYx(1cot+hO*>~novqY_&C3)u0)Z*`*kVjd53PK*U6`|lEz~mI;GSU(SyG*^z?gY- zr%ZqATEC6yoP}Q0o?eTb_hkqW%~~^lw7D0y$e{V$(7%|1r!2I#&A87#VVL!6$!%TO zJ~v-n!B!Kfv7t(W-xY}{)*1a){K7(_*6#bX6g=2om;W4;ZF|>y?bTc9vqcuc0GcJd*5%pJ^qZc%zHX!G@5U_U;62K%l9*(lmzFp=jG&xGxBP|n$Kmeg%$dh zRPezI!G_~4**rdwIcYQg;T|3^_|L0ON6E1_)FD?J#P21SD03AZguQR zuO!SAzDYt5lLM4C=ld)!ugtIa9oz}oV{8=3yBgOkGug^t!(}=+O`mV1IBR((&GsTkBL~ zPc$S7$jI)rf`_#D@mVsFRrPz~v6SMJT-lYydq?dfZnh$#Qvx(QWPGaXeWI7RqXC1k5fm6)4aUi7_a#f-Md}+ap~W7xEFS+oEA1v36Y#|JPqhR zuDbv9`Yn&8JKh&MB$&1zFVIwo?>yO|EiX*#cm7hoQN8`5GNj!ttM7X6WueQ2%Qt05 zxc$8PII1h#N2ca{tLG=4I9vE+SWHUpu07bWTn%?4Mz+td8%oyXRk@z*oeq!Hv*9a7 zC8^Q|M!JQvCipa*P3+8=w!QTBuq)UuzgcbiqX9i^3ih|FZmDh$@;Jj8f`q*QVKv&jd>Hp9z$Kk2?XXLUf}j6UeqCM|TPl7_^ z2D-F5LRDDU4#=%43v5(ozebG+@hjBc+++%7kp3Db9`>qAA<$6{KG8o%UQMEkW^1-4i(&{s!m zsMeU(d~alM4Gv`3&rSErCvML~`(^2Tms6aU`4PJ<9&amI#dx2}ix;#f7P1<_pTqJ+ zK>IsCpXN0ErjsY3dM-zY%b_H*?IE@&&^>;sNo35nCAu|uo~cjr>le*7$F()eEn_7B&hPEl;4>{1-OB;s0dqKb42-d)iQ_G%z4}5`IV(@nixu_y3y! zjwCWg$pKHI7FHAg*BT&%k)s{Ofoui?V}Jso08SvWl3;{{1lZzdHUD85m`XxD5Y&Kp z(an)M(*^%1>F-7UWg@Hs2mkQ?cZI*_t1>b_@d5h%gw-VxZS@HhGbk`-h8hsODHgx6 z=U-{i#oH60x_A9>2(G|c z0C4bbc6gE<2&xN4QC$NT_F!rqft3Onn3d}Q74RW?QlPpncw2%iU<0b?LbP?J1`O2z z{QWI-Dh9P702_bQ`gd!}L|acP0@WJ)4|5u3RA2SLza#$8`xDZDL?Gx9iBzZ`iKSkH z;XeRCP(7jxK@0B=IPrIUP~E@${bBXrCVt70wx=uE3I=29 zWWe6P$_XUK(T;5PGmlhqDiJ+_+QWZAg8v&z2@fezEQw8 zntBb80yrFYp0u9@gD-*q zlA$HgKw#7t*zIo_3>qe&I2LFYzvH1%l7GrDm_PSQ!2a+B1C#v27YrQesQo1hD2aON^&NeIbBG0wAQ2$^k8THoQb`7e|GdjW)s=z6C4+=vQIZlcv=SDERz#_w zRg_^WDsTiEts()#!4Z;jp#NWmda{6)qU1oZbtZedf}x5S3=$4xT?vkogrgPFI22q3 isfbiT!vO&jhousmOu>^VKPZC1pb;P;A)Jm1=>Gt%KQea! literal 0 HcmV?d00001 diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/updates/update.json b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/updates/update.json new file mode 100644 index 0000000..6651b5c --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/updates/update.json @@ -0,0 +1,25 @@ +{ + "neues_update_wurde_installiert": true, + "Updates": { + "3a259cd3-31c7-45be-952a-17b8948f5505": { + "name": "UpdateName", + "version": "0.0.1", + "date": "01.03.2024", + "fullpatch_pdf": "098-765-4321", + "changes": { + "bugfixes": [ + "Korrekturen im XYZ", + "Korrekturen im ABC" + ], + "features": [ + "Cooles neue features" + ], + "general_updates": [ + "Verbesserungen der Benutzerdokumentation" + ] + } + } + } +} + + diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py index e69de29..0219b7a 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py @@ -0,0 +1,176 @@ +import subprocess +import platform +import os +import json +import shutil + +from datetime import datetime + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import KUNDENDATEN_ORDNER_APP, ARCHIV_ORDNER, BASE_DIR, UPDATE_ORDNER_NAME_USER, UPDATE_ORDNER_NAME_APP + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import ( + get_base_dir_path, +) + +""" +Vorlage for JSON-Datei anbindung +def set_xyz_data(beweismittel_data): + ensure_beweismittel_OWi_data_file_exists() + file_path = get_beweismittel_OWi_data_file_path() + with open(file_path, "w") as f: + json.dump(beweismittel_data, f, indent=4) + + +def get_xyz_data(): + ensure_beweismittel_OWi_data_file_exists() + file_path = get_beweismittel_OWi_data_file_path() + with open(file_path, "r") as f: + return json.load(f) +""" +def setup_folders(): + # Grund Verzeichnisstruktur anlegen + ensure_base_folder_exits() + versionsordner_erstellen() + pass + +def versionsordner_erstellen(): + folder_name = UPDATE_ORDNER_NAME_USER + ensure_folder_exists(folder_name) + +def ensure_folder_exists(folder_name): + base_dir = get_base_dir_path() + folder_path = os.path.join(base_dir, folder_name) + if not os.path.exists(folder_path): + os.makedirs(folder_path) + return folder_path + +def ensure_base_folder_exits(): + base_dir = os.path.join(os.path.expanduser("~"), BASE_DIR) + if not os.path.exists(base_dir): + os.makedirs(base_dir) + +def open_file(file_path): + if file_path: + try: + if platform.system() == "Windows": + subprocess.run(["explorer", file_path], check=True) + elif platform.system() == "Darwin": # macOS + subprocess.run(["open", file_path], check=True) + else: # Assuming Linux + subprocess.run(["xdg-open", file_path], check=True) + except subprocess.CalledProcessError as e: + print(f"Error opening file: {e}") + else: + print("File path is not set. Unable to open file.") + +def get_updates_datei_user(): + #ich möchte den dateipfad der update.json datei aus dem updates ordner haben, welcher im updates ordner im user verzeichnis liegt + updates_dateipfad_user = os.path.join(os.path.expanduser("~"), get_base_dir(), UPDATE_ORDNER_NAME_USER, "update.json") + return updates_dateipfad_user + +def get_updates_pdf_path(app): + updates_pdf_dateipfad = os.path.join(app.paths.app, "resources", "update.pdf") + return updates_pdf_dateipfad + +def update_daten_laden_user(): + file_path = get_updates_datei_user() + with open(file_path, "r") as f: + return json.load(f) + +def update_daten_laden_app(app): + file_path = get_updates_datei_app(app) + with open(file_path, "r") as f: + return json.load(f) + +def get_updates_datei_app(app): + updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") + return updates_datei_app + +def update_in_updates_ordner_uebertragen(app): + updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") + updates_datei_user = get_updates_datei_user() + + shutil.copy(updates_datei_app, updates_datei_user) + +def get_help_file_path(app): + return os.path.join(app.paths.app, "resources", "help.pdf") + +def get_base_dir(): + return get_base_dir_path() + +""" +#TODO #96 Veralgemeinern +def update_daten_in_basis_ordner_uebertragen(app): + kundendaten_ordner_app = os.path.join(app.paths.app, "resources", KUNDENDATEN_ORDNER_APP) + basis_ordner_user = get_base_dir() # Angenommen, diese Funktion gibt den Basisordner des Benutzers zurück + archiv_ordner = os.path.join(basis_ordner_user,ARCHIV_ORDNER, "archivierte_anwalt_daten") + ziel_json_datei_name = "lawyer_details.json" + update_datei_name = "update_lawyer_details.json" + + ziel_datei_pfad = os.path.join(basis_ordner_user, ziel_json_datei_name) + update_datei_pfad = os.path.join(kundendaten_ordner_app, update_datei_name) + + # Stelle sicher, dass der Archivordner existiert + if not os.path.exists(archiv_ordner): + os.makedirs(archiv_ordner) + + # Überprüfe, ob die Datei lawyer_details.json bereits im Zielordner existiert + if os.path.isfile(ziel_datei_pfad): + # Wenn ja, und update_lawyer_details.json ist vorhanden, archiviere die alte Datei + if os.path.isfile(update_datei_pfad): + # Generiere einen eindeutigen Namen für die archivierte Datei + datumsanhang = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + archivierte_datei_name = f"lawyer_details_{datumsanhang}.json" + archivierte_datei_pfad = os.path.join(archiv_ordner, archivierte_datei_name) + + # Verschiebe die alte lawyer_details.json in den Archivordner + shutil.move(ziel_datei_pfad, archivierte_datei_pfad) + + # Kopiere die update_lawyer_details.json in den Zielordner und benenne sie um + shutil.copy(update_datei_pfad, ziel_datei_pfad) + + else: + # Wenn keine lawyer_details.json im Zielordner, kopiere diese aus dem Kundendatenordner, falls vorhanden + source_datei_pfad = os.path.join(kundendaten_ordner_app, ziel_json_datei_name) + if os.path.isfile(source_datei_pfad): + shutil.copy(source_datei_pfad, ziel_datei_pfad) + +def vorlagen_in_vorlagen_ordner_uebertragen(app): + vorlagen_ordner_app = os.path.join(app.paths.app, "resources", KUNDENDATEN_ORDNER_APP, VORLAGEN_ORDNER_APP) + vorlagen_ordner_user = get_template_folder() # Annahme, dass diese Funktion das Benutzerverzeichnis für Vorlagen zurückgibt + archiv_ordner = os.path.join(vorlagen_ordner_user, ARCHIV_ORDNER) + placeholder_file_name = "placeholder.txt" + + # Stelle sicher, dass der Archivordner existiert + if not os.path.exists(archiv_ordner): + os.makedirs(archiv_ordner) + + # Überprüfe, ob der Vorlagenordner existiert + if not os.path.isdir(vorlagen_ordner_app): + return # Beende die Funktion, wenn der Ordner nicht existiert + + for file in os.listdir(vorlagen_ordner_app): + if file == placeholder_file_name: + continue # Ignoriere die Platzhalterdatei + + vorlage_app_pfad = os.path.join(vorlagen_ordner_app, file) + neuer_name_ohne_update = file.replace("update_", "") + vorlage_user_pfad = os.path.join(vorlagen_ordner_user, neuer_name_ohne_update) + + # Wenn es sich um eine Update-Datei handelt ODER keine Datei im Benutzerverzeichnis existiert, führe den Kopiervorgang durch + if file.startswith("update_") or not os.path.exists(vorlage_user_pfad): + if os.path.exists(vorlage_user_pfad): + # Archiviere die existierende Datei, bevor sie durch die Update-Datei ersetzt wird + basisname, erweiterung = os.path.splitext(neuer_name_ohne_update) + datumsanhang = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + archivierte_datei_name = f"{basisname}_{datumsanhang}{erweiterung}" + shutil.move(vorlage_user_pfad, os.path.join(archiv_ordner, archivierte_datei_name)) + + # Kopiere die Vorlage oder aktualisierte Vorlage in das Benutzerverzeichnis + shutil.copy(vorlage_app_pfad, vorlage_user_pfad) + + if file.startswith("update_"): + # Umbenennen der "update_"-Datei im Anwendungsordner, indem das "update_" Präfix entfernt wird + neuer_app_pfad_ohne_update = os.path.join(vorlagen_ordner_app, neuer_name_ohne_update) + os.rename(vorlage_app_pfad, neuer_app_pfad_ohne_update) +""" \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/styling.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/styling.py new file mode 100644 index 0000000..7a9272d --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/styling.py @@ -0,0 +1,77 @@ +import sys +from toga.style import Pack + +class StandardStyling: + @staticmethod + def _get_style_for_platform(): + if sys.platform == "win32": + # Windows styles + return { + "standard_label_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=7), + "standard_input_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=15), + "standard_selection_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=15), + "standard_button_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=7), + "standard_switch_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=7), + "standard_highlighted_input_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=15, background_color="red"), + } + elif sys.platform == "darwin": + # macOS styles + return { + "standard_label_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=25, font_size=15), + "standard_input_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=17), + "standard_selection_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=16), + "standard_button_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=9), + "standard_switch_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=9), + "standard_highlighted_input_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=17, background_color="blue"), + "standard_box_style": Pack(flex=1, padding_top=12, padding_bottom=12, height=30, font_size=17), + } + elif sys.platform == "linux": + # Linux styles + return { + "standard_label_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=25, font_size=8), + "standard_input_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=25, font_size=16), + "standard_selection_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=30, font_size=16), + "standard_button_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=30, font_size=8), + "standard_switch_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=30, font_size=8), + "standard_highlighted_input_style": Pack(flex=1, padding_top=11, padding_bottom=11, height=25, font_size=16, background_color="green"), + } + else: + # Default styles for any other platform + return { + "standard_label_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=7), + "standard_input_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=15), + "standard_selection_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=15), + "standard_button_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=7), + "standard_switch_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=30, font_size=7), + "standard_highlighted_input_style": Pack(flex=1, padding_top=10, padding_bottom=10, height=25, font_size=15, background_color="red"), + } + + + @staticmethod + def standard_label_style(): + return StandardStyling._get_style_for_platform()["standard_label_style"] + + @staticmethod + def standard_input_style(): + return StandardStyling._get_style_for_platform()["standard_input_style"] + + @staticmethod + def standard_selection_style(): + return StandardStyling._get_style_for_platform()["standard_selection_style"] + + @staticmethod + def standard_button_style(): + return StandardStyling._get_style_for_platform()["standard_button_style"] + + @staticmethod + def standard_switch_style(): + return StandardStyling._get_style_for_platform()["standard_switch_style"] + + @staticmethod + def standard_box_style(): + return StandardStyling._get_style_for_platform()["standard_box_style"] + + @staticmethod + def standard_highlighted_input_style(): + return StandardStyling._get_style_for_platform()["standard_highlighted_input_style"] + diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py index e69de29..107ee28 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py @@ -0,0 +1,189 @@ +import os +import json +import subprocess +import platform +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import BASE_DIR +def ensure_folder_exists(folder_name): + base_dir = get_base_dir_path() + folder_path = os.path.join(base_dir, folder_name) + if not os.path.exists(folder_path): + os.makedirs(folder_path) + return folder_path + +def get_base_dir_path(): + ensure_base_folder_exits() + return os.path.join(os.path.expanduser("~"), BASE_DIR) + +def ensure_base_folder_exits(): + base_dir = os.path.join(os.path.expanduser("~"), BASE_DIR) + if not os.path.exists(base_dir): + os.makedirs(base_dir) + +""" Beispiel für Abrufen von Dateipfad +def get_xyz_data_file_path(): + base_dir = get_base_dir_path() + return os.path.join(base_dir, XYZ) +""" +""" +def ensure_editible_dropdown_file_exists(): + file_path = get_editable_dropdown_data_file_path() + if not os.path.isfile(file_path): + # Initialize the file with default data if it doesn't exist + default_data = { + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4", + "Option 5", + ] + + + } + with open(file_path, "w") as f: + json.dump(default_data, f, indent=4) +""" + +def translate_date_to_german(date_str): + # Map of English month names to German month names + month_translation = { + "January": "Januar", + "February": "Februar", + "March": "März", + "April": "April", + "May": "Mai", + "June": "Juni", + "July": "Juli", + "August": "August", + "September": "September", + "October": "Oktober", + "November": "November", + "December": "Dezember", + } + # Split the date string to extract the month + parts = date_str.split() + if len(parts) == 3: + day, month, year = parts + # Translate the month to German + german_month = month_translation.get(month, month) + # Return the date string in German format + return f"{day} {german_month} {year}" + return date_str # Return the original string if format is unexpected + +def convert_docx_to_pdf_with_libreoffice(self, docx_path, output_folder): + # Determine the LibreOffice command based on the operating system + if platform.system() == "Darwin": # macOS + libreoffice_command = "/Applications/LibreOffice.app/Contents/MacOS/soffice" + elif platform.system() == "Windows": + libreoffice_command = "C:\\Program Files\\LibreOffice\\program\\soffice.exe" + else: + raise OSError("Unsupported operating system for this conversion script") + + # Correctly determining the output directory from the provided path + if not os.path.isdir(output_folder): # If the provided path is not a directory + # Assuming the provided path might be a file path, use its directory as the output folder + output_folder = os.path.dirname(docx_path) + + # Ensuring the output directory exists + os.makedirs(output_folder, exist_ok=True) + + # Define the base name for the output files without the extension + base_output_name = os.path.splitext(os.path.basename(docx_path))[0] + + # Define the PDF export options for different versions + pdf_versions = { + "_PDF_A-1": 'pdf:writer_pdf_Export:{"SelectPdfVersion":{"type":"long","value":"1"}}', + "_PDF_A-2": 'pdf:writer_pdf_Export:{"SelectPdfVersion":{"type":"long","value":"2"}}', + "_PDF_UA": 'pdf:writer_pdf_Export:{"SelectPdfVersion":{"type":"long","value":"2"},"PDFUACompliance":{"type":"boolean","value":true}}', + "": "", + } + + # Ensure the output directory exists + os.makedirs(output_folder, exist_ok=True) + + # Iterate over the PDF versions and export each one + for suffix, export_options in pdf_versions.items(): + output_pdf_path = os.path.join( + output_folder, f"{base_output_name}{suffix}.pdf" + ) + try: + + print(f"Converting to PDF: '{output_pdf_path}'") + # TODO on Windows the "normal" PDF does not work + # Construct the command as a list + command = [ + libreoffice_command, + "--headless", + "--convert-to", + export_options, + "--outdir", + output_folder, + docx_path, + ] + + # Print the command to see what will be executed + print("Executing command:", " ".join(command)) + + # Execute the command using subprocess.run + subprocess.run( + command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + # Check if the default output PDF was created + default_output_pdf_path = os.path.join( + output_folder, f"{base_output_name}.pdf" + ) + + print(f"Checking if {default_output_pdf_path} exists...") + + if os.path.exists(default_output_pdf_path): + # Rename the output file to include the suffix + os.rename(default_output_pdf_path, output_pdf_path) + print(f"Converted to PDF: '{output_pdf_path}'") + self.pdf_file_path = output_pdf_path + else: + print( + f"Expected PDF '{default_output_pdf_path}' not found after conversion." + ) + except subprocess.CalledProcessError as e: + print(f"Error during conversion: {e}") + print(e.stdout.decode()) + print(e.stderr.decode()) + except FileNotFoundError: + print( + f"LibreOffice executable not found at '{libreoffice_command}'. Please ensure LibreOffice is installed at the specified path." + ) + except OSError as e: + print(f"Error during file renaming: {e}") + +def open_folder(self, widget): + folder_path = self.folder_path # Assuming this is the path you want to open + try: + if platform.system() == "Windows": + subprocess.run(["explorer", folder_path], check=True) + elif platform.system() == "Darwin": # macOS + subprocess.run(["open", folder_path], check=True) + else: # Assuming Linux + subprocess.run(["xdg-open", folder_path], check=True) + except subprocess.CalledProcessError as e: + print(f"Error opening folder: {e}") + +def is_libreoffice_installed(): + if platform.system() == "Darwin": # macOS + libreoffice_command = "/Applications/LibreOffice.app/Contents/MacOS/soffice" + elif platform.system() == "Windows": + libreoffice_command = "C:\\Program Files\\LibreOffice\\program\\soffice.exe" + else: + libreoffice_command = ( + "libreoffice" # For Linux and other OSes, try the generic command + ) + + try: + subprocess.run( + [libreoffice_command, "--version"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return True + except (subprocess.CalledProcessError, FileNotFoundError): + return False \ No newline at end of file From e13bcdc65fc7fdbd285e038b7a04313a7634a4bc Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:06:08 +0100 Subject: [PATCH 03/34] up --- requirements.txt | 3 ++- .../src/{{ cookiecutter.module_name }}/CONSTANTS.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d63bff8..f1c8ede 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ pre-commit == 3.5.0 pytest == 7.4.3 toml == 0.10.2 tox == 4.11.3 -screeninfo == 1.5.0 \ No newline at end of file +screeninfo == 1.5.0 +toga \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py index 525d2f8..b28ef54 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py @@ -1,4 +1,5 @@ UPDATE_ORDNER_NAME_APP = "updates" UPDATE_ORDNER_NAME_USER = "Updates" BASE_DIR = "{{ cookiecutter.app_name|lower|replace('-', '_') }}" -ARCHIV_ORDNER = "Archive" \ No newline at end of file +ARCHIV_ORDNER = "Archive" +KUNDENDATEN_ORDNER_APP = "kundendaten" \ No newline at end of file From 95935ad3b6fc97206b190ff5c0956e64781d26f3 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:06:28 +0100 Subject: [PATCH 04/34] Delete models.py --- .../src/{{ cookiecutter.module_name }}/models.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/models.py deleted file mode 100644 index e69de29..0000000 From 6ab2f60ab6ea7a528587f995ad7e324699b72177 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:13:36 +0100 Subject: [PATCH 05/34] changed default app --- tests/test_app_template.py | 194 +---------------- .../src/{{ cookiecutter.module_name }}/app.py | 198 +++++++++++++++++- 2 files changed, 197 insertions(+), 195 deletions(-) diff --git a/tests/test_app_template.py b/tests/test_app_template.py index 44ef4ae..a7a3078 100644 --- a/tests/test_app_template.py +++ b/tests/test_app_template.py @@ -32,203 +32,11 @@ """ APP_SOURCE = """\ -import os -import sys -import subprocess from datetime import datetime -import toga -from toga.style.pack import COLUMN -from toga.style import Pack -from toga import Command, Group - -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow - -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen -from screeninfo import get_monitors - -class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): - - # Define the action handlers for the commands - def show_license(self, widget): - # Instantiate the LicenseWindow - license_window = LicenseWindow(title="Lizenzinformationen") - - # Show the license window - license_window.show() - - def show_updates(self, *argv): - # Instantiate the UpdateWindow - update_window = UpdateWindow(title="Updateinformationen") - - # Show the update window - update_window.show() - - def show_help(self, widget): - help_file_path = get_help_file_path(self.app) - if os.path.exists(help_file_path): - open_file(help_file_path) - - def base_ordner_offnen(self, widget): - # Retrieve the template folder path - base_folder_path = get_base_dir() - - # Open the template folder in the system's file explorer - if sys.platform == "win32": - os.startfile(base_folder_path) - elif sys.platform == "darwin": - subprocess.Popen(["open", base_folder_path]) - else: # 'linux', 'linux2', 'cygwin', 'os2', 'os2emx', 'riscos', 'atheos' - subprocess.Popen(["xdg-open", base_folder_path]) - - def aktualisierung_anzeigen(self): - self.show_updates() - - def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): - #beim start ausführen - update_daten = update_daten_laden_user() - neues_update_wurde_installiert = update_daten.get("neues_update_wurde_installiert") - if neues_update_wurde_installiert: - return True - else: - return False - - def neues_update_existiert(self): - updates_user = update_daten_laden_user()['Updates'] - update_keys_user = list(updates_user.keys()) # Extrahiere alle Schlüssel - anzahl_update_user = len(update_keys_user) - updates_app = update_daten_laden_app(self)['Updates'] - update_keys_app = list(updates_app.keys()) # Extrahiere alle Schlüssel - anzahl_update_app = len(update_keys_app) - if anzahl_update_app > anzahl_update_user: - return True - else: - return False - - def startup(self): - - # Erstellen von Vorraussetung damit die App funktioniert. - setup_folders() - - # Menü - help_group = Group("Help", order=100) - settings_group = Group("Settings", order=50) - - # Define commands - license_command = Command( - action=self.show_license, - text="Lizenzinformationen", - tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", - group=help_group, - ) - open_help_file_command = Command( - action=self.show_help, - text="Hilfe öffnen", - tooltip="Öffent die Hilfe PDF", - group=help_group, - ) - update_command = Command( - action=self.show_updates, - text="Updateinformationen", - tooltip="Zeigt die Veränderung in der Software an", - group=help_group, - ) - basis_ordner_öffnen_command = Command( - action=self.base_ordner_offnen, - text="Basis Ordner öffnen", - tooltip="Öffnet den Programmordner", - group=settings_group, - ) - - # Menü - help_group = Group("Help", order=100) - settings_group = Group("Settings", order=50) - - # Define commands - license_command = Command( - action=self.show_license, - text="Lizenzinformationen", - tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", - group=help_group, - ) - - open_help_file_command = Command( - action=self.show_help, - text="Hilfe öffnen", - tooltip="Öffent die Hilfe PDF", - group=help_group, - ) - - update_command = Command( - action=self.show_updates, - text="Updateinformationen", - tooltip="Zeigt die Veränderung in der Software an", - group=help_group, - ) - - basis_ordner_öffnen_command = Command( - action=self.base_ordner_offnen, - text="Basis Ordner öffnen", - tooltip="Öffnet den Programmordner", - group=settings_group, - ) - - # Add commands to the app - self.commands.add( - license_command, - open_help_file_command, - basis_ordner_öffnen_command, - update_command, - ) - - # Get the size of the primary monitor - monitor = get_monitors()[0] - screen_width, screen_height = monitor.width, monitor.height - - # Calculate half screen width and use full screen height - half_screen_width = screen_width // 2 - - # Main layout Box - self.main_box = toga.Box( - style=Pack( - direction=COLUMN, - padding_left=30, - padding_right=60, - #width=half_screen_width * 0.95, - flex=1, - ) - ) - - # ScrollContainer to allow infinite scrolling - self.scroll_container = toga.ScrollContainer( - content=self.main_box, style=Pack(direction=COLUMN, flex=1), vertical=True - ) - self.main_window = toga.MainWindow( - title=self.formal_name, size=(half_screen_width, screen_height * 80 / 100) - ) - self.main_window.content = ( - self.scroll_container - ) # Set the ScrollContainer as the main content - - # Set the window position to the right side of the screen - self.main_window.position = (half_screen_width, 0) - - self.main_window.show() - - if not os.path.isfile(get_updates_datei_user()) or self.neues_update_existiert(): - update_in_updates_ordner_uebertragen(self.app) - self.aktualisierung_anzeigen() - - def main(self): - pass - - def exit(self): - pass - def main(): - return {{ cookiecutter.app_name|lower|replace('-', '_') }}() + print(f"hello world - it's {datetime.now()}") """ APP_START_SOURCE = """\ diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index b4bcd8c..6486145 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -5,8 +5,202 @@ {{ cookiecutter.app_source }} {% else %} +import os +import sys +import subprocess +from datetime import datetime + +import toga +from toga.style.pack import COLUMN +from toga.style import Pack +from toga import Command, Group + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen +from screeninfo import get_monitors + +class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): + + # Define the action handlers for the commands + def show_license(self, widget): + # Instantiate the LicenseWindow + license_window = LicenseWindow(title="Lizenzinformationen") + + # Show the license window + license_window.show() + + def show_updates(self, *argv): + # Instantiate the UpdateWindow + update_window = UpdateWindow(title="Updateinformationen") + + # Show the update window + update_window.show() + + def show_help(self, widget): + help_file_path = get_help_file_path(self.app) + if os.path.exists(help_file_path): + open_file(help_file_path) + + def base_ordner_offnen(self, widget): + # Retrieve the template folder path + base_folder_path = get_base_dir() + + # Open the template folder in the system's file explorer + if sys.platform == "win32": + os.startfile(base_folder_path) + elif sys.platform == "darwin": + subprocess.Popen(["open", base_folder_path]) + else: # 'linux', 'linux2', 'cygwin', 'os2', 'os2emx', 'riscos', 'atheos' + subprocess.Popen(["xdg-open", base_folder_path]) + + def aktualisierung_anzeigen(self): + self.show_updates() + + def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): + #beim start ausführen + update_daten = update_daten_laden_user() + neues_update_wurde_installiert = update_daten.get("neues_update_wurde_installiert") + if neues_update_wurde_installiert: + return True + else: + return False + + def neues_update_existiert(self): + updates_user = update_daten_laden_user()['Updates'] + update_keys_user = list(updates_user.keys()) # Extrahiere alle Schlüssel + anzahl_update_user = len(update_keys_user) + updates_app = update_daten_laden_app(self)['Updates'] + update_keys_app = list(updates_app.keys()) # Extrahiere alle Schlüssel + anzahl_update_app = len(update_keys_app) + if anzahl_update_app > anzahl_update_user: + return True + else: + return False + + def startup(self): + + # Erstellen von Vorraussetung damit die App funktioniert. + setup_folders() + + # Menü + help_group = Group("Help", order=100) + settings_group = Group("Settings", order=50) + + # Define commands + license_command = Command( + action=self.show_license, + text="Lizenzinformationen", + tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", + group=help_group, + ) + open_help_file_command = Command( + action=self.show_help, + text="Hilfe öffnen", + tooltip="Öffent die Hilfe PDF", + group=help_group, + ) + update_command = Command( + action=self.show_updates, + text="Updateinformationen", + tooltip="Zeigt die Veränderung in der Software an", + group=help_group, + ) + basis_ordner_öffnen_command = Command( + action=self.base_ordner_offnen, + text="Basis Ordner öffnen", + tooltip="Öffnet den Programmordner", + group=settings_group, + ) + + # Menü + help_group = Group("Help", order=100) + settings_group = Group("Settings", order=50) + + # Define commands + license_command = Command( + action=self.show_license, + text="Lizenzinformationen", + tooltip="Zeigt die Lizenzinformationen der verwendeten Pakete an", + group=help_group, + ) + + open_help_file_command = Command( + action=self.show_help, + text="Hilfe öffnen", + tooltip="Öffent die Hilfe PDF", + group=help_group, + ) + + update_command = Command( + action=self.show_updates, + text="Updateinformationen", + tooltip="Zeigt die Veränderung in der Software an", + group=help_group, + ) + + basis_ordner_öffnen_command = Command( + action=self.base_ordner_offnen, + text="Basis Ordner öffnen", + tooltip="Öffnet den Programmordner", + group=settings_group, + ) + + # Add commands to the app + self.commands.add( + license_command, + open_help_file_command, + basis_ordner_öffnen_command, + update_command, + ) + + # Get the size of the primary monitor + monitor = get_monitors()[0] + screen_width, screen_height = monitor.width, monitor.height + + # Calculate half screen width and use full screen height + half_screen_width = screen_width // 2 + + # Main layout Box + self.main_box = toga.Box( + style=Pack( + direction=COLUMN, + padding_left=30, + padding_right=60, + #width=half_screen_width * 0.95, + flex=1, + ) + ) + + # ScrollContainer to allow infinite scrolling + self.scroll_container = toga.ScrollContainer( + content=self.main_box, style=Pack(direction=COLUMN, flex=1), vertical=True + ) + self.main_window = toga.MainWindow( + title=self.formal_name, size=(half_screen_width, screen_height * 80 / 100) + ) + self.main_window.content = ( + self.scroll_container + ) # Set the ScrollContainer as the main content + + # Set the window position to the right side of the screen + self.main_window.position = (half_screen_width, 0) + + self.main_window.show() + + if not os.path.isfile(get_updates_datei_user()) or self.neues_update_existiert(): + update_in_updates_ordner_uebertragen(self.app) + self.aktualisierung_anzeigen() + + def main(self): + pass + + def exit(self): + pass + def main(): - # This should start and launch your app! - pass + return {{ cookiecutter.app_name|lower|replace('-', '_') }}() + {% endif %} From 84d6359cea00854cafefc9ae00e23604cd90ccfc Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:16:42 +0100 Subject: [PATCH 06/34] Update pyproject.toml --- {{ cookiecutter.app_name }}/pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/{{ cookiecutter.app_name }}/pyproject.toml b/{{ cookiecutter.app_name }}/pyproject.toml index f0a3837..f097692 100644 --- a/{{ cookiecutter.app_name }}/pyproject.toml +++ b/{{ cookiecutter.app_name }}/pyproject.toml @@ -25,8 +25,7 @@ test_sources = [ requires = [ "python-dotenv", "toml", - "django", - + "screeninfo", ] {{- cookiecutter.pyproject_table_briefcase_app_extra_content }} From 5fe37aa3df697769c6165b95f305b23842a84818 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:18:13 +0100 Subject: [PATCH 07/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 6486145..b20ad6e 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -1,10 +1,6 @@ """ {{ cookiecutter.description|escape_toml }} """ -{% if cookiecutter.app_source %} -{{ cookiecutter.app_source }} -{% else %} - import os import sys import subprocess @@ -203,4 +199,3 @@ def exit(self): def main(): return {{ cookiecutter.app_name|lower|replace('-', '_') }}() -{% endif %} From d45fcec61262ae34c3f6b9f85a12a9f49986b6a6 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:38:57 +0100 Subject: [PATCH 08/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index b20ad6e..e61597d 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -14,7 +14,7 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user from screeninfo import get_monitors class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): @@ -173,7 +173,7 @@ def startup(self): self.scroll_container = toga.ScrollContainer( content=self.main_box, style=Pack(direction=COLUMN, flex=1), vertical=True ) - self.main_window = toga.MainWindow( + self.main_window = toga.MainWindow( title=self.formal_name, size=(half_screen_width, screen_height * 80 / 100) ) self.main_window.content = ( From 934af2f9a3a1e02c43a5ed0fc21066b5b553a9e2 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:28:17 +0100 Subject: [PATCH 09/34] add logo_script for mac icons --- {{ cookiecutter.app_name }}/logo_script.py | 47 +++++++++++++++++++ .../components/UpdateWindow.py | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 {{ cookiecutter.app_name }}/logo_script.py diff --git a/{{ cookiecutter.app_name }}/logo_script.py b/{{ cookiecutter.app_name }}/logo_script.py new file mode 100644 index 0000000..e588b9b --- /dev/null +++ b/{{ cookiecutter.app_name }}/logo_script.py @@ -0,0 +1,47 @@ +from PIL import Image +import os + + +def create_icon_files(src_path, dist_path): + # Ensure the source logo.png exists + logo_path = os.path.join(src_path, "{{ cookiecutter.module_name }}.png") + if not os.path.exists(logo_path): + raise FileNotFoundError(f"The source file {logo_path} does not exist.") + + # Open the source image + img = Image.open(logo_path) + + # Define icon sizes for .ico file + icon_sizes = [(16, 16), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)] + + # Create the .ico file + img.save( + os.path.join(dist_path, "{{ cookiecutter.module_name }}.ico"), format="ICO", sizes=icon_sizes + ) + + # For .icns, macOS typically uses 10 different sizes + icns_sizes = [16, 32, 64, 128, 256, 512, 1024] + iconset_path = os.path.join(dist_path, "{{ cookiecutter.module_name }}.iconset") + os.makedirs(iconset_path, exist_ok=True) + + for size in icns_sizes: + icon = img.resize((size, size), Image.LANCZOS) + icon.save(os.path.join(iconset_path, f"icon_{size}x{size}.png")) + + # Use `iconutil` to create the .icns file from the .iconset directory + os.system( + f'iconutil -c icns {iconset_path} -o {os.path.join(dist_path, "nadoo_law.icns")}' + ) + + # Clean up the .iconset directory after creating the .icns file + for file in os.listdir(iconset_path): + os.remove(os.path.join(iconset_path, file)) + os.rmdir(iconset_path) + + +# Paths +source_path = "." +dist_path = "{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/" + +# Create the icon files +create_icon_files(source_path, dist_path) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py index 22cb671..33790ce 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py @@ -30,7 +30,7 @@ def build(self): box.add(vorheriges_update_button) nächstes_update_button = toga.Button('Nächstes Update', style=StandardStyling.standard_button_style(), on_press=self.nächstes_update) box.add(nächstes_update_button) - fenster_schliessen_button = toga.Button('Fenster schließen', style=StandardStyling.standard_button_style(), on_press=self.close) + fenster_schliessen_button = toga.Button('Fenster schließen', style=StandardStyling.standard_button_style(), on_press=self.fenster_schließen) box.add(fenster_schliessen_button) return box From f040da76e04fe67c23249061463894e08ca1fabc Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:45:02 +0100 Subject: [PATCH 10/34] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1c8ede..fc6d786 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ pytest == 7.4.3 toml == 0.10.2 tox == 4.11.3 screeninfo == 1.5.0 -toga \ No newline at end of file +toga +pillow \ No newline at end of file From 52daa5b52bb897fc6f78bc236dad1ad07e0aac06 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:19:51 +0100 Subject: [PATCH 11/34] Create MainStreamExampleComponent.py --- .../components/MainStreamExampleComponent.py | 997 ++++++++++++++++++ 1 file changed, 997 insertions(+) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py new file mode 100644 index 0000000..99d1904 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py @@ -0,0 +1,997 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING +import os +import shutil +from uuid import uuid4 + +import toga +from toga.style import Pack +from toga.style.pack import COLUMN +from datetime import datetime + +from {{ cookiecutter.module_name }}.services import ( + +) +from {{ cookiecutter.module_name }}.components.Fehlermeldung import Fehlermeldung +from {{ cookiecutter.module_name }}.styling import StandardStyling + + +if TYPE_CHECKING: + from {{ cookiecutter.module_name }}.app import {{ cookiecutter.app_name|lower|replace('-', '_') }} + + +class MainStreamExampleComponent(toga.Box): + name_der_template_datei = "MUSTER_SCHEIDUNG.docx" + formular_name = "Scheidungsverfahren" + kategorie = "Familienrecht" + kundenprogramm_ID = "03a547bd-2ae9-4c86-8ebf-2d881da11b8f" + + def __init__( + self, + app: {{ cookiecutter.app_name|lower|replace('-', '_') }}, + id: str | None = None, + start_values: dict | None = None + ): + style = Pack(direction=COLUMN) + super().__init__(id=id, style=style) + self.app = app + + # Initialize the fehlermeldung_widget and antrag_manager_widget attributes + self.fehlermeldung_widget = None + + self.standardeingabe_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) + self.umstaende_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) + + """ + self.beweismittel_box = toga.Box(style=Pack(direction=COLUMN, padding=padding_value)) + self.beweismittel_edit_box = toga.Box( + style=Pack(direction=COLUMN, padding=padding_value) + ) + """ + + self.footer_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) + if start_values is not None: + self.setup_input_fields(start_values) + + self.create_umstaende_section() + # self.create_beweismittel_section() + + self.create_application_button = toga.Button( + "Antrag erstellen", + on_press=self.create_application, + style=StandardStyling.standard_button_style(), + ) + self.footer_box.add(self.create_application_button) + + self.add(self.standardeingabe_box) + self.add(toga.Divider()) + self.add(toga.Label("Kindersektion", style=StandardStyling.standard_label_style())) + self.add(self.kinder_box) + self.add(toga.Divider()) + self.add(self.umstaende_box) + # self.add(self.beweismittel_box) # This will be switched with beweismittel_edit_box when editing + self.add(self.footer_box) + + # HIER DEAKTIVIEREN ODER AKTIVIEREN UM TESTDATEN ZU LADEN + #self.alle_formular_elemente_mit_test_daten_fuellen() + + def alle_formular_elemente_mit_test_daten_fuellen(self): + """ + Diese Funktion füllt alle Formularfelder mit Testdaten auf. + """ + # Testdaten + test_daten = { + "COURT_NAME": "Kreis Musterstadt", + "COURT_DEPARTMENT": "Straßenverkehrsamt", + "COURT_ADDRESS": "Musterstraße 5", + "COURT_ZIP_CITY": "99999 Musterstadt", + "LAWYER_NAME": "Sebastian Kutzner", + "LAWYER_EMAIL": "sebastian.kutzner@procivis.koeln", + "LAWYER_PHONE": "+49 (221) 99898-179", + "LAWYER_FAX": "+49 (221) 99898-499", + "LAWYER_CONTACT_DATE": "01. Januar 2024", + "LAWYER_REF": "000001-23", + "PROCEDURE_VALUE": "12.000,00", + "APPLICANT_NAME": "Max Mustermann", + "APPLICANT_GENDER": "Herr", + "APPLICANT_BIRTH_DATE": "01.01.1975", + "APPLICANT_BIRTH_NAME": None, + "APPLICANT_ADDRESS": "Musterstraße 1, 99999 Musterstadt", + "MARRIAGE_DATE": "01.01.2000", + "MARRIAGE_LOCATION": "Musterstadt", + "REGISTRATION_NUMBER": "E 11/2000", + "MARRIAGE_OFFICE": "Standesamt in Musterstadt", + "RESPONDENT_NAME": "Erika Mustermann", + "RESPONDENT_GENDER": "Frau", + "RESPONDENT_BIRTH_DATE": "12.08.1964", + "RESPONDENT_BIRTH_NAME": "Musterfrau", + "RESPONDENT_ADDRESS": "Musterstraße 1, 99999 Musterstadt", + "SEPARATION_DATE": "01.01.2023", + "AUSGEZOGEN": False, + "SCHEIDUNGSFOLGEVEREINBARUNG": True, + "VERFAHRENSKOSTENHILFE": True, + "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": "02.01.2001", + "AGREEMENT_DATE_SIGNED": "02.01.2001", + "APPLICANT_NET_INCOME": "3.000,00 €", + "RESPONDENT_NET_INCOME": "1.000,00 €", + "TOTAL_PROCEDURAL_VALUE": "12.000,00 €", + "children": [ + { + "name": "Julia Mustermann", + "birth_date": "01.01.2014", + "custody": "Antragsgegner", + }, + { + "name": "Dennis Mustermann", + "birth_date": "01.01.2014", + "custody": "Antragsgegnerin", + }, + # Weitere Kinderdaten können hier hinzugefügt werden + ], + } + + # Zuweisen der Testdaten zu den Formularfeldern + self.court_name_input.value = test_daten["COURT_NAME"] + self.court_department_input.value = test_daten["COURT_DEPARTMENT"] + self.court_address_input.value = test_daten["COURT_ADDRESS"] + self.court_zip_city_input.value = test_daten["COURT_ZIP_CITY"] + self.lawyer_name_input.value = test_daten["LAWYER_NAME"] + self.lawyer_email_input.value = test_daten["LAWYER_EMAIL"] + self.lawyer_phone_input.value = test_daten["LAWYER_PHONE"] + self.lawyer_fax_input.value = test_daten["LAWYER_FAX"] + self.lawyer_contact_date_input.value = test_daten["LAWYER_CONTACT_DATE"] + self.reference_input.value = test_daten["LAWYER_REF"] + self.verfahrenswert_eingabefeld.value = test_daten["PROCEDURE_VALUE"] + applicant_name_parts = test_daten["APPLICANT_NAME"].split(" ") + self.applicant_vorname_eingabefeld.value = applicant_name_parts[0] + if len(applicant_name_parts) > 1: + self.applicant_nachname_eingabefeld.value = " ".join( + applicant_name_parts[1:] + ) + else: + self.applicant_nachname_eingabefeld.value = "" + self.applicant_geschlecht_select.value = test_daten["APPLICANT_GENDER"] + self.applicant_birthdate_eingabefeld.value = test_daten["APPLICANT_BIRTH_DATE"] + self.applicant_geburtsname_eingabefeld.value = ( + test_daten["APPLICANT_BIRTH_NAME"] + if test_daten["APPLICANT_BIRTH_NAME"] + else "" + ) + self.applicant_adress_eingabefeld.value = test_daten["APPLICANT_ADDRESS"] + + self.datum_eheschließung_eingabefeld.value = test_daten["MARRIAGE_DATE"] + self.ort_eheschließung_eingabefeld.value = test_daten["MARRIAGE_LOCATION"] + self.eheschließungsnummer_eingabefeld.value = test_daten["REGISTRATION_NUMBER"] + self.standesamt_eingabefeld.value = test_daten["MARRIAGE_OFFICE"] + respondent_name_parts = test_daten["RESPONDENT_NAME"].split(" ") + self.respondent_vorname_eingabefeld.value = respondent_name_parts[0] + if len(respondent_name_parts) > 1: + self.respondent_nachname_eingabefeld.value = " ".join( + respondent_name_parts[1:] + ) + else: + self.respondent_nachname_eingabefeld.value = "" + self.respondent_geschlecht_select.value = test_daten["RESPONDENT_GENDER"] + self.respondent_birthdate_eingabefeld.value = test_daten[ + "RESPONDENT_BIRTH_DATE" + ] + self.respondent_geburtsname_eingabefeld.value = ( + test_daten["RESPONDENT_BIRTH_NAME"] + if test_daten["RESPONDENT_BIRTH_NAME"] + else "" + ) + self.respondent_adress_eingabefeld.value = test_daten["RESPONDENT_ADDRESS"] + + self.getrennt_seit_eingabefeld.value = test_daten["SEPARATION_DATE"] + self.scheidungsfolgevereinbarung_existenz.value = test_daten[ + "SCHEIDUNGSFOLGEVEREINBARUNG" + ] + self.datum_scheidungsfolgevereinbarung_eingabefeld.value = ( + test_daten["SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM"] + if test_daten["SCHEIDUNGSFOLGEVEREINBARUNG"] + else "" + ) + self.applicant_nettoeinkommen_eingabefeld.value = test_daten[ + "APPLICANT_NET_INCOME" + ] + self.respondent_nettoeinkommen_eingabefeld.value = test_daten[ + "RESPONDENT_NET_INCOME" + ] + self.verfahrenswert_eingabefeld.value = test_daten["TOTAL_PROCEDURAL_VALUE"] + self.verfahrenskostenhilfe.value = test_daten["VERFAHRENSKOSTENHILFE"] + + # Anpassung der Zuweisungslogik für das wohnort_dropdown + custody_mapping = { + "Antragsgegner": "Antraggegner*in", + "Antragsgegnerin": "Antraggegner*in", + # Fügen Sie weitere Zuordnungen hinzu, falls nötig + } + + # Behandlung der Kinderdaten + for kid_data in test_daten["children"]: + # Füge ein neues Kind hinzu + self.kinder_box.add_child_entity( + None + ) # None, weil das Event-Argument hier irrelevant ist + + # Das zuletzt hinzugefügte Kindeingabe-Objekt erhalten + new_kid = self.kinder_box.kindeingabe_list[-1] + + # Aufspalten des Namens in Vor- und Nachname + kid_name_parts = kid_data["name"].split() + kid_vorname = kid_name_parts[0] if len(kid_name_parts) > 0 else "" + kid_nachname = ( + " ".join(kid_name_parts[1:]) if len(kid_name_parts) > 1 else "" + ) + + # Zuweisen der Daten zum zuletzt hinzugefügten Kindeingabe-Objekt + new_kid.vorname_eingabefeld.value = kid_vorname + new_kid.nachname_eingabefeld.value = kid_nachname + new_kid.geburtsdatum_eingabefeld.value = kid_data["birth_date"] + # Anwendung der Zuordnungslogik für das Dropdown + dropdown_value = custody_mapping.get( + kid_data["custody"], "Antragsteller*in" + ) # Standardwert als Fallback + new_kid.wohnort_dropdown.value = dropdown_value + + def setup_input_fields(self, start_values): + + + # Input fields and labels + self.reference_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.court_name_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.court_department_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.court_address_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.court_zip_city_input = toga.TextInput(style=StandardStyling.standard_input_style()) + + self.lawyer_name_input = toga.TextInput( + value=start_values.get("name", ""), style=StandardStyling.standard_input_style() + ) + self.lawyer_email_input = toga.TextInput( + value=start_values.get("email", ""), style=StandardStyling.standard_input_style() + ) + self.lawyer_phone_input = toga.TextInput( + value=start_values.get("phone", ""), style=StandardStyling.standard_input_style() + ) + self.lawyer_fax_input = toga.TextInput( + value=start_values.get("fax", ""), style=StandardStyling.standard_input_style() + ) + + self.lawyer_contact_date_input = toga.TextInput( + value=self.translate_date_to_german(self.get_current_date_in_word_format()), + style=StandardStyling.standard_input_style(), + ) + # self.case_file_number_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.defendant_name_input = toga.TextInput(style=StandardStyling.standard_input_style()) + self.fine_notice_date_input = toga.TextInput( + value=self.get_current_date(), style=StandardStyling.standard_highlighted_input_style() + ) + self.fine_notice_delivery_date_input = toga.TextInput( + value=self.get_current_date(), style=StandardStyling.standard_highlighted_input_style() + ) + self.gender_select = toga.Selection( + items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_input_style() + ) + + # Append all input fields and labels to the standardeingabe_box + # Append new input fields and labels to the standardeingabe_box + + # DATEV Abfrage + self.standardeingabe_box.add( + toga.Label("Unser Zeichen:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.reference_input) + # self.standardeingabe_box.add( + # toga.Button("Daten importieren", on_press=self.datenabgleich_datev,style=StandardStyling.standard_button_style()) + # ) + + self.standardeingabe_box.add( + toga.Label("Gerichtsname:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.court_name_input) + self.standardeingabe_box.add( + toga.Label("Abteilung:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.court_department_input) + self.standardeingabe_box.add( + toga.Label("Adresse:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.court_address_input) + self.standardeingabe_box.add( + toga.Label("PLZ und Stadt:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.court_zip_city_input) + self.standardeingabe_box.add( + toga.Label("Anwaltsname:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.lawyer_name_input) + self.standardeingabe_box.add( + toga.Label("E-Mail:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.lawyer_email_input) + self.standardeingabe_box.add( + toga.Label("Telefonnummer:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.lawyer_phone_input) + self.standardeingabe_box.add( + toga.Label("Fax:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.lawyer_fax_input) + self.standardeingabe_box.add( + toga.Label("Datum des Antrags:", style=StandardStyling.standard_label_style()) + ) + self.standardeingabe_box.add(self.lawyer_contact_date_input) + # self.standardeingabe_box.add(toga.Label("Az.:", style=StandardStyling.standard_input_style())) + # self.standardeingabe_box.add(self.case_file_number_input) + + def create_umstaende_section(self): + + self.verfahrenskostenhilfe = toga.Switch( + "Verfahrenskostenhilfe beantragen", style=StandardStyling.standard_switch_style() + ) + self.umstaende_box.add(self.verfahrenskostenhilfe) + # Ehe + self.umstaende_box.add(toga.Label("Ehe", style=StandardStyling.standard_label_style())) + self.umstaende_box.add( + toga.Label("Datum der Eheschließung", style=StandardStyling.standard_label_style()) + ) + self.datum_eheschließung_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.datum_eheschließung_eingabefeld) + self.umstaende_box.add( + toga.Label("Ort der Eheschließung", style=StandardStyling.standard_label_style()) + ) + self.ort_eheschließung_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.ort_eheschließung_eingabefeld) + self.umstaende_box.add( + toga.Label("Eheschließungsnummer", style=StandardStyling.standard_label_style()) + ) + self.eheschließungsnummer_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.eheschließungsnummer_eingabefeld) + self.umstaende_box.add( + toga.Label("Standesamt", style=StandardStyling.standard_label_style()) + ) + self.standesamt_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.standesamt_eingabefeld) + self.umstaende_box.add( + toga.Label("Getrennt seit:", style=StandardStyling.standard_label_style()) + ) + self.getrennt_seit_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.getrennt_seit_eingabefeld) + + self.scheidungsfolgevereinbarung_existenz = toga.Switch( + "Scheidungsfolgevereinbarung existent", + style=StandardStyling.standard_switch_style(), + on_change=self.scheidungsfolgevereinbarung_existenz_changed, + ) + self.umstaende_box.add(self.scheidungsfolgevereinbarung_existenz) + + # Antragsteller/in + self.umstaende_box.add( + toga.Label("Antragsteller*in", style=StandardStyling.standard_label_style()) + ) + self.umstaende_box.add( + toga.Label("Geschlecht:", style=StandardStyling.standard_label_style()) + ) + self.applicant_geschlecht_select = toga.Selection( + items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_selection_style() + ) + self.umstaende_box.add(self.applicant_geschlecht_select) + self.umstaende_box.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) + self.applicant_vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_vorname_eingabefeld) + self.umstaende_box.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) + self.applicant_nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_nachname_eingabefeld) + self.umstaende_box.add( + toga.Label("Geburtsname:", style=StandardStyling.standard_label_style()) + ) + self.applicant_geburtsname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_geburtsname_eingabefeld) + + self.umstaende_box.add( + toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style()) + ) + self.applicant_birthdate_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_birthdate_eingabefeld) + self.umstaende_box.add(toga.Label("Adresse:", style=StandardStyling.standard_label_style())) + self.applicant_adress_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_adress_eingabefeld) + self.umstaende_box.add( + toga.Label("Nettoeinkommen:", style=StandardStyling.standard_label_style()) + ) + self.applicant_nettoeinkommen_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.applicant_nettoeinkommen_eingabefeld) + self.umstaende_box.add( + toga.Label("Wohnsituation:", style=StandardStyling.standard_label_style()) + ) + self.applicant_wohnsituation_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), + items=[ + "Räumlich getrennt in gemeinsamer Immobilie", + "In einer anderen Immobilie (ausgezogen)", + "Wohnt in gemeinsamer Immobilie", + ] + ) + self.umstaende_box.add(self.applicant_wohnsituation_dropdown) + + # Antraggegner/in + self.umstaende_box.add( + toga.Label("Antraggegner*in", style=StandardStyling.standard_label_style()) + ) + self.umstaende_box.add( + toga.Label("Geschlecht:", style=StandardStyling.standard_label_style()) + ) + self.respondent_geschlecht_select = toga.Selection( + items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_input_style() + ) + self.umstaende_box.add(self.respondent_geschlecht_select) + self.umstaende_box.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) + self.respondent_vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_vorname_eingabefeld) + self.umstaende_box.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) + self.respondent_nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_nachname_eingabefeld) + self.umstaende_box.add( + toga.Label("Geburtsname:", style=StandardStyling.standard_label_style()) + ) + self.respondent_geburtsname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_geburtsname_eingabefeld) + + self.umstaende_box.add( + toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style()) + ) + self.respondent_birthdate_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_birthdate_eingabefeld) + self.umstaende_box.add(toga.Label("Adresse:", style=StandardStyling.standard_label_style())) + self.respondent_adress_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_adress_eingabefeld) + self.umstaende_box.add( + toga.Label("Nettoeinkommen:", style=StandardStyling.standard_label_style()) + ) + self.respondent_nettoeinkommen_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.respondent_nettoeinkommen_eingabefeld) + self.umstaende_box.add( + toga.Label("Wohnsituation:", style=StandardStyling.standard_label_style()) + ) + self.wohnsituation_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), + items=[ + "Räumlich getrennt in gemeinsamer Immobilie", + "In einer anderen Immobilie (ausgezogen)", + "Wohnt in gemeinsamer Immobilie", + ] + ) + self.umstaende_box.add(self.wohnsituation_dropdown) + + # Verfahrenswert + self.umstaende_box.add( + toga.Label("Verfahrenswert", style=StandardStyling.standard_label_style()) + ) + self.verfahrenswert_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.umstaende_box.add(self.verfahrenswert_eingabefeld) + + def scheidungsfolgevereinbarung_existenz_changed(self, widget): + # wenn checkbox gesetzt ist, dann wird das label und das e ingabefeld angezeigt, wenn der hacken entfernt wird verschwinden beide elemente + if self.scheidungsfolgevereinbarung_existenz.value: + # position für label ist die position nach der checkbox + position_fuer_label = ( + self.umstaende_box.children.index( + self.scheidungsfolgevereinbarung_existenz + ) + + 1 + ) + self.scheidungsfolgevereinbarung_label = toga.Label( + "Datum der Scheidungsfolgevereinbarung:", + style=StandardStyling.standard_label_style(), + ) + self.umstaende_box.insert( + position_fuer_label, self.scheidungsfolgevereinbarung_label + ) + self.datum_scheidungsfolgevereinbarung_eingabefeld = toga.TextInput( + style=StandardStyling.standard_input_style() + ) + position_fuer_eingabefeld = position_fuer_label + 1 + self.umstaende_box.insert( + position_fuer_eingabefeld, + self.datum_scheidungsfolgevereinbarung_eingabefeld, + ) + else: + # wenn checkbox nicht gesetzt ist, dann wird das label und das eingabefeld entfernt + self.umstaende_box.remove(self.scheidungsfolgevereinbarung_label) + self.umstaende_box.remove( + self.datum_scheidungsfolgevereinbarung_eingabefeld + ) + + def create_beweismittel_section(self): + self.beweismittel_data = get_beweismittel_data_scheidung() + + self.beweismittel_box.clear() + + self.beweismittel_switches: List[toga.Switch] = [] # Store the switch widgets + for option in self.beweismittel_data.get("options", []): + switch = toga.Switch(text=option, style=StandardStyling.standard_switch_style()) + self.beweismittel_switches.append(switch) # Add the switch to the list + self.beweismittel_box.add(switch) + + edit_button = toga.Button( + "Liste bearbeiten", + on_press=self.switch_beweismittel_section_to_edit_mode, + style=StandardStyling.standard_button_style(), + ) + self.beweismittel_box.add(edit_button) + + def switch_beweismittel_section_to_edit_mode(self, widget): + self.beweismittel_edit_box.clear() # Clear previous content if any + + # Add text inputs for each option in the beweismittel_edit_box + self.edit_inputs = [] + for option in self.beweismittel_data.get("options", []): + text_input = toga.TextInput(value=option, style=StandardStyling.standard_input_style()) + self.edit_inputs.append(text_input) + self.beweismittel_edit_box.add(text_input) + + # Add an empty text input to add new options in the beweismittel_edit_box + self.new_option_input = toga.TextInput( + placeholder="New option", style=StandardStyling.standard_input_style() + ) + self.beweismittel_edit_box.add(self.new_option_input) + + # Add Save and Cancel buttons in the beweismittel_edit_box + save_button = toga.Button( + "Speichern", on_press=self.save_edit_window, style=StandardStyling.standard_button_style() + ) + cancel_button = toga.Button( + "Abbrechen", on_press=self.close_edit_window, style=StandardStyling.standard_button_style() + ) + self.beweismittel_edit_box.add(save_button) + self.beweismittel_edit_box.add(cancel_button) + + # Switch from displaying the beweismittel_box to the beweismittel_edit_box + self.remove(self.beweismittel_box) + self.insert(1, self.beweismittel_edit_box) + + def close_edit_window(self, widget): + + # TODO Find a way to figure out at what position the beweismittel_edit_box in the child array is so that the beweismittel_box can be put there and not accidently somewhere else in the order + # Close the edit window and switch back to the beweismittel switches + self.remove(self.beweismittel_edit_box) + self.insert(1, self.beweismittel_box) + + def save_edit_window(self, widget): + # Placeholder implementation for saving the edited options + # Collect the values from the text inputs + edited_options = [ + text_input.value for text_input in self.edit_inputs if text_input.value + ] + if self.new_option_input.value: + edited_options.append(self.new_option_input.value) + + # Update the beweismittel_data with the edited options + self.beweismittel_data["options"] = edited_options + + set_beweismittel_data_Scheidung(self.beweismittel_data) + + # Refresh the beweismittel section to display the updated options + self.remove_beweismittel_switches() + self.create_beweismittel_section() + self.close_edit_window(widget) + + def remove_beweismittel_switches(self): + # Remove all beweismittel switches from the view + self.beweismittel_box.clear() + + def get_current_date_in_word_format(self): + # Get the current date in the "XX. Month YYYY" format + return datetime.now().strftime("%d. %B %Y") + + def translate_date_to_german(self, date_str): + # Map of English month names to German month names + month_translation = { + "January": "Januar", + "February": "Februar", + "March": "März", + "April": "April", + "May": "Mai", + "June": "Juni", + "July": "Juli", + "August": "August", + "September": "September", + "October": "Oktober", + "November": "November", + "December": "Dezember", + } + + # Split the date string to extract the month + parts = date_str.split() + if len(parts) == 3: + day, month, year = parts + # Translate the month to German + german_month = month_translation.get(month, month) + # Return the date string in German format + return f"{day} {german_month} {year}" + return date_str # Return the original string if format is unexpected + + def get_current_date(self): + # Get the current date in the format "DD.MM.YYYY" + return datetime.now().strftime("%d.%m.%Y") + + def generate_lawyer_list(self): + # Implement the logic to generate lawyer list from data + lawyer_data = get_lawyer_data() + start_values = lawyer_data.get("start_values", {}) + lawyer_list = [] + for lawyer_id, details in start_values.items(): + lawyer_list.append( + f"{details['title']} {details['name']}: {details['specialty']}" + ) + return "\n".join(lawyer_list) + def datenabgleich_datev(self): + self.datevdata = datenabfrage_datev_for_aktenzeichen( + aktenzeichen=self.case_file_number_input.value, + passwort=self.app.nutzerdateneingabekomponente.passwort_eingabefeld.value, + ) + + """def import_data(self, widget): + + + + self.court_name_input.value = self.datevdata.get("court_name") + self.court_department_input.value = self.datevdata.get("court_department") + self.court_address_input.value = self.datevdata.get("court_address") + self.court_zip_city_input.value = self.datevdata.get("court_zip_city") + self.lawyer_name_input.value = self.datevdata.get("lawyer_name") + self.lawyer_email_input.value = self.datevdata.get("lawyer_email") + self.lawyer_phone_input.value = self.datevdata.get("lawyer_phone") + self.lawyer_fax_input.value = self.datevdata.get("lawyer_fax") + self.lawyer_contact_date_input.value = self.datevdata.get("contact_date") + self.reference_input.value = self.datevdata.get("reference") + self.gender_select.value = self.datevdata.get("gender") + self.defendant_name_input.value = self.datevdata.get("defendant_name")""" + + """ + Um die Daten aus dem JSON-Datensatz, den Sie erhalten, den Elementen Ihrer Benutzeroberfläche zuzuordnen, müssen wir zunächst eine angepasste Datenstruktur (datevdata) erstellen, die alle benötigten Informationen in einer Weise enthält, die mit den .get() Aufrufen in Ihrem import_data-Methode kompatibel ist. + + Aus dem von Ihnen bereitgestellten JSON-Datensatz und den UI-Elementen scheint es, als ob einige der erforderlichen Informationen direkt aus dem JSON abgerufen werden können, während andere möglicherweise nicht direkt verfügbar sind und daher Annahmen oder Standardwerte benötigen könnten. + + Hier ist ein Ansatz, wie Sie eine solche Datenstruktur aufbauen können, basierend auf den Informationen, die Sie aus dem JSON-Datensatz extrahieren möchten: + + Erstellen einer Datenstruktur (datevdata), die alle erforderlichen Felder enthält. Da im bereitgestellten JSON-Datensatz einige der spezifisch angefragten Felder wie court_name, court_department, court_address, court_zip_city, lawyer_name, lawyer_email, lawyer_phone, lawyer_fax, contact_date, reference, gender, defendant_name nicht direkt vorhanden sind, werde ich ein Beispiel geben, wie man eine solche Struktur basierend auf verfügbaren Daten und angenommenen Feldern erstellen könnte. + Zuordnung der Daten zu den entsprechenden UI-Elementen. + Zunächst ein Beispiel, wie die datevdata Struktur basierend auf den vorhandenen Daten aussehen könnte: + + datevdata = { + "court_name": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "court_department": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "court_address": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "court_zip_city": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "lawyer_name": json_data[0]["partner"]["display_name"], # Beispiel: "Ernst Exempeladvokat" + "lawyer_email": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "lawyer_phone": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "lawyer_fax": "Nicht verfügbar", # Nicht im Datensatz, Annahme + "contact_date": json_data[0]["created"]["date"], # Beispiel: "2018-09-27" + "reference": json_data[0]["file_number"], # Beispiel: "000001-2005/001:00" + "gender": "Nicht verfügbar", # Nicht im Datensatz, Annahme oder AI-Detektion erforderlich + "defendant_name": "Mustermann" # Annahme basierend auf "file_name" + } + + Für die .get() Aufrufe in Ihrer import_data-Methode können Sie diese dann wie folgt verwenden: + + self.court_name_input.value = datevdata.get("court_name", "Standardwert") + self.court_department_input.value = datevdata.get("court_department", "Standardwert") + self.court_address_input.value = datevdata.get("court_address", "Standardwert") + self.court_zip_city_input.value = datevdata.get("court_zip_city", "Standardwert") + self.lawyer_name_input.value = datevdata.get("lawyer_name", "Standardwert") + self.lawyer_email_input.value = datevdata.get("lawyer_email", "Standardwert") + self.lawyer_phone_input.value = datevdata.get("lawyer_phone", "Standardwert") + self.lawyer_fax_input.value = datevdata.get("lawyer_fax", "Standardwert") + self.lawyer_contact_date_input.value = datevdata.get("contact_date", "Standardwert") + self.reference_input.value = datevdata.get("reference", "Standardwert") + self.gender_select.value = datevdata.get("gender", "Standardwert") # Möglicherweise müssen Sie die Logik für die Zuweisung des Geschlechts hier anpassen + self.defendant_name_input.value = datevdata.get("defendant_name", "Standardwert") + + Beachten Sie, dass die "Standardwert"-Platzhalter durch tatsächliche Standardwerte ersetzt werden sollten, die Sie verwenden möchten, falls die Daten nicht verfügbar sind. Für Felder wie gender, die möglicherweise eine komplexere Logik für die Zuweisung benötigen (z.B. basierend auf dem Namen), müssen Sie eine separate Logik implementieren, um diesen Wert zu bestimmen, bevor Sie ihn in datevdata einfügen. + """ + + + def create_application(self, widget): + + # TODO Liste in format für Formular anpassen, weiteres Feld + # Collect data from input fields + kindeingabe_list = {} + + for kid in self.kinder_box.kindeingabe_list: + + kid_vorname = kid.vorname_eingabefeld.value + kid_nachname = kid.nachname_eingabefeld.value + kid_geburtsdatum = kid.geburtsdatum_eingabefeld.value + kid_wohnhaft = kid.wohnort_dropdown.value + kid_id = uuid4() + kindeingabe_list[kid_id] = { + "vorname": kid_vorname, + "nachname": kid_nachname, + "geburtsdatum": kid_geburtsdatum, + "wohnhaft": kid_wohnhaft, + } + + children_data = [] + for kid in self.kinder_box.kindeingabe_list: + kid_vorname = kid.vorname_eingabefeld.value + kid_nachname = kid.nachname_eingabefeld.value + kid_geburtsdatum = kid.geburtsdatum_eingabefeld.value + kid_custody_mapping = { + "Antragsteller*in": "Antragsteller", + "Antraggegner*in": "Antragsgegner", + } + kid_custody = kid_custody_mapping[ + kid.wohnort_dropdown.value + ] # Übersetzung des Sorgerechts + + children_data.append( + { + "name": f"{kid_vorname} {kid_nachname}", + "birth_date": kid_geburtsdatum, + "custody": kid_custody, + } + ) + + self.applicant_fullname = ( + self.applicant_vorname_eingabefeld.value + + " " + + self.applicant_nachname_eingabefeld.value + ) + self.respondent_fullname = ( + self.respondent_vorname_eingabefeld.value + + " " + + self.respondent_nachname_eingabefeld.value + ) + daten_aus_formular = { + "COURT_NAME": self.court_name_input.value, + "COURT_DEPARTMENT": self.court_department_input.value, + "COURT_ADDRESS": self.court_address_input.value, + "COURT_ZIP_CITY": self.court_zip_city_input.value, + "LAWYER_NAME": self.lawyer_name_input.value, + "LAWYER_EMAIL": self.lawyer_email_input.value, + "LAWYER_PHONE": self.lawyer_phone_input.value, + "LAWYER_FAX": self.lawyer_fax_input.value, + "LAWYER_CONTACT_DATE": self.lawyer_contact_date_input.value, + "LAWYER_REF": self.reference_input.value, + # "CASE_FILE_NUMBER": self.case_file_number_input.value, + "PROCEDURE_VALUE": self.verfahrenswert_eingabefeld.value, + "APPLICANT_NAME": self.applicant_fullname, + "APPLICANT_GENDER": self.applicant_geschlecht_select.value, + "APPLICANT_BIRTH_DATE": self.applicant_birthdate_eingabefeld.value, + # if the field for APPLICANT_BIRTH_NAME is empty set the value to None + "APPLICANT_BIRTH_NAME": ( + self.applicant_geburtsname_eingabefeld.value + if self.applicant_geburtsname_eingabefeld.value != "" + else None + ), + "APPLICANT_ADDRESS": self.applicant_adress_eingabefeld.value, + "MARRIAGE_DATE": self.datum_eheschließung_eingabefeld.value, + "MARRIAGE_LOCATION": self.ort_eheschließung_eingabefeld.value, + "REGISTRATION_NUMBER": self.eheschließungsnummer_eingabefeld.value, + "MARRIAGE_OFFICE": self.standesamt_eingabefeld.value, + "RESPONDENT_NAME": self.respondent_fullname, + "RESPONDENT_GENDER": self.respondent_geschlecht_select.value, + "RESPONDENT_BIRTH_DATE": self.respondent_birthdate_eingabefeld.value, + "RESPONDENT_BIRTH_NAME": ( + self.respondent_geburtsname_eingabefeld.value + if self.respondent_geburtsname_eingabefeld.value != "" + else None + ), # if available + "RESPONDENT_ADDRESS": self.respondent_adress_eingabefeld.value, + "SEPARATION_DATE": self.getrennt_seit_eingabefeld.value, + "AUSGEZOGEN": True, + "SCHEIDUNGSFOLGEVEREINBARUNG": self.scheidungsfolgevereinbarung_existenz.value, + "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": ( + self.datum_scheidungsfolgevereinbarung_eingabefeld.value + if self.scheidungsfolgevereinbarung_existenz.value + else None + ), + "AGREEMENT_DATE_SIGNED": "05.05.2023", # Date when the divorce consequences agreement was signed + "APPLICANT_NET_INCOME": self.applicant_nettoeinkommen_eingabefeld.value, # Applicant's monthly net income + "RESPONDENT_NET_INCOME": self.respondent_nettoeinkommen_eingabefeld.value, # Respondent's monthly net income + "TOTAL_PROCEDURAL_VALUE": self.verfahrenswert_eingabefeld.value, # Procedural value based on three months' income + "children": children_data, + "LAWYER_FULL_NAME": self.lawyer_name_input.value, + "LAWYER_TITLE": self.start_values.get("title"), + "LAWYER_SPECIALTY": self.start_values.get("specialty"), + # "BEWEISMITTEL_LISTE": { + # switch.text: switch.value for switch in self.beweismittel_switches + # }, + "VERFAHRENSKOSTENHILFE": self.verfahrenskostenhilfe.value, + } + + # daten_aus_formular = test_daten_anstelle_der_daten_aus_dem_eingabe_formular + + # Assuming there is a variable that holds the value for SCHEIDUNGSFOLGEVEREINBARUNG + scheidungsfolgevereinbarung = daten_aus_formular.get( + "SCHEIDUNGSFOLGEVEREINBARUNG", False + ) + + # Check if all required fields are filled and at least one beweismittel_switch is selected + required_fields = [ + #"COURT_NAME", + # "COURT_DEPARTMENT", + # "COURT_ADDRESS", + # "COURT_ZIP_CITY", + #"LAWYER_NAME", + #"LAWYER_EMAIL", + #"LAWYER_PHONE", + #"LAWYER_CONTACT_DATE", + #"LAWYER_REF", + #"APPLICANT_NAME", + ] + # Add the additional required field if SCHEIDUNGSFOLGEVEREINBARUNG is True + if scheidungsfolgevereinbarung: + required_fields.append("SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM") + + missing_fields = [ + field for field in required_fields if not daten_aus_formular.get(field) + ] + # if missing_fields or not any(self.beweismittel_switches): + if missing_fields: + error_message = "" + field_labels = { + "COURT_NAME": "Gerichtsname", + "COURT_DEPARTMENT": "Abteilung", + "COURT_ADDRESS": "Adresse", + "COURT_ZIP_CITY": "PLZ und Stadt", + "LAWYER_NAME": "Anwaltsname", + "LAWYER_EMAIL": "Anwalt E-Mail", + "LAWYER_PHONE": "Anwalt Telefonnummer", + "LAWYER_CONTACT_DATE": "Datum des Antrags", + "LAWYER_REF": "Az.", + "APPLICANT_NAME": "Antragsteller", + "FINE_NOTICE_DATE": "Zugestellt am", + "FINE_NOTICE_DELIVERY_DATE": "Datum des Bußgeldbescheids", + "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": "Datum der Scheidungsfolgevereinbarung", + } + if missing_fields: + error_message += "Bitte füllen Sie die folgenden Pflichtfelder aus:\n" + for field in missing_fields: + error_message += f"{field_labels[field]}\n\n" + # error_message += "Bitte wählen Sie mindestens ein Beweismittel aus." + + fehlermeldung = Fehlermeldung( + self.app, + retry_function=lambda: self.create_application(widget), + fehlermeldung=error_message, + ) + self.fehlermeldung_widget = fehlermeldung + self.footer_box.add(self.fehlermeldung_widget) + return + + daten_aus_formular = hinzufuegen_anwaltsdaten_zum_kontext(daten_aus_formular) + + # daten_aus_formular = self.hinzufuegen_beweismittel_zum_kontext_fuer_form_daten(daten_aus_formular, daten_aus_formular) + + # Initialize the data processor + data_processor = AntragVorlageDatenVerarbeitung( + name_der_template_datei=self.name_der_template_datei + ) + + print(daten_aus_formular) + + # Generate the output file + success, error_message = ( + data_processor.erzeuge_antragsdatei_mit_platzhalterinformationen( + daten_aus_formular=daten_aus_formular + ) + ) + if success: + # Entferne das Antrag-Manager-Widget, falls es existiert, und setze es zurück. + if self.antrag_manager_widget: + self.footer_box.remove(self.antrag_manager_widget) + self.antrag_manager_widget = None + + # Generiere einen einzigartigen Dateinamen für die Ausgabedatei. + unique_filename = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{self.formular_name}_{daten_aus_formular['APPLICANT_NAME']}.docx".replace(" ", "_") + + # Setze den Pfad für die Ausgabedatei. + mandant_folder = get_mandanten_folder() + output_folder_path = os.path.join(mandant_folder, daten_aus_formular["LAWYER_REF"]) + output_file_path = os.path.join(output_folder_path, unique_filename) + + # Erstelle den Ausgabeordner, falls er nicht existiert. + if not os.path.exists(output_folder_path): + os.makedirs(output_folder_path) + + # Verschiebe die temporäre Datei des ausgefüllten Antrags in den Ausgabeordner. + shutil.copy(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags, output_file_path) + + # Initialisiere den Datei-Manager mit der Ausgabedatei als Basis. + self.antrag_manager_widget = AntragDateiManager(app=self.app, base_docx_datei=output_file_path) + + # temp file löschen + if data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags is not None: + os.remove(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags) + + # Füge das Datei-Manager-Interface zum Hauptcontainer der App hinzu. + self.footer_box.add(self.antrag_manager_widget) + + # Erstelle eine Ausführung in der Datenbank, um die Nutzung der Software zu dokumentieren. + # Dies hilft festzuhalten, wie oft Nutzer die Software für spezifische Anträge eingesetzt haben. + add_ausfuehrung(reference=self.reference_input.value, kundenprogramm_ID=self.kundenprogramm_ID, antrags_name=self.formular_name) + + else: + + # temp file löschen + os.remove(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags) + + # Create a Fehlermeldung component + fehlermeldung = Fehlermeldung( + self.app, + retry_function=lambda: self.create_application(widget), + fehlermeldung=error_message, + ) + self.fehlermeldung_widget = fehlermeldung + self.footer_box.add(self.fehlermeldung_widget) + + +class Kindereingabe(toga.Box): + + def __init__( + self, + id: str | None = None, + ): + style = Pack(direction=COLUMN) + super().__init__(id=id, style=style) + + self.kindeingabe_list: List[Kindeingabe] = ( + [] + ) # List to keep track of Kindeingabe instances + + self.add_child_button = toga.Button( + "Kind hinzufügen", on_press=self.add_child_entity, style=StandardStyling.standard_button_style() + ) + # Initially, add the buttons to the container + self.add(self.add_child_button) + + self.kind_delete_button = toga.Button( + "Kind entfernen", on_press=self.kind_entfernen, style=StandardStyling.standard_button_style() + ) + self.add(self.kind_delete_button) + + def add_child_entity(self, widget): + kind_box = Kindeingabe() + self.kindeingabe_list.append( + kind_box + ) # Add the new Kindeingabe to the tracking list + # Insert the Kindeingabe just above the buttons but below the last Kindeingabe + insert_index = len( + self.kindeingabe_list + ) # Calculate the position where the new Kindeingabe should be inserted + self.insert(insert_index - 1, kind_box) # Insert at the calculated index + + def kind_entfernen(self, widget): + if self.kindeingabe_list: # Check if there's any Kindeingabe to remove + last_kindeingabe = ( + self.kindeingabe_list.pop() + ) # Remove the last Kindeingabe from the list + self.remove( + last_kindeingabe + ) # Remove the last Kindeingabe from the Kindereingabe container + + +class Kindeingabe(toga.Box): + + def __init__( + self, + id: str | None = None, + ): + style = Pack(direction=COLUMN) + super().__init__(id=id, style=style) + + self.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) + self.vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.add(self.vorname_eingabefeld) + self.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) + self.nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.add(self.nachname_eingabefeld) + self.add(toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style())) + self.geburtsdatum_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) + self.add(self.geburtsdatum_eingabefeld) + self.add(toga.Label("Wohnt bei:", style=StandardStyling.standard_label_style())) + self.wohnort_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), + items=["Antragsteller*in", "Antraggegner*in"] + ) + self.add(self.wohnort_dropdown) From ebdcb5275aaedb87c817caec19855d3912075ef9 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:04:06 +0100 Subject: [PATCH 12/34] Update MainStreamExampleComponent.py --- .../components/MainStreamExampleComponent.py | 969 +----------------- 1 file changed, 8 insertions(+), 961 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py index 99d1904..0088133 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py @@ -1,31 +1,14 @@ from __future__ import annotations from typing import List, TYPE_CHECKING -import os -import shutil -from uuid import uuid4 - import toga from toga.style import Pack from toga.style.pack import COLUMN -from datetime import datetime - -from {{ cookiecutter.module_name }}.services import ( - -) -from {{ cookiecutter.module_name }}.components.Fehlermeldung import Fehlermeldung from {{ cookiecutter.module_name }}.styling import StandardStyling - if TYPE_CHECKING: from {{ cookiecutter.module_name }}.app import {{ cookiecutter.app_name|lower|replace('-', '_') }} - class MainStreamExampleComponent(toga.Box): - name_der_template_datei = "MUSTER_SCHEIDUNG.docx" - formular_name = "Scheidungsverfahren" - kategorie = "Familienrecht" - kundenprogramm_ID = "03a547bd-2ae9-4c86-8ebf-2d881da11b8f" - def __init__( self, app: {{ cookiecutter.app_name|lower|replace('-', '_') }}, @@ -35,963 +18,27 @@ def __init__( style = Pack(direction=COLUMN) super().__init__(id=id, style=style) self.app = app - - # Initialize the fehlermeldung_widget and antrag_manager_widget attributes - self.fehlermeldung_widget = None - self.standardeingabe_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) - self.umstaende_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) + self.standardeingabe_box = toga.Box(style=Pack(direction=COLUMN, flex=1)) - """ - self.beweismittel_box = toga.Box(style=Pack(direction=COLUMN, padding=padding_value)) - self.beweismittel_edit_box = toga.Box( - style=Pack(direction=COLUMN, padding=padding_value) - ) - """ - - self.footer_box = toga.Box(style=Pack(direction=COLUMN, flex= 1)) if start_values is not None: self.setup_input_fields(start_values) - - self.create_umstaende_section() - # self.create_beweismittel_section() - self.create_application_button = toga.Button( - "Antrag erstellen", - on_press=self.create_application, + self.next_button = toga.Button( + "Next", + on_press=self.next_step, style=StandardStyling.standard_button_style(), ) - self.footer_box.add(self.create_application_button) - self.add(self.standardeingabe_box) - self.add(toga.Divider()) - self.add(toga.Label("Kindersektion", style=StandardStyling.standard_label_style())) - self.add(self.kinder_box) - self.add(toga.Divider()) - self.add(self.umstaende_box) - # self.add(self.beweismittel_box) # This will be switched with beweismittel_edit_box when editing - self.add(self.footer_box) - - # HIER DEAKTIVIEREN ODER AKTIVIEREN UM TESTDATEN ZU LADEN - #self.alle_formular_elemente_mit_test_daten_fuellen() - - def alle_formular_elemente_mit_test_daten_fuellen(self): - """ - Diese Funktion füllt alle Formularfelder mit Testdaten auf. - """ - # Testdaten - test_daten = { - "COURT_NAME": "Kreis Musterstadt", - "COURT_DEPARTMENT": "Straßenverkehrsamt", - "COURT_ADDRESS": "Musterstraße 5", - "COURT_ZIP_CITY": "99999 Musterstadt", - "LAWYER_NAME": "Sebastian Kutzner", - "LAWYER_EMAIL": "sebastian.kutzner@procivis.koeln", - "LAWYER_PHONE": "+49 (221) 99898-179", - "LAWYER_FAX": "+49 (221) 99898-499", - "LAWYER_CONTACT_DATE": "01. Januar 2024", - "LAWYER_REF": "000001-23", - "PROCEDURE_VALUE": "12.000,00", - "APPLICANT_NAME": "Max Mustermann", - "APPLICANT_GENDER": "Herr", - "APPLICANT_BIRTH_DATE": "01.01.1975", - "APPLICANT_BIRTH_NAME": None, - "APPLICANT_ADDRESS": "Musterstraße 1, 99999 Musterstadt", - "MARRIAGE_DATE": "01.01.2000", - "MARRIAGE_LOCATION": "Musterstadt", - "REGISTRATION_NUMBER": "E 11/2000", - "MARRIAGE_OFFICE": "Standesamt in Musterstadt", - "RESPONDENT_NAME": "Erika Mustermann", - "RESPONDENT_GENDER": "Frau", - "RESPONDENT_BIRTH_DATE": "12.08.1964", - "RESPONDENT_BIRTH_NAME": "Musterfrau", - "RESPONDENT_ADDRESS": "Musterstraße 1, 99999 Musterstadt", - "SEPARATION_DATE": "01.01.2023", - "AUSGEZOGEN": False, - "SCHEIDUNGSFOLGEVEREINBARUNG": True, - "VERFAHRENSKOSTENHILFE": True, - "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": "02.01.2001", - "AGREEMENT_DATE_SIGNED": "02.01.2001", - "APPLICANT_NET_INCOME": "3.000,00 €", - "RESPONDENT_NET_INCOME": "1.000,00 €", - "TOTAL_PROCEDURAL_VALUE": "12.000,00 €", - "children": [ - { - "name": "Julia Mustermann", - "birth_date": "01.01.2014", - "custody": "Antragsgegner", - }, - { - "name": "Dennis Mustermann", - "birth_date": "01.01.2014", - "custody": "Antragsgegnerin", - }, - # Weitere Kinderdaten können hier hinzugefügt werden - ], - } - - # Zuweisen der Testdaten zu den Formularfeldern - self.court_name_input.value = test_daten["COURT_NAME"] - self.court_department_input.value = test_daten["COURT_DEPARTMENT"] - self.court_address_input.value = test_daten["COURT_ADDRESS"] - self.court_zip_city_input.value = test_daten["COURT_ZIP_CITY"] - self.lawyer_name_input.value = test_daten["LAWYER_NAME"] - self.lawyer_email_input.value = test_daten["LAWYER_EMAIL"] - self.lawyer_phone_input.value = test_daten["LAWYER_PHONE"] - self.lawyer_fax_input.value = test_daten["LAWYER_FAX"] - self.lawyer_contact_date_input.value = test_daten["LAWYER_CONTACT_DATE"] - self.reference_input.value = test_daten["LAWYER_REF"] - self.verfahrenswert_eingabefeld.value = test_daten["PROCEDURE_VALUE"] - applicant_name_parts = test_daten["APPLICANT_NAME"].split(" ") - self.applicant_vorname_eingabefeld.value = applicant_name_parts[0] - if len(applicant_name_parts) > 1: - self.applicant_nachname_eingabefeld.value = " ".join( - applicant_name_parts[1:] - ) - else: - self.applicant_nachname_eingabefeld.value = "" - self.applicant_geschlecht_select.value = test_daten["APPLICANT_GENDER"] - self.applicant_birthdate_eingabefeld.value = test_daten["APPLICANT_BIRTH_DATE"] - self.applicant_geburtsname_eingabefeld.value = ( - test_daten["APPLICANT_BIRTH_NAME"] - if test_daten["APPLICANT_BIRTH_NAME"] - else "" - ) - self.applicant_adress_eingabefeld.value = test_daten["APPLICANT_ADDRESS"] - - self.datum_eheschließung_eingabefeld.value = test_daten["MARRIAGE_DATE"] - self.ort_eheschließung_eingabefeld.value = test_daten["MARRIAGE_LOCATION"] - self.eheschließungsnummer_eingabefeld.value = test_daten["REGISTRATION_NUMBER"] - self.standesamt_eingabefeld.value = test_daten["MARRIAGE_OFFICE"] - respondent_name_parts = test_daten["RESPONDENT_NAME"].split(" ") - self.respondent_vorname_eingabefeld.value = respondent_name_parts[0] - if len(respondent_name_parts) > 1: - self.respondent_nachname_eingabefeld.value = " ".join( - respondent_name_parts[1:] - ) - else: - self.respondent_nachname_eingabefeld.value = "" - self.respondent_geschlecht_select.value = test_daten["RESPONDENT_GENDER"] - self.respondent_birthdate_eingabefeld.value = test_daten[ - "RESPONDENT_BIRTH_DATE" - ] - self.respondent_geburtsname_eingabefeld.value = ( - test_daten["RESPONDENT_BIRTH_NAME"] - if test_daten["RESPONDENT_BIRTH_NAME"] - else "" - ) - self.respondent_adress_eingabefeld.value = test_daten["RESPONDENT_ADDRESS"] - - self.getrennt_seit_eingabefeld.value = test_daten["SEPARATION_DATE"] - self.scheidungsfolgevereinbarung_existenz.value = test_daten[ - "SCHEIDUNGSFOLGEVEREINBARUNG" - ] - self.datum_scheidungsfolgevereinbarung_eingabefeld.value = ( - test_daten["SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM"] - if test_daten["SCHEIDUNGSFOLGEVEREINBARUNG"] - else "" - ) - self.applicant_nettoeinkommen_eingabefeld.value = test_daten[ - "APPLICANT_NET_INCOME" - ] - self.respondent_nettoeinkommen_eingabefeld.value = test_daten[ - "RESPONDENT_NET_INCOME" - ] - self.verfahrenswert_eingabefeld.value = test_daten["TOTAL_PROCEDURAL_VALUE"] - self.verfahrenskostenhilfe.value = test_daten["VERFAHRENSKOSTENHILFE"] - - # Anpassung der Zuweisungslogik für das wohnort_dropdown - custody_mapping = { - "Antragsgegner": "Antraggegner*in", - "Antragsgegnerin": "Antraggegner*in", - # Fügen Sie weitere Zuordnungen hinzu, falls nötig - } - - # Behandlung der Kinderdaten - for kid_data in test_daten["children"]: - # Füge ein neues Kind hinzu - self.kinder_box.add_child_entity( - None - ) # None, weil das Event-Argument hier irrelevant ist - - # Das zuletzt hinzugefügte Kindeingabe-Objekt erhalten - new_kid = self.kinder_box.kindeingabe_list[-1] - - # Aufspalten des Namens in Vor- und Nachname - kid_name_parts = kid_data["name"].split() - kid_vorname = kid_name_parts[0] if len(kid_name_parts) > 0 else "" - kid_nachname = ( - " ".join(kid_name_parts[1:]) if len(kid_name_parts) > 1 else "" - ) - - # Zuweisen der Daten zum zuletzt hinzugefügten Kindeingabe-Objekt - new_kid.vorname_eingabefeld.value = kid_vorname - new_kid.nachname_eingabefeld.value = kid_nachname - new_kid.geburtsdatum_eingabefeld.value = kid_data["birth_date"] - # Anwendung der Zuordnungslogik für das Dropdown - dropdown_value = custody_mapping.get( - kid_data["custody"], "Antragsteller*in" - ) # Standardwert als Fallback - new_kid.wohnort_dropdown.value = dropdown_value + self.add(self.next_button) def setup_input_fields(self, start_values): - - - # Input fields and labels self.reference_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.court_name_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.court_department_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.court_address_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.court_zip_city_input = toga.TextInput(style=StandardStyling.standard_input_style()) - - self.lawyer_name_input = toga.TextInput( - value=start_values.get("name", ""), style=StandardStyling.standard_input_style() - ) - self.lawyer_email_input = toga.TextInput( - value=start_values.get("email", ""), style=StandardStyling.standard_input_style() - ) - self.lawyer_phone_input = toga.TextInput( - value=start_values.get("phone", ""), style=StandardStyling.standard_input_style() - ) - self.lawyer_fax_input = toga.TextInput( - value=start_values.get("fax", ""), style=StandardStyling.standard_input_style() - ) - - self.lawyer_contact_date_input = toga.TextInput( - value=self.translate_date_to_german(self.get_current_date_in_word_format()), - style=StandardStyling.standard_input_style(), - ) - # self.case_file_number_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.defendant_name_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.fine_notice_date_input = toga.TextInput( - value=self.get_current_date(), style=StandardStyling.standard_highlighted_input_style() - ) - self.fine_notice_delivery_date_input = toga.TextInput( - value=self.get_current_date(), style=StandardStyling.standard_highlighted_input_style() - ) - self.gender_select = toga.Selection( - items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_input_style() - ) - - # Append all input fields and labels to the standardeingabe_box - # Append new input fields and labels to the standardeingabe_box - - # DATEV Abfrage self.standardeingabe_box.add( toga.Label("Unser Zeichen:", style=StandardStyling.standard_label_style()) ) self.standardeingabe_box.add(self.reference_input) - # self.standardeingabe_box.add( - # toga.Button("Daten importieren", on_press=self.datenabgleich_datev,style=StandardStyling.standard_button_style()) - # ) - - self.standardeingabe_box.add( - toga.Label("Gerichtsname:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.court_name_input) - self.standardeingabe_box.add( - toga.Label("Abteilung:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.court_department_input) - self.standardeingabe_box.add( - toga.Label("Adresse:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.court_address_input) - self.standardeingabe_box.add( - toga.Label("PLZ und Stadt:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.court_zip_city_input) - self.standardeingabe_box.add( - toga.Label("Anwaltsname:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.lawyer_name_input) - self.standardeingabe_box.add( - toga.Label("E-Mail:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.lawyer_email_input) - self.standardeingabe_box.add( - toga.Label("Telefonnummer:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.lawyer_phone_input) - self.standardeingabe_box.add( - toga.Label("Fax:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.lawyer_fax_input) - self.standardeingabe_box.add( - toga.Label("Datum des Antrags:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.lawyer_contact_date_input) - # self.standardeingabe_box.add(toga.Label("Az.:", style=StandardStyling.standard_input_style())) - # self.standardeingabe_box.add(self.case_file_number_input) - - def create_umstaende_section(self): - - self.verfahrenskostenhilfe = toga.Switch( - "Verfahrenskostenhilfe beantragen", style=StandardStyling.standard_switch_style() - ) - self.umstaende_box.add(self.verfahrenskostenhilfe) - # Ehe - self.umstaende_box.add(toga.Label("Ehe", style=StandardStyling.standard_label_style())) - self.umstaende_box.add( - toga.Label("Datum der Eheschließung", style=StandardStyling.standard_label_style()) - ) - self.datum_eheschließung_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.datum_eheschließung_eingabefeld) - self.umstaende_box.add( - toga.Label("Ort der Eheschließung", style=StandardStyling.standard_label_style()) - ) - self.ort_eheschließung_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.ort_eheschließung_eingabefeld) - self.umstaende_box.add( - toga.Label("Eheschließungsnummer", style=StandardStyling.standard_label_style()) - ) - self.eheschließungsnummer_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.eheschließungsnummer_eingabefeld) - self.umstaende_box.add( - toga.Label("Standesamt", style=StandardStyling.standard_label_style()) - ) - self.standesamt_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.standesamt_eingabefeld) - self.umstaende_box.add( - toga.Label("Getrennt seit:", style=StandardStyling.standard_label_style()) - ) - self.getrennt_seit_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.getrennt_seit_eingabefeld) - - self.scheidungsfolgevereinbarung_existenz = toga.Switch( - "Scheidungsfolgevereinbarung existent", - style=StandardStyling.standard_switch_style(), - on_change=self.scheidungsfolgevereinbarung_existenz_changed, - ) - self.umstaende_box.add(self.scheidungsfolgevereinbarung_existenz) - - # Antragsteller/in - self.umstaende_box.add( - toga.Label("Antragsteller*in", style=StandardStyling.standard_label_style()) - ) - self.umstaende_box.add( - toga.Label("Geschlecht:", style=StandardStyling.standard_label_style()) - ) - self.applicant_geschlecht_select = toga.Selection( - items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_selection_style() - ) - self.umstaende_box.add(self.applicant_geschlecht_select) - self.umstaende_box.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) - self.applicant_vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_vorname_eingabefeld) - self.umstaende_box.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) - self.applicant_nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_nachname_eingabefeld) - self.umstaende_box.add( - toga.Label("Geburtsname:", style=StandardStyling.standard_label_style()) - ) - self.applicant_geburtsname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_geburtsname_eingabefeld) - self.umstaende_box.add( - toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style()) - ) - self.applicant_birthdate_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_birthdate_eingabefeld) - self.umstaende_box.add(toga.Label("Adresse:", style=StandardStyling.standard_label_style())) - self.applicant_adress_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_adress_eingabefeld) - self.umstaende_box.add( - toga.Label("Nettoeinkommen:", style=StandardStyling.standard_label_style()) - ) - self.applicant_nettoeinkommen_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.applicant_nettoeinkommen_eingabefeld) - self.umstaende_box.add( - toga.Label("Wohnsituation:", style=StandardStyling.standard_label_style()) - ) - self.applicant_wohnsituation_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), - items=[ - "Räumlich getrennt in gemeinsamer Immobilie", - "In einer anderen Immobilie (ausgezogen)", - "Wohnt in gemeinsamer Immobilie", - ] - ) - self.umstaende_box.add(self.applicant_wohnsituation_dropdown) - - # Antraggegner/in - self.umstaende_box.add( - toga.Label("Antraggegner*in", style=StandardStyling.standard_label_style()) - ) - self.umstaende_box.add( - toga.Label("Geschlecht:", style=StandardStyling.standard_label_style()) - ) - self.respondent_geschlecht_select = toga.Selection( - items=["Herr", "Frau", "Divers"], style=StandardStyling.standard_input_style() - ) - self.umstaende_box.add(self.respondent_geschlecht_select) - self.umstaende_box.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) - self.respondent_vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_vorname_eingabefeld) - self.umstaende_box.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) - self.respondent_nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_nachname_eingabefeld) - self.umstaende_box.add( - toga.Label("Geburtsname:", style=StandardStyling.standard_label_style()) - ) - self.respondent_geburtsname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_geburtsname_eingabefeld) - - self.umstaende_box.add( - toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style()) - ) - self.respondent_birthdate_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_birthdate_eingabefeld) - self.umstaende_box.add(toga.Label("Adresse:", style=StandardStyling.standard_label_style())) - self.respondent_adress_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_adress_eingabefeld) - self.umstaende_box.add( - toga.Label("Nettoeinkommen:", style=StandardStyling.standard_label_style()) - ) - self.respondent_nettoeinkommen_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.respondent_nettoeinkommen_eingabefeld) - self.umstaende_box.add( - toga.Label("Wohnsituation:", style=StandardStyling.standard_label_style()) - ) - self.wohnsituation_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), - items=[ - "Räumlich getrennt in gemeinsamer Immobilie", - "In einer anderen Immobilie (ausgezogen)", - "Wohnt in gemeinsamer Immobilie", - ] - ) - self.umstaende_box.add(self.wohnsituation_dropdown) - - # Verfahrenswert - self.umstaende_box.add( - toga.Label("Verfahrenswert", style=StandardStyling.standard_label_style()) - ) - self.verfahrenswert_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.umstaende_box.add(self.verfahrenswert_eingabefeld) - - def scheidungsfolgevereinbarung_existenz_changed(self, widget): - # wenn checkbox gesetzt ist, dann wird das label und das e ingabefeld angezeigt, wenn der hacken entfernt wird verschwinden beide elemente - if self.scheidungsfolgevereinbarung_existenz.value: - # position für label ist die position nach der checkbox - position_fuer_label = ( - self.umstaende_box.children.index( - self.scheidungsfolgevereinbarung_existenz - ) - + 1 - ) - self.scheidungsfolgevereinbarung_label = toga.Label( - "Datum der Scheidungsfolgevereinbarung:", - style=StandardStyling.standard_label_style(), - ) - self.umstaende_box.insert( - position_fuer_label, self.scheidungsfolgevereinbarung_label - ) - self.datum_scheidungsfolgevereinbarung_eingabefeld = toga.TextInput( - style=StandardStyling.standard_input_style() - ) - position_fuer_eingabefeld = position_fuer_label + 1 - self.umstaende_box.insert( - position_fuer_eingabefeld, - self.datum_scheidungsfolgevereinbarung_eingabefeld, - ) - else: - # wenn checkbox nicht gesetzt ist, dann wird das label und das eingabefeld entfernt - self.umstaende_box.remove(self.scheidungsfolgevereinbarung_label) - self.umstaende_box.remove( - self.datum_scheidungsfolgevereinbarung_eingabefeld - ) - - def create_beweismittel_section(self): - self.beweismittel_data = get_beweismittel_data_scheidung() - - self.beweismittel_box.clear() - - self.beweismittel_switches: List[toga.Switch] = [] # Store the switch widgets - for option in self.beweismittel_data.get("options", []): - switch = toga.Switch(text=option, style=StandardStyling.standard_switch_style()) - self.beweismittel_switches.append(switch) # Add the switch to the list - self.beweismittel_box.add(switch) - - edit_button = toga.Button( - "Liste bearbeiten", - on_press=self.switch_beweismittel_section_to_edit_mode, - style=StandardStyling.standard_button_style(), - ) - self.beweismittel_box.add(edit_button) - - def switch_beweismittel_section_to_edit_mode(self, widget): - self.beweismittel_edit_box.clear() # Clear previous content if any - - # Add text inputs for each option in the beweismittel_edit_box - self.edit_inputs = [] - for option in self.beweismittel_data.get("options", []): - text_input = toga.TextInput(value=option, style=StandardStyling.standard_input_style()) - self.edit_inputs.append(text_input) - self.beweismittel_edit_box.add(text_input) - - # Add an empty text input to add new options in the beweismittel_edit_box - self.new_option_input = toga.TextInput( - placeholder="New option", style=StandardStyling.standard_input_style() - ) - self.beweismittel_edit_box.add(self.new_option_input) - - # Add Save and Cancel buttons in the beweismittel_edit_box - save_button = toga.Button( - "Speichern", on_press=self.save_edit_window, style=StandardStyling.standard_button_style() - ) - cancel_button = toga.Button( - "Abbrechen", on_press=self.close_edit_window, style=StandardStyling.standard_button_style() - ) - self.beweismittel_edit_box.add(save_button) - self.beweismittel_edit_box.add(cancel_button) - - # Switch from displaying the beweismittel_box to the beweismittel_edit_box - self.remove(self.beweismittel_box) - self.insert(1, self.beweismittel_edit_box) - - def close_edit_window(self, widget): - - # TODO Find a way to figure out at what position the beweismittel_edit_box in the child array is so that the beweismittel_box can be put there and not accidently somewhere else in the order - # Close the edit window and switch back to the beweismittel switches - self.remove(self.beweismittel_edit_box) - self.insert(1, self.beweismittel_box) - - def save_edit_window(self, widget): - # Placeholder implementation for saving the edited options - # Collect the values from the text inputs - edited_options = [ - text_input.value for text_input in self.edit_inputs if text_input.value - ] - if self.new_option_input.value: - edited_options.append(self.new_option_input.value) - - # Update the beweismittel_data with the edited options - self.beweismittel_data["options"] = edited_options - - set_beweismittel_data_Scheidung(self.beweismittel_data) - - # Refresh the beweismittel section to display the updated options - self.remove_beweismittel_switches() - self.create_beweismittel_section() - self.close_edit_window(widget) - - def remove_beweismittel_switches(self): - # Remove all beweismittel switches from the view - self.beweismittel_box.clear() - - def get_current_date_in_word_format(self): - # Get the current date in the "XX. Month YYYY" format - return datetime.now().strftime("%d. %B %Y") - - def translate_date_to_german(self, date_str): - # Map of English month names to German month names - month_translation = { - "January": "Januar", - "February": "Februar", - "March": "März", - "April": "April", - "May": "Mai", - "June": "Juni", - "July": "Juli", - "August": "August", - "September": "September", - "October": "Oktober", - "November": "November", - "December": "Dezember", - } - - # Split the date string to extract the month - parts = date_str.split() - if len(parts) == 3: - day, month, year = parts - # Translate the month to German - german_month = month_translation.get(month, month) - # Return the date string in German format - return f"{day} {german_month} {year}" - return date_str # Return the original string if format is unexpected - - def get_current_date(self): - # Get the current date in the format "DD.MM.YYYY" - return datetime.now().strftime("%d.%m.%Y") - - def generate_lawyer_list(self): - # Implement the logic to generate lawyer list from data - lawyer_data = get_lawyer_data() - start_values = lawyer_data.get("start_values", {}) - lawyer_list = [] - for lawyer_id, details in start_values.items(): - lawyer_list.append( - f"{details['title']} {details['name']}: {details['specialty']}" - ) - return "\n".join(lawyer_list) - def datenabgleich_datev(self): - self.datevdata = datenabfrage_datev_for_aktenzeichen( - aktenzeichen=self.case_file_number_input.value, - passwort=self.app.nutzerdateneingabekomponente.passwort_eingabefeld.value, - ) - - """def import_data(self, widget): - - - - self.court_name_input.value = self.datevdata.get("court_name") - self.court_department_input.value = self.datevdata.get("court_department") - self.court_address_input.value = self.datevdata.get("court_address") - self.court_zip_city_input.value = self.datevdata.get("court_zip_city") - self.lawyer_name_input.value = self.datevdata.get("lawyer_name") - self.lawyer_email_input.value = self.datevdata.get("lawyer_email") - self.lawyer_phone_input.value = self.datevdata.get("lawyer_phone") - self.lawyer_fax_input.value = self.datevdata.get("lawyer_fax") - self.lawyer_contact_date_input.value = self.datevdata.get("contact_date") - self.reference_input.value = self.datevdata.get("reference") - self.gender_select.value = self.datevdata.get("gender") - self.defendant_name_input.value = self.datevdata.get("defendant_name")""" - - """ - Um die Daten aus dem JSON-Datensatz, den Sie erhalten, den Elementen Ihrer Benutzeroberfläche zuzuordnen, müssen wir zunächst eine angepasste Datenstruktur (datevdata) erstellen, die alle benötigten Informationen in einer Weise enthält, die mit den .get() Aufrufen in Ihrem import_data-Methode kompatibel ist. - - Aus dem von Ihnen bereitgestellten JSON-Datensatz und den UI-Elementen scheint es, als ob einige der erforderlichen Informationen direkt aus dem JSON abgerufen werden können, während andere möglicherweise nicht direkt verfügbar sind und daher Annahmen oder Standardwerte benötigen könnten. - - Hier ist ein Ansatz, wie Sie eine solche Datenstruktur aufbauen können, basierend auf den Informationen, die Sie aus dem JSON-Datensatz extrahieren möchten: - - Erstellen einer Datenstruktur (datevdata), die alle erforderlichen Felder enthält. Da im bereitgestellten JSON-Datensatz einige der spezifisch angefragten Felder wie court_name, court_department, court_address, court_zip_city, lawyer_name, lawyer_email, lawyer_phone, lawyer_fax, contact_date, reference, gender, defendant_name nicht direkt vorhanden sind, werde ich ein Beispiel geben, wie man eine solche Struktur basierend auf verfügbaren Daten und angenommenen Feldern erstellen könnte. - Zuordnung der Daten zu den entsprechenden UI-Elementen. - Zunächst ein Beispiel, wie die datevdata Struktur basierend auf den vorhandenen Daten aussehen könnte: - - datevdata = { - "court_name": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "court_department": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "court_address": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "court_zip_city": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "lawyer_name": json_data[0]["partner"]["display_name"], # Beispiel: "Ernst Exempeladvokat" - "lawyer_email": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "lawyer_phone": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "lawyer_fax": "Nicht verfügbar", # Nicht im Datensatz, Annahme - "contact_date": json_data[0]["created"]["date"], # Beispiel: "2018-09-27" - "reference": json_data[0]["file_number"], # Beispiel: "000001-2005/001:00" - "gender": "Nicht verfügbar", # Nicht im Datensatz, Annahme oder AI-Detektion erforderlich - "defendant_name": "Mustermann" # Annahme basierend auf "file_name" - } - - Für die .get() Aufrufe in Ihrer import_data-Methode können Sie diese dann wie folgt verwenden: - - self.court_name_input.value = datevdata.get("court_name", "Standardwert") - self.court_department_input.value = datevdata.get("court_department", "Standardwert") - self.court_address_input.value = datevdata.get("court_address", "Standardwert") - self.court_zip_city_input.value = datevdata.get("court_zip_city", "Standardwert") - self.lawyer_name_input.value = datevdata.get("lawyer_name", "Standardwert") - self.lawyer_email_input.value = datevdata.get("lawyer_email", "Standardwert") - self.lawyer_phone_input.value = datevdata.get("lawyer_phone", "Standardwert") - self.lawyer_fax_input.value = datevdata.get("lawyer_fax", "Standardwert") - self.lawyer_contact_date_input.value = datevdata.get("contact_date", "Standardwert") - self.reference_input.value = datevdata.get("reference", "Standardwert") - self.gender_select.value = datevdata.get("gender", "Standardwert") # Möglicherweise müssen Sie die Logik für die Zuweisung des Geschlechts hier anpassen - self.defendant_name_input.value = datevdata.get("defendant_name", "Standardwert") - - Beachten Sie, dass die "Standardwert"-Platzhalter durch tatsächliche Standardwerte ersetzt werden sollten, die Sie verwenden möchten, falls die Daten nicht verfügbar sind. Für Felder wie gender, die möglicherweise eine komplexere Logik für die Zuweisung benötigen (z.B. basierend auf dem Namen), müssen Sie eine separate Logik implementieren, um diesen Wert zu bestimmen, bevor Sie ihn in datevdata einfügen. - """ - - - def create_application(self, widget): - - # TODO Liste in format für Formular anpassen, weiteres Feld - # Collect data from input fields - kindeingabe_list = {} - - for kid in self.kinder_box.kindeingabe_list: - - kid_vorname = kid.vorname_eingabefeld.value - kid_nachname = kid.nachname_eingabefeld.value - kid_geburtsdatum = kid.geburtsdatum_eingabefeld.value - kid_wohnhaft = kid.wohnort_dropdown.value - kid_id = uuid4() - kindeingabe_list[kid_id] = { - "vorname": kid_vorname, - "nachname": kid_nachname, - "geburtsdatum": kid_geburtsdatum, - "wohnhaft": kid_wohnhaft, - } - - children_data = [] - for kid in self.kinder_box.kindeingabe_list: - kid_vorname = kid.vorname_eingabefeld.value - kid_nachname = kid.nachname_eingabefeld.value - kid_geburtsdatum = kid.geburtsdatum_eingabefeld.value - kid_custody_mapping = { - "Antragsteller*in": "Antragsteller", - "Antraggegner*in": "Antragsgegner", - } - kid_custody = kid_custody_mapping[ - kid.wohnort_dropdown.value - ] # Übersetzung des Sorgerechts - - children_data.append( - { - "name": f"{kid_vorname} {kid_nachname}", - "birth_date": kid_geburtsdatum, - "custody": kid_custody, - } - ) - - self.applicant_fullname = ( - self.applicant_vorname_eingabefeld.value - + " " - + self.applicant_nachname_eingabefeld.value - ) - self.respondent_fullname = ( - self.respondent_vorname_eingabefeld.value - + " " - + self.respondent_nachname_eingabefeld.value - ) - daten_aus_formular = { - "COURT_NAME": self.court_name_input.value, - "COURT_DEPARTMENT": self.court_department_input.value, - "COURT_ADDRESS": self.court_address_input.value, - "COURT_ZIP_CITY": self.court_zip_city_input.value, - "LAWYER_NAME": self.lawyer_name_input.value, - "LAWYER_EMAIL": self.lawyer_email_input.value, - "LAWYER_PHONE": self.lawyer_phone_input.value, - "LAWYER_FAX": self.lawyer_fax_input.value, - "LAWYER_CONTACT_DATE": self.lawyer_contact_date_input.value, - "LAWYER_REF": self.reference_input.value, - # "CASE_FILE_NUMBER": self.case_file_number_input.value, - "PROCEDURE_VALUE": self.verfahrenswert_eingabefeld.value, - "APPLICANT_NAME": self.applicant_fullname, - "APPLICANT_GENDER": self.applicant_geschlecht_select.value, - "APPLICANT_BIRTH_DATE": self.applicant_birthdate_eingabefeld.value, - # if the field for APPLICANT_BIRTH_NAME is empty set the value to None - "APPLICANT_BIRTH_NAME": ( - self.applicant_geburtsname_eingabefeld.value - if self.applicant_geburtsname_eingabefeld.value != "" - else None - ), - "APPLICANT_ADDRESS": self.applicant_adress_eingabefeld.value, - "MARRIAGE_DATE": self.datum_eheschließung_eingabefeld.value, - "MARRIAGE_LOCATION": self.ort_eheschließung_eingabefeld.value, - "REGISTRATION_NUMBER": self.eheschließungsnummer_eingabefeld.value, - "MARRIAGE_OFFICE": self.standesamt_eingabefeld.value, - "RESPONDENT_NAME": self.respondent_fullname, - "RESPONDENT_GENDER": self.respondent_geschlecht_select.value, - "RESPONDENT_BIRTH_DATE": self.respondent_birthdate_eingabefeld.value, - "RESPONDENT_BIRTH_NAME": ( - self.respondent_geburtsname_eingabefeld.value - if self.respondent_geburtsname_eingabefeld.value != "" - else None - ), # if available - "RESPONDENT_ADDRESS": self.respondent_adress_eingabefeld.value, - "SEPARATION_DATE": self.getrennt_seit_eingabefeld.value, - "AUSGEZOGEN": True, - "SCHEIDUNGSFOLGEVEREINBARUNG": self.scheidungsfolgevereinbarung_existenz.value, - "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": ( - self.datum_scheidungsfolgevereinbarung_eingabefeld.value - if self.scheidungsfolgevereinbarung_existenz.value - else None - ), - "AGREEMENT_DATE_SIGNED": "05.05.2023", # Date when the divorce consequences agreement was signed - "APPLICANT_NET_INCOME": self.applicant_nettoeinkommen_eingabefeld.value, # Applicant's monthly net income - "RESPONDENT_NET_INCOME": self.respondent_nettoeinkommen_eingabefeld.value, # Respondent's monthly net income - "TOTAL_PROCEDURAL_VALUE": self.verfahrenswert_eingabefeld.value, # Procedural value based on three months' income - "children": children_data, - "LAWYER_FULL_NAME": self.lawyer_name_input.value, - "LAWYER_TITLE": self.start_values.get("title"), - "LAWYER_SPECIALTY": self.start_values.get("specialty"), - # "BEWEISMITTEL_LISTE": { - # switch.text: switch.value for switch in self.beweismittel_switches - # }, - "VERFAHRENSKOSTENHILFE": self.verfahrenskostenhilfe.value, - } - - # daten_aus_formular = test_daten_anstelle_der_daten_aus_dem_eingabe_formular - - # Assuming there is a variable that holds the value for SCHEIDUNGSFOLGEVEREINBARUNG - scheidungsfolgevereinbarung = daten_aus_formular.get( - "SCHEIDUNGSFOLGEVEREINBARUNG", False - ) - - # Check if all required fields are filled and at least one beweismittel_switch is selected - required_fields = [ - #"COURT_NAME", - # "COURT_DEPARTMENT", - # "COURT_ADDRESS", - # "COURT_ZIP_CITY", - #"LAWYER_NAME", - #"LAWYER_EMAIL", - #"LAWYER_PHONE", - #"LAWYER_CONTACT_DATE", - #"LAWYER_REF", - #"APPLICANT_NAME", - ] - # Add the additional required field if SCHEIDUNGSFOLGEVEREINBARUNG is True - if scheidungsfolgevereinbarung: - required_fields.append("SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM") - - missing_fields = [ - field for field in required_fields if not daten_aus_formular.get(field) - ] - # if missing_fields or not any(self.beweismittel_switches): - if missing_fields: - error_message = "" - field_labels = { - "COURT_NAME": "Gerichtsname", - "COURT_DEPARTMENT": "Abteilung", - "COURT_ADDRESS": "Adresse", - "COURT_ZIP_CITY": "PLZ und Stadt", - "LAWYER_NAME": "Anwaltsname", - "LAWYER_EMAIL": "Anwalt E-Mail", - "LAWYER_PHONE": "Anwalt Telefonnummer", - "LAWYER_CONTACT_DATE": "Datum des Antrags", - "LAWYER_REF": "Az.", - "APPLICANT_NAME": "Antragsteller", - "FINE_NOTICE_DATE": "Zugestellt am", - "FINE_NOTICE_DELIVERY_DATE": "Datum des Bußgeldbescheids", - "SCHEIDUNGSFOLGEVEREINBARUNG_ABSCHLUSSDATUM": "Datum der Scheidungsfolgevereinbarung", - } - if missing_fields: - error_message += "Bitte füllen Sie die folgenden Pflichtfelder aus:\n" - for field in missing_fields: - error_message += f"{field_labels[field]}\n\n" - # error_message += "Bitte wählen Sie mindestens ein Beweismittel aus." - - fehlermeldung = Fehlermeldung( - self.app, - retry_function=lambda: self.create_application(widget), - fehlermeldung=error_message, - ) - self.fehlermeldung_widget = fehlermeldung - self.footer_box.add(self.fehlermeldung_widget) - return - - daten_aus_formular = hinzufuegen_anwaltsdaten_zum_kontext(daten_aus_formular) - - # daten_aus_formular = self.hinzufuegen_beweismittel_zum_kontext_fuer_form_daten(daten_aus_formular, daten_aus_formular) - - # Initialize the data processor - data_processor = AntragVorlageDatenVerarbeitung( - name_der_template_datei=self.name_der_template_datei - ) - - print(daten_aus_formular) - - # Generate the output file - success, error_message = ( - data_processor.erzeuge_antragsdatei_mit_platzhalterinformationen( - daten_aus_formular=daten_aus_formular - ) - ) - if success: - # Entferne das Antrag-Manager-Widget, falls es existiert, und setze es zurück. - if self.antrag_manager_widget: - self.footer_box.remove(self.antrag_manager_widget) - self.antrag_manager_widget = None - - # Generiere einen einzigartigen Dateinamen für die Ausgabedatei. - unique_filename = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{self.formular_name}_{daten_aus_formular['APPLICANT_NAME']}.docx".replace(" ", "_") - - # Setze den Pfad für die Ausgabedatei. - mandant_folder = get_mandanten_folder() - output_folder_path = os.path.join(mandant_folder, daten_aus_formular["LAWYER_REF"]) - output_file_path = os.path.join(output_folder_path, unique_filename) - - # Erstelle den Ausgabeordner, falls er nicht existiert. - if not os.path.exists(output_folder_path): - os.makedirs(output_folder_path) - - # Verschiebe die temporäre Datei des ausgefüllten Antrags in den Ausgabeordner. - shutil.copy(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags, output_file_path) - - # Initialisiere den Datei-Manager mit der Ausgabedatei als Basis. - self.antrag_manager_widget = AntragDateiManager(app=self.app, base_docx_datei=output_file_path) - - # temp file löschen - if data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags is not None: - os.remove(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags) - - # Füge das Datei-Manager-Interface zum Hauptcontainer der App hinzu. - self.footer_box.add(self.antrag_manager_widget) - - # Erstelle eine Ausführung in der Datenbank, um die Nutzung der Software zu dokumentieren. - # Dies hilft festzuhalten, wie oft Nutzer die Software für spezifische Anträge eingesetzt haben. - add_ausfuehrung(reference=self.reference_input.value, kundenprogramm_ID=self.kundenprogramm_ID, antrags_name=self.formular_name) - - else: - - # temp file löschen - os.remove(data_processor.pfad_zur_temp_datei_des_ausgefüllten_antrags) - - # Create a Fehlermeldung component - fehlermeldung = Fehlermeldung( - self.app, - retry_function=lambda: self.create_application(widget), - fehlermeldung=error_message, - ) - self.fehlermeldung_widget = fehlermeldung - self.footer_box.add(self.fehlermeldung_widget) - - -class Kindereingabe(toga.Box): - - def __init__( - self, - id: str | None = None, - ): - style = Pack(direction=COLUMN) - super().__init__(id=id, style=style) - - self.kindeingabe_list: List[Kindeingabe] = ( - [] - ) # List to keep track of Kindeingabe instances - - self.add_child_button = toga.Button( - "Kind hinzufügen", on_press=self.add_child_entity, style=StandardStyling.standard_button_style() - ) - # Initially, add the buttons to the container - self.add(self.add_child_button) - - self.kind_delete_button = toga.Button( - "Kind entfernen", on_press=self.kind_entfernen, style=StandardStyling.standard_button_style() - ) - self.add(self.kind_delete_button) - - def add_child_entity(self, widget): - kind_box = Kindeingabe() - self.kindeingabe_list.append( - kind_box - ) # Add the new Kindeingabe to the tracking list - # Insert the Kindeingabe just above the buttons but below the last Kindeingabe - insert_index = len( - self.kindeingabe_list - ) # Calculate the position where the new Kindeingabe should be inserted - self.insert(insert_index - 1, kind_box) # Insert at the calculated index - - def kind_entfernen(self, widget): - if self.kindeingabe_list: # Check if there's any Kindeingabe to remove - last_kindeingabe = ( - self.kindeingabe_list.pop() - ) # Remove the last Kindeingabe from the list - self.remove( - last_kindeingabe - ) # Remove the last Kindeingabe from the Kindereingabe container - - -class Kindeingabe(toga.Box): - - def __init__( - self, - id: str | None = None, - ): - style = Pack(direction=COLUMN) - super().__init__(id=id, style=style) - - self.add(toga.Label("Vorname:", style=StandardStyling.standard_label_style())) - self.vorname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.add(self.vorname_eingabefeld) - self.add(toga.Label("Nachname:", style=StandardStyling.standard_label_style())) - self.nachname_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.add(self.nachname_eingabefeld) - self.add(toga.Label("Geburtsdatum:", style=StandardStyling.standard_label_style())) - self.geburtsdatum_eingabefeld = toga.TextInput(style=StandardStyling.standard_input_style()) - self.add(self.geburtsdatum_eingabefeld) - self.add(toga.Label("Wohnt bei:", style=StandardStyling.standard_label_style())) - self.wohnort_dropdown = toga.Selection(style=StandardStyling.standard_selection_style(), - items=["Antragsteller*in", "Antraggegner*in"] - ) - self.add(self.wohnort_dropdown) + def next_step(self, widget): + # Implement the logic for the next step + pass \ No newline at end of file From edf1a310265419d92895b21106156d32a09adaac Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:05:23 +0100 Subject: [PATCH 13/34] backgroundremoval added --- {{ cookiecutter.app_name }}/logo_script.py | 20 +++++++++++++++++-- {{ cookiecutter.app_name }}/requirements.txt | 1 + .../components/MainStreamExampleComponent.py | 13 ++++++------ 3 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 {{ cookiecutter.app_name }}/requirements.txt diff --git a/{{ cookiecutter.app_name }}/logo_script.py b/{{ cookiecutter.app_name }}/logo_script.py index e588b9b..23fb324 100644 --- a/{{ cookiecutter.app_name }}/logo_script.py +++ b/{{ cookiecutter.app_name }}/logo_script.py @@ -1,13 +1,29 @@ from PIL import Image import os - - +from rembg import remove + +# HOW TO USE +# 1. Put Logo into the src/{{ cookiecutter.module_name }} folder +# 2. Install dependencies: pip install -r requirements.txt +# 3. Run script with: python logo_script.py +def remove_background(image_path): + # Load the image + img = Image.open(image_path) + + # Remove the background + output = remove(img) + + # Save the image with the background removed + output.save(image_path) + def create_icon_files(src_path, dist_path): # Ensure the source logo.png exists logo_path = os.path.join(src_path, "{{ cookiecutter.module_name }}.png") if not os.path.exists(logo_path): raise FileNotFoundError(f"The source file {logo_path} does not exist.") + remove_background(logo_path) + # Open the source image img = Image.open(logo_path) diff --git a/{{ cookiecutter.app_name }}/requirements.txt b/{{ cookiecutter.app_name }}/requirements.txt new file mode 100644 index 0000000..ac38609 --- /dev/null +++ b/{{ cookiecutter.app_name }}/requirements.txt @@ -0,0 +1 @@ +rembg \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py index 0088133..bdaeb2e 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/MainStreamExampleComponent.py @@ -20,7 +20,12 @@ def __init__( self.app = app self.standardeingabe_box = toga.Box(style=Pack(direction=COLUMN, flex=1)) - + self.standardeingabe_box.lable1 = toga.Label( + "Reference", + style=StandardStyling.standard_label_style(), + ) + self.standardeingabe_box.add(self.standardeingabe_box.lable1) + if start_values is not None: self.setup_input_fields(start_values) @@ -33,11 +38,7 @@ def __init__( self.add(self.next_button) def setup_input_fields(self, start_values): - self.reference_input = toga.TextInput(style=StandardStyling.standard_input_style()) - self.standardeingabe_box.add( - toga.Label("Unser Zeichen:", style=StandardStyling.standard_label_style()) - ) - self.standardeingabe_box.add(self.reference_input) + self.standardeingabe_box.lable1.text = start_values["lable1"] def next_step(self, widget): # Implement the logic for the next step From 531c1bde02ad1323dce77f3ceee87e064d8aba2d Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:18:44 +0100 Subject: [PATCH 14/34] Update logo_script.py --- {{ cookiecutter.app_name }}/logo_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/logo_script.py b/{{ cookiecutter.app_name }}/logo_script.py index 23fb324..9302eae 100644 --- a/{{ cookiecutter.app_name }}/logo_script.py +++ b/{{ cookiecutter.app_name }}/logo_script.py @@ -57,7 +57,7 @@ def create_icon_files(src_path, dist_path): # Paths source_path = "." -dist_path = "{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/resources/" +dist_path = "src/{{ cookiecutter.module_name }}/resources/" # Create the icon files create_icon_files(source_path, dist_path) From ecc513e500bb6a15b6a3fdffb144958e8a85a540 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:23:39 +0100 Subject: [PATCH 15/34] Update logo_script.py --- {{ cookiecutter.app_name }}/logo_script.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{ cookiecutter.app_name }}/logo_script.py b/{{ cookiecutter.app_name }}/logo_script.py index 9302eae..6dcdad8 100644 --- a/{{ cookiecutter.app_name }}/logo_script.py +++ b/{{ cookiecutter.app_name }}/logo_script.py @@ -18,7 +18,7 @@ def remove_background(image_path): def create_icon_files(src_path, dist_path): # Ensure the source logo.png exists - logo_path = os.path.join(src_path, "{{ cookiecutter.module_name }}.png") + logo_path = os.path.join(src_path, "logo.png") if not os.path.exists(logo_path): raise FileNotFoundError(f"The source file {logo_path} does not exist.") @@ -46,7 +46,7 @@ def create_icon_files(src_path, dist_path): # Use `iconutil` to create the .icns file from the .iconset directory os.system( - f'iconutil -c icns {iconset_path} -o {os.path.join(dist_path, "nadoo_law.icns")}' + f'iconutil -c icns {iconset_path} -o {os.path.join(dist_path, "{{ cookiecutter.module_name }}.icns")}' ) # Clean up the .iconset directory after creating the .icns file From 7883ed068ceb38ffa1c325d8eecb83e054806b02 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:16:34 +0100 Subject: [PATCH 16/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index e61597d..5fb5268 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -14,7 +14,7 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user,update_daten_laden_user from screeninfo import get_monitors class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): From 3d2cdcd4fd03df0e5684b38794b67599bf2db252 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:19:19 +0100 Subject: [PATCH 17/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index e61597d..835d417 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -54,7 +54,7 @@ def base_ordner_offnen(self, widget): def aktualisierung_anzeigen(self): self.show_updates() - def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): + def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): #beim start ausführen update_daten = update_daten_laden_user() neues_update_wurde_installiert = update_daten.get("neues_update_wurde_installiert") From 8c9ef0e13c6569d03163700ee68095b236818752 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:20:05 +0100 Subject: [PATCH 18/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 835d417..be97fa8 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -53,7 +53,8 @@ def base_ordner_offnen(self, widget): def aktualisierung_anzeigen(self): self.show_updates() - + + #fixed indenting def überprüfung_auf_erstausführung_nach_aktualisierung_oder_installation(self): #beim start ausführen update_daten = update_daten_laden_user() From 48eb63d9368ed63c4c47c1eb43151f92527049a3 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:30:35 +0100 Subject: [PATCH 19/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index be97fa8..13995a8 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -14,7 +14,7 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.LicenseWindow import LicenseWindow from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.UpdateWindow import UpdateWindow -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user,update_daten_laden_app,setup_folders from screeninfo import get_monitors class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): From e6ba98418dc22abc3430be585cb996be5aa97607 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:35:28 +0100 Subject: [PATCH 20/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 22867d3..075a2a9 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -191,12 +191,6 @@ def startup(self): update_in_updates_ordner_uebertragen(self.app) self.aktualisierung_anzeigen() - def main(self): - pass - - def exit(self): - pass - def main(): return {{ cookiecutter.app_name|lower|replace('-', '_') }}() From d569c748223e5c6f480253a2ed53be525dbd7b7c Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:05:49 +0100 Subject: [PATCH 21/34] =?UTF-8?q?Startcomponente=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {{ cookiecutter.app_name }}/pyproject.toml | 1 + .../src/{{ cookiecutter.module_name }}/app.py | 15 + .../components/Startfenster.py | 505 ++++++++++++++++++ 3 files changed, 521 insertions(+) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py diff --git a/{{ cookiecutter.app_name }}/pyproject.toml b/{{ cookiecutter.app_name }}/pyproject.toml index f097692..65a91de 100644 --- a/{{ cookiecutter.app_name }}/pyproject.toml +++ b/{{ cookiecutter.app_name }}/pyproject.toml @@ -26,6 +26,7 @@ requires = [ "python-dotenv", "toml", "screeninfo", + "toga", ] {{- cookiecutter.pyproject_table_briefcase_app_extra_content }} diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 075a2a9..12f36a4 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -16,6 +16,8 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user,update_daten_laden_app,setup_folders,update_daten_laden_user +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Dateiauswahl import Startfenster + from screeninfo import get_monitors class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): @@ -185,6 +187,19 @@ def startup(self): # Set the window position to the right side of the screen self.main_window.position = (half_screen_width, 0) + # Datei Auswahl + startfenster = Startfenster(app = self.app) + + # Create the main window + self.main_box.add( + toga.Label( + "Willkommen bei {{ cookiecutter.project_name|escape_toml }}", + style=Pack(font_size=20, padding_top=10, padding_bottom=10), + ), + startfenster + ) + + self.main_window.show() if not os.path.isfile(get_updates_datei_user()) or self.neues_update_existiert(): diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py new file mode 100644 index 0000000..5f08b9e --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -0,0 +1,505 @@ +from __future__ import annotations +import datetime +import re +from time import sleep +from typing import TYPE_CHECKING, List +from openpyxl import load_workbook +import openpyxl + +import toga +from toga.style import Pack +from toga.style.pack import COLUMN +from toga.widgets.multilinetextinput import MultilineTextInput + +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import finde_letzte_zeile_in_excel_blatt, lade_daten_aus_excel_blatt_als_DataFrame, loesche_alle_daten_von_blatt, open_file, uebertrage_daten_in_excel_blatt +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import translate_date_to_german +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.styling import StandardStyling + +import pandas as pd +from openpyxl.worksheet.worksheet import Worksheet +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Fehlermeldung import Fehlermeldung + +if TYPE_CHECKING: + from {{ cookiecutter.app_name|lower|replace('-', '_') }}.app import {{ cookiecutter.app_name|lower|replace('-', '_') }} +import os +import shutil +import io +import sys + + +class GUIOutputStream(io.StringIO): + def __init__(self, console_output): + super().__init__() + self.console_output = console_output + + def write(self, text): + self.console_output.value += text + sys.__stdout__.write(text) + + +def create_test_run_copy(file_path): + # Pfad zum "Testlauf" Ordner in "~/Library/Containers/com.microsoft.Excel/Data" erstellen + test_run_folder = os.path.expanduser("~/Library/Containers/com.microsoft.Excel/Data/Testlauf") + + # Überprüfen, ob der "Testlauf" Ordner bereits existiert, ansonsten erstellen + if not os.path.exists(test_run_folder): + os.makedirs(test_run_folder) + + # Dateiname und Erweiterung der Exceldatei extrahieren + file_name, file_extension = os.path.splitext(os.path.basename(file_path)) + + # Überprüfen, ob bereits Testlauf Dateien existieren + test_run_files = [f for f in os.listdir(test_run_folder) if f.startswith("Testlauf_")] + + if test_run_files: + # Letzte Testlauf Datei finden und Nummer extrahieren + last_test_run_file = max(test_run_files) + last_test_run_number = int(last_test_run_file.split("_")[1].split(".")[0]) + + # Neue Testlauf Nummer generieren + new_test_run_number = last_test_run_number + 1 + else: + # Wenn keine Testlauf Dateien existieren, mit Nummer 001 beginnen + new_test_run_number = 1 + + # Neuen Dateinamen für den Testlauf erstellen + new_test_run_file_name = f"Testlauf_{new_test_run_number:03d}{file_extension}" + + # Pfad zur neuen Testlauf Datei erstellen + new_test_run_file_path = os.path.join(test_run_folder, new_test_run_file_name) + + # Kopie der Exceldatei im "Testlauf" Ordner erstellen + shutil.copy2(file_path, new_test_run_file_path) + + return new_test_run_file_path + + +class Startfenster(toga.Box): + def __init__( + self, + app: {{ cookiecutter.app_name|lower|replace('-', '_') }}, + id: str | None = None, + start_values: dict | None = None + ): + style = Pack(direction=COLUMN) + super().__init__(id=id, style=style) + self.app = app + + self.standardeingabe_box = toga.Box(style=Pack(direction=COLUMN, flex=1)) + + self.console_output = MultilineTextInput(id=id, style=Pack(flex=1), readonly=True) + + self.standardeingabe_box.excelDatei = toga.MultilineTextInput( + placeholder="Excel Datei hier ablegen", + style=StandardStyling.standard_input_style(), + ) + self.standardeingabe_box.add(self.standardeingabe_box.excelDatei) + + #self.standardeingabe_box.dateiname = toga.TextInput(style=StandardStyling.standard_input_style()) + #self.standardeingabe_box.add(self.standardeingabe_box.dateiname) + + """ + start_values = { + "excelDatei": "/Users/christophbackhaus/Documents/GitHub/NADOO-Telemarketing/Testdatei.xlsx", + "dateiname": "test", + } + """ + + if start_values is not None: + self.setup_input_fields(start_values) + + + + self.datei_auswaehlen_button = toga.Button( + "Datei auswählen", + on_press=self.datei_auswaehlen, + style=StandardStyling.standard_button_style(), + ) + + + + self.next_button = toga.Button( + "Datei verarbeiten", + on_press=self.next_step, + style=StandardStyling.standard_button_style(), + ) + + self.gui_output = GUIOutputStream(self.console_output) + sys.stdout = self.gui_output + self.add(self.console_output) + self.add(self.standardeingabe_box) + self.add(self.datei_auswaehlen_button) + self.add(self.next_button) + + #self.next_step(self) + + async def datei_auswaehlen(self, widget): + dateipfad = await self.app.main_window.open_file_dialog( + title="Datei auswählen", + ) + if dateipfad is not None: + self.standardeingabe_box.excelDatei.value = dateipfad + + + def setup_input_fields(self, start_values): + self.standardeingabe_box.excelDatei.value = start_values["excelDatei"] + + def next_step(self, widget=None): + file_path = self.standardeingabe_box.excelDatei.value + print(file_path) + try: + + + + # um iterativ arbeiten zu können aber die Vorlage nicht zu ändern werden wir zunächst eine kopie der Excepliste anfertigen + # Der Name ist "Testlauf_001" wobei die Nummer der Datei inkrementiert wird + # Die Datei werden im Ordner "Testlauf" abgelegt, der neben der Exceldatei im Projektordner platziert ist + # Überprüfe das Verzeichnis auf bereits existierende Testlauf Datein, teile den Namen auf, und erzeuge den neuen Namen + + pfad_zur_temp_datei_in_ordner_mit_zugriffrechten = create_test_run_copy(file_path) + print(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) + + + wb = load_workbook(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) + + print(wb.sheetnames) + print(wb.worksheets[0].title) + print(wb.worksheets[0].cell(row=1, column=1).value) + + # Get the first sheet in the workbook + #print(wb.sheet_names) + + # Die Excel Datei hat mehrere Blätter + # Bsp. 'Statuskürzel', 'freie Termine', 'WV März 2024', 'WV April 2024', + # 'WV Folgemonate', 'NICHT erreichbar', 'KI D etc', 'Mails', 'Termine', 'Auffüller' + + # WV steht für Wiedervorlage + # Diese Blätter haben die folgenden Spalten: + # 'Stadt', 'Associated Company', 'Vorname Ansprechpartner', 'Name Ansprechpartner', 'Position / Titel', + # 'Straße Hausnr.', 'PLZ', 'Ort ', 'Bundesland', 'Webseite', 'E-Mail', 'Ansprechpartner AP', 'E-Mail AP', + # 'Telefon primär', 'Telefon Zentrale', 'Telefon Mobil', 'Bemerkung (Kontakt besteht, Kaltakquise etc.)', + # 'Gesprächsnotiz TM ', 'WV Datum', 'Status' + + # Was mit den Datenreihen passieren soll wird bestimmt durch WV Datum und Status + """ + Status Bedeutung kopieren verschieben ins Tabellenblatt verbleibt in oder wird verschoben + Info Bitte Infos per E-Mail versenden ja, bei WV ja, bei Selbstmelder oder WV Folgemonate Mails (alle); WV, WV Folgemonate Mails (alle); Selbstmelder oder WV + S Selbstmelder nein ja WV Folgemonate nach Zeitspanne X in WV + D Doppelte Adresse nein ja KI KK D etc KI KK D etc + KI Kein Interesse nein ja KI KK D etc KI KK D etc + KK Kein Kontakt möglich, nicht erreichbar nein ja KI KK D etc KI KK D etc + T Termin nein ja Termine Termine + + TS Terminstorno nein ja Termine je nach Fall in Termine oder WV + TV Terminverlegung, in Bearbeitung nein ja WV WV, dann wieder Termine + WA Wiederanruf nein nein WV + WAI Wiederanruf, Infomail verschickt nein nein WV + WV Wiedervorlage Monat x nein ja WV Folgemonate WV Folgemonate + WVI Wiedervorlage Monat x, Infomail verschickt nein ja WV Folgemonate WV Folgemonate + ES Entscheider-Sekretärin + FA falscher Ansprechpartner + KL keine Leitungsposition hinterlegt + R Registrierung für Testphase + """ + + # Finde das Blatt mit dem aktuellen Monat + + aktuelles_monat_blatt:Worksheet = None + Excel_Blatt_WV_Folgemonate:Worksheet = None + Excel_Blatt_Mails:Worksheet = None + Excel_Blatt_KI_D_etc:Worksheet = None + Excel_Blatt_NICHT_erreichbar:Worksheet = None + Excel_Blatt_Termine:Worksheet = None + + aktueller_monat_deutsch = "März" + #print(f"Aktueller Monat: {aktueller_monat_deutsch}") + + for blatt in wb.worksheets: + blatt: Worksheet + + #print(blatt.title) + + if aktueller_monat_deutsch in blatt.title: + aktuelles_monat_blatt = blatt + + if "WV Folgemonate" in blatt.title or "WV Folgemonate" == blatt.title: + Excel_Blatt_WV_Folgemonate = blatt + + if "Mails" in blatt.title or "Mails" == blatt.title: + Excel_Blatt_Mails = blatt + + if "KI D etc" in blatt.title or "KI D etc" == blatt.title: + Excel_Blatt_KI_D_etc = blatt + + if "NICHT erreichbar" in blatt.title or "NICHT erreichbar" == blatt.title: + Excel_Blatt_NICHT_erreichbar = blatt + + if "Termine" in blatt.title or "Termine" == blatt.title: + Excel_Blatt_Termine = blatt + + + print(f"Akuelles Monatsblatt: {aktuelles_monat_blatt.title}") + print(f"Folge Monatsblatt: {Excel_Blatt_WV_Folgemonate.title}") + + + # Finde die Spaltenbezeichner (A1, B1, C1 ...) mit den Namen Gesprächsnotiz TM, WV Datum und Status + spaltenbezeichner_gesprächsnotiz_tm = None + spaltenbezeichner_wv_datum = None + spaltenbezeichner_status = None + + # Entferne Leerzeichen am Ende der Spaltenüberschriften + for spalte in aktuelles_monat_blatt.columns: + #print(f"Spalte: {spalte}") + #print(f"Spalte[0]: {spalte[0]}") + print(f"Spalte[0].value: {spalte[0].value}") + + if spalte[0].value is not None: + spalte[0].value = spalte[0].value.strip() + else: + break + # Finde die Spaltenbezeichner basierend auf den bereinigten Spaltenüberschriften + for spalte in aktuelles_monat_blatt.columns: + if spalte[0].value == "Gesprächsnotiz TM": + spaltenbezeichner_gesprächsnotiz_tm = spalte + elif spalte[0].value == "WV Datum": + spaltenbezeichner_wv_datum = spalte + elif spalte[0].value == "Status": + spaltenbezeichner_status = spalte + break + #print(f"Gesprächsnotiz TM: {spaltenbezeichner_gesprächsnotiz_tm}") + #print(f"WV Datum: {spaltenbezeichner_wv_datum}") + #print(f"Status: {spaltenbezeichner_status}") + + + #print(f"Gesprächsnotiz Tm: {spaltenbezeichner_gesprächsnotiz_tm}") + #print(f"WV Datum: {spaltenbezeichner_wv_datum}") + #print(f"Status: {spaltenbezeichner_status}") + + # Da noch unklar ist in welcher spalte alle zeilen einen Wert haben suchen wir den höchsten. + # Dadurch wir die letzte Zeile + # Der Weg ist, dass wir in spalte 1 anfangen bis zur letzten gefüllten Zeile zu springen + # Die Anzahl der reihen wird gespeichert + # danach wird die selbe Anzahl von Reihen bei allen Spalten die bis Status vorhanden sind durchgeführt + # Hierbei gehen wir wieder von links nach rechts und speichern den höchsten Wert + # Wichtig ist aber das wir sollten in den späteren Spalten nicht mehr Zeilen haben als in der ersten + # Werden dennoch weitere Reihen geprüft. Ist in allen Spalten in der gefundenen Reihe kein Wert ist anzunehmen, dass es die letzte Reihe war + # So wird verhindert, dass ein einzelner fehlender Wert die überprüfung stoppt + # Es wird also geprüft ob in allen Spalten einer Reihe None ist und wenn ja war die vorherige die letzte + + + + """ + max_rows_aktueller_monat_blatt = finde_letzte_zeile_in_excel_blatt(aktuelles_monat_blatt) + + # Jetzt da wir wissen wo die Tabelle liegt ist es Zeit die Daten zu laden und dann im Speicher zu verarbeiten. + # Lade die Daten aus dem aktuellen Monatsblatt in ein DataFrame + Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat = aktuelles_monat_blatt.range((1, 1), + (max_rows_aktueller_monat_blatt, spaltenbezeichner_status.column)).options(pd.DataFrame).value + Dataframe_aller_Daten_vom_aktuellen_Monat = pd.DataFrame(Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat[1:], + columns=Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat.columns) + + """ + Dataframe_aller_Daten_vom_aktuellen_Monat = lade_daten_aus_excel_blatt_als_DataFrame(aktuelles_monat_blatt) + + Dataframe_aller_Daten_vom_aktuellen_Monat.reset_index(drop=False, inplace=True) + + #print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + # -------------------------------- Datenbereinigung --------------------------------# + + def korrigiere_tippfehler(text): + text = str(text) # Konvertiere den Wert in eine Zeichenkette + text = text.replace('Selbmelder', 'Selbstmelder') + # Füge hier weitere häufige Tippfehler und ihre Korrekturen hinzu + return text + + # Korrigiere Tippfehler in der Spalte 'Status' + Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].apply(korrigiere_tippfehler) + + # Ersetze fehlende Werte in der Spalte 'Status' durch einen leeren String + Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].fillna('') + + # Ersetze fehlende Werte in der Spalte 'Gesprächsnotiz TM' durch einen leeren String + Dataframe_aller_Daten_vom_aktuellen_Monat['Gesprächsnotiz TM'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Gesprächsnotiz TM'].fillna('') + + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + # ----------------------------------------------------------------------------------- # + + # Erfasse Datensätze mit Bezug zu Infomail und Selbstmelder + status_info = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'Info'] + + # Übertrage die Daten aus dem DataFrame in das Excel Blatt + uebertrage_daten_in_excel_blatt(Excel_Blatt_Mails, status_info) + + # Passe den Status der übertragenen Positionen mit dem Wert 'Info' auf 'WAI' an + status_info.loc[status_info['Status'] == 'Info', 'Status'] = 'WAI' + + # Aktualisiere die Werte in Dataframe_aller_Daten_vom_aktuellen_Monat basierend auf den Indexwerten von status_info + for index, row in status_info.iterrows(): + Dataframe_aller_Daten_vom_aktuellen_Monat.loc[index, 'Status'] = row['Status'] + + #print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + # ----------------------------------------------------------------------------------- # + # Erfasse Datensätze mit Bezug zu Infomail und Selbstmelder. Ein Beispielstatus wäre hier Info / Selbstmelder + status_s = Dataframe_aller_Daten_vom_aktuellen_Monat[ + Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains('Info') & + Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains('Selbstmelder') + ] + + print(status_s) + + # Übertrage die Daten aus dem DataFrame in das Excel Blatt + print("Übertrage Daten in Excel Blatt Mails") + uebertrage_daten_in_excel_blatt(Excel_Blatt_Mails, status_s) + print("Übertrage Daten in Excel Blatt WV Folgemonate") + uebertrage_daten_in_excel_blatt(Excel_Blatt_WV_Folgemonate, status_s) + + + + # Entferne die Zeilen aus dem Ursprungsdataframe + print("Entferne Zeilen aus Dataframe_aller_Daten_vom_aktuellen_Monat") + Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_s.index, inplace=True) + + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + # ----------------------------------------------------------------------------------- # + status_values = ['D', 'KI', 'KK'] + status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].isin(status_values)] + uebertrage_daten_in_excel_blatt(Excel_Blatt_KI_D_etc, status_result) + print("Nach Übertragung und vor Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) + + print("Nach Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + # ----------------------------------------------------------------------------------- # + + status_values = ['nicht erreichbar'] + status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].isin(status_values)] + uebertrage_daten_in_excel_blatt(Excel_Blatt_NICHT_erreichbar, status_result) + print("Nach Übertragung und vor Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) + + print("Nach Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + # ----------------------------------------------------------------------------------- # + + + status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[ + Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains(r'^(?:T|Telefontermin) am \d{2}\.\d{2}\.\d{4}(?: um \d{2}:\d{2})?', regex=True) + ] + uebertrage_daten_in_excel_blatt(Excel_Blatt_Termine, status_result) + print("Nach Übertragung und vor Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) + + print("Nach Entfernen:") + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + # ----------------------------------------------------------------------------------- # + + Dataframe_aller_Daten_vom_aktuellen_Monat['WV Datum'] = pd.to_datetime(Dataframe_aller_Daten_vom_aktuellen_Monat['WV Datum']) + Dataframe_aller_Daten_vom_aktuellen_Monat.sort_values(by='WV Datum', ascending=True, na_position='first', inplace=True) + + print(Dataframe_aller_Daten_vom_aktuellen_Monat) + + + + + + + + status_wa = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WA'] + status_ts = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'TS'] + status_tv = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'TV'] + status_wai = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WAI'] + status_wv = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WV'] + status_wvi = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WVI'] + + + + loesche_alle_daten_von_blatt(aktuelles_monat_blatt) + + + + # füge die nun bereinigte und geordnete Liste wieder ein + # Füge die bereinigten und geordneten DataFrames wieder in das Excel-Blatt ein + uebertrage_daten_in_excel_blatt(aktuelles_monat_blatt, Dataframe_aller_Daten_vom_aktuellen_Monat) + + + + + # Die Blätter mit der Bezeichnung WV beinhalten die Daten der Telefonisten + # Die Datensätze werden wie folgt verteilt: + # Die Datensätze von WV März usw. werden überprüft. + + # Get the first 10 rows and columns of the sheet + #Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat = sheet.range("A1:T1").value + + + #print(Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat) + + + # Pfad der Quelldatei + quelldatei = self.standardeingabe_box.excelDatei.value + + print(f"Die Quelldatei ist {quelldatei}") + + # Überprüfe, ob die Quelldatei existiert + if not os.path.exists(quelldatei): + print(f"Die Quelldatei {quelldatei} existiert nicht.") + else: + # Pfad und Name der Zieldatei + zielordner = os.path.dirname(quelldatei) + print(f"Der Zielordner ist {zielordner}") + print(f"Der Dateiname ist {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten}") + + # Trenne Dateiname und Erweiterung + dateiname, erweiterung = os.path.splitext(os.path.basename(quelldatei)) + + # Füge "_ueberarbeitet" zum Dateinamen hinzu + neuer_dateiname = f"{dateiname}_ueberarbeitet{erweiterung}" + + zieldatei = os.path.join(zielordner, neuer_dateiname) + + # Überprüfe, ob die Zieldatei bereits existiert + if os.path.exists(zieldatei): + # Wenn die Zieldatei bereits existiert, füge eine fortlaufende Nummer hinzu + i = 1 + while True: + neuer_dateiname = f"{dateiname}_ueberarbeitet_{i}{erweiterung}" + zieldatei = os.path.join(zielordner, neuer_dateiname) + if not os.path.exists(zieldatei): + break + i += 1 + + print(f"Die Zieldatei ist {zieldatei}") + + # Überprüfe, ob die Datei pfad_zur_temp_datei_in_ordner_mit_zugriffrechten im aktuellen Verzeichnis existiert + if not os.path.exists(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten): + print(f"Die Datei {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten} existiert nicht im aktuellen Verzeichnis.") + else: + try: + # Änderungen speichern + wb.save(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) + wb.close() + self.zieldatei = zieldatei + # Button zum Öffnen der neuen Excel-Datei + open_file_button = toga.Button("Neue Excel-Datei öffnen", on_press=lambda btn: open_file(self.zieldatei)) + self.add(open_file_button) + + shutil.move(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten, zieldatei) + print(f"Die Datei {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten} wurde erfolgreich nach {zieldatei} verschoben.") + except Exception as e: + print(f"Fehler beim Verschieben der Datei: {str(e)}") + + except Exception as e: + print(f"Fehler beim Verarbeiten der Datei: {str(e)}") + fehlermeldung_componente = Fehlermeldung(self,fehlermeldung=e) + self.add(fehlermeldung_componente) \ No newline at end of file From 0601da4f2ef72a0d494744facc93e73642c40484 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:10:37 +0100 Subject: [PATCH 22/34] Update app.py --- .../src/{{ cookiecutter.module_name }}/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 12f36a4..12add12 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -16,7 +16,7 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user,update_daten_laden_app,setup_folders,update_daten_laden_user -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Dateiauswahl import Startfenster +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Startfenster import Startfenster from screeninfo import get_monitors From 45003cbeb669d6ab8632657ea1f5b55e04681090 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:37:49 +0100 Subject: [PATCH 23/34] Update Startfenster.py --- .../components/Startfenster.py | 415 +----------------- 1 file changed, 9 insertions(+), 406 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py index 5f08b9e..453759c 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -15,8 +15,6 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import translate_date_to_german from {{ cookiecutter.app_name|lower|replace('-', '_') }}.styling import StandardStyling -import pandas as pd -from openpyxl.worksheet.worksheet import Worksheet from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Fehlermeldung import Fehlermeldung if TYPE_CHECKING: @@ -36,44 +34,6 @@ def write(self, text): self.console_output.value += text sys.__stdout__.write(text) - -def create_test_run_copy(file_path): - # Pfad zum "Testlauf" Ordner in "~/Library/Containers/com.microsoft.Excel/Data" erstellen - test_run_folder = os.path.expanduser("~/Library/Containers/com.microsoft.Excel/Data/Testlauf") - - # Überprüfen, ob der "Testlauf" Ordner bereits existiert, ansonsten erstellen - if not os.path.exists(test_run_folder): - os.makedirs(test_run_folder) - - # Dateiname und Erweiterung der Exceldatei extrahieren - file_name, file_extension = os.path.splitext(os.path.basename(file_path)) - - # Überprüfen, ob bereits Testlauf Dateien existieren - test_run_files = [f for f in os.listdir(test_run_folder) if f.startswith("Testlauf_")] - - if test_run_files: - # Letzte Testlauf Datei finden und Nummer extrahieren - last_test_run_file = max(test_run_files) - last_test_run_number = int(last_test_run_file.split("_")[1].split(".")[0]) - - # Neue Testlauf Nummer generieren - new_test_run_number = last_test_run_number + 1 - else: - # Wenn keine Testlauf Dateien existieren, mit Nummer 001 beginnen - new_test_run_number = 1 - - # Neuen Dateinamen für den Testlauf erstellen - new_test_run_file_name = f"Testlauf_{new_test_run_number:03d}{file_extension}" - - # Pfad zur neuen Testlauf Datei erstellen - new_test_run_file_path = os.path.join(test_run_folder, new_test_run_file_name) - - # Kopie der Exceldatei im "Testlauf" Ordner erstellen - shutil.copy2(file_path, new_test_run_file_path) - - return new_test_run_file_path - - class Startfenster(toga.Box): def __init__( self, @@ -81,7 +41,7 @@ def __init__( id: str | None = None, start_values: dict | None = None ): - style = Pack(direction=COLUMN) + style = Pack(direction=COLUMN) super().__init__(id=id, style=style) self.app = app @@ -110,396 +70,39 @@ def __init__( - self.datei_auswaehlen_button = toga.Button( - "Datei auswählen", - on_press=self.datei_auswaehlen, - style=StandardStyling.standard_button_style(), - ) + self.next_button = toga.Button( - "Datei verarbeiten", + "Nächster Schritt", on_press=self.next_step, style=StandardStyling.standard_button_style(), ) self.gui_output = GUIOutputStream(self.console_output) sys.stdout = self.gui_output + self.add(self.console_output) + + self.add(self.standardeingabe_box) - self.add(self.datei_auswaehlen_button) self.add(self.next_button) #self.next_step(self) - - async def datei_auswaehlen(self, widget): - dateipfad = await self.app.main_window.open_file_dialog( - title="Datei auswählen", - ) - if dateipfad is not None: - self.standardeingabe_box.excelDatei.value = dateipfad def setup_input_fields(self, start_values): self.standardeingabe_box.excelDatei.value = start_values["excelDatei"] def next_step(self, widget=None): - file_path = self.standardeingabe_box.excelDatei.value - print(file_path) - try: - - - - # um iterativ arbeiten zu können aber die Vorlage nicht zu ändern werden wir zunächst eine kopie der Excepliste anfertigen - # Der Name ist "Testlauf_001" wobei die Nummer der Datei inkrementiert wird - # Die Datei werden im Ordner "Testlauf" abgelegt, der neben der Exceldatei im Projektordner platziert ist - # Überprüfe das Verzeichnis auf bereits existierende Testlauf Datein, teile den Namen auf, und erzeuge den neuen Namen - - pfad_zur_temp_datei_in_ordner_mit_zugriffrechten = create_test_run_copy(file_path) - print(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) - - - wb = load_workbook(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) - - print(wb.sheetnames) - print(wb.worksheets[0].title) - print(wb.worksheets[0].cell(row=1, column=1).value) - - # Get the first sheet in the workbook - #print(wb.sheet_names) - - # Die Excel Datei hat mehrere Blätter - # Bsp. 'Statuskürzel', 'freie Termine', 'WV März 2024', 'WV April 2024', - # 'WV Folgemonate', 'NICHT erreichbar', 'KI D etc', 'Mails', 'Termine', 'Auffüller' - - # WV steht für Wiedervorlage - # Diese Blätter haben die folgenden Spalten: - # 'Stadt', 'Associated Company', 'Vorname Ansprechpartner', 'Name Ansprechpartner', 'Position / Titel', - # 'Straße Hausnr.', 'PLZ', 'Ort ', 'Bundesland', 'Webseite', 'E-Mail', 'Ansprechpartner AP', 'E-Mail AP', - # 'Telefon primär', 'Telefon Zentrale', 'Telefon Mobil', 'Bemerkung (Kontakt besteht, Kaltakquise etc.)', - # 'Gesprächsnotiz TM ', 'WV Datum', 'Status' - - # Was mit den Datenreihen passieren soll wird bestimmt durch WV Datum und Status - """ - Status Bedeutung kopieren verschieben ins Tabellenblatt verbleibt in oder wird verschoben - Info Bitte Infos per E-Mail versenden ja, bei WV ja, bei Selbstmelder oder WV Folgemonate Mails (alle); WV, WV Folgemonate Mails (alle); Selbstmelder oder WV - S Selbstmelder nein ja WV Folgemonate nach Zeitspanne X in WV - D Doppelte Adresse nein ja KI KK D etc KI KK D etc - KI Kein Interesse nein ja KI KK D etc KI KK D etc - KK Kein Kontakt möglich, nicht erreichbar nein ja KI KK D etc KI KK D etc - T Termin nein ja Termine Termine - - TS Terminstorno nein ja Termine je nach Fall in Termine oder WV - TV Terminverlegung, in Bearbeitung nein ja WV WV, dann wieder Termine - WA Wiederanruf nein nein WV - WAI Wiederanruf, Infomail verschickt nein nein WV - WV Wiedervorlage Monat x nein ja WV Folgemonate WV Folgemonate - WVI Wiedervorlage Monat x, Infomail verschickt nein ja WV Folgemonate WV Folgemonate - ES Entscheider-Sekretärin - FA falscher Ansprechpartner - KL keine Leitungsposition hinterlegt - R Registrierung für Testphase - """ - - # Finde das Blatt mit dem aktuellen Monat - - aktuelles_monat_blatt:Worksheet = None - Excel_Blatt_WV_Folgemonate:Worksheet = None - Excel_Blatt_Mails:Worksheet = None - Excel_Blatt_KI_D_etc:Worksheet = None - Excel_Blatt_NICHT_erreichbar:Worksheet = None - Excel_Blatt_Termine:Worksheet = None - - aktueller_monat_deutsch = "März" - #print(f"Aktueller Monat: {aktueller_monat_deutsch}") - - for blatt in wb.worksheets: - blatt: Worksheet - - #print(blatt.title) - - if aktueller_monat_deutsch in blatt.title: - aktuelles_monat_blatt = blatt - - if "WV Folgemonate" in blatt.title or "WV Folgemonate" == blatt.title: - Excel_Blatt_WV_Folgemonate = blatt - - if "Mails" in blatt.title or "Mails" == blatt.title: - Excel_Blatt_Mails = blatt - - if "KI D etc" in blatt.title or "KI D etc" == blatt.title: - Excel_Blatt_KI_D_etc = blatt - - if "NICHT erreichbar" in blatt.title or "NICHT erreichbar" == blatt.title: - Excel_Blatt_NICHT_erreichbar = blatt - - if "Termine" in blatt.title or "Termine" == blatt.title: - Excel_Blatt_Termine = blatt - - - print(f"Akuelles Monatsblatt: {aktuelles_monat_blatt.title}") - print(f"Folge Monatsblatt: {Excel_Blatt_WV_Folgemonate.title}") - - - # Finde die Spaltenbezeichner (A1, B1, C1 ...) mit den Namen Gesprächsnotiz TM, WV Datum und Status - spaltenbezeichner_gesprächsnotiz_tm = None - spaltenbezeichner_wv_datum = None - spaltenbezeichner_status = None - - # Entferne Leerzeichen am Ende der Spaltenüberschriften - for spalte in aktuelles_monat_blatt.columns: - #print(f"Spalte: {spalte}") - #print(f"Spalte[0]: {spalte[0]}") - print(f"Spalte[0].value: {spalte[0].value}") - - if spalte[0].value is not None: - spalte[0].value = spalte[0].value.strip() - else: - break - # Finde die Spaltenbezeichner basierend auf den bereinigten Spaltenüberschriften - for spalte in aktuelles_monat_blatt.columns: - if spalte[0].value == "Gesprächsnotiz TM": - spaltenbezeichner_gesprächsnotiz_tm = spalte - elif spalte[0].value == "WV Datum": - spaltenbezeichner_wv_datum = spalte - elif spalte[0].value == "Status": - spaltenbezeichner_status = spalte - break - #print(f"Gesprächsnotiz TM: {spaltenbezeichner_gesprächsnotiz_tm}") - #print(f"WV Datum: {spaltenbezeichner_wv_datum}") - #print(f"Status: {spaltenbezeichner_status}") - - - #print(f"Gesprächsnotiz Tm: {spaltenbezeichner_gesprächsnotiz_tm}") - #print(f"WV Datum: {spaltenbezeichner_wv_datum}") - #print(f"Status: {spaltenbezeichner_status}") - - # Da noch unklar ist in welcher spalte alle zeilen einen Wert haben suchen wir den höchsten. - # Dadurch wir die letzte Zeile - # Der Weg ist, dass wir in spalte 1 anfangen bis zur letzten gefüllten Zeile zu springen - # Die Anzahl der reihen wird gespeichert - # danach wird die selbe Anzahl von Reihen bei allen Spalten die bis Status vorhanden sind durchgeführt - # Hierbei gehen wir wieder von links nach rechts und speichern den höchsten Wert - # Wichtig ist aber das wir sollten in den späteren Spalten nicht mehr Zeilen haben als in der ersten - # Werden dennoch weitere Reihen geprüft. Ist in allen Spalten in der gefundenen Reihe kein Wert ist anzunehmen, dass es die letzte Reihe war - # So wird verhindert, dass ein einzelner fehlender Wert die überprüfung stoppt - # Es wird also geprüft ob in allen Spalten einer Reihe None ist und wenn ja war die vorherige die letzte - - - - """ - max_rows_aktueller_monat_blatt = finde_letzte_zeile_in_excel_blatt(aktuelles_monat_blatt) - - # Jetzt da wir wissen wo die Tabelle liegt ist es Zeit die Daten zu laden und dann im Speicher zu verarbeiten. - # Lade die Daten aus dem aktuellen Monatsblatt in ein DataFrame - Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat = aktuelles_monat_blatt.range((1, 1), - (max_rows_aktueller_monat_blatt, spaltenbezeichner_status.column)).options(pd.DataFrame).value - Dataframe_aller_Daten_vom_aktuellen_Monat = pd.DataFrame(Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat[1:], - columns=Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat.columns) - - """ - Dataframe_aller_Daten_vom_aktuellen_Monat = lade_daten_aus_excel_blatt_als_DataFrame(aktuelles_monat_blatt) - - Dataframe_aller_Daten_vom_aktuellen_Monat.reset_index(drop=False, inplace=True) - - #print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - # -------------------------------- Datenbereinigung --------------------------------# - - def korrigiere_tippfehler(text): - text = str(text) # Konvertiere den Wert in eine Zeichenkette - text = text.replace('Selbmelder', 'Selbstmelder') - # Füge hier weitere häufige Tippfehler und ihre Korrekturen hinzu - return text - - # Korrigiere Tippfehler in der Spalte 'Status' - Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].apply(korrigiere_tippfehler) - - # Ersetze fehlende Werte in der Spalte 'Status' durch einen leeren String - Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].fillna('') - - # Ersetze fehlende Werte in der Spalte 'Gesprächsnotiz TM' durch einen leeren String - Dataframe_aller_Daten_vom_aktuellen_Monat['Gesprächsnotiz TM'] = Dataframe_aller_Daten_vom_aktuellen_Monat['Gesprächsnotiz TM'].fillna('') - - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - # ----------------------------------------------------------------------------------- # - - # Erfasse Datensätze mit Bezug zu Infomail und Selbstmelder - status_info = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'Info'] - - # Übertrage die Daten aus dem DataFrame in das Excel Blatt - uebertrage_daten_in_excel_blatt(Excel_Blatt_Mails, status_info) - - # Passe den Status der übertragenen Positionen mit dem Wert 'Info' auf 'WAI' an - status_info.loc[status_info['Status'] == 'Info', 'Status'] = 'WAI' - - # Aktualisiere die Werte in Dataframe_aller_Daten_vom_aktuellen_Monat basierend auf den Indexwerten von status_info - for index, row in status_info.iterrows(): - Dataframe_aller_Daten_vom_aktuellen_Monat.loc[index, 'Status'] = row['Status'] - - #print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - # ----------------------------------------------------------------------------------- # - # Erfasse Datensätze mit Bezug zu Infomail und Selbstmelder. Ein Beispielstatus wäre hier Info / Selbstmelder - status_s = Dataframe_aller_Daten_vom_aktuellen_Monat[ - Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains('Info') & - Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains('Selbstmelder') - ] - - print(status_s) - - # Übertrage die Daten aus dem DataFrame in das Excel Blatt - print("Übertrage Daten in Excel Blatt Mails") - uebertrage_daten_in_excel_blatt(Excel_Blatt_Mails, status_s) - print("Übertrage Daten in Excel Blatt WV Folgemonate") - uebertrage_daten_in_excel_blatt(Excel_Blatt_WV_Folgemonate, status_s) - - - - # Entferne die Zeilen aus dem Ursprungsdataframe - print("Entferne Zeilen aus Dataframe_aller_Daten_vom_aktuellen_Monat") - Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_s.index, inplace=True) - - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - # ----------------------------------------------------------------------------------- # - status_values = ['D', 'KI', 'KK'] - status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].isin(status_values)] - uebertrage_daten_in_excel_blatt(Excel_Blatt_KI_D_etc, status_result) - print("Nach Übertragung und vor Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) - - print("Nach Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - # ----------------------------------------------------------------------------------- # - - status_values = ['nicht erreichbar'] - status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].isin(status_values)] - uebertrage_daten_in_excel_blatt(Excel_Blatt_NICHT_erreichbar, status_result) - print("Nach Übertragung und vor Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) - - print("Nach Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - # ----------------------------------------------------------------------------------- # - - - status_result = Dataframe_aller_Daten_vom_aktuellen_Monat[ - Dataframe_aller_Daten_vom_aktuellen_Monat['Status'].str.contains(r'^(?:T|Telefontermin) am \d{2}\.\d{2}\.\d{4}(?: um \d{2}:\d{2})?', regex=True) - ] - uebertrage_daten_in_excel_blatt(Excel_Blatt_Termine, status_result) - print("Nach Übertragung und vor Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - - Dataframe_aller_Daten_vom_aktuellen_Monat.drop(status_result.index, inplace=True) - - print("Nach Entfernen:") - print(Dataframe_aller_Daten_vom_aktuellen_Monat) - # ----------------------------------------------------------------------------------- # - - Dataframe_aller_Daten_vom_aktuellen_Monat['WV Datum'] = pd.to_datetime(Dataframe_aller_Daten_vom_aktuellen_Monat['WV Datum']) - Dataframe_aller_Daten_vom_aktuellen_Monat.sort_values(by='WV Datum', ascending=True, na_position='first', inplace=True) - - print(Dataframe_aller_Daten_vom_aktuellen_Monat) + try: - - - - - - status_wa = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WA'] - status_ts = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'TS'] - status_tv = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'TV'] - status_wai = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WAI'] - status_wv = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WV'] - status_wvi = Dataframe_aller_Daten_vom_aktuellen_Monat[Dataframe_aller_Daten_vom_aktuellen_Monat['Status'] == 'WVI'] - - - - loesche_alle_daten_von_blatt(aktuelles_monat_blatt) - - - - # füge die nun bereinigte und geordnete Liste wieder ein - # Füge die bereinigten und geordneten DataFrames wieder in das Excel-Blatt ein - uebertrage_daten_in_excel_blatt(aktuelles_monat_blatt, Dataframe_aller_Daten_vom_aktuellen_Monat) - - - - - # Die Blätter mit der Bezeichnung WV beinhalten die Daten der Telefonisten - # Die Datensätze werden wie folgt verteilt: - # Die Datensätze von WV März usw. werden überprüft. - - # Get the first 10 rows and columns of the sheet - #Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat = sheet.range("A1:T1").value - - - #print(Tabelle_mit_allen_Daten_von_Excel_Blatt_Aktueller_Monat) - - - # Pfad der Quelldatei - quelldatei = self.standardeingabe_box.excelDatei.value - - print(f"Die Quelldatei ist {quelldatei}") - - # Überprüfe, ob die Quelldatei existiert - if not os.path.exists(quelldatei): - print(f"Die Quelldatei {quelldatei} existiert nicht.") - else: - # Pfad und Name der Zieldatei - zielordner = os.path.dirname(quelldatei) - print(f"Der Zielordner ist {zielordner}") - print(f"Der Dateiname ist {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten}") - - # Trenne Dateiname und Erweiterung - dateiname, erweiterung = os.path.splitext(os.path.basename(quelldatei)) - - # Füge "_ueberarbeitet" zum Dateinamen hinzu - neuer_dateiname = f"{dateiname}_ueberarbeitet{erweiterung}" - - zieldatei = os.path.join(zielordner, neuer_dateiname) - - # Überprüfe, ob die Zieldatei bereits existiert - if os.path.exists(zieldatei): - # Wenn die Zieldatei bereits existiert, füge eine fortlaufende Nummer hinzu - i = 1 - while True: - neuer_dateiname = f"{dateiname}_ueberarbeitet_{i}{erweiterung}" - zieldatei = os.path.join(zielordner, neuer_dateiname) - if not os.path.exists(zieldatei): - break - i += 1 - - print(f"Die Zieldatei ist {zieldatei}") + print("STUFF") - # Überprüfe, ob die Datei pfad_zur_temp_datei_in_ordner_mit_zugriffrechten im aktuellen Verzeichnis existiert - if not os.path.exists(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten): - print(f"Die Datei {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten} existiert nicht im aktuellen Verzeichnis.") - else: - try: - # Änderungen speichern - wb.save(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten) - wb.close() - self.zieldatei = zieldatei - # Button zum Öffnen der neuen Excel-Datei - open_file_button = toga.Button("Neue Excel-Datei öffnen", on_press=lambda btn: open_file(self.zieldatei)) - self.add(open_file_button) - - shutil.move(pfad_zur_temp_datei_in_ordner_mit_zugriffrechten, zieldatei) - print(f"Die Datei {pfad_zur_temp_datei_in_ordner_mit_zugriffrechten} wurde erfolgreich nach {zieldatei} verschoben.") - except Exception as e: - print(f"Fehler beim Verschieben der Datei: {str(e)}") except Exception as e: - print(f"Fehler beim Verarbeiten der Datei: {str(e)}") + print(f"Fehler: {str(e)}") fehlermeldung_componente = Fehlermeldung(self,fehlermeldung=e) self.add(fehlermeldung_componente) \ No newline at end of file From 3127e63adc78e3fc39dcd1b05c3bc20b7fbfaa66 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 25 Mar 2024 19:35:21 +0100 Subject: [PATCH 24/34] Update Startfenster.py --- .../components/Startfenster.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py index 453759c..2f98ffe 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -101,8 +101,29 @@ def next_step(self, widget=None): print("STUFF") - + # Erzeuge einen Fake Fehler um das Fehlerfenster zu testen + # raise ValueError("Dies ist eine Fake-Fehlermeldung.") + + except Exception as e: - print(f"Fehler: {str(e)}") - fehlermeldung_componente = Fehlermeldung(self,fehlermeldung=e) - self.add(fehlermeldung_componente) \ No newline at end of file + print(f"Fehler beim Verarbeiten der Datei: {str(e)}") + fehlermeldung = Fehlermeldung( + self.app, + retry_function=lambda: self.next_step(), + fehlermeldung=e, + ) + self.fehlermeldung_widget = fehlermeldung + self.standardeingabe_box.add(self.fehlermeldung_widget) + finally: + # Lösche die Datei mit den Daten, die in die temporäre Datei geschrieben wurden + dateipfade_der_zu_loeschenden_datei = self.pfad_zur_temp_datei_in_ordner_mit_zugriffrechten + + try: + os.remove(dateipfade_der_zu_loeschenden_datei) + print(f"Die temporäre Datei '{dateipfade_der_zu_loeschenden_datei}' wurde erfolgreich gelöscht.") + except FileNotFoundError: + print(f"Die temporäre Datei '{dateipfade_der_zu_loeschenden_datei}' konnte nicht gefunden werden.") + except PermissionError: + print(f"Keine ausreichenden Berechtigungen zum Löschen der temporären Datei '{dateipfade_der_zu_loeschenden_datei}'.") + except Exception as e: + print(f"Ein unerwarteter Fehler ist beim Löschen der temporären Datei aufgetreten: {str(e)}") \ No newline at end of file From 84e609a20c1419b262216c9b97562aec829d4189 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Mon, 25 Mar 2024 20:54:46 +0100 Subject: [PATCH 25/34] Create EinklappbarerContainer.py --- .../components/EinklappbarerContainer.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/EinklappbarerContainer.py diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/EinklappbarerContainer.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/EinklappbarerContainer.py new file mode 100644 index 0000000..d8f9bbe --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/EinklappbarerContainer.py @@ -0,0 +1,41 @@ +import toga +from toga.style import Pack +from toga.style.pack import COLUMN + +class EinklappbarerContainer(toga.Box): + def __init__(self, titel, eingeklappt_zu_beginn=False, *args, **kwargs): + super().__init__(*args, **kwargs) + self.titel = titel + self.eingeklappt = eingeklappt_zu_beginn + + self.titel_button = toga.Button( + f'{self.titel} {"+" if eingeklappt_zu_beginn else "-"}', + on_press=self.toggle_ein_ausklappen, + style=Pack(padding=5) + ) + + self.inhalt_container = toga.Box(style=Pack(direction=COLUMN, flex=1)) + if eingeklappt_zu_beginn: + self.inhalt_container.style.height = 0 + self.add(self.titel_button) + self.add(self.inhalt_container) + + def toggle_ein_ausklappen(self, widget:toga.Button): + self.eingeklappt = not self.eingeklappt + + # Ändere die Sichtbarkeit des inhalt_containers + if self.eingeklappt: + # Verwende die 'del' Anweisung, um die Höhe zu entfernen, was den Container ausblendet + self.inhalt_container.style.height = 0 # Setze die Höhe auf 0, um den Container auszublenden + widget.text = f'{self.titel} +' + else: + # Entferne die Höheneinstellung, um den Container wieder einzublenden + del self.inhalt_container.style.height + widget.text = f'{self.titel} -' + + self.inhalt_container.refresh() + + + def add_inhalt(self, widget): + # Füge Widgets zum inhalt_container hinzu + self.inhalt_container.add(widget) From f0fba1c407a2c50c03caa08ad3db13a4db718b55 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:02:12 +0100 Subject: [PATCH 26/34] Update UpdateWindow.py --- .../{{ cookiecutter.module_name }}/components/UpdateWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py index 33790ce..46390c9 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py @@ -41,7 +41,7 @@ def build(self): def update_keys_berechnen(self): updates = update_daten_laden_user()['Updates'] self.update_keys = list(updates.keys()) # Extrahiere alle Schlüssel - self.current_update_index = len(self.update_keys) - 2 + self.current_update_index = len(self.update_keys) - 1 def daten_in_felder_übertragen(self): if self.update_keys: From 059e9ed815371e7d25652254f86f8a9b4b4e45ed Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:46:23 +0100 Subject: [PATCH 27/34] Settings --- .../CONSTANTS.py | 3 +- .../src/{{ cookiecutter.module_name }}/app.py | 14 +++++ .../components/SettingsWindow.py | 62 +++++++++++++++++++ .../services.py | 33 ++++++++-- .../{{ cookiecutter.module_name }}/utils.py | 9 ++- 5 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py index b28ef54..6b3b490 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/CONSTANTS.py @@ -2,4 +2,5 @@ UPDATE_ORDNER_NAME_USER = "Updates" BASE_DIR = "{{ cookiecutter.app_name|lower|replace('-', '_') }}" ARCHIV_ORDNER = "Archive" -KUNDENDATEN_ORDNER_APP = "kundendaten" \ No newline at end of file +KUNDENDATEN_ORDNER_APP = "kundendaten" +SETTINGS_ORDNER = "Settings" \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py index 12add12..b387d36 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/app.py @@ -17,12 +17,19 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import get_base_dir, open_file, get_help_file_path,update_in_updates_ordner_uebertragen, get_updates_datei_user,update_daten_laden_app,setup_folders,update_daten_laden_user from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Startfenster import Startfenster +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.SettingsWindow import SettingsWindow from screeninfo import get_monitors class {{ cookiecutter.app_name|lower|replace('-', '_') }}(toga.App): # Define the action handlers for the commands + def show_settings(self, widget): + # Instantiate the LicenseWindow + license_window = SettingsWindow(title="Einstellungen") + + # Show the license window + license_window.show() def show_license(self, widget): # Instantiate the LicenseWindow license_window = LicenseWindow(title="Lizenzinformationen") @@ -146,6 +153,12 @@ def startup(self): tooltip="Öffnet den Programmordner", group=settings_group, ) + settings_command = Command( + action=self.show_settings, + text="Einstellungen", + tooltip="Zeigt die Einstellungen an", + group=settings_group, + ) # Add commands to the app self.commands.add( @@ -153,6 +166,7 @@ def startup(self): open_help_file_command, basis_ordner_öffnen_command, update_command, + settings_command, ) # Get the size of the primary monitor diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py new file mode 100644 index 0000000..3827dd0 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py @@ -0,0 +1,62 @@ +import os +import json +import toga +from toga.style import Pack +from toga.style.pack import COLUMN + +from nadoo_travel.src.nadoo_travel.services import set_settings, get_settings,set_user_code, set_api_key + +class SettingsWindow(toga.Window): + def __init__(self, title, width=400, height=400): + super().__init__(title, size=(width, height)) + self.content = self.build() + self.load_settings() + + def build(self): + # Hauptcontainer für den Inhalt erstellen + box = toga.Box(style=Pack(direction=COLUMN, padding=10, flex=1)) + + # Eingabefeld für den User Code erstellen + self.user_code_input = toga.TextInput( + placeholder="User Code", + style=Pack(padding_bottom=10) + ) + box.add(self.user_code_input) + + # Eingabefeld für den NADOO API Schlüssel erstellen + self.api_key_input = toga.TextInput( + placeholder="NADOO API Schlüssel", + style=Pack(padding_bottom=10) + ) + box.add(self.api_key_input) + + # Speichern-Button hinzufügen + save_button = toga.Button( + 'Speichern', + on_press=self.save_settings, + style=Pack(padding_top=10) + ) + box.add(save_button) + + # Schließen-Button hinzufügen + close_button = toga.Button( + 'Schließen', + on_press=self.close_window, + style=Pack(padding_top=10) + ) + box.add(close_button) + + return box + + def close_window(self, widget): + self.close() + + + def save_settings(self, widget): + set_user_code(self.user_code_input.value) + set_api_key(self.api_key_input.value) + + def load_settings(self): + settings = get_settings() + self.user_code_input.value = settings.get("user_code", "") + self.api_key_input.value = settings.get("api_key", "") \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py index 0219b7a..de4d316 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py @@ -6,10 +6,10 @@ from datetime import datetime -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import KUNDENDATEN_ORDNER_APP, ARCHIV_ORDNER, BASE_DIR, UPDATE_ORDNER_NAME_USER, UPDATE_ORDNER_NAME_APP +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import KUNDENDATEN_ORDNER_APP, ARCHIV_ORDNER, BASE_DIR, UPDATE_ORDNER_NAME_USER, UPDATE_ORDNER_NAME_APP, SETTINGS_ORDNER from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import ( - get_base_dir_path, + get_base_dir_path, get_settings_file_path ) """ @@ -30,12 +30,10 @@ def get_xyz_data(): def setup_folders(): # Grund Verzeichnisstruktur anlegen ensure_base_folder_exits() - versionsordner_erstellen() + ensure_folder_exists(UPDATE_ORDNER_NAME_USER) + ensure_folder_exists(SETTINGS_ORDNER) pass -def versionsordner_erstellen(): - folder_name = UPDATE_ORDNER_NAME_USER - ensure_folder_exists(folder_name) def ensure_folder_exists(folder_name): base_dir = get_base_dir_path() @@ -98,6 +96,29 @@ def get_help_file_path(app): def get_base_dir(): return get_base_dir_path() + +def get_settings(): + settings_file = get_settings_file_path() + if os.path.exists(settings_file): + with open(settings_file, "r") as file: + return json.load(file) + else: + return {} +def set_settings(settings): + settings_file = get_settings_file_path() + with open(settings_file, "w") as file: + json.dump(settings, file, indent=4) + +def set_user_code(user_code): + settings = get_settings() + settings["user_code"] = user_code + set_settings(settings) + +def set_api_key(api_key): + settings = get_settings() + settings["api_key"] = api_key + set_settings(settings) + """ #TODO #96 Veralgemeinern def update_daten_in_basis_ordner_uebertragen(app): diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py index 107ee28..eb9380d 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py @@ -2,7 +2,7 @@ import json import subprocess import platform -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import BASE_DIR +from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import BASE_DIR, SETTINGS_ORDNER def ensure_folder_exists(folder_name): base_dir = get_base_dir_path() folder_path = os.path.join(base_dir, folder_name) @@ -186,4 +186,9 @@ def is_libreoffice_installed(): ) return True except (subprocess.CalledProcessError, FileNotFoundError): - return False \ No newline at end of file + return False + +def get_settings_file_path(): + settings_folder = ensure_folder_exists(SETTINGS_ORDNER) + settings_file = os.path.join(settings_folder, "settings.json") + return settings_file From f8db8b6ae52ddb45a5f437bdf681bd73c7bc6cd8 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:41:04 +0200 Subject: [PATCH 28/34] fix + httpx for API --- {{ cookiecutter.app_name }}/pyproject.toml | 1 + .../{{ cookiecutter.module_name }}/components/Startfenster.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/{{ cookiecutter.app_name }}/pyproject.toml b/{{ cookiecutter.app_name }}/pyproject.toml index 65a91de..4f307e9 100644 --- a/{{ cookiecutter.app_name }}/pyproject.toml +++ b/{{ cookiecutter.app_name }}/pyproject.toml @@ -27,6 +27,7 @@ requires = [ "toml", "screeninfo", "toga", + "httpx", ] {{- cookiecutter.pyproject_table_briefcase_app_extra_content }} diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py index 2f98ffe..371326e 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -41,7 +41,7 @@ def __init__( id: str | None = None, start_values: dict | None = None ): - style = Pack(direction=COLUMN) + style = Pack(direction=COLUMN) super().__init__(id=id, style=style) self.app = app From 4d92dd00481211e94bdffc6f53f0d62fb30a84c9 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:43:17 +0200 Subject: [PATCH 29/34] Update Startfenster.py --- .../components/Startfenster.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py index 371326e..ca215b9 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -3,16 +3,13 @@ import re from time import sleep from typing import TYPE_CHECKING, List -from openpyxl import load_workbook -import openpyxl + import toga from toga.style import Pack from toga.style.pack import COLUMN from toga.widgets.multilinetextinput import MultilineTextInput -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.services import finde_letzte_zeile_in_excel_blatt, lade_daten_aus_excel_blatt_als_DataFrame, loesche_alle_daten_von_blatt, open_file, uebertrage_daten_in_excel_blatt -from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import translate_date_to_german from {{ cookiecutter.app_name|lower|replace('-', '_') }}.styling import StandardStyling from {{ cookiecutter.app_name|lower|replace('-', '_') }}.components.Fehlermeldung import Fehlermeldung From 1021a99d2a5cb72fbfd53e4a0e885445268dc2ee Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:59:37 +0200 Subject: [PATCH 30/34] Update Startfenster.py --- .../components/Startfenster.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py index ca215b9..b3ac157 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/Startfenster.py @@ -103,7 +103,7 @@ def next_step(self, widget=None): except Exception as e: - print(f"Fehler beim Verarbeiten der Datei: {str(e)}") + print(f"Fehler: {str(e)}") fehlermeldung = Fehlermeldung( self.app, retry_function=lambda: self.next_step(), @@ -112,15 +112,10 @@ def next_step(self, widget=None): self.fehlermeldung_widget = fehlermeldung self.standardeingabe_box.add(self.fehlermeldung_widget) finally: - # Lösche die Datei mit den Daten, die in die temporäre Datei geschrieben wurden - dateipfade_der_zu_loeschenden_datei = self.pfad_zur_temp_datei_in_ordner_mit_zugriffrechten + pass + try: - os.remove(dateipfade_der_zu_loeschenden_datei) - print(f"Die temporäre Datei '{dateipfade_der_zu_loeschenden_datei}' wurde erfolgreich gelöscht.") - except FileNotFoundError: - print(f"Die temporäre Datei '{dateipfade_der_zu_loeschenden_datei}' konnte nicht gefunden werden.") - except PermissionError: - print(f"Keine ausreichenden Berechtigungen zum Löschen der temporären Datei '{dateipfade_der_zu_loeschenden_datei}'.") + print("Aufräumen was bei einem Fehler mit dem Programm über ist") except Exception as e: - print(f"Ein unerwarteter Fehler ist beim Löschen der temporären Datei aufgetreten: {str(e)}") \ No newline at end of file + print(f"EFehler der dabei möglicherweise aufgetreten ist: {str(e)}") \ No newline at end of file From db512ea3205548822bdfe7cbfd2cec70b6ae6fce Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:39:03 +0200 Subject: [PATCH 31/34] Update services.py --- .../src/{{ cookiecutter.module_name }}/services.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py index de4d316..cb59484 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py @@ -119,6 +119,14 @@ def set_api_key(api_key): settings["api_key"] = api_key set_settings(settings) +def audio_zu_text_konvertieren(audio_dateipfad): + # wenn verwendet wir muss openai-whisper installiert sein + import whisper + + model = whisper.load_model("base") + result = model.transcribe(audio_dateipfad) + return result["text"] + """ #TODO #96 Veralgemeinern def update_daten_in_basis_ordner_uebertragen(app): From 509bb649f0ae06a0bc1ceaabaa2a1fc50789c8e9 Mon Sep 17 00:00:00 2001 From: Christoph Backhaus <106314951+NADOOITChristophBa@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:48:15 +0200 Subject: [PATCH 32/34] Update UpdateWindow.py --- .../{{ cookiecutter.module_name }}/components/UpdateWindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py index 46390c9..fee5bb4 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/UpdateWindow.py @@ -11,6 +11,7 @@ from {{ cookiecutter.app_name|lower|replace('-', '_') }}.utils import ensure_folder_exists from {{ cookiecutter.app_name|lower|replace('-', '_') }}.styling import StandardStyling + class UpdateWindow(toga.Window): def __init__(self, title, size=(400, 400)): super().__init__(title, size=size) From 4a0d2eab0eb401306b9ec7f3ac558bcfa2206479 Mon Sep 17 00:00:00 2001 From: NADOOITLaurinK <156072167+NADOOITLaurinK@users.noreply.github.com> Date: Sat, 6 Apr 2024 11:39:35 +0200 Subject: [PATCH 33/34] added. services and utils --- .../services.py | 794 +++++++++++++++++- .../{{ cookiecutter.module_name }}/utils.py | 469 ++++++++++- 2 files changed, 1256 insertions(+), 7 deletions(-) diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py index cb59484..7d035ad 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py @@ -33,7 +33,6 @@ def setup_folders(): ensure_folder_exists(UPDATE_ORDNER_NAME_USER) ensure_folder_exists(SETTINGS_ORDNER) pass - def ensure_folder_exists(folder_name): base_dir = get_base_dir_path() @@ -96,7 +95,6 @@ def get_help_file_path(app): def get_base_dir(): return get_base_dir_path() - def get_settings(): settings_file = get_settings_file_path() if os.path.exists(settings_file): @@ -109,6 +107,11 @@ def set_settings(settings): with open(settings_file, "w") as file: json.dump(settings, file, indent=4) +def get_settings_file_path(): + settings_folder = ensure_folder_exists(SETTINGS_ORDNER) + settings_file = os.path.join(settings_folder, "settings.json") + return settings_file + def set_user_code(user_code): settings = get_settings() settings["user_code"] = user_code @@ -127,6 +130,63 @@ def audio_zu_text_konvertieren(audio_dateipfad): result = model.transcribe(audio_dateipfad) return result["text"] +def load_settings(): + settings_file = get_settings_file_path() + if os.path.exists(settings_file): + with open(settings_file, "r") as file: + return json.load(file) + else: + return {} + + +def measure_execution_time(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + execution_time = end_time - start_time + #print(f"Die Funktion '{func.__name__}' hat {execution_time:.2f} Sekunden gedauert.") + return result + return wrapper + +def ermittel_den_aktuellen_monat_als_deutsches_wort(monat_offset=0): + """ + Ermittelt den Monat relativ zum aktuellen Monat als Zahl und gibt den entsprechenden deutschen Monatsnamen zurück. + + Args: + monat_offset (int, optional): Die Anzahl der Monate, die zum aktuellen Monat addiert werden sollen. Standard ist 0. + + Returns: + str: Der ermittelte Monat auf Deutsch (z.B. "Januar", "Februar", ...). + """ + # Erstelle ein Dictionary, das die Monatszahlen auf die deutschen Monatsnamen abbildet + monatsnamen = { + 1: "Januar", + 2: "Februar", + 3: "März", + 4: "April", + 5: "Mai", + 6: "Juni", + 7: "Juli", + 8: "August", + 9: "September", + 10: "Oktober", + 11: "November", + 12: "Dezember" + } + + # Ermittle den aktuellen Monat als Zahl + aktueller_monat_als_zahl = datetime.now().month + + # Addiere den Offset zum aktuellen Monat + ziel_monat = (aktueller_monat_als_zahl + monat_offset) % 12 + if ziel_monat == 0: + ziel_monat = 12 + + # Gib den entsprechenden deutschen Monatsnamen zurück + return monatsnamen[ziel_monat] + """ #TODO #96 Veralgemeinern def update_daten_in_basis_ordner_uebertragen(app): @@ -202,4 +262,732 @@ def vorlagen_in_vorlagen_ordner_uebertragen(app): # Umbenennen der "update_"-Datei im Anwendungsordner, indem das "update_" Präfix entfernt wird neuer_app_pfad_ohne_update = os.path.join(vorlagen_ordner_app, neuer_name_ohne_update) os.rename(vorlage_app_pfad, neuer_app_pfad_ohne_update) -""" \ No newline at end of file +""" + + + + +#LaunchPad + +def ermittel_den_aktuellen_monat_als_deutsches_wort(monat_offset=0): + """ + Ermittelt den Monat relativ zum aktuellen Monat als Zahl und gibt den entsprechenden deutschen Monatsnamen zurück. + + Args: + monat_offset (int, optional): Die Anzahl der Monate, die zum aktuellen Monat addiert werden sollen. Standard ist 0. + + Returns: + str: Der ermittelte Monat auf Deutsch (z.B. "Januar", "Februar", ...). + """ + # Erstelle ein Dictionary, das die Monatszahlen auf die deutschen Monatsnamen abbildet + monatsnamen = { + 1: "Januar", + 2: "Februar", + 3: "März", + 4: "April", + 5: "Mai", + 6: "Juni", + 7: "Juli", + 8: "August", + 9: "September", + 10: "Oktober", + 11: "November", + 12: "Dezember" + } + + # Ermittle den aktuellen Monat als Zahl + aktueller_monat_als_zahl = datetime.now().month + + # Addiere den Offset zum aktuellen Monat + ziel_monat = (aktueller_monat_als_zahl + monat_offset) % 12 + if ziel_monat == 0: + ziel_monat = 12 + + # Gib den entsprechenden deutschen Monatsnamen zurück + return monatsnamen[ziel_monat] + +#LAW + +import json +import requests +from requests.auth import HTTPBasicAuth +import logging +import os +import uuid +import shutil +from nadoo_law.CONSTANTS import TEMP_FOLDER, BASE_DIR,MANDATEN_ORDNER_NAME,KUNDENDATEN_ORDNER_APP, UPDATE_ORDNER_NAME_APP, UPDATE_ORDNER_NAME_USER,VORLAGEN_ORDNER_APP, ARCHIV_ORDNER +from datetime import datetime +import subprocess +import platform + +from nadoo_law.utils import ( + ensure_beweismittel_OWi_data_file_exists, + ensure_beweismittel_Scheidung_data_file_exists, + ensure_data_file_exists, + ensure_lawyer_data_file_exists, + get_base_dir_path, + get_ausfuehrungen_file_path, + get_beweismittel_OWi_data_file_path, + get_beweismittel_Scheidung_data_file_path, + get_lawyer_data_file_path, + ensure_folder_exists, + get_login_information, + set_login_information, +) + + + +def set_beweismittel_data_Scheidung(beweismittel_data): + ensure_beweismittel_Scheidung_data_file_exists() + file_path = get_beweismittel_Scheidung_data_file_path() + with open(file_path, "w") as f: + json.dump(beweismittel_data, f, indent=4) + + +def get_beweismittel_data_scheidung(): + ensure_beweismittel_Scheidung_data_file_exists() + file_path = get_beweismittel_Scheidung_data_file_path() + with open(file_path, "r") as f: + return json.load(f) + +def set_beweismittel_data_OWi(beweismittel_data): + ensure_beweismittel_OWi_data_file_exists() + file_path = get_beweismittel_OWi_data_file_path() + with open(file_path, "w") as f: + json.dump(beweismittel_data, f, indent=4) + + +def get_beweismittel_data_OWi(): + ensure_beweismittel_OWi_data_file_exists() + file_path = get_beweismittel_OWi_data_file_path() + with open(file_path, "r") as f: + return json.load(f) + + + +def set_lawyer_data(lawyer_data): + ensure_lawyer_data_file_exists() + file_path = get_lawyer_data_file_path() + with open(file_path, "w") as f: + json.dump(lawyer_data, f, indent=4) + + +def get_lawyer_data(): + ensure_lawyer_data_file_exists() + file_path = get_lawyer_data_file_path() + with open(file_path, "r") as f: + return json.load(f) + + +def setup_folders(): + ensure_folder_exists(get_template_folder()) + ensure_folder_exists(get_temp_folder()) + ensure_folder_exists(get_mandanten_folder()) + versionsordner_erstellen() + + +def vorlagen_in_vorlagen_ordner_uebertragen(app): + vorlagen_ordner_app = os.path.join(app.paths.app, "resources", KUNDENDATEN_ORDNER_APP, VORLAGEN_ORDNER_APP) + vorlagen_ordner_user = get_template_folder() # Annahme, dass diese Funktion das Benutzerverzeichnis für Vorlagen zurückgibt + archiv_ordner = os.path.join(vorlagen_ordner_user, ARCHIV_ORDNER) + placeholder_file_name = "placeholder.txt" + + # Stelle sicher, dass der Archivordner existiert + if not os.path.exists(archiv_ordner): + os.makedirs(archiv_ordner) + + # Überprüfe, ob der Vorlagenordner existiert + if not os.path.isdir(vorlagen_ordner_app): + return # Beende die Funktion, wenn der Ordner nicht existiert + + for file in os.listdir(vorlagen_ordner_app): + if file == placeholder_file_name: + continue # Ignoriere die Platzhalterdatei + + vorlage_app_pfad = os.path.join(vorlagen_ordner_app, file) + neuer_name_ohne_update = file.replace("update_", "") + vorlage_user_pfad = os.path.join(vorlagen_ordner_user, neuer_name_ohne_update) + + # Wenn es sich um eine Update-Datei handelt ODER keine Datei im Benutzerverzeichnis existiert, führe den Kopiervorgang durch + if file.startswith("update_") or not os.path.exists(vorlage_user_pfad): + if os.path.exists(vorlage_user_pfad): + # Archiviere die existierende Datei, bevor sie durch die Update-Datei ersetzt wird + basisname, erweiterung = os.path.splitext(neuer_name_ohne_update) + datumsanhang = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + archivierte_datei_name = f"{basisname}_{datumsanhang}{erweiterung}" + shutil.move(vorlage_user_pfad, os.path.join(archiv_ordner, archivierte_datei_name)) + + # Kopiere die Vorlage oder aktualisierte Vorlage in das Benutzerverzeichnis + shutil.copy(vorlage_app_pfad, vorlage_user_pfad) + + if file.startswith("update_"): + # Umbenennen der "update_"-Datei im Anwendungsordner, indem das "update_" Präfix entfernt wird + neuer_app_pfad_ohne_update = os.path.join(vorlagen_ordner_app, neuer_name_ohne_update) + os.rename(vorlage_app_pfad, neuer_app_pfad_ohne_update) + +def anwalt_liste_in_basis_ordner_uebertragen(app): + kundendaten_ordner_app = os.path.join(app.paths.app, "resources", KUNDENDATEN_ORDNER_APP) + basis_ordner_user = get_base_dir() # Angenommen, diese Funktion gibt den Basisordner des Benutzers zurück + archiv_ordner = os.path.join(basis_ordner_user,ARCHIV_ORDNER, "archivierte_anwalt_daten") + name_der_anwalt_daten_json_datei = "lawyer_details.json" + update_datei_name = "update_lawyer_details.json" + + ziel_datei_pfad = os.path.join(basis_ordner_user, name_der_anwalt_daten_json_datei) + update_datei_pfad = os.path.join(kundendaten_ordner_app, update_datei_name) + + # Stelle sicher, dass der Archivordner existiert + if not os.path.exists(archiv_ordner): + os.makedirs(archiv_ordner) + + # Überprüfe, ob die Datei lawyer_details.json bereits im Zielordner existiert + if os.path.isfile(ziel_datei_pfad): + # Wenn ja, und update_lawyer_details.json ist vorhanden, archiviere die alte Datei + if os.path.isfile(update_datei_pfad): + # Generiere einen eindeutigen Namen für die archivierte Datei + datumsanhang = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + archivierte_datei_name = f"lawyer_details_{datumsanhang}.json" + archivierte_datei_pfad = os.path.join(archiv_ordner, archivierte_datei_name) + + # Verschiebe die alte lawyer_details.json in den Archivordner + shutil.move(ziel_datei_pfad, archivierte_datei_pfad) + + # Kopiere die update_lawyer_details.json in den Zielordner und benenne sie um + shutil.copy(update_datei_pfad, ziel_datei_pfad) + + else: + # Wenn keine lawyer_details.json im Zielordner, kopiere diese aus dem Kundendatenordner, falls vorhanden + source_datei_pfad = os.path.join(kundendaten_ordner_app, name_der_anwalt_daten_json_datei) + if os.path.isfile(source_datei_pfad): + shutil.copy(source_datei_pfad, ziel_datei_pfad) + + +def get_temp_folder(): + temp_folder_path = os.path.join(os.path.expanduser("~"), BASE_DIR, TEMP_FOLDER) + ensure_folder_exists(temp_folder_path) + return temp_folder_path + +def get_base_dir(): + return get_base_dir_path() + +def get_template_folder(): + template_folder_path = os.path.join( + os.path.expanduser("~"), BASE_DIR, VORLAGEN_ORDNER_APP + ) + ensure_folder_exists(template_folder_path) + return template_folder_path + +def get_update_folder(): + update_folder_path = os.path.join( + os.path.expanduser("~"), BASE_DIR, UPDATE_ORDNER_NAME + ) + ensure_folder_exists(update_folder_path) + return update_folder_path + +def get_mandanten_folder(): + mandanten_folder_path = os.path.join( + os.path.expanduser("~"), BASE_DIR, MANDATEN_ORDNER_NAME + ) + ensure_folder_exists(mandanten_folder_path) + return mandanten_folder_path + +def get_temp_file_for_template_file(template_file): + source_path = os.path.join(get_template_folder(), template_file) + + if not os.path.exists(source_path): + logging.error(f"Template file does not exist: {source_path}") + raise FileNotFoundError(f"Template file does not exist: {source_path}") + + # Generate a unique temporary file name + unique_filename = f"{uuid.uuid4()}_{template_file}" + temp_file_path = os.path.join(get_temp_folder(), unique_filename) + + # Copy the template to a temporary file and return its path + try: + shutil.copyfile(source_path, temp_file_path) + return temp_file_path + except IOError as e: + logging.error(f"Failed to create a temporary file from template: {e}") + raise + + + +def print_paragraph_details(paragraph): + print("This is one paragraph:") + if paragraph.text: + print(f"Text: {paragraph.text}") + if paragraph.style: + print(f"Style: {paragraph.style.name}") + if paragraph.alignment: + print(f"Alignment: {paragraph.alignment}") + if paragraph.contains_page_break: + print(f"Contains page break: {paragraph.contains_page_break}") + if paragraph.hyperlinks: + print(f"Hyperlinks: {paragraph.hyperlinks}") + if paragraph.paragraph_format: + print("Paragraph format details:") + format = paragraph.paragraph_format + if format.alignment: + print(f" Alignment: {format.alignment}") + if format.keep_together: + print(f" Keep together: {format.keep_together}") + if format.keep_with_next: + print(f" Keep with next: {format.keep_with_next}") + if format.left_indent: + print(f" Left indent: {format.left_indent}") + if format.line_spacing: + print(f" Line spacing: {format.line_spacing}") + if format.line_spacing_rule: + print(f" Line spacing rule: {format.line_spacing_rule}") + if format.page_break_before: + print(f" Page break before: {format.page_break_before}") + if format.right_indent: + print(f" Right indent: {format.right_indent}") + if format.space_after: + print(f" Space after: {format.space_after}") + if format.space_before: + print(f" Space before: {format.space_before}") + if format.tab_stops: + print(" Tab stops:") + for tabstop in format.tab_stops: + print(f" Alignment: {tabstop.alignment}") + print(f" Leader: {tabstop.leader}") + print(f" Position: {tabstop.position}") + if format.widow_control: + print(f" Widow control: {format.widow_control}") + if paragraph.runs: + print("Runs:") + for run in paragraph.runs: + if run.text: + print(f" Text: {run.text}") + if run.bold: + print(f" Bold: {run.bold}") + if run.italic: + print(f" Italic: {run.italic}") + if run.underline: + print(f" Underline: {run.underline}") + if run.font: + if run.font.name: + print(f" Font name: {run.font.name}") + if run.font.size: + print(f" Font size: {run.font.size}") + +def datenabfrage_datev_for_aktenzeichen(aktenzeichen:str, passwort:str): + url = 'https://localhost:58452/datev/api/law/v1/files/' + params = { + 'select': 'id, name, number', + 'filter': f"file_number eq '{aktenzeichen}'", # Verwendung eines f-Strings zur Einsetzung + 'orderby': 'my_property_name desc,other_property_name asc', + 'top': '100', + 'skip': '10' + } + headers = { + 'accept': 'application/json; charset=utf-8' + } + # Ersetzen Sie 'your_username' und 'your_password' mit Ihren tatsächlichen Anmeldedaten + username = get_nutzername() + auth = HTTPBasicAuth(username, passwort) + + response = requests.get(url, params=params, headers=headers, auth=auth, verify=False) + + if response.status_code == 200: + return response.json() # Gibt das JSON-Antwortobjekt zurück + else: + return f"Error: {response.status_code}" + +def get_nutzername(): + login_information = get_login_information() + return login_information["username"] + +def set_nutzername(nutzername): + login_information = get_login_information() + login_information["username"] = nutzername + set_login_information(login_information) + +def get_datev_example_data(): + return [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "file_number_short": "000001-05", + "file_number": "000001-2005/001:00", + "file_name": "Insolvenzverfahren Mustermann", + "project_number": "123/331-12", + "short_reason": "Beratung", + "long_reason": "vacnatocbosa", + "department": { + "id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + "causes": [ + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 3, + "name": "Forderung aus Warenlieferung", + "department_id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 15, + "name": "sonstige zivilrechtliche Ansprüche", + "department_id": "e5a91019-af4d-4373-a18c-36e64e4ec478", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/e5a91019-af4d-4373-a18c-36e64e4ec478" + } + ], + "partner": { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "link": "https://localhost:58452/datev/api/law/v1/employees/c015c071-43c4-432f-be80-508d54c720e7" + }, + "case_handlers": [ + { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "primary_case_handler": true, + "commission": 100, + "employee_id": "f8586db2-4f22-44af-8cec-16f426bd5440", + "employee_link": "http://localhost:58454/datev/api/master-data/v1/employees/f8586db2-4f22-44af-8cec-16f426bd5440" + } + ], + "security_zone": { + "id": "174ddc49-e8c4-466b-8c35-d8eef5d655b6", + "short_name": "SB-0", + "name": "Öffentliche Akten" + }, + "establishment": { + "number": 1, + "name": "Musterniederlassung", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6/establishments/59bb1870-5e0a-4ce9-bb7d-42e95f5cdb4e", + "organization": { + "id": "2da7f880-6c24-44cd-be38-32746a268b0f", + "number": 1, + "name": "Musterkanzlei", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "economic_data": { + "cause_value": { + "amount": 20000, + "currency": "EUR" + }, + "budget": { + "amount": 10000, + "currency": "EUR" + }, + "budget_timespan": "total", + "base_currency": "EUR" + }, + "accounting_area": { + "id": "7447f931-b42e-4e71-84f3-1319a49fb076", + "number": 1, + "name": "Standardbuchungskreis", + "link": "https://localhost:58452/datev/api/law/v1/accounting-areas/7447f931-b42e-4e71-84f3-1319a49fb076" + }, + "reactivated": false, + "filing": { + "date": "2019-08-12", + "number": "000001-2005", + "retention_period_end": "2029-08-12", + "location": "Keller" + }, + "note": "umamonabp", + "created": { + "date": "2018-09-27", + "creator": "Ernst Exempeladvokat" + }, + "modified": { + "date": "2019-08-11", + "creator": "Ernst Exempeladvokat" + } + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "file_number_short": "000001-05", + "file_number": "000001-2005/001:00", + "file_name": "Insolvenzverfahren Mustermann", + "project_number": "123/331-12", + "short_reason": "Beratung", + "long_reason": "morigejofo", + "department": { + "id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + "causes": [ + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 3, + "name": "Forderung aus Warenlieferung", + "department_id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 15, + "name": "sonstige zivilrechtliche Ansprüche", + "department_id": "e5a91019-af4d-4373-a18c-36e64e4ec478", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/e5a91019-af4d-4373-a18c-36e64e4ec478" + } + ], + "partner": { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "link": "https://localhost:58452/datev/api/law/v1/employees/c015c071-43c4-432f-be80-508d54c720e7" + }, + "case_handlers": [ + { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "primary_case_handler": true, + "commission": 100, + "employee_id": "f8586db2-4f22-44af-8cec-16f426bd5440", + "employee_link": "http://localhost:58454/datev/api/master-data/v1/employees/f8586db2-4f22-44af-8cec-16f426bd5440" + } + ], + "security_zone": { + "id": "174ddc49-e8c4-466b-8c35-d8eef5d655b6", + "short_name": "SB-0", + "name": "Öffentliche Akten" + }, + "establishment": { + "number": 1, + "name": "Musterniederlassung", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6/establishments/59bb1870-5e0a-4ce9-bb7d-42e95f5cdb4e", + "organization": { + "id": "2da7f880-6c24-44cd-be38-32746a268b0f", + "number": 1, + "name": "Musterkanzlei", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "economic_data": { + "cause_value": { + "amount": 20000, + "currency": "EUR" + }, + "budget": { + "amount": 10000, + "currency": "EUR" + }, + "budget_timespan": "total", + "base_currency": "EUR" + }, + "accounting_area": { + "id": "7447f931-b42e-4e71-84f3-1319a49fb076", + "number": 1, + "name": "Standardbuchungskreis", + "link": "https://localhost:58452/datev/api/law/v1/accounting-areas/7447f931-b42e-4e71-84f3-1319a49fb076" + }, + "reactivated": false, + "filing": { + "date": "2019-08-12", + "number": "000001-2005", + "retention_period_end": "2029-08-12", + "location": "Keller" + }, + "note": "supvujsekdiras", + "created": { + "date": "2018-09-27", + "creator": "Ernst Exempeladvokat" + }, + "modified": { + "date": "2019-08-11", + "creator": "Ernst Exempeladvokat" + } + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "file_number_short": "000001-05", + "file_number": "000001-2005/001:00", + "file_name": "Insolvenzverfahren Mustermann", + "project_number": "123/331-12", + "short_reason": "Beratung", + "long_reason": "tanejomeve", + "department": { + "id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + "causes": [ + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 3, + "name": "Forderung aus Warenlieferung", + "department_id": "ebd93cfc-1c2e-4927-aee5-24b448b050fd", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/ebd93cfc-1c2e-4927-aee5-24b448b050fd" + }, + { + "id": "051534f8-7b78-441a-aa9e-6f708b49d855", + "number": 15, + "name": "sonstige zivilrechtliche Ansprüche", + "department_id": "e5a91019-af4d-4373-a18c-36e64e4ec478", + "department_link": "https://localhost:58452/datev/api/law/v1/departments/e5a91019-af4d-4373-a18c-36e64e4ec478" + } + ], + "partner": { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "link": "https://localhost:58452/datev/api/law/v1/employees/c015c071-43c4-432f-be80-508d54c720e7" + }, + "case_handlers": [ + { + "id": "c015c071-43c4-432f-be80-508d54c720e7", + "number": 62, + "display_name": "Ernst Exempeladvokat", + "primary_case_handler": true, + "commission": 100, + "employee_id": "f8586db2-4f22-44af-8cec-16f426bd5440", + "employee_link": "http://localhost:58454/datev/api/master-data/v1/employees/f8586db2-4f22-44af-8cec-16f426bd5440" + } + ], + "security_zone": { + "id": "174ddc49-e8c4-466b-8c35-d8eef5d655b6", + "short_name": "SB-0", + "name": "Öffentliche Akten" + }, + "establishment": { + "number": 1, + "name": "Musterniederlassung", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6/establishments/59bb1870-5e0a-4ce9-bb7d-42e95f5cdb4e", + "organization": { + "id": "2da7f880-6c24-44cd-be38-32746a268b0f", + "number": 1, + "name": "Musterkanzlei", + "link": "http://localhost:58454/datev/api/master-data/v1/corporate-structures/3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "economic_data": { + "cause_value": { + "amount": 20000, + "currency": "EUR" + }, + "budget": { + "amount": 10000, + "currency": "EUR" + }, + "budget_timespan": "total", + "base_currency": "EUR" + }, + "accounting_area": { + "id": "7447f931-b42e-4e71-84f3-1319a49fb076", + "number": 1, + "name": "Standardbuchungskreis", + "link": "https://localhost:58452/datev/api/law/v1/accounting-areas/7447f931-b42e-4e71-84f3-1319a49fb076" + }, + "reactivated": false, + "filing": { + "date": "2019-08-12", + "number": "000001-2005", + "retention_period_end": "2029-08-12", + "location": "Keller" + }, + "note": "tuhoweswurisa", + "created": { + "date": "2018-09-27", + "creator": "Ernst Exempeladvokat" + }, + "modified": { + "date": "2019-08-11", + "creator": "Ernst Exempeladvokat" + } + } + ] + +def hinzufuegen_anwaltsdaten_zum_kontext(kontext, anwalt_json=None): + + if anwalt_json is None: + anwalt_json = get_lawyer_data() + + # Hier werden die Anwaltsdetails aus der anwalt_json extrahiert + anwalt_details = anwalt_json.get('lawyer_details', {}) + kontext['ANWÄLTE'] = [ + { + 'NAME': details.get('name', ''), + 'FACHGEBIETE': details.get('specialty', '').split('; ') if details.get('specialty') else [] + } + for anwalt_id, details in anwalt_details.items() + ] + return kontext + + + +def add_ausfuehrung(reference, kundenprogramm_ID, antrags_name): + ensure_data_file_exists() + new_ausfuehrung = { + "id": str(uuid.uuid4()), + "reference": reference, + "kundenprogramm_ID": kundenprogramm_ID, + "datum": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "antrags_name": antrags_name, + "fehlerhaft": False + } + with open(get_ausfuehrungen_file_path(), "r+") as file: + data = json.load(file) + data.append(new_ausfuehrung) + file.seek(0) + json.dump(data, file, indent=4) + +def mark_ausfuehrung_as_fehlerhaft(ausfuehrung_id): + ensure_data_file_exists() + with open(get_ausfuehrungen_file_path(), "r+") as file: + data = json.load(file) + for ausfuehrung in data: + if ausfuehrung["id"] == ausfuehrung_id: + ausfuehrung["fehlerhaft"] = True + break + file.seek(0) + file.truncate() + json.dump(data, file, indent=4) + + +def open_file(file_path): + if file_path: + try: + if platform.system() == "Windows": + subprocess.run(["explorer", file_path], check=True) + elif platform.system() == "Darwin": # macOS + subprocess.run(["open", file_path], check=True) + else: # Assuming Linux + subprocess.run(["xdg-open", file_path], check=True) + except subprocess.CalledProcessError as e: + print(f"Error opening file: {e}") + else: + print("File path is not set. Unable to open file.") + +def get_help_file_path(app): + return os.path.join(app.paths.app, "resources", "help.pdf") + +def get_updates_datei_user(): + #ich möchte den dateipfad der update.json datei aus dem updates ordner haben, welcher im updates ordner im user verzeichnis liegt + updates_dateipfad_user = os.path.join(os.path.expanduser("~"), get_base_dir(), UPDATE_ORDNER_NAME_USER, "update.json") + return updates_dateipfad_user + +def get_updates_pdf_path(app): + updates_pdf_dateipfad = os.path.join(app.paths.app, "resources", "update.pdf") + return updates_pdf_dateipfad + +def update_daten_laden_user(): + file_path = get_updates_datei_user() + with open(file_path, "r") as f: + return json.load(f) + +def update_daten_laden_app(app): + file_path = get_updates_datei_app(app) + with open(file_path, "r") as f: + return json.load(f) + +def get_updates_datei_app(app): + updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") + return updates_datei_app + +def update_in_updates_ordner_uebertragen(app): + updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") + updates_datei_user = get_updates_datei_user() + + shutil.copy(updates_datei_app, updates_datei_user) + +def versionsordner_erstellen(): + folder_name = UPDATE_ORDNER_NAME_USER + ensure_folder_exists(folder_name) \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py index eb9380d..774d607 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py @@ -3,6 +3,7 @@ import subprocess import platform from {{ cookiecutter.app_name|lower|replace('-', '_') }}.CONSTANTS import BASE_DIR, SETTINGS_ORDNER + def ensure_folder_exists(folder_name): base_dir = get_base_dir_path() folder_path = os.path.join(base_dir, folder_name) @@ -188,7 +189,467 @@ def is_libreoffice_installed(): except (subprocess.CalledProcessError, FileNotFoundError): return False -def get_settings_file_path(): - settings_folder = ensure_folder_exists(SETTINGS_ORDNER) - settings_file = os.path.join(settings_folder, "settings.json") - return settings_file +#Law + + import json +import os +import uuid +from nadoo_law.CONSTANTS import ( + BEWEISMITTEL_OWI_DATA_FILE_NAME, + BEWEISMITTEL_SCHEIDUNG_DATA_FILE_NAME, + LAWYER_DATA_FILE_NAME, + BASE_DIR, + LOGIN_INFORMATION_FILE_NAME, + AUSFÜHRUNGEN_DATEI_NAME +) + + +def get_lawyer_data_file_path(): + base_dir = get_base_dir_path() + return os.path.join(base_dir, LAWYER_DATA_FILE_NAME) + + +def get_beweismittel_OWi_data_file_path(): + base_dir = get_base_dir_path() + return os.path.join(base_dir, BEWEISMITTEL_OWI_DATA_FILE_NAME) + +def get_beweismittel_Scheidung_data_file_path(): + base_dir = get_base_dir_path() + return os.path.join(base_dir, BEWEISMITTEL_SCHEIDUNG_DATA_FILE_NAME) + +def get_base_dir_path(): + ensure_base_folder_exits() + return os.path.join(os.path.expanduser("~"), BASE_DIR) + +def ensure_folder_exists(folder_name): + base_dir = get_base_dir_path() + folder_path = os.path.join(base_dir, folder_name) + if not os.path.exists(folder_path): + os.makedirs(folder_path) + return folder_path + +def ensure_base_folder_exits(): + base_dir = os.path.join(os.path.expanduser("~"), BASE_DIR) + if not os.path.exists(base_dir): + os.makedirs(base_dir) + +def ensure_lawyer_data_file_exists(): + file_path = get_lawyer_data_file_path() + if not os.path.isfile(file_path): + # Initialize the file with a dummy lawyer if it doesn't exist + dummy_data = { + "selected_lawyer_id": str(uuid.uuid4()), + "lawyer_details": { + "dummy_id": { + "name": "Dummy Lawyer", + "email": "dummy@lawfirm.com", + "phone": "123-456-7890", + "fax": "098-765-4321", + "title": "Lawyer", + "specialty": "General", + } + }, + } + with open(file_path, "w") as f: + json.dump(dummy_data, f, indent=4) + + +def ensure_beweismittel_OWi_data_file_exists(): + file_path = get_beweismittel_OWi_data_file_path() + if not os.path.isfile(file_path): + # Initialize the file with default data if it doesn't exist + default_data = { + "options": [ + "Messprotokolle", + "Ausbildungsnachweise der Mess- und Auswertebeamten", + "Originalbeweisfotos", + "Eichscheine", + "Gesamte Messreihe vom Tattag", + "Digitale Rohmessdaten sowie die dazugehörigen öff. Token und Passwörter", + "Statistikdatei mit Case List", + "Konformitätsbescheinigung und –erklärung zum Messgerät", + "Kalibrier- und Testfotos", + "Bedienungsanleitung der zum Tattag gültigen Version", + "Auskunft über Reparaturen, Wartungen, vorgezogene Neueichung oder vgl. die Funktionsfähigkeit des hier verwendeten Messgerätes berührende Ereignisse", + "Beschilderungsnachweise für 2 km vor und nach der Messstelle", + "Liste aller am Tattag aufgenommenen Verkehrsverstöße", + ] + + + } + with open(file_path, "w") as f: + json.dump(default_data, f, indent=4) + +def ensure_beweismittel_Scheidung_data_file_exists(): + file_path = get_beweismittel_Scheidung_data_file_path() + if not os.path.isfile(file_path): + # Initialize the file with default data if it doesn't exist + default_data = { + "options": [ + "Geburtsurkunde", + "Scheidungsfolgevereinbarung", + ] + } + with open(file_path, "w") as f: + json.dump(default_data, f, indent=4) + +def ensure_user_auth_data_file_exists(): + file_path = get_user_auth_data_file_path() + if not os.path.isfile(file_path): + # Initialize the file with default data if it doesn't exist + default_data = { + "user_name": "", + } + with open(file_path, "w") as f: + json.dump(default_data, f, indent=4) + +def get_user_auth_data_file_path(): + base_dir = os.path.join(os.path.expanduser("~"), BASE_DIR) + if not os.path.exists(base_dir): + os.makedirs(base_dir) + return os.path.join(base_dir, "user_auth_data.json") + +def get_login_information_test(): + login_information = { + "username": "test", + } + return login_information + +def get_login_information(): + ensure_login_information_file_exists() + file_path = get_login_information_file_path() + with open(file_path, "r") as f: + return json.load(f) + + +def get_login_information_file_path(): + base_dir_path = get_base_dir_path() + return os.path.join(base_dir_path, LOGIN_INFORMATION_FILE_NAME) + +def get_ausfuehrungen_file_path(): + base_dir_path = get_base_dir_path() + return os.path.join(base_dir_path, AUSFÜHRUNGEN_DATEI_NAME) + + +def ensure_login_information_file_exists(): + file_path = get_login_information_file_path() + if not os.path.isfile(file_path): + # Initialize the file with a dummy lawyer if it doesn't exist + dummy_data = { + "username": "Bitte DATEV-Nutzername eingeben", + } + with open(file_path, "w") as f: + json.dump(dummy_data, f, indent=4) + + +def set_login_information(login_information): + ensure_login_information_file_exists() + file_path = get_login_information_file_path() + with open(file_path, "w") as f: + json.dump(login_information, f, indent=4) + +def ensure_data_file_exists(): + file_path = get_ausfuehrungen_file_path() + if not os.path.isfile(file_path): + with open(file_path, "w") as file: + json.dump([], file) + +#LaunchPad + +import os +import platform +import re +import unicodedata +import subprocess +from briefcase.exceptions import ( + InvalidTemplateRepository, + NetworkFailure, + TemplateUnsupportedVersion, + BriefcaseCommandError, +) +from cookiecutter import exceptions as cookiecutter_exceptions +from cookiecutter.main import cookiecutter +from cookiecutter.repository import is_repo_url +from pathlib import Path +import toga +import toml + +cookiecutter = staticmethod(cookiecutter) + +def make_app_name(formal_name): + """Construct a candidate app name from a formal name. + + :param formal_name: The formal name + :returns: The candidate app name + """ + normalized = unicodedata.normalize("NFKD", formal_name) + stripped = re.sub("[^0-9a-zA-Z_]+", "", normalized).lstrip("_") + if stripped: + return stripped.lower() + else: + # If stripping removes all the content, + # use a dummy app name as the suggestion. + return "myapp" + + +def make_module_name(app_name): + """Construct a valid module name from an app name. + + :param app_name: The app name + :returns: The app's module name. + """ + return app_name.replace("-", "_") + + +def generate_template(template, branch, output_path, extra_context): + """Ensure the named template is up-to-date for the given branch, and roll out + that template. + + :param template: The template URL or path to generate + :param branch: The branch of the template to use + :param output_path: The filesystem path where the template will be generated. + :param extra_context: Extra context to pass to the cookiecutter template + """ + # Make sure we have an updated cookiecutter template, + # checked out to the right branch + cached_template = update_cookiecutter_cache(template=template, branch=branch) + + try: + # Unroll the template + cookiecutter( + str(cached_template), + no_input=True, + output_dir=str(output_path), + checkout=branch, + extra_context=extra_context, + ) + except subprocess.CalledProcessError as e: + # Computer is offline + # status code == 128 - certificate validation error. + raise NetworkFailure("clone template repository") from e + except cookiecutter_exceptions.RepositoryNotFound as e: + # Either the template path is invalid, + # or it isn't a cookiecutter template (i.e., no cookiecutter.json) + raise InvalidTemplateRepository(template) from e + except cookiecutter_exceptions.RepositoryCloneFailed as e: + # Branch does not exist. + raise TemplateUnsupportedVersion(branch) from e + + +def update_cookiecutter_cache(template: str, branch="master"): + """Ensure that we have a current checkout of a template path. + + If the path is a local path, use the path as is. + + If the path is a URL, look for a local cache; if one exists, update it, + including checking out the required branch. + + :param template: The template URL or path. + :param branch: The template branch to use. Default: ``master`` + :return: The path to the cached template. This may be the originally + provided path if the template was a file path. + """ + if is_repo_url(template): + # The app template is a repository URL. + # + # When in `no_input=True` mode, cookiecutter deletes and reclones + # a template directory, rather than updating the existing repo. + # + # Look for a cookiecutter cache of the template; if one exists, + # try to update it using git. If no cache exists, or if the cache + # directory isn't a git directory, or git fails for some reason, + # fall back to using the specified template directly. + cached_template = cookiecutter_cache_path(template) + try: + repo = self.tools.git.Repo(cached_template) + # Raises ValueError if "origin" isn't a valid remote + remote = repo.remote(name="main") + try: + # Attempt to update the repository + remote.fetch() + except self.tools.git.exc.GitCommandError as e: + # We are offline, or otherwise unable to contact + # the origin git repo. It's OK to continue; but + # capture the error in the log and warn the user + # that the template may be stale. + pass + + try: + # Check out the branch for the required version tag. + head = remote.refs[branch] + + self.logger.info( + f"Using existing template (sha {head.commit.hexsha}, " + f"updated {head.commit.committed_datetime.strftime('%c')})" + ) + head.checkout() + except IndexError as e: + # No branch exists for the requested version. + raise TemplateUnsupportedVersion(branch) from e + except self.tools.git.exc.NoSuchPathError: + # Template cache path doesn't exist. + # Just use the template directly, rather than attempting an update. + cached_template = template + except self.tools.git.exc.InvalidGitRepositoryError: + # Template cache path exists, but isn't a git repository + # Just use the template directly, rather than attempting an update. + cached_template = template + except ValueError as e: + raise BriefcaseCommandError( + f"Git repository in a weird state, delete {cached_template} and try briefcase create again" + ) from e + else: + # If this isn't a repository URL, treat it as a local directory + cached_template = template + + return cached_template + + +def cookiecutter_cache_path(template): + """Determine the cookiecutter template cache directory given a template URL. + + This will return a valid path, regardless of whether `template` + + :param template: The template to use. This can be a filesystem path or + a URL. + :returns: The path that cookiecutter would use for the given template name. + """ + template = template.rstrip("/") + tail = template.split("/")[-1] + cache_name = tail.rsplit(".git")[0] + return Path.home() / ".cookiecutters" / cache_name + +def update_ui(self:toga.App): + # Refresh the UI to show changes + self.main_window.content = self.new_project_form + +def set_installation_state(app:toga.App): + config_path = app.paths.config / "install_state.toml" + + # Ensure the directory exists + config_path.parent.mkdir(parents=True, exist_ok=True) + + # Check if the file exists + if not config_path.exists(): + # If not, create it with the initial 'installed' state + config_data = {"installed": True} + else: + # If it exists, load the existing data + with open(config_path, "r") as config_file: + config_data = toml.load(config_file) + + # Update the 'installed' state to True + config_data["installed"] = True + + # Save the updated data + with open(config_path, "w") as config_file: + toml.dump(config_data, config_file) + +def update_ui_post_install(self:toga.App): + # Remove the 'Install' button and add the 'New Project' button + self.main_box.remove(self.install_btn) + +def install_python_with_pyenv(): + # Check if the Python version already exists + pyenv_version_exists = ( + subprocess.run( + ["pyenv", "versions", "--bare", "--skip-aliases", "3.11.7"], + capture_output=True, + ).returncode + == 0 + ) + + # If the version exists, skip installation + if not pyenv_version_exists: + # Use 'yes' to automatically answer 'y' to any prompts + subprocess.run("yes | pyenv install 3.11.7", shell=True) + else: + print("Python 3.11.7 is already installed.") + + # Set global version + subprocess.run(["pyenv", "global", "3.11.7"]) + +def install_pyenv(): + # Example command, adjust based on OS + subprocess.run(["curl", "-L", "https://pyenv.run", "|", "bash"]) + # Additional commands may be needed to integrate pyenv into the shell + +def setup_project_folder(): + # Get the current user's home directory and set the project folder path + project_folder = get_project_folder_path() + return project_folder + +def get_project_folder_path(): + home_dir = os.path.expanduser("~") + project_folder = os.path.join(home_dir, "Documents", "GitHub") + + # Create the folder if it doesn't exist + if not os.path.exists(project_folder): + os.makedirs(project_folder) + + return project_folder + +def create_and_activate_venv(self): + os_type = platform.system() + if os_type == "Darwin": # macOS + return self.create_and_activate_venv_mac() + # Add more conditions for other OS types here + else: + print(f"OS {os_type} not supported yet") + +def create_and_activate_venv_mac(self): + python_path = ( + subprocess.check_output(["pyenv", "which", "python"]).decode().strip() + ) + # Create the virtual environment inside the project folder + venv_path = os.path.join(get_project_folder_path(), "env") + subprocess.run([python_path, "-m", "venv", venv_path]) + # Activate the virtual environment - for macOS + activate_command = f"source {venv_path}/bin/activate" + subprocess.run(["bash", "-c", activate_command]) + + return venv_path + +def on_new_developer_name_entered(self, widget): + # Generate the email based on the entered name + new_name = widget.value + if new_name: + new_email = f"{new_name.replace(' ', '.').lower()}@nadooit.de" + self.author_email_input.value = new_email + +def create_pyproject_file(self, user_data, project_folder): + # Construct the path to the template file + template_file_name = "base_project_template.toml" + template_path = Path(self.app.paths.app / "resources" / template_file_name) + + # Ensure the project subfolder exists + project_subfolder = Path(project_folder, user_data["app_name"]) + project_subfolder.mkdir(parents=True, exist_ok=True) + + # New project file path + new_project_path = project_subfolder / "pyproject.toml" + + try: + with open(template_path, "r") as template_file: + template_content = template_file.read() + + # Replace placeholders with actual data + for key, value in user_data.items(): + placeholder = "{{" + key.upper() + "}}" + template_content = template_content.replace(placeholder, value) + + print(template_content) + + # Write the new pyproject.toml file + with open(new_project_path, "w") as new_project_file: + new_project_file.write(template_content) + + return new_project_file + + except FileNotFoundError as e: + self.display_error(f"Template file not found: {e}") + except IOError as e: + self.display_error(f"Error while handling the file: {e}") + except Exception as e: + self.display_error(f"An unexpected error occurred: {e}") + \ No newline at end of file From 688499060f83d56b99e3fc452e20dff50d02ac3b Mon Sep 17 00:00:00 2001 From: NADOOITLaurinK <156072167+NADOOITLaurinK@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:37:21 +0200 Subject: [PATCH 34/34] updated --- .../components/SettingNadooitAPISchluessel.py | 28 ++ .../SettingUhrzeitWerktagswechsel.py | 49 +++ .../components/SettingUserCode.py | 28 ++ .../components/SettingsElement.py | 15 + .../components/SettingsWindow.py | 38 +-- .../services.py | 322 +++++++++++++----- .../{{ cookiecutter.module_name }}/utils.py | 14 - 7 files changed, 363 insertions(+), 131 deletions(-) create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingNadooitAPISchluessel.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUhrzeitWerktagswechsel.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUserCode.py create mode 100644 {{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsElement.py diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingNadooitAPISchluessel.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingNadooitAPISchluessel.py new file mode 100644 index 0000000..05da830 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingNadooitAPISchluessel.py @@ -0,0 +1,28 @@ +from {{ cookiecutter.module_name }}.components.SettingsElement import SettingsElement +import toga +from toga.style import Pack +from {{ cookiecutter.module_name }}.services import set_api_key, get_settings + +class SettingNadooitAPISchluessel(SettingsElement): + + def __init__(self): + super().__init__() + # Eingabefeld für den User Code erstellen + self.api_key_input = toga.TextInput( + placeholder="NADOO API Schlüssel", + ) + + setting_lable = toga.Label( + "NADOO API Schlüssel", style=Pack(padding_bottom=10) + ) + + self.gui.add(setting_lable) + self.gui.add(self.api_key_input) + self.load_settings() + + def save(self): + set_api_key(self.api_key_input.value) + + def load_settings(self): + settings = get_settings() + self.api_key_input.value = settings.get("api_key", "") \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUhrzeitWerktagswechsel.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUhrzeitWerktagswechsel.py new file mode 100644 index 0000000..7326610 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUhrzeitWerktagswechsel.py @@ -0,0 +1,49 @@ +from {{ cookiecutter.module_name }}.components.SettingsElement import SettingsElement +import toga +from toga.style import Pack +from toga.style.pack import ROW +from {{ cookiecutter.module_name }}.services import set_uhrzeit_nächsten_werktag_wechsel, get_settings + +class SettingUhrzeitWerktagswechsel(SettingsElement): + + def __init__(self): + super().__init__() + + # Dropdown-Menü für die Stunde erstellen + self.stunde_dropdown = toga.Selection( + items=[f"{stunde:02d}" for stunde in range(24)], + style=Pack(flex=1) + ) + + # Dropdown-Menü für die Minute erstellen + self.minute_dropdown = toga.Selection( + items=[f"{minute:02d}" for minute in range(0, 60, 15)], + style=Pack(flex=1) + ) + + # Dropdown-Menüs nebeneinander platzieren + dropdown_box = toga.Box( + children=[self.stunde_dropdown, self.minute_dropdown], + style=Pack(direction=ROW) + ) + + setting_lable = toga.Label( + "Uhrzeit für den nächsten Werktag wechseln", style=Pack(padding_bottom=10) + ) + + self.gui.add(setting_lable) + self.gui.add(dropdown_box) + self.load_settings() + + def save(self): + stunde = self.stunde_dropdown.value + minute = self.minute_dropdown.value + uhrzeit = f"{stunde}:{minute}:00" + set_uhrzeit_nächsten_werktag_wechsel(uhrzeit) + + def load_settings(self): + settings = get_settings() + uhrzeit = settings.get("uhrzeit_nächsten_werktag_wechsel", "16:00:00") + stunde, minute, _ = uhrzeit.split(":") + self.stunde_dropdown.value = stunde + self.minute_dropdown.value = minute diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUserCode.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUserCode.py new file mode 100644 index 0000000..629d837 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingUserCode.py @@ -0,0 +1,28 @@ +from {{ cookiecutter.module_name }}.SettingsElement import SettingsElement +import toga +from toga.style import Pack +from {{ cookiecutter.module_name }}.services import set_user_code, get_settings + +class SettingUserCode(SettingsElement): + + def __init__(self): + super().__init__() + # Eingabefeld für den User Code erstellen + self.user_code_input = toga.TextInput( + placeholder="User Code", + ) + + setting_lable = toga.Label( + "NADOO Nutzercode", style=Pack(padding_bottom=10) + ) + + self.gui.add(setting_lable) + self.gui.add(self.user_code_input) + self.load_settings() + + def save(self): + set_user_code(self.user_code_input.value) + + def load_settings(self): + settings = get_settings() + self.user_code_input.value = settings.get("user_code", "") \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsElement.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsElement.py new file mode 100644 index 0000000..c5191e1 --- /dev/null +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsElement.py @@ -0,0 +1,15 @@ +import toga +from toga.style import Pack +from toga.style.pack import COLUMN + +class SettingsElement: + + def __init__(self): + self.gui = toga.Box(style=Pack(direction=COLUMN, padding_bottom=5)) + pass + + def save(self): + pass + + def load_settings(self): + pass \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py index 3827dd0..45b0c80 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/components/SettingsWindow.py @@ -1,34 +1,30 @@ import os import json +from typing import List, Optional import toga from toga.style import Pack from toga.style.pack import COLUMN -from nadoo_travel.src.nadoo_travel.services import set_settings, get_settings,set_user_code, set_api_key +from {{ cookiecutter.module_name }}.services import get_settings,set_user_code, set_api_key +from {{ cookiecutter.module_name }}.SettingsElement import SettingsElement + + class SettingsWindow(toga.Window): - def __init__(self, title, width=400, height=400): + def __init__(self, title, settings_elemente:Optional[List[SettingsElement]] = None, width=400, height=400): super().__init__(title, size=(width, height)) + + self.settings_elemente = settings_elemente or [] + self.content = self.build() - self.load_settings() def build(self): # Hauptcontainer für den Inhalt erstellen box = toga.Box(style=Pack(direction=COLUMN, padding=10, flex=1)) + - # Eingabefeld für den User Code erstellen - self.user_code_input = toga.TextInput( - placeholder="User Code", - style=Pack(padding_bottom=10) - ) - box.add(self.user_code_input) - - # Eingabefeld für den NADOO API Schlüssel erstellen - self.api_key_input = toga.TextInput( - placeholder="NADOO API Schlüssel", - style=Pack(padding_bottom=10) - ) - box.add(self.api_key_input) + for elemnt in self.settings_elemente: + box.add(elemnt.gui) # Speichern-Button hinzufügen save_button = toga.Button( @@ -53,10 +49,6 @@ def close_window(self, widget): def save_settings(self, widget): - set_user_code(self.user_code_input.value) - set_api_key(self.api_key_input.value) - - def load_settings(self): - settings = get_settings() - self.user_code_input.value = settings.get("user_code", "") - self.api_key_input.value = settings.get("api_key", "") \ No newline at end of file + + for settings_element in self.settings_elemente: + settings_element.save() diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py index 7d035ad..2d8e5be 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/services.py @@ -1,8 +1,22 @@ +import openpyxl +import pandas import subprocess import platform import os import json import shutil +import pandas as pd +from pandas import NaT +from datetime import datetime +from openpyxl.utils import get_column_letter +from openpyxl.styles import PatternFill +import pandas as pd +from deutschland import feiertage +from deutschland.feiertage.api import default_api +import time +import functools +from openpyxl.worksheet.worksheet import Worksheet +from openpyxl.utils import get_column_letter from datetime import datetime @@ -102,6 +116,7 @@ def get_settings(): return json.load(file) else: return {} + def set_settings(settings): settings_file = get_settings_file_path() with open(settings_file, "w") as file: @@ -138,7 +153,6 @@ def load_settings(): else: return {} - def measure_execution_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): @@ -264,48 +278,8 @@ def vorlagen_in_vorlagen_ordner_uebertragen(app): os.rename(vorlage_app_pfad, neuer_app_pfad_ohne_update) """ - - - #LaunchPad -def ermittel_den_aktuellen_monat_als_deutsches_wort(monat_offset=0): - """ - Ermittelt den Monat relativ zum aktuellen Monat als Zahl und gibt den entsprechenden deutschen Monatsnamen zurück. - - Args: - monat_offset (int, optional): Die Anzahl der Monate, die zum aktuellen Monat addiert werden sollen. Standard ist 0. - - Returns: - str: Der ermittelte Monat auf Deutsch (z.B. "Januar", "Februar", ...). - """ - # Erstelle ein Dictionary, das die Monatszahlen auf die deutschen Monatsnamen abbildet - monatsnamen = { - 1: "Januar", - 2: "Februar", - 3: "März", - 4: "April", - 5: "Mai", - 6: "Juni", - 7: "Juli", - 8: "August", - 9: "September", - 10: "Oktober", - 11: "November", - 12: "Dezember" - } - - # Ermittle den aktuellen Monat als Zahl - aktueller_monat_als_zahl = datetime.now().month - - # Addiere den Offset zum aktuellen Monat - ziel_monat = (aktueller_monat_als_zahl + monat_offset) % 12 - if ziel_monat == 0: - ziel_monat = 12 - - # Gib den entsprechenden deutschen Monatsnamen zurück - return monatsnamen[ziel_monat] - #LAW import json @@ -335,15 +309,12 @@ def ermittel_den_aktuellen_monat_als_deutsches_wort(monat_offset=0): set_login_information, ) - - def set_beweismittel_data_Scheidung(beweismittel_data): ensure_beweismittel_Scheidung_data_file_exists() file_path = get_beweismittel_Scheidung_data_file_path() with open(file_path, "w") as f: json.dump(beweismittel_data, f, indent=4) - def get_beweismittel_data_scheidung(): ensure_beweismittel_Scheidung_data_file_exists() file_path = get_beweismittel_Scheidung_data_file_path() @@ -356,22 +327,18 @@ def set_beweismittel_data_OWi(beweismittel_data): with open(file_path, "w") as f: json.dump(beweismittel_data, f, indent=4) - def get_beweismittel_data_OWi(): ensure_beweismittel_OWi_data_file_exists() file_path = get_beweismittel_OWi_data_file_path() with open(file_path, "r") as f: return json.load(f) - - def set_lawyer_data(lawyer_data): ensure_lawyer_data_file_exists() file_path = get_lawyer_data_file_path() with open(file_path, "w") as f: json.dump(lawyer_data, f, indent=4) - def get_lawyer_data(): ensure_lawyer_data_file_exists() file_path = get_lawyer_data_file_path() @@ -385,7 +352,6 @@ def setup_folders(): ensure_folder_exists(get_mandanten_folder()) versionsordner_erstellen() - def vorlagen_in_vorlagen_ordner_uebertragen(app): vorlagen_ordner_app = os.path.join(app.paths.app, "resources", KUNDENDATEN_ORDNER_APP, VORLAGEN_ORDNER_APP) vorlagen_ordner_user = get_template_folder() # Annahme, dass diese Funktion das Benutzerverzeichnis für Vorlagen zurückgibt @@ -460,7 +426,6 @@ def anwalt_liste_in_basis_ordner_uebertragen(app): if os.path.isfile(source_datei_pfad): shutil.copy(source_datei_pfad, ziel_datei_pfad) - def get_temp_folder(): temp_folder_path = os.path.join(os.path.expanduser("~"), BASE_DIR, TEMP_FOLDER) ensure_folder_exists(temp_folder_path) @@ -911,8 +876,6 @@ def hinzufuegen_anwaltsdaten_zum_kontext(kontext, anwalt_json=None): ] return kontext - - def add_ausfuehrung(reference, kundenprogramm_ID, antrags_name): ensure_data_file_exists() new_ausfuehrung = { @@ -941,53 +904,224 @@ def mark_ausfuehrung_as_fehlerhaft(ausfuehrung_id): file.truncate() json.dump(data, file, indent=4) +# telemarketing + +def finde_letzte_zeile_in_excel_blatt(sheet:Worksheet): + + # finde die letzte Spalte mit Werten + anzahl_der_spalten = 1 + max_rows = 1 + + while ( + sheet.cell(1, anzahl_der_spalten).value is not None + and sheet.cell(1, anzahl_der_spalten).value != "Spalte1" + ): + anzahl_der_spalten += 1 + + vollstaendig_leere_zeile_gefunden = False + leere_spalten_gefunden = 0 + spalte = 1 + while spalte <= anzahl_der_spalten and not vollstaendig_leere_zeile_gefunden: + + while sheet.cell(max_rows, spalte).value is not None: + max_rows += 1 + + if sheet.cell(max_rows, spalte).value is None: + #print("Leere Spalte gefunden") + leere_spalten_gefunden += 1 + else: + leere_spalten_gefunden = 0 + + spalte += 1 + + if leere_spalten_gefunden == anzahl_der_spalten: + vollstaendig_leere_zeile_gefunden = True + elif spalte > anzahl_der_spalten: + spalte = 1 + + max_rows = max_rows - 1 + return max_rows + +def uebertrage_daten_in_excel_blatt(sheet: Worksheet, daten: pd.DataFrame): + max_rows = finde_letzte_zeile_in_excel_blatt(sheet) + """ + print( + f"Die Tabelle in der Region von Zeile 1 bis {max_rows} und Spalte 1 bis {len(daten.columns)} hat Daten" + ) + """ -def open_file(file_path): - if file_path: - try: - if platform.system() == "Windows": - subprocess.run(["explorer", file_path], check=True) - elif platform.system() == "Darwin": # macOS - subprocess.run(["open", file_path], check=True) - else: # Assuming Linux - subprocess.run(["xdg-open", file_path], check=True) - except subprocess.CalledProcessError as e: - print(f"Error opening file: {e}") - else: - print("File path is not set. Unable to open file.") + # Übertrage die Daten aus dem DataFrame in das Excel-Blatt unterhalb der vorhandenen Daten + for row_num, row_data in enumerate(daten.values, start=max_rows + 1): -def get_help_file_path(app): - return os.path.join(app.paths.app, "resources", "help.pdf") + #print(f"Übertrage Zeile {row_num} mit Daten") + #print(row_data) + + for col_num, cell_value in enumerate(row_data, start=1): + + #print(f"Übertrage Spalte {col_num} mit Wert {cell_value}") + + if col_num == 1: + # Index wird übersprungen + continue + col_letter = get_column_letter(col_num-1) + + # Überprüfe, ob der Zellwert ein Datum ist und formatiere es entsprechend + if isinstance(cell_value, datetime): + if pd.isna(cell_value): # Überprüfung, ob der Wert NaT ist + cell_value = '' # Oder ein anderer Platzhalterwert deiner Wahl + else: + cell_value = cell_value.strftime("%d.%m.%Y") + + #print(f"Übertrage Zelle {col_letter}{row_num} mit Wert {cell_value}") + + sheet[f"{col_letter}{row_num}"] = cell_value + +def zeilentrennung_am_ende_des_excel_blatt_anfuegen(sheet: Worksheet): + max_rows = finde_letzte_zeile_in_excel_blatt(sheet) + if max_rows > 1: + spaltenbezeichner_status_column = finde_spaltenbezeichner_zahlenwert(sheet, "Status") + print(f"Spaltenbezeichner Status: {spaltenbezeichner_status_column}") + # Füge eine Zeile mit - in alle Zellen der letzten Reihe hinzu und färbe den Hintergrund rot + for col_num in range(1, spaltenbezeichner_status_column+1): + print(f"Spalte {col_num}") + col_letter = get_column_letter(col_num) + sheet[f"{col_letter}{max_rows+1}"] = "-" + sheet[f"{col_letter}{max_rows+1}"].fill = PatternFill( + start_color="FFC7CE", end_color="FFC7CE", fill_type="solid" + ) + +def finde_spaltenbezeichner_zahlenwert(excel_blatt, spaltenbezeichner): + """ + Findet den Zahlenwert für den gegebenen Spaltenbezeichner in einem Excel-Blatt. -def get_updates_datei_user(): - #ich möchte den dateipfad der update.json datei aus dem updates ordner haben, welcher im updates ordner im user verzeichnis liegt - updates_dateipfad_user = os.path.join(os.path.expanduser("~"), get_base_dir(), UPDATE_ORDNER_NAME_USER, "update.json") - return updates_dateipfad_user + :param excel_blatt: Das Excel-Blatt, in dem nach dem Spaltenbezeichner gesucht werden soll. + :param spaltenbezeichner: Der Spaltenbezeichner (z.B. "Status"), nach dem gesucht werden soll. + :return: Der Zahlenwert des Spaltenbezeichners (z.B. 1 für Spalte A, 2 für Spalte B usw.) oder None, wenn der Spaltenbezeichner nicht gefunden wurde. + """ + # Entferne Leerzeichen am Ende der Spaltenüberschriften + headers = [cell.value.strip() if cell.value is not None else cell.value for cell in excel_blatt[1]] -def get_updates_pdf_path(app): - updates_pdf_dateipfad = os.path.join(app.paths.app, "resources", "update.pdf") - return updates_pdf_dateipfad + # Finde den Zahlenwert des Spaltenbezeichners basierend auf den bereinigten Spaltenüberschriften + for i, header in enumerate(headers, start=1): + if header == spaltenbezeichner: + return i -def update_daten_laden_user(): - file_path = get_updates_datei_user() - with open(file_path, "r") as f: - return json.load(f) - -def update_daten_laden_app(app): - file_path = get_updates_datei_app(app) - with open(file_path, "r") as f: - return json.load(f) - -def get_updates_datei_app(app): - updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") - return updates_datei_app + # Wenn der Spaltenbezeichner nicht gefunden wurde, gib None zurück + return None + +def lade_daten_aus_excel_blatt_als_DataFrame(excel_blatt: Worksheet, spaltenbezeichner_status: str = "Status") -> pd.DataFrame: + """ + Lädt alle Daten aus einem Excel-Blatt in ein DataFrame. -def update_in_updates_ordner_uebertragen(app): - updates_datei_app = os.path.join(app.paths.app, "resources", UPDATE_ORDNER_NAME_APP, "update.json") - updates_datei_user = get_updates_datei_user() + Args: + excel_blatt (openpyxl.worksheet.worksheet.Worksheet): Das Excel-Blatt, aus dem die Daten geladen werden sollen. + spaltenbezeichner_status (str, optional): Der Name des Spaltenbezeichners für den Status. Standard ist "Status". - shutil.copy(updates_datei_app, updates_datei_user) + Returns: + pd.DataFrame: Ein DataFrame mit allen Daten aus dem Excel-Blatt. + """ + # Finde den Spaltenbezeichner (A1, B1, C1 ...) mit dem Namen Status + spaltenbezeichner_status_column = None + + # Entferne Leerzeichen am Ende der Spaltenüberschriften + headers = [cell.value.strip() if cell.value is not None else cell.value for cell in excel_blatt[1]] + + # Finde den Spaltenbezeichner basierend auf den bereinigten Spaltenüberschriften + spaltenbezeichner_status_column = finde_spaltenbezeichner_zahlenwert(excel_blatt, spaltenbezeichner_status) + + # Finde die letzte Zeile im Excel-Blatt + max_rows = finde_letzte_zeile_in_excel_blatt(excel_blatt) + print(max_rows) + + # Lade die Daten aus dem Excel-Blatt in ein DataFrame + data = list(excel_blatt.iter_rows(min_row=2, max_row=max_rows, max_col=spaltenbezeichner_status_column, values_only=True)) + dataframe_aller_daten = pd.DataFrame(data, columns=headers[:spaltenbezeichner_status_column]) + + dataframe_aller_daten.reset_index(drop=False, inplace=True) + + return dataframe_aller_daten + +def loesche_alle_daten_von_blatt(excel_blatt: Worksheet): + """ + Löscht alle Daten von einem Excel-Blatt. + + Args: + excel_blatt: Das Excel-Blatt, aus dem die Daten gelöscht werden sollen. + spaltenbezeichner_status (str, optional): Der Name des Spaltenbezeichners für den Status. Standard ist "Status". + """ + + # Finde die letzte Zeile im Excel-Blatt + max_rows = finde_letzte_zeile_in_excel_blatt(excel_blatt) + + # Lösche alle Daten aus dem Excel-Blatt + excel_blatt.delete_rows(2, max_rows) + +def ermittel_den_aktuellen_monat_als_deutsches_wort(monat_offset=0): + """ + Ermittelt den Monat relativ zum aktuellen Monat als Zahl und gibt den entsprechenden deutschen Monatsnamen zurück. + + Args: + monat_offset (int, optional): Die Anzahl der Monate, die zum aktuellen Monat addiert werden sollen. Standard ist 0. -def versionsordner_erstellen(): - folder_name = UPDATE_ORDNER_NAME_USER - ensure_folder_exists(folder_name) \ No newline at end of file + Returns: + str: Der ermittelte Monat auf Deutsch (z.B. "Januar", "Februar", ...). + """ + # Erstelle ein Dictionary, das die Monatszahlen auf die deutschen Monatsnamen abbildet + monatsnamen = { + 1: "Januar", + 2: "Februar", + 3: "März", + 4: "April", + 5: "Mai", + 6: "Juni", + 7: "Juli", + 8: "August", + 9: "September", + 10: "Oktober", + 11: "November", + 12: "Dezember" + } + + # Ermittle den aktuellen Monat als Zahl + aktueller_monat_als_zahl = datetime.now().month + + # Addiere den Offset zum aktuellen Monat + ziel_monat = (aktueller_monat_als_zahl + monat_offset) % 12 + if ziel_monat == 0: + ziel_monat = 12 + + # Gib den entsprechenden deutschen Monatsnamen zurück + return monatsnamen[ziel_monat] + +def set_uhrzeit_nächsten_werktag_wechsel(uhrzeit_nächsten_werktag_wechsel): + settings = get_settings() + settings["uhrzeit_nächsten_werktag_wechsel"] = uhrzeit_nächsten_werktag_wechsel + set_settings(settings) + +def hole_uhrzeit_nächsten_werktag_wechsel(): + settings = get_settings() + uhrzeit_nächsten_werktag_wechsel = settings.get("uhrzeit_nächsten_werktag_wechsel", "16:00:00") + return pd.to_datetime(uhrzeit_nächsten_werktag_wechsel).time() + +def hole_feiertage(jahr:str, bundesland="NATIONAL"): + configuration = feiertage.Configuration(host="https://feiertage-api.de/api") + + with feiertage.ApiClient(configuration) as api_client: + api_instance = default_api.DefaultApi(api_client) + nur_daten = 1 + + try: + if bundesland == "NATIONAL": + api_response = api_instance.get_feiertage(jahr, nur_daten=nur_daten) + else: + api_response = api_instance.get_feiertage( + jahr, nur_land=bundesland, nur_daten=nur_daten + ) + feiertage_liste = [ + pd.to_datetime(feiertag, format="%Y-%m-%d") + for feiertag in api_response.values() + ] + return feiertage_liste + except feiertage.ApiException as e: + print("Exception when calling DefaultApi->get_feiertage: %s\n" % e) + return [] \ No newline at end of file diff --git a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py index 774d607..d4d8098 100644 --- a/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py +++ b/{{ cookiecutter.app_name }}/src/{{ cookiecutter.module_name }}/utils.py @@ -217,21 +217,7 @@ def get_beweismittel_Scheidung_data_file_path(): base_dir = get_base_dir_path() return os.path.join(base_dir, BEWEISMITTEL_SCHEIDUNG_DATA_FILE_NAME) -def get_base_dir_path(): - ensure_base_folder_exits() - return os.path.join(os.path.expanduser("~"), BASE_DIR) - -def ensure_folder_exists(folder_name): - base_dir = get_base_dir_path() - folder_path = os.path.join(base_dir, folder_name) - if not os.path.exists(folder_path): - os.makedirs(folder_path) - return folder_path -def ensure_base_folder_exits(): - base_dir = os.path.join(os.path.expanduser("~"), BASE_DIR) - if not os.path.exists(base_dir): - os.makedirs(base_dir) def ensure_lawyer_data_file_exists(): file_path = get_lawyer_data_file_path()