diff --git a/.gitignore b/.gitignore index e3cae33..fce84ba 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,5 @@ compile_commands.json .history/* **/tempCodeRunnerFile.py + +output/** \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..512893a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,111 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build LibrarySystem (Debug)", + "type": "shell", + "command": "pyinstaller", + "args": [ + "--noconfirm", + "--onedir", + "--console", + "--icon", + "'${config:ApplicationIconPath}'", + "--name", + "LibrarySystem-debug", + "--contents-directory", + ".", + "--clean", + "--add-data", + "'${config:configPath};config/'", + "--add-data", + "'${config:iconsPath};icons/'", + "'${workspaceFolder}/main_dev.py'" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "new", + "focus": false + }, + "dependsOn": "Compress dist Folder Debug", + "problemMatcher": "$pyinstaller" + }, + { + "label": "Build LibrarySystem (Release)", + "type": "shell", + "command": "pyinstaller", + "args": [ + "--noconfirm", + "--onedir", + "--windowed", + "--name", + "LibrarySystem", + "--contents-directory", + ".", + "--clean", + "--add-data", + "'${config:configPath};config/'", + "--add-data", + "'${config:iconsPath};icons/'", + "--icon", + "'${config:ApplicationIconPath}'", + "'${workspaceFolder}/main.py'" + ], + "group": { + "kind": "build", + "isDefault": false + }, + "presentation": { + "reveal": "always", + "panel": "new", + "focus": false + }, + "dependsOn": "Compress dist Folder Release", + "problemMatcher": "$pyinstaller" + }, + { + "label": "Run LibrarySystem (live)", + "type": "shell", + "command": "c:/Users/aky547/GitHub/LibrarySystem/.venv/Scripts/python.exe", + "args": [ + "'c:/Users/aky547/GitHub/LibrarySystem/main_dev.py'", + "--ic-logging" + ], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": "$python" + }, + { + "label": "Compress dist Folder Debug", + "type": "shell", + "command": "Compress-Archive", + "args": [ + "-Path", + "${workspaceFolder}/dist/LibrarySystem-debug/", + "-DestinationPath", + "${workspaceFolder}/output/LibrarySystem-debug.zip", + ], + "group": "build", + "presentation": "panel" + }, + { + "label": "Compress dist Folder Release", + "type": "shell", + "command": "Compress-Archive", + "args": [ + "-Path", + "${workspaceFolder}/dist/LibrarySystem/", + "-DestinationPath", + "${workspaceFolder}/output/LibrarySystem.zip", + ], + "group": "build", + "presentation": "panel" + } + ] +} \ No newline at end of file diff --git a/ATTRIBUTIONS.MD b/ATTRIBUTIONS.MD new file mode 100644 index 0000000..156321f --- /dev/null +++ b/ATTRIBUTIONS.MD @@ -0,0 +1,5 @@ +## Icons + if not stated elsewhere, all Icons are part of the Material Symbols Package provided by Google + ### Specific Icons + - borrow_boks.svg Credits to Maxicons, Icon downloaded frmo thenounproject.com [Link](https://thenounproject.com/icon/borrow-book-3317975/) + - borrow \ No newline at end of file diff --git a/README.md b/README.md index 5996908..ba0d310 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ # LibrarySystem -universal library system for facilities in the university \ No newline at end of file +universal library system for facilities in the university + + +## What is this? + +This is a library system for the different facilities in the university. Because some facilities lend their media, some wanted a software that allows them to keep track of who had what when. + +### Installation +either clone the Repo +``` +git clone https://git.theprivateserver.de/WorldTeacher/LibrarySystem.git +cd LibrarySystem +./build(.exe) +``` + +or head over to [releases](https://git.theprivateserver.de/WorldTeacher/LibrarySystem/releases) +and download the latest version + +### Configuration + +the software contains a config file in configs, as well as a config for icons, located in icons. + +#### config +tbd + +#### icons +- color is a value to re-paint the icons +- icons/* a dict of the icons structured like: icon_Name_in_Application : icon_File_name \ No newline at end of file diff --git a/build.app.json b/build.app.json new file mode 100644 index 0000000..813c03a --- /dev/null +++ b/build.app.json @@ -0,0 +1,77 @@ +{ + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Users/aky547/GitHub/LibrarySystem/main.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": false + }, + { + "optionDest": "icon_file", + "value": "C:/Users/aky547/Downloads/1490971308-map-icons-7_82746.ico" + }, + { + "optionDest": "name", + "value": "LibrarySystem" + }, + { + "optionDest": "contents_directory", + "value": "." + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/config;config/" + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/icons;icons/" + } + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/build.debug.app.json b/build.debug.app.json new file mode 100644 index 0000000..0e01b4b --- /dev/null +++ b/build.debug.app.json @@ -0,0 +1,77 @@ +{ + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Users/aky547/GitHub/LibrarySystem/main_dev.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": true + }, + { + "optionDest": "icon_file", + "value": "C:/Users/aky547/Downloads/1490971308-map-icons-7_82746.ico" + }, + { + "optionDest": "name", + "value": "LibrarySystem-debug" + }, + { + "optionDest": "contents_directory", + "value": "." + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/config;config/" + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/icons;icons/" + } + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/build.release.app.json b/build.release.app.json new file mode 100644 index 0000000..813c03a --- /dev/null +++ b/build.release.app.json @@ -0,0 +1,77 @@ +{ + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Users/aky547/GitHub/LibrarySystem/main.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": false + }, + { + "optionDest": "icon_file", + "value": "C:/Users/aky547/Downloads/1490971308-map-icons-7_82746.ico" + }, + { + "optionDest": "name", + "value": "LibrarySystem" + }, + { + "optionDest": "contents_directory", + "value": "." + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/config;config/" + }, + { + "optionDest": "datas", + "value": "C:/Users/aky547/GitHub/LibrarySystem/icons;icons/" + } + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..3558f42 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +from .config import Config \ No newline at end of file diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..871778e --- /dev/null +++ b/config/config.py @@ -0,0 +1,176 @@ +from typing import Optional + +import omegaconf +class Config: + _config: Optional[omegaconf.DictConfig] = None + + def __init__(self, config_path: str): + """ + Loads the configuration file and stores it for future access. + + Args: + config_path (str): Path to the YAML configuration file. + """ + self._config = omegaconf.OmegaConf.load(config_path) + + @property + def institution_name(self)->str: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.institution_name + @institution_name.setter + def institution_name(self, value: str): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.institution_name = value + @property + def loan_duration(self)->int: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.default_loan_duration + @loan_duration.setter + def loan_duration(self, value: int): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.default_loan_duration = value + @property + def delete_inactive_user_duration(self)->int: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.inactive_user_deletion + @delete_inactive_user_duration.setter + def delete_inactive_user_duration(self, value: int): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.inactive_user_deletion = value + @property + def catalogue(self)->bool: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.catalogue + @catalogue.setter + def catalogue(self, value: bool): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.catalogue = value + + @property + def shortcuts(self)->omegaconf.DictConfig: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.shortcuts + @shortcuts.setter + def shortcuts(self, value: omegaconf.DictConfig): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.shortcuts = value + + @property + def advanced_refresh(self)->omegaconf.DictConfig: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.advanced_refresh + + @advanced_refresh.setter + def advanced_refresh(self, value: omegaconf.DictConfig): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.advanced_refresh = value + + @property + def database(self)->omegaconf.DictConfig: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.database + @database.setter + def database(self, value: omegaconf.DictConfig): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.database = value + @property + def report(self)->omegaconf.DictConfig: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.report + @report.setter + def report(self, value: omegaconf.DictConfig): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.report = value + @property + def debug(self)->bool: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.debug + @debug.setter + def debug(self, value: bool): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.debug = value + @property + def log_debug(self)->bool: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.log_debug + @log_debug.setter + def log_debug(self, value: bool): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.log_debug = value + @property + def ic_logging(self)->bool: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.ic_logging + @ic_logging.setter + def ic_logging(self, value:bool): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.ic_logging = value + def save(self): + if self._config is None: + raise RuntimeError("Configuration not loaded") + omegaconf.OmegaConf.save(self._config, "config/settings.yaml") + + def apply_options(self, options:list): + if self._config is None: + raise RuntimeError("Configuration not loaded") + for option in options: + if option in self._config: + self._config[option] = True + else: + raise KeyError(f"Option {option} not found in configuration") + + @property + def documentation(self)->bool: + if self._config is None: + raise RuntimeError("Configuration not loaded") + return self._config.documentation + @documentation.setter + def documentation(self, value: bool): + if self._config is None: + raise RuntimeError("Configuration not loaded") + self._config.documentation = value + + + def to_Omegaconf(self): + return omegaconf.OmegaConf.create(self._config) + + def updateValue(self, key:str, value): + if self._config is None: + raise RuntimeError("Configuration not loaded") + if "." in key: + keys = key.split(".") + if keys[0] in self._config: + self._config[keys[0]][keys[1]] = value + else: + raise KeyError(f"Option {keys[0]} not found in configuration") + else: + self._config[key] = value +if __name__ == "__main__": + cfg = Config("config/settings.yaml") + #print(cfg.database.path) + cfg.database.path = "nopathhere" + #print(cfg.database.path) + cfg.save() + #cfg.updateValue("database.path", "Test") \ No newline at end of file diff --git a/config/settings.yaml b/config/settings.yaml index 250b073..dcd0da8 100644 --- a/config/settings.yaml +++ b/config/settings.yaml @@ -1,8 +1,34 @@ -institution_name: HB Testbibliothek Psychologie +institution_name: Test default_loan_duration: 7 +inactive_user_deletion: 365 database: - path: C:/db + path: ./database name: library.db backupLocation: ./backup -library_id: 20735 - + do_backup: true +report: + generate_report: true + path: ./report + report_day: 0 +shortcuts: +- name: Rueckgabemodus + default: F5 + current: F5 +- name: Nutzer + default: F6 + current: F6 +- name: Hilfe + default: F1 + current: F1 +- name: Bericht_erstellen + default: F7 + current: F7 +- name: Ausleihhistorie + default: F8 + current: F8 +advanced_refresh: false +catalogue: true +debug: false +log_debug: false +ic_logging: false +documentation: true \ No newline at end of file diff --git a/docs/Ausleihe_Sonderfälle.md b/docs/Ausleihe_Sonderfälle.md new file mode 100644 index 0000000..e9a9d02 --- /dev/null +++ b/docs/Ausleihe_Sonderfälle.md @@ -0,0 +1,16 @@ +# Sonderfälle bei der Ausleihe + +In einigen Fällen kann es bei der Ausleihe zu Problemen kommen. Diese werden hier aufgeführt. + +## Medium bereits entliehen + +Diese Meldung erscheint, wenn ein Medium bereits entliehen ist und erneut ausgeliehen werden soll. +Es erscheint folgende Meldung: + +![FehlerAusleihe](images/book_loaned.png) + +Wenn "Yes" geklickt wird, erscheint ein neues Fenster zum anlegen eines neuen Exemplars. + +![AddNewBook](images/book_addNew.png) + +Über das + Symbol können neue Exemplare hinzugefügt werden. Es ist empfohlen, die Signatur jeweils um +1 zu erhöhen, um eine eindeutige Zuordnung zu gewährleisten. diff --git a/docs/Ausleihhistorie.md b/docs/Ausleihhistorie.md new file mode 100644 index 0000000..8a9fd77 --- /dev/null +++ b/docs/Ausleihhistorie.md @@ -0,0 +1,15 @@ +# Ausleihhistorie +![Oberfläche](images/loanhistory.png) + +!!! info + + Die Ausleihhistorie kann vom Ausleihsystem immer aufgerufen werden. Dazu kann man entweder das Menu öffnen (Fenster -> Ausleihhistorie) oder den festgelegten Shortcut verwenden. + +# Bedienung +Hier werden alle Medien angezeigt. +Über die Filter kann man gezielt + - alle Ausleihen + - aktuell entliehene Medien + - überzogene Medien +anzeigen. +Zudem kann man mithilfe der Eingabezeile unter den Filteroptionen gezielt nach einem Titel, Nutzer oder einer Signatur gesucht werden. \ No newline at end of file diff --git a/docs/Ausleihsystem.md b/docs/Ausleihsystem.md new file mode 100644 index 0000000..4fa3510 --- /dev/null +++ b/docs/Ausleihsystem.md @@ -0,0 +1,83 @@ +# Ausleihsystem + + + +## Oberfläche + +![Nutzeroberfläche ohne Bewerkungen](images/main_marked%20areas.png) + +Die Oberfläche kann generell in drei Bereiche unterteilt werden: + + - Konto und Ausleihe + - Nutzerdaten + - Historie + +### Konto und Ausleihe + +Hier werden die Kontodaten und die Ausleihen angezeigt. +Der Bereich beschränkt sich auf folgende Inhalte: +![Kontobereich](images/main_ktoarea.png) + +Hier werden folgende Daten angezeigt + +- Modus: entweder "Ausleihe" oder "Rückgabe" (1) Kann entweder durch anklicken des Knopfes oder mit dem Shortcut (Standard: `F5`) geändert werden +- Matrikelnummer: die Matrikelnummer des Nutzers, um das Konto zu öffnen (3) +- Benutzername: der Benutzername des Nutzers (3) +- Signatur: die Signatur des Mediums, welches entliehen oder zurückgegeben wird (4) +- Ausleihe bis: bis wann das Medium ausgeliehen wird, Zeitraum wird in den Einstellungen angepasst +- Nutzer anlegen: ein Knopf, um einen neuen Nutzer anzulegen (2) + +### Nutzerdaten +Hier werden die Nutzerdaten angezeigt. Vorraussetzung ist, dass ein Nutzer angelegt und geöffnet wurde, oder dass ein Medium zurückgegeben wurde. +Dieser Bereich beschränkt sich auf folgende Inhalte: + +![Nutzerdaten](images/main_userdata.png) + +- Das Feld Nutzerdaten (6)beinhaltet + - Namen + - Matrikelnummer + - E-Mail +- Das Feld Ausleihdaten beinhaltet: + - Anzahl Ausleihen (5) : ein klickbares Feld, welches die Anzahl der Ausleihen anzeigt. Beim Klick wird die Übersicht des aktiven Nutzers angezeigt. + - Nächstes Rückgabedatum (wird angezeigt wenn ein Nutzer (mehr als) eine Ausleihe hat und ein Medium zurückgegeben wurde oder ein Nutzer geöffnet wird) + +### Historie (7) +Das Feld der Historie listet alle Medien auf, die im aktiven Prozess ausgeliehen oder zurückgegeben wurden. + +## Ausleihen + +Um ein Medium auszuleihen, muss zuerst ein Nutzer geöffnet sein. Dazu entweder den Knopf neben Modus drücken, oder den Shortcut (Standard: `F5`) verwenden. +Die Oberfläche sieht dann wie folgt aus: + +![Ausleihe](images/main_loan_active.png) + + +Der Cursor wird automatisch auf die Matrikelnummer gesetzt. Hier kann entweder die vollständige Nummer, oder ein Teil eingegeben werden. Sollten mehrere Nummern dem Teilfilter ensprechen, wird eine Auswahl angezeigt. (s. [MultiUser](MultiUser.md)) +Nach der Auswahl wird das entsprechende Konto geöffnet und die Ausleihe kann durchgeführt werden. + +![Ausleihe](images/activeLoan.png) + + +Sollte der aktive Nutzer aktive Ausleihen haben, so wird die Anzahl der Medien neben "Anzahl Ausleihen" angezeigt. Das nächste Rückgabedatum wird ebenfalls angezeigt. Über einen Klick auf die Zahl der Ausleihen gelangen Sie zur [Übersicht der Ausleihen des Nutzers](Nutzeroberfläche.md). + +Um ein Medium auszuleihen, muss die Signatur in die entsprechende Zeile eingegeben werden. Sollte die Signatur nicht in der Datenbank existieren, wird der definierte Katalog geprüft. Wird das Medium gefunden, wird es in die Datenbank übernommen und die Ausleihe wird durchgeführt. Sollte das Medium nicht gefunden werden, wird eine Fehlermeldung angezeigt. + +![FehlerAusleihe](images/err_noBook.png) + +!!! info + + Die Signatur muss mindestens ein Leerzeichen enthalten, ansonsten wird eine Fehlermeldung angezeigt. + +Sonderfälle siehe [AusleiheSonderfälle](Ausleihe_Sonderfälle.md) + + +## Rückgaben + +!!! info + Rückgaben funktionieren nur, wenn kein Nutzer offen ist. Vor einer Rückgabe bitte sicherstellen, dass der Modus auf "Rückgabe" gesetzt ist. + +Um ein Medium zurückzugeben, muss die Signatur in die entsprechende Zeile eingegeben werden. Sollte die Signatur nicht existieren, wird eine Fehlermeldung angezeigt. + +![FehlerRückgabe](images/err_return.png) + +Existiert das Medium in der Datenbank und ist entliehen, wird die Rückgabe durchgeführt. Die Rückgabe wird in der Historie gespeichert und das Medium wird wieder als verfügbar markiert. Die Daten des Nutzers werden in der Oberfläche angezeigt. diff --git a/docs/Bericht erstellen.md b/docs/Bericht erstellen.md new file mode 100644 index 0000000..76522fe --- /dev/null +++ b/docs/Bericht erstellen.md @@ -0,0 +1,27 @@ +# Bericht erstellen + +![Bericht erstellen](images/generateReport.png) + +## Information +Diese Oberfläche kann immer von der [Hauptoberfläche](Ausleihsystem.md) geöffnet werden. Hierzu entweder +`Fenster -> Bericht erstellen` oder den Shortcut F7 verwenden + + +## Bericht generieren + +Um einen Bericht zu erstellen, müssen folgende Kriterien erfüllt sein: + +- Zeitspanne festgelegt (Entweder über den Slider, oder über die Woche / Monat / Jahr Knöpfe) +- Datenformat ausgewählt + +Nachdem diese Kriterien erfüllt sind, kann der Bericht über den Knopf `Bericht erstellen` erstellt werden. Der Bericht wird erstellt, bei größeren Datensätzen kann es länger dauern, eine Fortschrittsanzeige gibt an, wie weit der Prozess ist. + +Wurde der Bericht erfolgreich erstellt, sieht die Oberfläche wie folgt aus: + +![Bericht erstellt](images/generatedReport.png) + +Über einen Klick auf `Report` wird die entsprechende Datei geöffnet. + +!!! info + + Text öffnet das Notepad, Excel öffnet Excel \ No newline at end of file diff --git a/docs/Einstellungen.md b/docs/Einstellungen.md new file mode 100644 index 0000000..19d3963 --- /dev/null +++ b/docs/Einstellungen.md @@ -0,0 +1,21 @@ +# Einstellungen +![Einstellungen](images/settings.png) + +## Bedienung +Hier werden die Einstellungen geändert. Sobald ein Wert geändert wird, ist es möglich die Einstellungen rückgängig zu machen, oder die Änderungen zu speichern. + +## Besonderheiten +Der Knopf neben Speicherort, Datenbankname, Sicherungspfad und Speicherpfad kann verwendet werden, um den Pfad gezielt zu setzen. + +Beim Datenbanknamen wird allerdings nur der Name der Datenbank übernommen + +## Hinweis + +Einige Aktionen (bspw. Änderungen der Shortcuts) erfordern einen Neustart der Anwendung. Dies wird beim Speichern der Einstellungen mit folgendem Dialog dargestelt: + +![restart](images/restart.png) + +Im Anschluss an diesen Dialog erscheint ein neuer Dialog: + +![restart Application](images/settings_changed_restart.png) + diff --git a/docs/MultiUser.md b/docs/MultiUser.md new file mode 100644 index 0000000..99be7a8 --- /dev/null +++ b/docs/MultiUser.md @@ -0,0 +1,5 @@ +# Mehrere Nutzer gefunden +![Mehrere Nutzer gefunden](images/multiUser.png) + +!!! info + Es wurden mehrere Nutzer gefunden, die auf die Suchanfrage passen. Bitte wählen Sie einen Nutzer aus der Liste aus, und bestätigen Sie mit `OK`. \ No newline at end of file diff --git a/docs/Nutzer anlegen.md b/docs/Nutzer anlegen.md new file mode 100644 index 0000000..56eb6cd --- /dev/null +++ b/docs/Nutzer anlegen.md @@ -0,0 +1,18 @@ +# Nutzer anlegen + +![Nutzer erstellen](images/add_user.png) + +## Information +Diese Oberfläche kann nur geöffnet werden, wenn die [Hauptoberfläche](Ausleihsystem.md) geöffnet ist. Hierzu muss bei dieser auf folgenden Knopf gedrückt werden: +![create user](images/createUser.png) + +## Bedienung +Um einen Nutzer anzulegen, müssen alle Angaben korrekt ausgefüllt sein. +### Limitierungen +Folgende Regeln sind zwingend einzuhalten: +- Nachname, Vorname muss mit `, ` getrennt sein +- Matrikelnummer darf nicht länger als 20 Zeichen sein und nur Nummern enthalten +- Mail muss einem validen Schema entsprechen (s. Bild) + +Nachdem alle Kriterien erfüllt sind, kann der Knopf `Save` angeklickt werden. Der Nutzer wird gespeichert und in der [Hauptoberfläche](Ausleihsystem.md) geöffnet. + diff --git a/docs/Nutzeroberfläche.md b/docs/Nutzeroberfläche.md new file mode 100644 index 0000000..dc13a15 --- /dev/null +++ b/docs/Nutzeroberfläche.md @@ -0,0 +1,31 @@ +# Nutzerdatenfenster +![alt text](images/user_main.png) + +!!! info + Die Nutzeroberfläche kann nur geöffnet werden, wenn ein Nutzer offen ist. Ansonsten wird ein Fehler angezeigt. (s. unten) + +![Error](images/err_nouser.png) + +# Bedeutung der Felder +- (1) Nutzerdaten: + + Name des Nutzers, Matrikelnummer, E-Mail + Wird eine Angabe geändert, erscheint Feld (3) um diese Angaben entweder zu speichern oder um die Änderungen rückgängig zu machen. + +- (2) Nutzer Löschen: + + Mit dem Klick auf den Mülleimer wird der Nutzer gelöscht. Alle zugewiesenen Ausleihen werden in der [Ausleihhistorie](Ausleihhistorie.md) mit den Nutzerkonto `gelöscht` angezeigt. Diese Option ist nur vorhanden, wenn keine Ausleihen aktiv sind. + +- (4) Medien: + + Umfasst unter anderem Feld (5), welches ein neues Fenster zum [verlängern](ausleihe verlängern.md) der Ausleihe öffnet. + Mit einem Klick auf + - Alle Ausleihen + - Aktuell entliehen + - Überzogen +werden die Einträge der Tabelle gefiltert. Zusätzlich kann mithilfe der Eingabezeile unter den Filteroptionen gezielt nach einem Titel oder einer Signatur gesucht werden. + +- (5) Verlängern: + + Um Medien zu verlängern, müssen diese in der Tabelle angeklickt werden. Mit Strg können mehrere Medien gleichzeitig ausgewählt und verlängert werden. Hierzu wird ein neues Fenster geöffnet, siehe [Ausleihe verlängern](ausleihe verlängern.md). + diff --git a/docs/ausleihe verlängern.md b/docs/ausleihe verlängern.md new file mode 100644 index 0000000..6f79498 --- /dev/null +++ b/docs/ausleihe verlängern.md @@ -0,0 +1,9 @@ +# Medien verlängern +![Oberfläche](images/extend.png) + +## Information +Die Ausleihe verlängern kann nur geöffnet werden, wenn ein Nutzer offen ist. + +Diese Oberfläche erlaubt es, eine oder mehrere Medien zu verlängern. Hierzu muss ein neues, in der Zukunft liegendes Datum ausgewählt und mit OK bestätigt werden. + +In der Datenbank wird nun das neue Datum gespeichert und die Einträge in der Tabelle werden aktualisiert. \ No newline at end of file diff --git a/docs/images/activeLoan.png b/docs/images/activeLoan.png new file mode 100644 index 0000000..83a6e1e Binary files /dev/null and b/docs/images/activeLoan.png differ diff --git a/docs/images/add_user.png b/docs/images/add_user.png new file mode 100644 index 0000000..a322daf Binary files /dev/null and b/docs/images/add_user.png differ diff --git a/docs/images/book_addNew.png b/docs/images/book_addNew.png new file mode 100644 index 0000000..020ad68 Binary files /dev/null and b/docs/images/book_addNew.png differ diff --git a/docs/images/book_loaned.png b/docs/images/book_loaned.png new file mode 100644 index 0000000..9e699fe Binary files /dev/null and b/docs/images/book_loaned.png differ diff --git a/docs/images/createUser.png b/docs/images/createUser.png new file mode 100644 index 0000000..4fdec9f Binary files /dev/null and b/docs/images/createUser.png differ diff --git a/docs/images/err_noBook.png b/docs/images/err_noBook.png new file mode 100644 index 0000000..4a656f5 Binary files /dev/null and b/docs/images/err_noBook.png differ diff --git a/docs/images/err_nouser.png b/docs/images/err_nouser.png new file mode 100644 index 0000000..fd10a9d Binary files /dev/null and b/docs/images/err_nouser.png differ diff --git a/docs/images/err_return.png b/docs/images/err_return.png new file mode 100644 index 0000000..05201bd Binary files /dev/null and b/docs/images/err_return.png differ diff --git a/docs/images/extend.png b/docs/images/extend.png new file mode 100644 index 0000000..3103f64 Binary files /dev/null and b/docs/images/extend.png differ diff --git a/docs/images/generateReport.png b/docs/images/generateReport.png new file mode 100644 index 0000000..2ee367a Binary files /dev/null and b/docs/images/generateReport.png differ diff --git a/docs/images/generatedReport.png b/docs/images/generatedReport.png new file mode 100644 index 0000000..6122367 Binary files /dev/null and b/docs/images/generatedReport.png differ diff --git a/docs/images/loanhistory.png b/docs/images/loanhistory.png new file mode 100644 index 0000000..cb03be1 Binary files /dev/null and b/docs/images/loanhistory.png differ diff --git a/docs/images/main_ktoarea.png b/docs/images/main_ktoarea.png new file mode 100644 index 0000000..1ff5e41 Binary files /dev/null and b/docs/images/main_ktoarea.png differ diff --git a/docs/images/main_loan_active.png b/docs/images/main_loan_active.png new file mode 100644 index 0000000..0cd0009 Binary files /dev/null and b/docs/images/main_loan_active.png differ diff --git a/docs/images/main_marked areas.png b/docs/images/main_marked areas.png new file mode 100644 index 0000000..8ff89d8 Binary files /dev/null and b/docs/images/main_marked areas.png differ diff --git a/docs/images/main_no_user.png b/docs/images/main_no_user.png new file mode 100644 index 0000000..e283cd3 Binary files /dev/null and b/docs/images/main_no_user.png differ diff --git a/docs/images/main_user_active.png b/docs/images/main_user_active.png new file mode 100644 index 0000000..f13e02a Binary files /dev/null and b/docs/images/main_user_active.png differ diff --git a/docs/images/main_userdata.png b/docs/images/main_userdata.png new file mode 100644 index 0000000..41a6bf8 Binary files /dev/null and b/docs/images/main_userdata.png differ diff --git a/docs/images/multiUser.png b/docs/images/multiUser.png new file mode 100644 index 0000000..612d822 Binary files /dev/null and b/docs/images/multiUser.png differ diff --git a/docs/images/restart.png b/docs/images/restart.png new file mode 100644 index 0000000..249f44d Binary files /dev/null and b/docs/images/restart.png differ diff --git a/docs/images/settings.png b/docs/images/settings.png new file mode 100644 index 0000000..4e69c82 Binary files /dev/null and b/docs/images/settings.png differ diff --git a/docs/images/settings_changed_restart.png b/docs/images/settings_changed_restart.png new file mode 100644 index 0000000..cc3b71d Binary files /dev/null and b/docs/images/settings_changed_restart.png differ diff --git a/docs/images/user_main.png b/docs/images/user_main.png new file mode 100644 index 0000000..fd55dc5 Binary files /dev/null and b/docs/images/user_main.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0f0c53a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,21 @@ +# Übersicht + +!!! info + + Einige Knöpfe sind auf Englisch. Diese Einstellung kann ich leider nicht ändern, da diese Übersetzung von der Anwendung automatisch ausgeführt werden + + +## [Hauptoberfläche](Ausleihsystem.md) +Die Hauptoberfläche wird angezeigt, wenn die Anwendung gestartet wird. +Von hier aus können Nutzer angelegt und bearbeitet werden, Medien ausgeliehen und zurückgegeben werden, sowie verschiedene Unterbereiche anzeigen. + +Unterbereiche umfassen: + +- [Hauptoberfläche](Ausleihsystem.md) +- [Nutzerdaten](Nutzeroberfläche.md) - Daten für den aktuellen Nutzer anzeigen und bearbeiten +- [Nutzer anlegen](Nutzer anlegen.md) - Neuen Nutzer anlegen +- [Ausleihhistorie](Ausleihhistorie.md) - Historie der Ausleihen +- [Bericht erstellen](Bericht erstellen.md) - Bericht für einen festgelegten Zeitrahmen erstellen +- [Einstellungen](Einstellungen.md) - Einstellungen der Anwendung ändern + + diff --git a/docs/shortcuts.md b/docs/shortcuts.md new file mode 100644 index 0000000..9368ebe --- /dev/null +++ b/docs/shortcuts.md @@ -0,0 +1,16 @@ +# Shortcuts + +!!! info + + Die Shortcuts können für die Anwendung manuell festgelegt werden. + Dafür in den [Einstellungen](Einstellungen.md) unter Shortcuts den neuen Shortcut eingeben und speichern + +Standardmäßig sind die Shortcuts wie folgt festgelegt: + +| Shortcut | Standard | +| ------- | -------- | +| Ausleihhistorie | F7 | +| Bericht erstellen | F6 | +| Hilfe | F1 | +| Nutzer | F5 | +| Rückgabemodus | F8 | diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..dd4026f --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,5 @@ +.md-typeset .admonition, +.md-typeset details { + border-width: 0; + border-left-width: 4px; +} \ No newline at end of file diff --git a/filehandle.py b/filehandle.py new file mode 100644 index 0000000..43c8bdb --- /dev/null +++ b/filehandle.py @@ -0,0 +1,103 @@ +import os +import shutil +from pathlib import Path +ORIGIN = "C:/testing/source_2" +BACKUP = "C:/Databasebackup" +FILE = "database.db" +def handle_file(): + """ + Handles file creation, movement, and backup based on path and backup existence. + + Args: + path (str): The desired path for the file. + filename (str): The name of the file. + backup_location (str): The location for backups. + + Returns: + str: The final path of the file. + """ + full = Path(ORIGIN) / FILE + full_path = str(full) + backup = Path(BACKUP) / FILE + backup_path = str(backup) + origin_reachable = os.path.exists(full_path) + backup_reachable = os.path.exists(backup_path) + print(origin_reachable, backup_reachable) + if not origin_reachable and not backup_reachable: + make_dirs(ORIGIN, BACKUP) + + # if not os.path.exists(full_path): + # print("File does not exist, creating file") + # create_file(full_path) + if os.path.exists(backup_path) and ".backup" in os.listdir(BACKUP): + print("Used backup previously, overwriting file") + #overwrite file at source with backup + shutil.copy(backup_path, full_path) + os.remove(BACKUP + "/.backup") + + +def make_dirs(full_path, backup_path): + """ + Creates directories if they do not exist and moves the file to the desired location. + + Args: + full_path (str): The full path of the file. + backup_path (str): The backup path of the file. + """ + if not os.path.exists(full_path): + os.makedirs(full_path) + if not os.path.exists(backup_path): + os.makedirs(backup_path) + +def create_file(full_path): + """ + Creates a file at the desired location. + + Args: + full_path (str): The full path of the file. + """ + with open(full_path, "w") as f: + f.write("") + +def handle_folder_reachability(original_path, backup_path): + """ + Checks if the original folder is reachable. If not, creates a backup. + If the original folder becomes reachable again, restores the backup. + + Args: + original_path (str): Path to the original folder. + backup_path (str): Path to the backup folder. + + Returns: + str: Path to the current accessible folder. + """ + + backup_file = os.path.join(backup_path, ".backup") + try: + os.makedirs(original_path) + except FileExistsError: + pass + + if not os.path.exists(original_path): + #original folder not reachable, use backup path and create .backup file + if not os.path.exists(backup_path): + os.makedirs(backup_path) + with open(backup_file, "w") as f: + f.write("") + + # Create an empty backup file as a marker + return backup_path +"/" + FILE + + else: + # Original folder is reachable, check for backup + if os.path.exists(backup_file): + # Restore backup + shutil.rmtree(original_path) # Remove original folder to avoid conflicts + shutil.move(backup_path, original_path) + #os.remove(backup_file) + #remove backup file from original path + os.remove(original_path + "/.backup") + os.makedirs(backup_path) + return original_path +"/" + FILE +if __name__=="__main__": + print(handle_folder_reachability(ORIGIN, BACKUP)) # should create a new file at C:/newdatabase/database.db) \ No newline at end of file diff --git a/icons/add_book.svg b/icons/add_book.svg new file mode 100644 index 0000000..acd98b6 --- /dev/null +++ b/icons/add_book.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/add_user.svg b/icons/add_user.svg new file mode 100644 index 0000000..3904edf --- /dev/null +++ b/icons/add_user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/backup.svg b/icons/backup.svg new file mode 100644 index 0000000..f65b40b --- /dev/null +++ b/icons/backup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/book.svg b/icons/book.svg new file mode 100644 index 0000000..237f73c --- /dev/null +++ b/icons/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/calendar_event.svg b/icons/calendar_event.svg new file mode 100644 index 0000000..87dd2e3 --- /dev/null +++ b/icons/calendar_event.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/db_backup.svg b/icons/db_backup.svg new file mode 100644 index 0000000..93d9936 --- /dev/null +++ b/icons/db_backup.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/delete.svg b/icons/delete.svg new file mode 100644 index 0000000..339e314 --- /dev/null +++ b/icons/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/duplicate.svg b/icons/duplicate.svg new file mode 100644 index 0000000..4d5cc44 --- /dev/null +++ b/icons/duplicate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/error.svg b/icons/error.svg new file mode 100644 index 0000000..5ed37a1 --- /dev/null +++ b/icons/error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/group.svg b/icons/group.svg new file mode 100644 index 0000000..6eae0ab --- /dev/null +++ b/icons/group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/icon.ico b/icons/icon.ico new file mode 100644 index 0000000..9c09b65 Binary files /dev/null and b/icons/icon.ico differ diff --git a/icons/icons.yaml b/icons/icons.yaml new file mode 100644 index 0000000..2c9a3d1 --- /dev/null +++ b/icons/icons.yaml @@ -0,0 +1,18 @@ +color: '#B89230' #Hex code of the color +icons: + addBook: add_book.svg + add_user: add_user.svg + backup: db_backup.svg + borrow: book.svg + duplicate: duplicate.svg + error: error.svg + loan_extend: calendar_event.svg + main: library.svg + multiuser: multiple_user.svg + newentry: library_add.svg + report: report.svg + settings: settings.svg + user: user.svg + warning: warning.svg + delete: delete.svg + restart: restart.svg diff --git a/icons/library.svg b/icons/library.svg new file mode 100644 index 0000000..52dfdd1 --- /dev/null +++ b/icons/library.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/library_add.svg b/icons/library_add.svg new file mode 100644 index 0000000..a3f0045 --- /dev/null +++ b/icons/library_add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/library_shelf.png b/icons/library_shelf.png new file mode 100644 index 0000000..cb1961f Binary files /dev/null and b/icons/library_shelf.png differ diff --git a/icons/multiple_user.svg b/icons/multiple_user.svg new file mode 100644 index 0000000..aef3514 --- /dev/null +++ b/icons/multiple_user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/report.svg b/icons/report.svg new file mode 100644 index 0000000..e29e721 --- /dev/null +++ b/icons/report.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/restart.svg b/icons/restart.svg new file mode 100644 index 0000000..8fa96d4 --- /dev/null +++ b/icons/restart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/settings.svg b/icons/settings.svg new file mode 100644 index 0000000..328e2f0 --- /dev/null +++ b/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/user.svg b/icons/user.svg new file mode 100644 index 0000000..9b6e823 --- /dev/null +++ b/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/warning.svg b/icons/warning.svg new file mode 100644 index 0000000..d0be798 --- /dev/null +++ b/icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/main.py b/main.py index 1d2aae4..c47644c 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ -hello_world = lambda: "Hello, World!" +from src.ui.main_ui import launch -print(hello_world()) +if __name__ == "__main__": + launch() + diff --git a/main_dev.py b/main_dev.py new file mode 100644 index 0000000..295af3d --- /dev/null +++ b/main_dev.py @@ -0,0 +1,5 @@ +from src.ui.main_ui import launch + +if __name__ == "__main__": + launch("--debug") + diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..52fbb20 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,30 @@ +site_name: LibrarySystem +theme: + features: + - search.suggest + - search.highlight + name: material + icon: + admonition: + note: fontawesome/solid/note-sticky + abstract: fontawesome/solid/book + info: fontawesome/solid/circle-info + tip: fontawesome/solid/bullhorn + success: fontawesome/solid/check + question: fontawesome/solid/circle-question + warning: fontawesome/solid/triangle-exclamation + failure: fontawesome/solid/bomb + danger: fontawesome/solid/skull + bug: fontawesome/solid/robot + example: fontawesome/solid/flask + quote: fontawesome/solid/quote-left + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - tables +extra_css: + - stylesheets/extra.css +plugins: + - search \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bc5f67d Binary files /dev/null and b/requirements.txt differ diff --git a/src/__init__.py b/src/__init__.py index 0fc8435..0a6f585 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,2 +1,61 @@ -import omegaconf -config = omegaconf.OmegaConf.load("config/settings.yaml") \ No newline at end of file +import sys +from config import Config +__version__ = "0.1.0" +__author__ = "Alexander Kirchner" +__email__ = "alexander.kirchner@ph-freiburg.de" +__license__ = "MIT" +docport = 6543 + + +config = Config("config/settings.yaml") +valid_args = ["--debug", "--log", "--no-backup", "--ic-logging", "--version", "-h","--no-documentation"] + +args_description = { + "--debug": "Enable debug mode", + "--log": "Enable logging", + "--no-backup": "Disable database backup", + "--ic-logging": "Enable icecream logging (not available in production)", + "--version": "Show version", + "-h": "Show help message and exit", + "--no-documentation": "Disable documentation server and shortcut" +} + +args = sys.argv[1:] +if any(arg not in valid_args for arg in args): + print("Invalid argument present") + print([arg for arg in args if arg not in valid_args]) + sys.exit() + +def help(): + print("Ausleihsystem") + print("Ein Ausleihsystem für Handbibliotheken") + print("Version: {}".format(__version__)) + print("Valide Argumente:") + print("args") + print("--------") + print("usage: main.py [-h] [--debug] [--log] [--no-backup] [--ic-logging] [--version] [--no-documentation]") + print("options:") + for arg in valid_args: + print(f"{arg} : {args_description[arg]}") + +# based on the arguments, set the config values +if"-h" in args: + help() + sys.exit() +if "--debug" in args: + config.debug = True +if "--log" in args: + config.log_debug = True +if "--no-backup" in args: + config.no_backup = True +if "--ic-logging" in args: + config.ic_logging = True +if "--no-documentation" in args: + config.documentation = False +if "--version" in args: + print(__version__) + sys.exit() + + + + diff --git a/src/logic/__init__.py b/src/logic/__init__.py index fc1efde..d9dd733 100644 --- a/src/logic/__init__.py +++ b/src/logic/__init__.py @@ -1,3 +1,4 @@ __help__ = "This package contains the logic of the application." from .database import Database -from .catalogue import Catalogue \ No newline at end of file +from .catalogue import Catalogue +from .backup import Backup \ No newline at end of file diff --git a/src/logic/backup.py b/src/logic/backup.py new file mode 100644 index 0000000..8e194af --- /dev/null +++ b/src/logic/backup.py @@ -0,0 +1,29 @@ +import os +import sys +import shutil +from src import config + + +class Backup: + def __init__(self): + self.source_path = config.database.path + "/" + config.database.name + self.backup_path = config.database.backupLocation + "/" + config.database.name + self.backup = False + if not os.path.exists(config.database.backupLocation): + os.makedirs(config.database.backupLocation) + if config.database.do_backup == True: + self.checkpaths() + config.database.do_backup = self.backup + + def checkpaths(self): + if os.path.exists(config.database.backupLocation): + self.backup = True + + def createBackup(self): + if self.backup: + if os.path.exists(self.source_path): + if os.path.exists(self.backup_path): + os.remove(self.backup_path) + shutil.copy(self.source_path, self.backup_path) + return True + return False diff --git a/src/logic/catalogue.py b/src/logic/catalogue.py index c1451e7..12a5ba5 100644 --- a/src/logic/catalogue.py +++ b/src/logic/catalogue.py @@ -2,15 +2,28 @@ import requests from bs4 import BeautifulSoup from src import config from src.schemas import Book - -URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndex/Search?lookfor={}+&type=AllFields&limit=10&sort=py+desc%2C+title" +from src.utils import Log +URL = "https://rds.ibs-bw.de/phfreiburg/opac/RDSIndex/Search?type0%5B%5D=allfields&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=au&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ti&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ct&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=isn&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=ta&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=co&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=py&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pp&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=pu&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=si&lookfor0%5B%5D={}&join=AND&bool0%5B%5D=AND&type0%5B%5D=zr&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND&type0%5B%5D=cc&lookfor0%5B%5D=&join=AND&bool0%5B%5D=AND" BASE = "https://rds.ibs-bw.de" +log = Log("Catalogue") class Catalogue: def __init__(self, timeout=5): self.timeout = timeout + reachable = self.check_connection() + if reachable: + config.catalogue = True + else: + config.catalogue = False + def check_connection(self): + try: + response = requests.get("https://www.google.com", timeout=self.timeout) + if response.status_code == 200: + return True + except requests.exceptions.RequestException as e: + log.error(f"Could not connect to google.com: {e}") def search_book(self, searchterm: str): response = requests.get(URL.format(searchterm), timeout=self.timeout) return response.text @@ -29,6 +42,8 @@ class Catalogue: return res def get_book(self, searchterm: str): + log.info(f"Searching for term: {searchterm}") + links = self.get_book_links(searchterm) for link in links: result = self.search(link) diff --git a/src/logic/database.py b/src/logic/database.py index 066ddde..7ba1d0d 100644 --- a/src/logic/database.py +++ b/src/logic/database.py @@ -1,72 +1,161 @@ import sqlite3 as sql import os +import shutil from src import config -from pathlib import Path -from src.schemas import USERS, MEDIA, LOANS, User, Book - +from src.schemas import USERS, MEDIA, LOANS, User, Book, Loan +from src.utils import stringToDate, Log, debugMessage as dbg +from PyQt6 import QtCore +log = Log("Database") +FILE = config.database.name class Database: def __init__(self, db_path: str = None): - """ + ''' Default constructor for the database class Args: db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None. - """ + ''' if db_path is None: - self.db_path = config.database.path + "/" + config.database.name + self.db_path = self.handle_folder_reachability(config.database.path, config.database.backupLocation) else: self.db_path = db_path + if not os.path.exists(config.database.path): - os.makedirs(config.database.path) + try: + os.makedirs(config.database.path) + except FileNotFoundError: + dbg(self.db_path) + if not os.path.exists(config.database.backupLocation): + os.makedirs(config.database.backupLocation) + #if main path does not exist, try to create it. if that fails, use the backuplocation + dbg(self.db_path) self.checkDatabaseStatus() + def handle_folder_reachability(self, original_path, backup_path): + """ + Checks if the original folder is reachable. If not, creates a backup. + If the original folder becomes reachable again, restores the backup. + + Args: + original_path (str): Path to the original folder. + backup_path (str): Path to the backup folder. + + Returns: + str: Path to the current accessible folder. + """ + + backup_file = os.path.join(backup_path, ".backup") + + if not os.path.exists(original_path): + #original folder not reachable, use backup path and create .backup file + if not os.path.exists(backup_path): + os.makedirs(backup_path) + with open(backup_file, "w") as f: + f.write("") + + # Create an empty backup file as a marker + return backup_path +"/" + FILE + + else: + dbg("Original Path Exists, using this path") + # Original folder is reachable, check for backup + if os.path.exists(backup_file): + # Restore backup + shutil.rmtree(original_path) # Remove original folder to avoid conflicts + os.rename(backup_path, original_path) + # (backup_path, original_path) + #os.remove(backup_file) + #remove backup file from original path + os.remove(original_path + "/.backup") + os.makedirs(backup_path) + return original_path +"/" + FILE + + def checkDatabasePath(self): + self.db_path = config.database.path + "/" + config.database.name + #if backup file in backup location, move database to main location, delete backup file + if os.path.exists(config.database.backupLocation + "/backup"): + if os.path.exists(self.db_path): + os.remove(self.db_path) + os.rename(f"{config.database.backupLocation}/{config.database.name}", self.db_path) + #remove backup file + os.remove(config.database.backupLocation + "/backup") + return self.db_path + else: + #keep using backup file + self.db_path = config.database.backupLocation + "/" + config.database.name + if not os.path.exists(config.database.path): + try: + os.makedirs(config.database.path) + except: + self.db_path = config.database.backupLocation + "/" + config.database.name + if not os.path.exists(config.database.backupLocation): + os.makedirs(config.database.backupLocation) + #create a backup file in the backup location + with open(f"{config.database.backupLocation}/backup.txt", "w") as f: + f.write("Backup File") + return self.db_path + def checkDatabaseStatus(self): + log.info("Checking Database Status") if self.tableCheck() == []: # self.logger.log_critical("Database does not exist, creating tables") self.createDatabase() # self.insertSubjects() def connect(self) -> sql.Connection: - """ + ''' Connect to the database Returns: sql.Connection: The active connection to the database - """ + ''' return sql.connect(self.db_path) def close_connection(self, conn: sql.Connection): - """ + ''' closes the connection to the database Args: ---- - conn (sql.Connection): the connection to be closed - """ + ''' conn.close() def createDatabase(self): - print("Creating Database") - if not os.path.exists(config.database.path): - os.makedirs(config.database.path) + log.info("Creating Database") + #print("Creating Database") + if not os.path.exists(self.db_path): + os.makedirs(self.db_path) conn = self.connect() cursor = conn.cursor() cursor.execute(USERS) + log.debug("Creating Users Table") cursor.execute(MEDIA) + log.debug("Creating Media Table") cursor.execute(LOANS) + log.debug("Creating Loans Table") conn.commit() self.close_connection(conn) + def getLoanCount(self): + query = "SELECT COUNT(*) FROM loans" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + result = cursor.fetchone() + self.close_connection(conn) + return result[0] + def tableCheck(self): # check if database has tables - """ + ''' Get the contents of the Returns: - Union[List[Tuple], None]: _description_ - """ + Union[List[Tuple], None]: Returns a list of tuples containing the table names or None if no tables are present + ''' try: with sql.connect(self.db_path) as conn: cursor = conn.cursor() @@ -79,7 +168,9 @@ class Database: conn = self.connect() cursor = conn.cursor() cursor.execute(query) - + result = cursor.fetchall() + self.close_connection(conn) + return result def checkUserExists(self, key, value) -> list[User] | bool: query = f"SELECT * FROM users WHERE {key} like '%{value}%'" conn = self.connect() @@ -91,29 +182,120 @@ class Database: users = cursor.fetchall() for index, user in enumerate(users): - users[index] = User(id=user[0], username=user[1], email=user[2]) + users[index] = User(userid=user[1], username=user[2], email=user[3], id=user[0]) self.close_connection(conn) return users def insertUser(self, username, userno, usermail): + log.debug(f"Inserting User {userno}, {username}, {usermail}") + conn = self.connect() + cursor = conn.cursor() + try: + cursor.execute( + f"INSERT INTO users (user_id,username, usermail) VALUES ('{userno}', '{username}', '{usermail}' )" + ) + conn.commit() + except sql.IntegrityError: + return False + self.close_connection(conn) + return True + + def getUserBy(self, key, value) -> User: + conn = self.connect() + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM users WHERE {key} = '{value}'") + result = cursor.fetchone() + self.close_connection(conn) + user = User(userid=result[1], username=result[2], email=result[3], id=result[0]) + return user + + def getUser(self, user_id) -> User: + + conn = self.connect() + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM users") + result = cursor.fetchall() + self.close_connection(conn) + if len(str(user_id)) == 1: + for res in result: + if res[0] == user_id: + user = User(userid=res[1], username=res[2], email=res[3], id=res[0]) + dbg(f"Returning User {user}") + log.info(f"Returning User {user}") + return user + else: + for res in result: + if res[1] == user_id: + user = User(userid=res[1], username=res[2], email=res[3], id=res[0]) + dbg(f"Returning User {user}") + log.info(f"Returning User {user}") + return user + raise ValueError(f"User {user_id} not found") + #return User(userid="gelöscht", username="gelöscht", email="gelöscht", id="gelöscht") + # user = User(userid=result[1], username=result[2], email=result[3],id = result[0]) + # return user + + def getUserId(self, username) -> User: + conn = self.connect() + cursor = conn.cursor() + cursor.execute(f"SELECT * FROM users WHERE username = '{username}'") + result = cursor.fetchone() + self.close_connection(conn) + user = User(userid=result[1], username=result[2], email=result[3],id = result[0]) + log.info(f"Returning User {user}") + return user + + def updateUser(self, username, user_id, usermail): + log.debug(f"Updating User {userno}, {username}, {usermail}") conn = self.connect() cursor = conn.cursor() cursor.execute( - f"INSERT INTO users (id,username, usermail) VALUES ('{userno}', '{username}', '{usermail}' )" + f"UPDATE users SET username = '{username}', usermail = '{usermail}' WHERE user_id = '{user_id}'" ) conn.commit() self.close_connection(conn) - - def getUser(self, userid) -> User: + + def setUserActiveDate(self, userid,date): + query = f"UPDATE users SET lastActive = '{date}' WHERE user_id = '{userid}'" conn = self.connect() cursor = conn.cursor() - cursor.execute(f"SELECT * FROM users WHERE id = '{userid}'") - result = cursor.fetchone() + cursor.execute(query) + conn.commit() + dbg(f"Setting User {userid} to active on {date}") + + def renameInactiveUsers(self): + lastYear = QtCore.QDate.currentDate().addDays(int(f"-{config.delete_inactive_user_duration}")).toString("yyyy-MM-dd") + query = f"SELECT id FROM users WHERE lastActive < '{lastYear}'" + conn = self.connect() + cursor = conn.cursor() + result = cursor.execute(query).fetchall() self.close_connection(conn) - user = User(id=result[0], username=result[1], email=result[2]) - return user - + if len(result) == 0: + log.info(f"Deleting {len(result)} inactive users") + for user in result: + hasLoans = self.hasLoans(user[0]) + if not hasLoans: + self.deleteUser(user) + + def hasLoans(self, userid)->bool: + query = f"SELECT * FROM loans WHERE user_id = '{userid}' AND returned = 0" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + result = cursor.fetchall() + self.close_connection(conn) + return False if len(result) == 0 else True + + def deleteUser(self, userid): + log.debug(f"Deleting User {userid}") + conn = self.connect() + cursor = conn.cursor() + cursor.execute(f"UPDATE users SET username='gelöscht', usermail = 'gelöscht', user_id='gelöscht' WHERE id = '{userid}'") + conn.commit() + self.close_connection(conn) + def getActiveLoans(self, userid): + dbg("id", str(userid)) conn = self.connect() cursor = conn.cursor() try: @@ -124,9 +306,37 @@ class Database: except sql.OperationalError: result = [] self.close_connection(conn) + log.info(f"Returning Active Loans {result}") return str(len(result)) + def getMediaList(self): + query = "SELECT signature FROM media" + result = self.query(query) + + return [res[0] for res in result] + def getAllLoans(self): + loan_data = [] + query = "SELECT * FROM loans" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + loans = cursor.fetchall() + for loan in loans: + l = Loan( + loan[0], + loan[1], + loan[2], + stringToDate(loan[3]), + stringToDate(loan[4]), + loan[5], + stringToDate(loan[6]), + self.getMedia(loan[2]), + user_name=self.getUser(loan[1]).username, + ) + loan_data.append(l) + return loan_data def insertLoan(self, userid, mediaid, loandate, duedate): + log.debug(f"Inserting Loan {userid}, {mediaid}, {loandate}, {duedate}") query = f"INSERT INTO loans (user_id, media_id, loan_date, return_date) Values ('{userid}', '{mediaid}', '{loandate}', '{duedate}')" conn = self.connect() cursor = conn.cursor() @@ -135,15 +345,51 @@ class Database: self.close_connection(conn) def insertMedia(self, media): - query = f"INSERT OR IGNORE INTO media (ppn, title, signature, isbn) VALUES ('{media.ppn}', '{media.title}', '{media.signature}', '{media.isbn}')" # , '{media.link}' + log.debug(f"Inserting Media {media}") + query = f"INSERT OR IGNORE INTO media (ppn, title, signature, isbn,link) VALUES ('{media.ppn}', '{media.title}', '{media.signature}', '{media.isbn}','{media.link}')" # , '{media.link}' + log.info(f"Query: |{query}|") conn = self.connect() cursor = conn.cursor() cursor.execute(query) + conn.commit() + self.close_connection(conn) return cursor.lastrowid + def getLoansBy(self, field, value): + # query all loans, sort by date descending and return + pass + def getMediaSimilarSignatureByID(self, media_id) -> list[Book]: + log.info(f"Getting Media Similar to {media_id}") + query = f"SELECT * FROM media WHERE id = '{media_id}'" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + result = cursor.fetchone() + signature = result[1] + #print(signature) + query = f"SELECT * FROM media WHERE signature LIKE '%{signature}%'" + cursor.execute(query) + result = cursor.fetchall() + + self.close_connection(conn) + data = [] + for res in result: + data.append( + Book( + signature=res[1], + isbn=res[2], + ppn=res[3], + title=res[4], + database_id=res[0], + ) + ) + log.debug(f"Returning Similar Media {data}") + return data + def getMedia(self, media_id): + # log.info(f"Getting Media {media_id}") query = f"SELECT * FROM media WHERE id = '{media_id}'" conn = self.connect() cursor = conn.cursor() @@ -159,7 +405,27 @@ class Database: ) return res + def getAllMedia(self, user_id): + # get all books that have the user id in the loans table + query = f"SELECT * FROM loans WHERE user_id = '{user_id}'" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + result = cursor.fetchall() + self.close_connection(conn) + books = [] + for res in result: + book = self.getMedia(res[2]) + book.loan_from = res[3] + book.loan_to = res[4] + book.returned = res[5] + book.returned_date = res[6] + books.append(book) + log.info(f"Returning All Media entries ({len(books)}) for user {user_id}") + return books + def checkMediaExists(self, media): + log.info(f"Checking Media {media}") conn = self.connect() cursor = conn.cursor() query = f"SELECT id, signature FROM media WHERE ppn = '{media.ppn}' OR title = '{media.title}' OR isbn = '{media.isbn}' OR signature = '{media.signature}'" @@ -174,6 +440,7 @@ class Database: return False def checkLoanState(self, book_id): + log.info(f"Checking Loan State for {book_id}") query = f"SELECT * FROM loans WHERE media_id = '{book_id}' AND returned = 0" conn = self.connect() cursor = conn.cursor() @@ -182,8 +449,9 @@ class Database: self.close_connection(conn) return result - def returnMedia(self, media_id): - query = f"UPDATE loans SET returned = 1 WHERE media_id = '{media_id}'" + def returnMedia(self, media_id, returndate): + log.info(f"Returning Media {media_id}") + query = f"UPDATE loans SET returned = 1, returned_date = '{returndate}' WHERE media_id = '{media_id}' AND returned = 0" conn = self.connect() cursor = conn.cursor() cursor.execute(query) @@ -214,11 +482,23 @@ class Database: # def selectClosestReturnDate(self, user_id): + log.info(f"Selecting Closest Return Date for {user_id}") query = f"SELECT return_date FROM loans WHERE user_id = '{user_id}' AND returned = 0 ORDER BY return_date ASC LIMIT 1" conn = self.connect() cursor = conn.cursor() cursor.execute(query) result = cursor.fetchone() + dbg("Result", response=result) self.close_connection(conn) if result is not None: return result[0] + + def extendLoanDuration(self, signature, newDate): + log.info(f"Extending Loan Duration for {signature} to {newDate}") + book_id = self.checkMediaExists(Book(signature=signature)) + query = f"UPDATE loans SET return_date = '{newDate}' WHERE media_id = '{book_id[0]}' AND returned = 0" + conn = self.connect() + cursor = conn.cursor() + cursor.execute(query) + conn.commit() + self.close_connection(conn) diff --git a/src/logic/documentation_thread.py b/src/logic/documentation_thread.py new file mode 100644 index 0000000..0ebd344 --- /dev/null +++ b/src/logic/documentation_thread.py @@ -0,0 +1,9 @@ +from PyQt6.QtCore import QThread, pyqtSignal +from src.utils import launch_documentation + +class DocumentationThread(QThread): + def __init__(self): + super().__init__() + + def run(self): + launch_documentation() \ No newline at end of file diff --git a/src/schemas/__init__.py b/src/schemas/__init__.py index 68e5795..6b2c6aa 100644 --- a/src/schemas/__init__.py +++ b/src/schemas/__init__.py @@ -1,3 +1,4 @@ from .database import LOANS, MEDIA, USERS from .user import User -from .book import Book \ No newline at end of file +from .book import Book +from .loan import Loan \ No newline at end of file diff --git a/src/schemas/book.py b/src/schemas/book.py index 1e8e292..76893c8 100644 --- a/src/schemas/book.py +++ b/src/schemas/book.py @@ -3,10 +3,14 @@ from dataclasses import dataclass @dataclass class Book: - title: str | None = None - ppn: int | None = None - signature: str | None = None - isbn: str | None = None - link: str | None = None - database_id: int | None = None - link: str | None = None + title: str = "" + ppn: int = "" + signature: str = "" + isbn: str = "" + link: str = "" + database_id: int = "" + link: str = "" + loan_from: str = "" + loan_to: str = "" + returned: int = "" + returned_date: str = "" diff --git a/src/schemas/database.py b/src/schemas/database.py index 614e947..0ca4ba2 100644 --- a/src/schemas/database.py +++ b/src/schemas/database.py @@ -1,15 +1,18 @@ USERS = """CREATE TABLE IF NOT EXISTS users ( -id INTEGER PRIMARY KEY, +id INTEGER PRIMARY KEY AUTOINCREMENT, +user_id INTEGER NOT NULL, username TEXT NOT NULL, -usermail TEXT NOT NULL); +usermail TEXT NOT NULL, +lastActive TEXT); """ # id == matrikelnr, +#matrikelnr TEXT NOT NULL, MEDIA = """CREATE TABLE IF NOT EXISTS media ( id INTEGER PRIMARY KEY AUTOINCREMENT, signature TEXT NOT NULL, -isbn TEXT NOT NULL, -ppn TEXT NOT NULL, +isbn TEXT, +ppn TEXT, title TEXT NOT NULL, -link TEXT NOT NULL,); +link TEXT); """ LOANS = """CREATE TABLE IF NOT EXISTS loans ( @@ -19,6 +22,7 @@ media_id INTEGER NOT NULL, loan_date TEXT NOT NULL, return_date TEXT NOT NULL, returned INTEGER DEFAULT 0, +returned_date TEXT, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (media_id) REFERENCES media(id)); """ diff --git a/src/schemas/loan.py b/src/schemas/loan.py new file mode 100644 index 0000000..1e4c8cf --- /dev/null +++ b/src/schemas/loan.py @@ -0,0 +1,15 @@ +from .book import Book +from dataclasses import dataclass + + +@dataclass +class Loan: + id: int + user_id: int + media_id: int + loan_date: str + return_date: str + returned: int + returned_date: str + book: Book + user_name: str \ No newline at end of file diff --git a/src/schemas/user.py b/src/schemas/user.py index 3a9ddd2..35a56dd 100644 --- a/src/schemas/user.py +++ b/src/schemas/user.py @@ -1,11 +1,12 @@ from dataclasses import dataclass - +from typing import Any @dataclass class User: username: str - id: int + userid: Any email: str + id : int = None def __repr__(self): - return f"Name: {self.username}\nMatrikelnr.: {self.id}\neMail: {self.email}" + return f"Name: {self.username}\nMatrikelnr.: {self.userid}\neMail: {self.email}" diff --git a/src/ui/createUser.py b/src/ui/createUser.py index 6481aa3..b90cd97 100644 --- a/src/ui/createUser.py +++ b/src/ui/createUser.py @@ -1,13 +1,15 @@ from .sources.Ui_dialog_createUser import Ui_Dialog from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6.QtGui import QRegularExpressionValidator from src.logic import Database -from src.schemas import User - +from src.utils import Icon, Log class CreateUser(QtWidgets.QDialog, Ui_Dialog): def __init__(self, fieldname, data): super(CreateUser, self).__init__() self.setupUi(self) + self.setWindowTitle("Benutzer erstellen") + self.setWindowIcon(Icon("user").icon) # disable buttonbox save self.db = Database() self.buttonBox.button( @@ -25,12 +27,22 @@ class CreateUser(QtWidgets.QDialog, Ui_Dialog): QtWidgets.QDialogButtonBox.StandardButton.Save ).clicked.connect(self.saveUser) self.userid = None - + self.userno.setMaxLength(20) + self.user_mail.setValidator( + QRegularExpressionValidator( + QtCore.QRegularExpression( + r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}" + ) + ) + ) + self.userno.textChanged.connect( + lambda: self.validateInputUserno(self.userno.text(), "int") + ) def checkFields(self): if ( - self.username.text() != "" - and self.userno.text() != "" - and self.user_mail.text() != "" + self.username.hasAcceptableInput() + and self.userno.hasAcceptableInput() + and self.user_mail.hasAcceptableInput() ): self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Save @@ -44,5 +56,33 @@ class CreateUser(QtWidgets.QDialog, Ui_Dialog): username = self.username.text() userno = int(self.userno.text()) usermail = self.user_mail.text() - self.db.insertUser(username, userno, usermail) - self.userid = userno + if self.db.insertUser(username, userno, usermail): + self.userid = userno + else: + self.setStatusTipMessage( + "Benutzer konnte nicht erstellt werden, bitte überprüfen Sie die Eingaben" + ) + + def setStatusTipMessage(self, message): + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Information") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Information) + dialog.setWindowIcon(Icon("error").overwriteColor("#EA3323")) + dialog.setText(message) + dialog.exec() + + def validateInputUserno(self, value, type): + lastchar = value[-1] if value else "" + # if lastchar is not of the type, remove it + if type == "int": + if not lastchar.isdigit(): + self.userno.setText(value[:-1]) + + def validateInputMail(self, value): + pass + + +def launch(): + app = QtWidgets.QApplication([]) + window = CreateUser("id", "123456") + window.exec() \ No newline at end of file diff --git a/src/ui/extendLoan.py b/src/ui/extendLoan.py new file mode 100644 index 0000000..82e43ef --- /dev/null +++ b/src/ui/extendLoan.py @@ -0,0 +1,32 @@ +from .sources.Ui_dialog_extendLoanDuration import Ui_Dialog +from PyQt6 import QtWidgets, QtCore +from src.utils import Icon + +class ExtendLoan(QtWidgets.QDialog, Ui_Dialog): + def __init__(self, user, media): + super(ExtendLoan, self).__init__() + self.setupUi(self) + self.setWindowTitle("Leihfrist verlängern") + self.setWindowIcon(Icon("loan_extend").icon) + self.user = user + self.media = media + self.currentDate = QtCore.QDate.currentDate().addDays(1) + self.extendDate = "" + self.extenduntil.setMinimumDate(self.currentDate) + self.show() + self.buttonBox.accepted.connect(self.extendLoan) + + def extendLoan(self): + #print("Extend Loan") + selectedDate = self.extenduntil.selectedDate() + #print(selectedDate) + self.extendDate = selectedDate + self.close() + + +def launch(user, media): + import sys + + app = QtWidgets.QApplication(sys.argv) + window = ExtendLoan(user, media) + sys.exit(app.exec()) diff --git a/src/ui/loans.py b/src/ui/loans.py new file mode 100644 index 0000000..ce86e52 --- /dev/null +++ b/src/ui/loans.py @@ -0,0 +1,138 @@ +from .sources.Ui_main_Loans import Ui_MainWindow +from PyQt6 import QtCore, QtGui, QtWidgets +from .user import UserUI +from src.logic import Database +from src.utils import stringToDate, Icon +from src.utils import debugMessage as dbg +from icecream import ic + +TABLETOFIELDTRANSLATE = { + "Titel": "book.title", + "Signatur": "book.signature", + "Nutzer": "user_name", +} + + +class LoanWindow(QtWidgets.QMainWindow, Ui_MainWindow): + def __init__(self): + super(LoanWindow, self).__init__() + self.setupUi(self) + self.setWindowTitle("Ausleihhistorie") + self.setWindowIcon(Icon("borrow").icon) + self.loanTable.horizontalHeader().setSectionResizeMode( + QtWidgets.QHeaderView.ResizeMode.Stretch + ) + self.db = Database() + self.loans = [] + self.loadLoans() + + # lineedits + self.searchbar.textChanged.connect(self.limitResults) + self.searchbar.returnPressed.connect(self.selfpass) + + # radio buttons + self.radio_all.clicked.connect(self.filterResults) + self.radio_current.clicked.connect(self.filterResults) + self.radio_overdue.clicked.connect(self.filterResults) + + # table + self.loanTable.doubleClicked.connect(self.showUser) + self.show() + + def selfpass(self): + pass + + + def insertRow(self, data): + dbg(contents=data) + + retdate = "" + if data.returned_date != "": + retdate = stringToDate(data.returned_date).toString("dd.MM.yyyy") + ic(retdate) + self.loanTable.insertRow(0) + self.loanTable.setItem(0, 0, QtWidgets.QTableWidgetItem(data.book.isbn)) + self.loanTable.setItem(0, 1, QtWidgets.QTableWidgetItem(data.book.signature)) + self.loanTable.setItem(0, 2, QtWidgets.QTableWidgetItem(data.book.title)) + self.loanTable.setItem( + 0, + 3, + QtWidgets.QTableWidgetItem(str(self.db.getUser(data.user_id).username)), + ) + self.loanTable.setItem( + 0, + 4, + QtWidgets.QTableWidgetItem( + stringToDate(data.loan_date).toString("dd.MM.yyyy") + ), + ) + self.loanTable.setItem( + 0, + 5, + QtWidgets.QTableWidgetItem( + stringToDate(data.return_date).toString("dd.MM.yyyy") + ), + ) + self.loanTable.setItem(0, 6, QtWidgets.QTableWidgetItem(retdate)) + + def loadLoans(self): + loans = self.db.getAllLoans() + for loan in loans: + self.insertRow(loan) + self.loans = loans + + def filterResults(self): + mode = ( + "all" + if self.radio_all.isChecked() + else "current" + if self.radio_current.isChecked() + else "overdue" + ) + self.loanTable.setRowCount(0) + today = QtCore.QDate.currentDate() + for loan in self.loans: + # convert string to Qdate + date = loan.return_date + qdate = stringToDate(date) + # current mode + if mode == "current": + if loan.returned == 1: + continue + elif mode == "overdue": + if loan.returned_date != "": + continue + else: + if qdate > today: + continue + self.insertRow(loan) + + def limitResults(self): + limiter = self.searchbar.text().lower() + limiter = str(limiter) + searchfield = self.searchFields.currentText() + searchfield = TABLETOFIELDTRANSLATE[searchfield] + dbg(limiter=limiter, search=searchfield) + self.loanTable.setRowCount(0) + for loan in self.loans: + fielddata = eval(f"loan.{searchfield}") + if isinstance(fielddata, str): + fielddata = fielddata.lower() + if limiter in fielddata: + self.insertRow(loan) + + def showUser(self): + row = self.loanTable.currentRow() + user_name = self.loanTable.item(row, 3).text() + user = self.db.getUserId(user_name) + self.user = UserUI(user.username, user.id, user.email) + self.user.show() + + +def launch(): + import sys + + app = QtWidgets.QApplication(sys.argv) + main_ui = LoanWindow() + # atexit.register(exit_handler) + sys.exit(app.exec()) diff --git a/src/ui/main_ui.py b/src/ui/main_ui.py index a7a2977..c6d2603 100644 --- a/src/ui/main_ui.py +++ b/src/ui/main_ui.py @@ -1,224 +1,498 @@ -import ast +import sys +import atexit +import datetime +import webbrowser +from src import config, __email__, docport +from src.logic import Database, Catalogue, Backup +from src.utils import stringToDate, Icon, Log +from src.utils import debugMessage as dbg +from src.utils.createReport import generate_report +from src.schemas import Book from .sources.Ui_main_UserInterface import Ui_MainWindow from .user import UserUI from .createUser import CreateUser from .multiUserInfo import MultiUserFound from .newentry import NewEntry -from src import config -from src.logic import Database, Catalogue -from src.utils.stringtodate import stringToDate -from src.schemas import User, Book -from PyQt6 import QtCore, QtGui, QtWidgets -import sys +from .settings import Settings +from .newBook import NewBook +from .loans import LoanWindow +from .reportUi import ReportUi +from PyQt6 import QtCore, QtWidgets +from omegaconf import OmegaConf +from src.logic.documentation_thread import DocumentationThread +backup = Backup() +cat = Catalogue() +log = Log("main") +dbg(backup=config.database.do_backup, catalogue=config.catalogue) +def getShortcut(shortcuts, name): + shortcut = [cut for cut in shortcuts if cut["name"] == name][0] + return shortcut["current"] class MainUI(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): super(MainUI, self).__init__() self.setupUi(self) - self.db = Database() - self.currentDate = QtCore.QDate.currentDate() + self.setWindowTitle(f"Handbibliotheksleihsystem {config.institution_name}") + self.setWindowIcon(Icon("main").icon) self.label_7.hide() self.nextReturnDate.hide() - self.activeUser = None # add default loan duration to current date - loanDate = self.currentDate.addDays(config.default_loan_duration) - self.duedate.setDate(loanDate) # hotkeys self.actionRueckgabemodus.triggered.connect(self.changeMode) self.actionNutzer.triggered.connect(self.showUser) - - #Buttons + self.actionEinstellungen.triggered.connect(self.showSettings) + self.actionAusleihhistorie.triggered.connect(self.showLoanHistory) + self.actionBericht_erstellen.triggered.connect(self.generateReport) + self.actionDokumentation_ffnen.triggered.connect(self.openDocumentation) + self.actionBeenden.triggered.connect(self.shutdown) + def __mail(): + webbrowser.open(f"mailto:{__email__}") + self.actionProblem_melden.triggered.connect(__mail) + #if close button is pressed call shutdown + self.closeEvent = self.shutdown + # Buttons self.btn_show_lentmedia.clicked.connect(self.showUser) + self.btn_createNewUser.clicked.connect(self.createUser) + self.btn_createNewUser.setText("") + self.btn_createNewUser.setIcon(Icon("add_user").overwriteColor("#1E90FF")) + self.mode.clicked.connect(self.changeMode) # LineEdits self.input_userno.returnPressed.connect( - lambda: self.checkUser("id", self.input_userno.text()) + lambda: self.checkUser("user_id", self.input_userno.text()) ) self.input_username.returnPressed.connect( lambda: self.checkUser("username", self.input_username.text()) ) - self.input_file_ident.returnPressed.connect(self.addMedia) - self.input_userno.setValidator(QtGui.QIntValidator()) - + self.input_file_ident.returnPressed.connect(self.handleLineInput) + self.input_userno.setMaxLength(40) + self.input_userno.textChanged.connect( + lambda: self.validateInput(self.input_userno.text(), "int") + ) + self.input_username.setEnabled(False) + self.input_userno.setEnabled(False) + self.duedate.setEnabled(False) # TableWidget # set header size to be width/number of columns self.mediaOverview.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeMode.Stretch ) - # self.input_userno. + self.input_file_ident.setFocus() + self.assignShortcuts() + # variables + self.db = Database() + self.currentDate = QtCore.QDate.currentDate() + loanDate = self.currentDate.addDays(config.loan_duration) + self.activeUser = None + self.activeState = "Rückgabe" + self.docu = DocumentationThread() + if config.documentation: + self.docu.start() + self.duedate.setDate(loanDate) + # functions + self.activateReturnMode() + + if backup.backup: + log.info("Backup enabled") + else: + log.warning("Backup disabled") self.show() + + def shutdown(self, *args): + #kill documentation thread + log.info("Shutting down") + if config.documentation: + self.docu.terminate() + sys.exit() + + + def assignShortcuts(self): + shortcuts = config.shortcuts + shortcuts = OmegaConf.to_container(shortcuts) + #convert to dictconfig + + self.actionDokumentation_ffnen.setShortcut(getShortcut(shortcuts, "Hilfe")) + self.actionAusleihhistorie.setShortcut(getShortcut(shortcuts, "Ausleihhistorie")) + self.actionBericht_erstellen.setShortcut(getShortcut(shortcuts, "Bericht_erstellen")) + self.actionNutzer.setShortcut(getShortcut(shortcuts, "Nutzer")) + self.actionRueckgabemodus.setShortcut(getShortcut(shortcuts, "Rueckgabemodus")) + + def generateReport(self): + log.info("Generating Report") + report = ReportUi() + report.exec() + + def showLoanHistory(self): + log.info("Showing Loan History") + self.loan = LoanWindow() + self.loan.show() + + def validateInput(self, value, type): + lastchar = value[-1] if value else "" + # if lastchar is not of the type, remove it + if type == "int": + if not lastchar.isdigit(): + self.input_userno.setText(value[:-1]) + + def showSettings(self): + log.info("Showing Settings") + settings = Settings() + settings.exec() + result = settings.result() + print(settings.settingschanged, settings.restart_required) + if result == 1: + #dialog to ask if program should be restarted + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Einstellungen geändert") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Information) + dialog.setWindowIcon(Icon("settings").icon) + dialog.setText("Einstellungen wurden geändert\nProgramm neu starten?") + dialog.setStandardButtons( + QtWidgets.QMessageBox.StandardButton.Yes + | QtWidgets.QMessageBox.StandardButton.No + ) + dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) + #translate buttons + yes = dialog.button(QtWidgets.QMessageBox.StandardButton.Yes) + yes.setText("Ja") + no = dialog.button(QtWidgets.QMessageBox.StandardButton.No) + no.setText("Nein") + dialog.exec() + result = dialog.result() + if result == QtWidgets.QMessageBox.StandardButton.Yes: + self.restart() + # reload settings + #print(config) + + def openDocumentation(self): + log.info("Opening Documentation") + if config.documentation: + webbrowser.open("http://localhost:{}/".format(docport)) + else: + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Dokumentation nicht verfügbar") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) + dialog.setWindowIcon(Icon("warning").icon) + dialog.setText("Dokumentation nicht verfügbar") + dialog.exec() + + + + + def restart(self): + #log restart + dbg("Restarting Program") + import os + python_executable = sys.executable + args = sys.argv[:] + args.insert(0, sys.executable) + os.execvp(python_executable, args) + def changeMode(self): - current = self.mode.text() - if current == "Ausleihe": - self.mode.setText("Rückgabe") - self.input_username.clear() - self.input_userno.clear() - self.userdata.clear() - self.btn_show_lentmedia.setText("") - self.label_7.hide() - self.nextReturnDate.hide() - if current == "Rückgabe": - self.input_username.clear() - self.input_userno.clear() - self.userdata.clear() - self.mediaOverview.clearContents() - self.btn_show_lentmedia.setText("") - self.input_file_ident.clear() - self.nextReturnDate.hide() - self.label_7.hide() + log.info("Changing Mode") + dbg(f"Current mode: {self.activeState}") + self.input_username.clear() + stayReturn = False + if config.advanced_refresh and self.userdata.toPlainText() != "": + stayReturn = True + self.userdata.clear() + self.input_userno.clear() + self.btn_show_lentmedia.setText("") + self.input_file_ident.clear() + self.label_7.hide() + self.nextReturnDate.hide() + self.mediaOverview.setRowCount(0) + self.activeUser = None #! remove if last user should be kept + if self.activeState == "Rückgabe": + if stayReturn: + self.activateReturnMode() + else: self.activateLoanMode() + + + else: + self.activateReturnMode() + + def activateLoanMode(self): + dbg("Activating Loan Mode") + self.input_username.setEnabled(True) + self.input_userno.setEnabled(True) + self.duedate.setEnabled(True) + self.input_username.setPlaceholderText("") + self.input_userno.setPlaceholderText("") + self.input_userno.setFocus() + # set mode background color to blue with rounded edges + self.mode.setStyleSheet("background-color: #1E90FF") + self.mode.setText("Ausleihe") + self.activeState = "Ausleihe" + if self.input_userno.text() == "" or self.input_username.text() == "": + self.input_file_ident.setEnabled(False) + self.input_file_ident.setPlaceholderText("Bitte zuerst Nutzerdaten eingeben") + else: + self.input_file_ident.setEnabled(True) + + def activateReturnMode(self): + dbg("Activating Return Mode") + self.input_username.setEnabled(False) + self.input_userno.setEnabled(False) + # set mode background color to orange + self.mode.setStyleSheet("background-color: #FFA500") + self.duedate.setEnabled(False) + self.activeState = "Rückgabe" + self.mode.setText("Rückgabe") + self.input_file_ident.setEnabled(True) + self.input_file_ident.setPlaceholderText("Buchidentifikation eingeben") + self.input_username.setPlaceholderText("Bitte erst in den Ausleihmodus wechseln") + self.input_userno.setPlaceholderText("Bitte erst in den Ausleihmodus wechseln") def showUser(self): + log.info(f"Showing User {self.activeUser}") if self.activeUser is None: # create warning dialog + log.info("Showing no user selected warning") + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Kein Nutzer ausgewählt") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) + dialog.setWindowIcon(Icon("warning").overwriteColor("#EA3323")) + dialog.setText("Kein Nutzer ausgewählt") + dialog.exec() return self.user_ui = UserUI( - self.activeUser.username, self.activeUser.id, self.activeUser.email + self.activeUser ) # self.user_ui.setFields("John Doe", "123456789", "test@mail.com") self.user_ui.show() - + def setUserData(self): - self.input_username.setText(self.activeUser.username) - self.input_userno.setText(str(self.activeUser.id)) + log.info("Setting User Data") + self.input_username.setText(str(self.activeUser.username)) + self.input_userno.setText(str(self.activeUser.userid)) self.userdata.setText(self.activeUser.__repr__()) + today = QtCore.QDate.currentDate().toString("yyyy-MM-dd") + self.db.setUserActiveDate(self.activeUser.userid, today) + + def createUser(self): + log.info("Creating User") + user = CreateUser(fieldname="id", data="") + user.exec() + userid = user.userid + print(userid) + if userid: + log.info(f"User created {userid}") + data = self.db.getUserBy("user_id", userid) + self.activeUser = data + # set user to active user + self.setUserData() + self.activateLoanMode() + self.input_file_ident.setPlaceholderText("Buchidentifikation eingeben") + + self.input_file_ident.setFocus() + + return def checkUser(self, fieldname, data): - print("Checking User", fieldname, data) + log.info(f"Checking User {fieldname}, {data}") + #print("Checking User", fieldname, data) # set fieldname as key and data as variable user = self.db.checkUserExists(fieldname, data) if not user: - user = CreateUser(fieldname, data) - user.exec() - userid = user.userid - if userid: - data = self.db.getUser(userid) - self.activeUser = data + warning = QtWidgets.QMessageBox() + warning.setWindowTitle("Nutzer nicht gefunden") + warning.setIcon(QtWidgets.QMessageBox.Icon.Warning) + warning.setWindowIcon(Icon("warning").overwriteColor("#EA3323")) + warning.setText("Nutzer nicht gefunden, bitte erst anlegen") + warning.exec() + self.input_username.clear() + self.input_userno.clear() + return else: if len(user) > 1: + log.info("Multiple Users found") multi = MultiUserFound(user) multi.exec() self.activeUser = multi.userdata else: + #print("User found", user[0]) self.activeUser = user[0] if self.activeUser is not None: + log.info(f"User found {self.activeUser}") + #print("User found", self.activeUser) self.setUserData() self.input_file_ident.setFocus() self.mode.setText("Ausleihe") - self.btn_show_lentmedia.setText(self.db.getActiveLoans(self.activeUser.id)) + #print(self.activeUser.__dict__) + loans = self.db.getActiveLoans(self.activeUser.id) + dbg(loans=loans) + self.btn_show_lentmedia.setText(loans) retdate = self.db.selectClosestReturnDate(self.activeUser.id) - date = stringToDate(retdate) - self.nextReturnDate.setText(date) - self.nextReturnDate.show() - self.label_7.show() - + if retdate: + date = stringToDate(retdate).toString("dd.MM.yyyy") + self.nextReturnDate.setText(date) + self.nextReturnDate.show() + self.label_7.show() + self.input_file_ident.setEnabled(True) + self.input_file_ident.setPlaceholderText("Buchidentifikation eingeben") + self.input_file_ident.setFocus() def moveToLine(self, line): + log.debug("Moving to Line", line) line.setFocus() + def handleLineInput(self): + value = self.input_file_ident.text().strip() + log.debug(f"Handling Line Input {value}") + if self.mode.text() == "Rückgabe": + if value in self.db.getMediaList(): + self.returnMedia(value) + else: + # create warning dialog + log.info("Invalid Input") + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Ungültige Eingabe") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) + dialog.setWindowIcon(Icon("warning").overwriteColor("#EA3323")) + dialog.setText("Eingabe ist nicht in der Datenbank\nBitte prüfen und erneut eingeben") + dialog.exec() + self.input_file_ident.setFocus() + self.input_file_ident.clear() + return + else: + if not " " in value: + # create warning dialog + log.info("Invalid Input") + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Ungültige Eingabe") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) + dialog.setWindowIcon(Icon("warning").overwriteColor("#EA3323")) + dialog.setText( + "Die Eingabe enthält kein Leerzeichen\nBitte prüfen und erneut eingeben" + ) + dialog.exec() + return + self.mediaAdd(value) + self.input_file_ident.setFocus() def mediaAdd(self, identifier): - print("Adding Media", identifier) - user_id = self.activeUser.id - cat = Catalogue() - media = cat.get_book(identifier) + dbg("Adding Media", identifier = identifier) + self.input_file_ident.clear() + self.input_file_ident.setEnabled(False) + user_id = self.activeUser.id + media = Book(signature=identifier) book_id = self.db.checkMediaExists(media) - print(book_id) - if book_id: - if len(book_id) > 1: - print("Multiple Books found") + dbg(f"Book ID: {book_id}, User ID: {user_id}", media=media) + if not book_id: + dbg("Book not found, searching catalogue") + if config.catalogue == True: + media = cat.get_book(identifier) + if not media: + self.setStatusTipMessage("Buch nicht gefunden") + self.input_file_ident.setEnabled(True) + return + book_id = self.db.insertMedia(media) + self.loanMedia(user_id, [book_id]) + else: + newbook = NewBook() + newbook.exec() + if newbook.result() == 1: + media = newbook.book + book_id = self.db.insertMedia(media) + + elif book_id: + if isinstance(book_id, list) and len(book_id) > 1: + #print("Multiple Books found") + # TODO: implement book selection dialog return else: + if isinstance(book_id, int): + book_id = [book_id] # check if book is already loaned loaned = self.db.checkLoanState(book_id[0]) if loaned: - print("Book already loaned") - self.setStatusTip("Book already loaned") + #print("Book already loaned") + self.setStatusTipMessage("Buch bereits entliehen") + # dialog with yes no to create new entry + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Buch bereits entliehen") + dialog.setWindowIcon(Icon("duplicate").icon) + dialog.setText( + "Buch bereits entliehen, soll ein neues hinzugefügt werden?" + ) + dialog.setStandardButtons( + QtWidgets.QMessageBox.StandardButton.Yes + | QtWidgets.QMessageBox.StandardButton.No + ) + dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.No) + dialog.exec() + result = dialog.result() + if result == QtWidgets.QMessageBox.StandardButton.No: + self.input_file_ident.setEnabled(True) + return + newentry = NewEntry([book_id[0]]) + newentry.exec() + if newentry.result() == 1: # only create dialog if new entry was created + self.setStatusTipMessage("Neues Exemplar hinzugefügt") + #print(created_ids) + self.input_file_ident.setEnabled(True) + newentries = newentry.newIds + if newentries: + for entry in newentries: + book = self.db.getMedia(entry) + self.loanMedia(user_id, [entry], book) + dbg("inserted duplicated book into database") return else: - print("Book not loaned, loaning now") - self.db.insertLoan( - user_id, - book_id[0], - self.currentDate.toString(), - self.duedate.date().toString(), - ) - self.mediaOverview.insertRow(0) - self.mediaOverview.setItem( - 0, 0, QtWidgets.QTableWidgetItem(media.isbn) - ) - self.mediaOverview.setItem( - 0, 1, QtWidgets.QTableWidgetItem(media.title) - ) - self.mediaOverview.setItem( - 0, 2, QtWidgets.QTableWidgetItem("Entliehen") - ) + #print("Book not loaned, loaning now") + self.loanMedia(user_id, book_id) - # print(media) - # if media: - # print(book_id, type(book_id)) - # loaned = self.db.checkLoanState(book_id) - # print(loaned) - # if self.db.checkMediaExists(media): - # print("Book already exists", book_id) - # else: - # book_id = self.db.insertMedia(media) - # print(book_id) - # self.db.insertLoan( - # self.activeUser.id, - # book_id, - # self.currentDate.toString(), - # self.duedate.date().toString(), - # ) - - # self.mediaOverview.insertRow(0) - # self.mediaOverview.setItem(0, 0, QtWidgets.QTableWidgetItem(media.isbn)) - # self.mediaOverview.setItem(0, 1, QtWidgets.QTableWidgetItem(media.title)) - # self.mediaOverview.setItem(0, 2, QtWidgets.QTableWidgetItem("Entliehen")) - - # # add media to database - # # check if book present in database - # print(self.db.getActiveLoans(self.activeUser.id)) - # self.btn_show_lentmedia.setText(self.db.getActiveLoans(self.activeUser.id)) - - def callShortcut(self, shortcut): - print("Calling Shortcut", shortcut) - # check if actions have shortcut key and call them - sysem_shortcuts = None - print(sysem_shortcuts) - - def addMedia(self): - mode = self.mode.text() - value = self.input_file_ident.text() - # if vaule is string, call shortcut, else mediaAdd - if mode == "Rückgabe": - self.returnMedia(value) - else: - self.lendMedia(value) + def loanMedia(self, user_id, book_id): + self.db.insertLoan( + user_id, + book_id[0], + self.currentDate.toString("yyyy-MM-dd"), + self.duedate.date().toString("yyyy-MM-dd"), + ) + media = self.db.getMedia(book_id[0]) + #print(media) + self.mediaOverview.insertRow(0) + self.mediaOverview.setItem(0, 0, QtWidgets.QTableWidgetItem(media.signature)) + self.mediaOverview.setItem(0, 1, QtWidgets.QTableWidgetItem(media.title)) + self.mediaOverview.setItem(0, 2, QtWidgets.QTableWidgetItem("Entliehen")) + self.btn_show_lentmedia.setText(self.db.getActiveLoans(self.activeUser.id)) + self.nextReturnDate.setText( + stringToDate(self.db.selectClosestReturnDate(self.activeUser.id)).toString( + "dd.MM.yyyy" + ) + ) + self.nextReturnDate.show() + self.label_7.show() + self.input_file_ident.setEnabled(True) def returnMedia(self, identifier): - print("Returning Media", identifier) + #print("Returning Media", identifier) # get book id from database + # self. identifier = Book( isbn=identifier, title=identifier, signature=identifier, ppn=identifier ) book_id = self.db.checkMediaExists(identifier) - print(book_id) + #print(book_id) if book_id: # check if book is already loaned loaned = self.db.checkLoanState(book_id[0]) if loaned: - print("Book already loaned, returning now") + #print("Book already loaned, returning now") user = self.db.getUserByLoan(book_id[0]) # set userdata in lineedits self.activeUser = user self.setUserData() - book = self.db.returnMedia(book_id[0]) + book = self.db.returnMedia( + book_id[0], self.currentDate.toString("yyyy-MM-dd") + ) self.mediaOverview.insertRow(0) - self.mediaOverview.setItem(0, 0, QtWidgets.QTableWidgetItem(book.isbn)) + self.mediaOverview.setItem(0, 0, QtWidgets.QTableWidgetItem(book.signature)) self.mediaOverview.setItem(0, 1, QtWidgets.QTableWidgetItem(book.title)) self.mediaOverview.setItem( 0, 2, QtWidgets.QTableWidgetItem("Zurückgegeben") @@ -227,23 +501,81 @@ class MainUI(QtWidgets.QMainWindow, Ui_MainWindow): self.btn_show_lentmedia.setText( self.db.getActiveLoans(self.activeUser.id) ) - # else: - print("Book not loaned") + #print("Book not loaned") + self.setStatusTipMessage("Buch nicht entliehen") + self.input_file_ident.clear() else: - print("Book not found") + dbg("Book not found") + #print("Book not found") + #self.input_file_ident.setPlaceholderText(f"Buch {identifier} nicht gefunden") + + - def lendMedia(self, value): - if value.isnumeric(): - self.mediaAdd(value) - else: - if len(value) <= 2: - self.callShortcut(value) - else: - self.mediaAdd(value) - - -def launch(): + def setStatusTipMessage(self, message): + dialog = QtWidgets.QMessageBox() + dialog.setWindowTitle("Information") + dialog.setIcon(QtWidgets.QMessageBox.Icon.Information) + dialog.setWindowIcon(Icon("error").overwriteColor("#EA3323")) + dialog.setText(message) + dialog.exec() +def exit_handler(): + dbg("Exiting, creating backup") app = QtWidgets.QApplication(sys.argv) + #print(backup.backup) + # generate report if monday + if datetime.datetime.now().weekday() == config.report.report_day: + generate_report() + dbg("Generated Report") + Database().renameInactiveUsers() + if config.database.do_backup: + state = backup.createBackup() + # create dialog to show state + if state == True: + return + else: + dialog = QtWidgets.QMessageBox() + # set icon + dialog.setWindowIcon(Icon("backup").icon) + dialog.setWindowTitle("Backup") + dialog.setText("Backup konnte nicht erstellt werden") + + dialog.exec() + dbg("Exiting", backupstate=state) + else: + dialog = QtWidgets.QMessageBox() + # set icon + reason = "Unbekannter Grund" + if config.database.do_backup is False: + reason = "Backup deaktiviert" + if backup.backup is False: + reason = "Backuppfad nicht gefunden" + dialog.setWindowIcon(Icon("backup").icon) + dialog.setWindowTitle("Backup nicht möglich") + dialog.setText("Backup konnte nicht erstellt werden\nGrund: {}".format(reason)) + dialog.exec() +def launch(*argv): + options = sys.argv + if argv: + options += [arg for arg in argv] + options = [arg for arg in options if arg.startswith("--")] + #print("Launching Main UI") + #print(options) + QtCore.QLocale().setDefault(QtCore.QLocale(QtCore.QLocale.Language.German, QtCore.QLocale.Country.Germany)) + SYSTEM_LANGUAGE = QtCore.QLocale().system().name() + print(SYSTEM_LANGUAGE) + + # Load base QT translations from the normal place + app = QtWidgets.QApplication([]) main_ui = MainUI() + #translate ui to system language + if SYSTEM_LANGUAGE: + translator = QtCore.QTranslator() + #do not use ascii encoding + translator.load(f"qt_{SYSTEM_LANGUAGE}", "translations") + translator.load("app.qm", "translations") + app.installTranslator(translator) + + atexit.register(exit_handler) sys.exit(app.exec()) + # sys.exit(app.exec()) diff --git a/src/ui/multiUserInfo.py b/src/ui/multiUserInfo.py index 4298dda..8f8c88c 100644 --- a/src/ui/multiUserInfo.py +++ b/src/ui/multiUserInfo.py @@ -1,35 +1,44 @@ from .sources.Ui_dialog_multipleUserfound import Ui_Dialog from PyQt6 import QtCore, QtGui, QtWidgets from src.schemas import User +from src.utils import Icon class MultiUserFound(QtWidgets.QDialog, Ui_Dialog): def __init__(self, users: list[User]): super(MultiUserFound, self).__init__() self.setupUi(self) + self.setWindowTitle("Mehrere Benutzer gefunden") + self.setWindowIcon(Icon("multiuser").icon) self.users = users self.userdata = None + self.row = None + self.displayUsers() self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Ok - ).clicked.connect(self.selectUser) + ).clicked.connect(self.accept) self.buttonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Cancel ).clicked.connect(self.reject) + self.tableWidget.horizontalHeader().setSectionResizeMode( + QtWidgets.QHeaderView.ResizeMode.Stretch + ) + self.tableWidget.cellClicked.connect(self.selectUser) - def selectUser(self, row): + def selectUser(self, row, column): + #print(row, column) user = User( - id=self.tableWidget.item(row, 0).text(), + userid=self.tableWidget.item(row, 0).text(), username=self.tableWidget.item(row, 1).text(), email=self.tableWidget.item(row, 2).text(), ) self.userdata = user - self.accept() def displayUsers(self): for user in self.users: self.tableWidget.insertRow(0) - self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem(str(user.id))) + self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem(str(user.userid))) self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(user.username)) self.tableWidget.setItem(0, 2, QtWidgets.QTableWidgetItem(user.email)) diff --git a/src/ui/newBook.py b/src/ui/newBook.py new file mode 100644 index 0000000..9de1d0a --- /dev/null +++ b/src/ui/newBook.py @@ -0,0 +1,38 @@ +from .sources.Ui_dialog_addBook import Ui_Dialog +from src.schemas import Book +from src.utils import Icon + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class NewBook(QtWidgets.QDialog, Ui_Dialog): + def __init__(self): + super(NewBook, self).__init__() + self.setupUi(self) + self.setWindowTitle("Buch hinzufügen") + self.setWindowIcon(Icon("addBook").icon) + self.btn_save.setEnabled(False) + self.btn_save.clicked.connect(self.saveBook) + self.btn_cancel.clicked.connect(self.reject) + self.book_title.setFocus() + self.book_title.textChanged.connect(self.checkFields) + self.book_signature.textChanged.connect(self.checkFields) + self.book = None + + self.show() + + def checkFields(self): + if ( + self.book_title.hasAcceptableInput() + and self.book_signature.hasAcceptableInput + ): + self.btn_save.setEnabled(True) + else: + self.btn_save.setEnabled(False) + + def saveBook(self): + title = self.book_title.text() + signature = self.book_signature.text() + book = Book(title=title, signature=signature) + self.book = book + self.accept() diff --git a/src/ui/newentry.py b/src/ui/newentry.py index 155f0b3..00f9445 100644 --- a/src/ui/newentry.py +++ b/src/ui/newentry.py @@ -2,23 +2,32 @@ from .sources.Ui_dialog_addNewTitleEntry import Ui_Dialog from PyQt6 import QtWidgets, QtCore, QtGui from src.logic import Database from src.schemas import Book - +from src.utils import Icon class NewEntry(QtWidgets.QDialog, Ui_Dialog): - def __init__(self, title): + def __init__(self, title_id: list[int]): super(NewEntry, self).__init__() self.setupUi(self) self.setWindowTitle("Neues Exemplar hinzufügen") + self.setWindowIcon(Icon("newentry").icon) self.tableWidget.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeMode.Stretch ) self.db = Database() - self.titles = title + self.titles = title_id + self.newIds = [] self.populateTable() self.btn_addNewBook.clicked.connect(self.addEntry) self.buttonBox.accepted.connect(self.insertEntry) + #disable buttonbox accepted + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Ok + ).setEnabled(False) def addEntry(self): + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Ok + ).setEnabled(True) # clone last row and its data row = self.tableWidget.rowCount() self.tableWidget.insertRow(row) @@ -26,16 +35,30 @@ class NewEntry(QtWidgets.QDialog, Ui_Dialog): self.tableWidget.setItem( row, i, QtWidgets.QTableWidgetItem(self.tableWidget.item(row - 1, i)) ) - + if i == 2 and "+" in self.tableWidget.item(row, i).text(): + entry = self.tableWidget.item(row, i).text().split("+")[1] + entry = str(int(entry) + 1) + self.tableWidget.setItem( + row, + i, + QtWidgets.QTableWidgetItem( + self.tableWidget.item(row, i).text().split("+")[0] + "+" + entry + ), + ) def populateTable(self): for title in self.titles: - print(title) - entry = self.db.getMedia(title) - self.tableWidget.insertRow(0) - self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem(entry.isbn)) - self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(entry.title)) - self.tableWidget.setItem(0, 2, QtWidgets.QTableWidgetItem(entry.signature)) - self.tableWidget.setItem(0, 3, QtWidgets.QTableWidgetItem(entry.ppn)) + #print(title) + entries = self.db.getMediaSimilarSignatureByID(title) + # sort by signature + entries.sort(key=lambda x: x.signature, reverse=True) + for entry in entries: + self.tableWidget.insertRow(0) + self.tableWidget.setItem(0, 0, QtWidgets.QTableWidgetItem(entry.isbn)) + self.tableWidget.setItem(0, 1, QtWidgets.QTableWidgetItem(entry.title)) + self.tableWidget.setItem( + 0, 2, QtWidgets.QTableWidgetItem(entry.signature) + ) + self.tableWidget.setItem(0, 3, QtWidgets.QTableWidgetItem(entry.ppn)) def insertEntry(self): # get all rows, convert them to Book and insert into database @@ -50,9 +73,10 @@ class NewEntry(QtWidgets.QDialog, Ui_Dialog): signature=signature, ppn=eval(ppn), ) - print(book) + #print(book) if not self.db.checkMediaExists(book): - self.db.insertMedia(book) + newBookId = self.db.insertMedia(book) + self.newIds.append(newBookId) def launch(): diff --git a/src/ui/reportUi.py b/src/ui/reportUi.py new file mode 100644 index 0000000..2ca07b3 --- /dev/null +++ b/src/ui/reportUi.py @@ -0,0 +1,105 @@ +from PyQt6 import QtCore, QtWidgets, QtGui +from .sources.Ui_dialog_generateReport import Ui_Dialog +from src.utils import Icon +from src.utils.reportThread import ReportThread +from src.logic import Database +import os + + +class ReportUi(QtWidgets.QDialog, Ui_Dialog): + def __init__(self, parent=None): + super(ReportUi, self).__init__(parent) + self.setupUi(self) + self.setWindowIcon(Icon("report").icon) + self.setWindowTitle("Report erstellen") + self.radioButton.hide() + self.db = Database() + + self.reportprogress.hide() + + # variables + self.maxrecords = 0 + self.days = 0 + self.rthread = ReportThread() + + # buttons + self.generateReport.setEnabled(False) + self.generateReport.clicked.connect(self.generate_report) + self.radio_year.clicked.connect(self.set_days_by_radio) + self.radio_month.clicked.connect(self.set_days_by_radio) + self.radio_week.clicked.connect(self.set_days_by_radio) + self.format_txt.clicked.connect(lambda: self.rthread.setFormat("txt")) + self.format_csv.clicked.connect(lambda: self.rthread.setFormat("tsv")) + self.format_csv.clicked.connect(lambda: self.generateReport.setEnabled(True)) + self.format_txt.clicked.connect(lambda: self.generateReport.setEnabled(True)) + # sliders + self.dayslider.valueChanged.connect(self.set_days) + self.show() + + # labels + self.label_4.hide() + + def set_days_by_radio(self): + if self.radio_year.isChecked(): + self.set_days(365) + self.dayslider.setValue(365) + elif self.radio_month.isChecked(): + self.set_days(30) + self.dayslider.setValue(30) + elif self.radio_week.isChecked(): + self.set_days(7) + self.dayslider.setValue(7) + + def set_days(self, value): + # if value is not 7,30,365, deactivate radio buttons + if value != 7 and value != 30 and value != 365: + self.radioButton.setChecked(True) + + self.days = value + self.dayValue.setText(str(value)) + + def generate_report(self): + self.rthread.setDays(self.days) + self.rthread.report_signal.connect(self.report_generated) + self.rthread.report_nums_signal.connect(self.show_progress) + self.rthread.report_progress_signal.connect(self.update_progress) + self.rthread.finished.connect(self.reset) + # self.rthread.finished.connect(self.rthread.deleteLater) + self.rthread.start() + + def reset(self): + self.days = 0 + self.reportprogress.hide() + self.reportprogress.setValue(0) + self.label_4.setText("Fortschritt:") + self.label_4.hide() + + def update_progress(self, num): + self.reportlink.clear() + self.reportprogress.setValue(num) + self.label_4.setText("Fortschritt: " + str(num) + "/" + str(self.maxrecords)) + if num == self.reportprogress.maximum(): + self.label_4.setText("Datei wird generiert") + + def show_progress(self, num): + self.reportprogress.show() + self.label_4.show() + self.maxrecords = num + self.reportprogress.setMaximum(self.maxrecords) + + def report_generated(self): + self.reportlink.setOpenExternalLinks(True) + fileformat = self.rthread.format + #print(fileformat) + self.reportlink.setText( + f'Report' + ) + self.reportprogress.hide() + + +def launch(): + import sys + + app = QtWidgets.QApplication(sys.argv) + window = ReportUi() + sys.exit(app.exec()) diff --git a/src/ui/settings.py b/src/ui/settings.py new file mode 100644 index 0000000..8acce08 --- /dev/null +++ b/src/ui/settings.py @@ -0,0 +1,269 @@ +from .sources.Ui_dialog_settings import Ui_Dialog +from PyQt6 import QtWidgets, QtCore +from src.utils import Icon +from src import config +from src.utils import debugMessage as dbg +from omegaconf import OmegaConf +import os + +class Settings(QtWidgets.QDialog, Ui_Dialog): + def __init__(self): + super(Settings, self).__init__() + self.setupUi(self) + self.setWindowTitle("Einstellungen") + self.setWindowIcon(Icon("settings").icon) + #variables + self.originalSettings = config.to_Omegaconf() + self.changedSettings = config.to_Omegaconf() + self.shortcuts = config.shortcuts + self.shortcuts = self.sortShortcuts(self.shortcuts) + self.settingschanged = False + self.restart_required = False + + + # buttonbox + self.buttonBox.accepted.connect(self.saveSettings) + self.buttonBox.rejected.connect(self.close) + self.loadSettings() + + + self.populateShortcuts() + # buttons + self.btn_select_database_backupLocation.clicked.connect( + self.selectBackupLocation + ) + self.btn_select_database_path.clicked.connect(self.selectDatabasePath) + self.btn_select_database_name.clicked.connect(self.selectDatabaseName) + self.btn_select_report_path.clicked.connect(self.selectReportPath) + self.returnMode.clicked.connect(self.returnModeSetting) + + + #other + #stretch columns + self.shortcutchanger.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) + def returnModeSetting(self): + currentstate = self.returnMode.isChecked() + if self.originalSettings.advanced_refresh != currentstate: + self.enableButtonBox() + + def populateShortcuts(self): + for shortcut in self.shortcuts: + name = shortcut["name"] + default = shortcut["default"] + current = shortcut["current"] + self.addShortcut(name, default, current) + #assume the shortcuts will be changed + self.settingschanged = True + def addShortcut(self, name, default, current): + #remove all pages from shortcutchanger + #add new page with name, default and current + + self.shortcutchanger.insertRow(0) + self.shortcutchanger.setItem(0, 0, QtWidgets.QTableWidgetItem(name)) + self.shortcutchanger.setItem(0, 1, QtWidgets.QTableWidgetItem(default)) + #add keysequenceedit + keysequenceedit = QtWidgets.QKeySequenceEdit() + keysequenceedit.setKeySequence(current) + self.shortcutchanger.setCellWidget(0, 2, keysequenceedit) + + def selectBackupLocation(self): + backupLocation = QtWidgets.QFileDialog.getExistingDirectory( + self, + "Select Backup Location", + self.originalSettings.database.backupLocation, + ) + self.database_backupLocation.setText(backupLocation) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Discard + ).setEnabled(True) + self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled( + True + ) + self.settingschanged = True + + def selectReportPath(self): + reportPath = QtWidgets.QFileDialog.getExistingDirectory( + self, "Select Report Path", self.originalSettings.report.path + ) + self.report_path.setText(reportPath) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Discard + ).setEnabled(True) + self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled( + True + ) + self.settingschanged = True + + def selectDatabasePath(self): + databasePath = QtWidgets.QFileDialog.getExistingDirectory( + self, "Select Database Path", self.originalSettings.database.path + ) + self.database_path.setText(databasePath) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Discard + ).setEnabled(True) + self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled( + True + ) + self.settingschanged = True + self.restart_required = True + + def selectDatabaseName(self): + # filepicker with filter to select only .db files if a file is selected, set name to the lineedit and set database_path + databaseName = QtWidgets.QFileDialog.getOpenFileName( + self, + "Select Database", + self.originalSettings.database.path, + "Database Files (*.db)", + ) + self.database_path.setText(databaseName[0].rsplit("/", 1)[0]) + self.buttonBox.button( + QtWidgets.QDialogButtonBox.StandardButton.Discard + ).setEnabled(True) + self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setEnabled( + True + ) + self.settingschanged = True + self.restart_required = True + + def getShortcuts(self): + shortcuts = [] + for row in range(self.shortcutchanger.rowCount()): + name = self.shortcutchanger.item(row, 0).text() + default = self.shortcutchanger.item(row, 1).text() + current = self.shortcutchanger.cellWidget(row, 2).keySequence().toString() + shortcuts.append( + { + "name": name, + "default": default, + "current": current, + } + ) + return shortcuts + + def sortShortcuts(self, shortcuts): + short = [] + for shortcut in shortcuts: + short.append(shortcut) + short.sort(key=lambda x: x["name"]) + return short + + def saveSettings(self): + # save settings to config file + institution_name = self.institution_name.text() + default_loan_duration = int(self.default_loan_duration.text()) + delete_inactive_users = int(self.delete_inactive_user_duration.text()) + database_backupLocation = self.database_backupLocation.text() + database_path = self.database_path.text() + database_name = self.database_name.text() + report_day = self.report_day.currentIndex() + report_generate = self.check_generate_report.isChecked() + report_path = self.report_path.text() + refresh_state = self.returnMode.isChecked() + shortcuts = self.getShortcuts() + #shortcuts to omegaconf.DictConfig + shortcuts = OmegaConf.create(shortcuts) + + + if database_path != self.originalSettings.database.path : + os.makedirs(database_path, exist_ok=True) + self.restart_required = True + + + + # create new Settings + self.changedSettings.institution_name = institution_name + self.changedSettings.loan_duration = default_loan_duration + self.changedSettings.database.backupLocation = database_backupLocation + self.changedSettings.database.path = database_path + self.changedSettings.database.name = database_name + self.changedSettings.delete_inactive_user_duration = delete_inactive_users + self.changedSettings.report.report_day = report_day + self.changedSettings.report.path = report_path + self.changedSettings.report.generate_report = report_generate + self.changedSettings.advanced_refresh = refresh_state + self.changedSettings.shortcuts = shortcuts + + changed = self.changedSettings + original = self.originalSettings + + + + if changed == original: + self.settingschanged = False + self.restart_required = False + dbg("Settings not changed") + else: + self.settingschanged = True + #compare if database or shortcuts were changed + database = original.database == changed.database + shortcuts = self.shortcuts == self.sortShortcuts(changed.shortcuts) + if not database or not shortcuts: + self.restart_required = True + dbg(f"Settings changed, restart required: {self.restart_required}",database=database,shortcuts=shortcuts) + + # save the new settings + if self.settingschanged: + # save the settings + config.updateValue("institution_name", self.changedSettings.institution_name) + config.updateValue("default_loan_duration", self.changedSettings.loan_duration) + config.updateValue("database.backupLocation", self.changedSettings.database.backupLocation) + config.updateValue("database.path", self.changedSettings.database.path) + config.updateValue("database.name", self.changedSettings.database.name) + config.updateValue("inactive_user_deletion", self.changedSettings.inactive_user_deletion) + config.updateValue("report.report_day", self.changedSettings.report.report_day) + config.updateValue("report.generate_report", self.changedSettings.report.generate_report) + config.updateValue("report.path", self.changedSettings.report.path) + config.updateValue("advanced_refresh", self.changedSettings.advanced_refresh) + config.updateValue("shortcuts", self.changedSettings.shortcuts) + self.originalSettings = self.changedSettings + config.save() + + if self.restart_required: + self.restart() + self.close() + + def restart(self): + dialog = QtWidgets.QMessageBox() + dialog.setIcon(QtWidgets.QMessageBox.Icon.Information) + dialog.setText("Neustart erforderlich") + dialog.setInformativeText( + "Das Programm muss neu gestartet werden, um die Änderungen zu übernehmen." + ) + dialog.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) + dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Ok) + dialog.setWindowTitle("Neustart erforderlich") + dialog.setWindowIcon(Icon("restart").icon) + dialog.exec() + + def DiscardSettings(self): + self.loadSettings() + self.restart_required = False + self.settingschanged = False + def loadSettings(self): + self.institution_name.setText(config.institution_name) + self.default_loan_duration.setValue( + int(config.loan_duration) + ) + self.delete_inactive_user_duration.setValue( + int(config.delete_inactive_user_duration) + ) + self.database_backupLocation.setText( + config.database.backupLocation + ) + self.database_path.setText(config.database.path) + self.database_name.setText(config.database.name) + self.report_day.setCurrentIndex(config.report.report_day) + self.check_generate_report.setChecked(config.report.generate_report) + self.report_path.setText(config.report.path) + self.returnMode.setChecked(config.advanced_refresh) + pass + + +def launch(): + import sys + + app = QtWidgets.QApplication([]) + settings = Settings() + settings.show() + sys.exit(app.exec()) diff --git a/src/ui/sources/Ui_dialog_addBook.py b/src/ui/sources/Ui_dialog_addBook.py new file mode 100644 index 0000000..01b4004 --- /dev/null +++ b/src/ui/sources/Ui_dialog_addBook.py @@ -0,0 +1,54 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_addBook.ui' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(262, 100) + self.gridLayout = QtWidgets.QGridLayout(Dialog) + self.gridLayout.setObjectName("gridLayout") + self.book_signature = QtWidgets.QLineEdit(parent=Dialog) + self.book_signature.setObjectName("book_signature") + self.gridLayout.addWidget(self.book_signature, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(parent=Dialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.label = QtWidgets.QLabel(parent=Dialog) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.book_title = QtWidgets.QLineEdit(parent=Dialog) + self.book_title.setObjectName("book_title") + self.gridLayout.addWidget(self.book_title, 0, 1, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.btn_save = QtWidgets.QPushButton(parent=Dialog) + self.btn_save.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) + self.btn_save.setObjectName("btn_save") + self.horizontalLayout.addWidget(self.btn_save) + self.btn_cancel = QtWidgets.QPushButton(parent=Dialog) + self.btn_cancel.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) + self.btn_cancel.setObjectName("btn_cancel") + self.horizontalLayout.addWidget(self.btn_cancel) + self.gridLayout.addLayout(self.horizontalLayout, 2, 1, 1, 1) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + Dialog.setTabOrder(self.book_title, self.book_signature) + Dialog.setTabOrder(self.book_signature, self.btn_save) + Dialog.setTabOrder(self.btn_save, self.btn_cancel) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label_2.setText(_translate("Dialog", "Signatur")) + self.label.setText(_translate("Dialog", "Titel")) + self.btn_save.setText(_translate("Dialog", "Speichern")) + self.btn_cancel.setText(_translate("Dialog", "Abbrechen")) diff --git a/src/ui/sources/Ui_dialog_addBook.ui.py b/src/ui/sources/Ui_dialog_addBook.ui.py new file mode 100644 index 0000000..b972dcd --- /dev/null +++ b/src/ui/sources/Ui_dialog_addBook.ui.py @@ -0,0 +1,8 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_addBook.ui.VLqeTH' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + diff --git a/src/ui/sources/Ui_dialog_createUser.py b/src/ui/sources/Ui_dialog_createUser.py index 039367a..adb164c 100644 --- a/src/ui/sources/Ui_dialog_createUser.py +++ b/src/ui/sources/Ui_dialog_createUser.py @@ -53,6 +53,9 @@ class Ui_Dialog(object): def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Nutzer anlegen")) + self.userno.setPlaceholderText(_translate("Dialog", "102888557")) self.label_2.setText(_translate("Dialog", "Matrikelnummer")) self.label_3.setText(_translate("Dialog", "Mail")) + self.user_mail.setPlaceholderText(_translate("Dialog", "email@ph-freiburg.de")) self.label.setText(_translate("Dialog", "Name, Vorname")) + self.username.setPlaceholderText(_translate("Dialog", "Nachname, Vorname")) diff --git a/src/ui/sources/Ui_dialog_extendLoanDuration.py b/src/ui/sources/Ui_dialog_extendLoanDuration.py index d542ee7..e6572a7 100644 --- a/src/ui/sources/Ui_dialog_extendLoanDuration.py +++ b/src/ui/sources/Ui_dialog_extendLoanDuration.py @@ -12,6 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") + Dialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) Dialog.resize(400, 300) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") diff --git a/src/ui/sources/Ui_dialog_generateReport.py b/src/ui/sources/Ui_dialog_generateReport.py new file mode 100644 index 0000000..ecc84da --- /dev/null +++ b/src/ui/sources/Ui_dialog_generateReport.py @@ -0,0 +1,119 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_generateReport.ui' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(375, 245) + Dialog.setMinimumSize(QtCore.QSize(40, 0)) + self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) + self.verticalLayout.setObjectName("verticalLayout") + self.label = QtWidgets.QLabel(parent=Dialog) + self.label.setObjectName("label") + self.verticalLayout.addWidget(self.label) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.label_2 = QtWidgets.QLabel(parent=Dialog) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.radio_week = QtWidgets.QRadioButton(parent=Dialog) + self.radio_week.setObjectName("radio_week") + self.horizontalLayout.addWidget(self.radio_week) + self.radio_month = QtWidgets.QRadioButton(parent=Dialog) + self.radio_month.setObjectName("radio_month") + self.horizontalLayout.addWidget(self.radio_month) + self.radio_year = QtWidgets.QRadioButton(parent=Dialog) + self.radio_year.setObjectName("radio_year") + self.horizontalLayout.addWidget(self.radio_year) + self.gridLayout.addLayout(self.horizontalLayout, 1, 1, 1, 1) + self.dayValue = QtWidgets.QLineEdit(parent=Dialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.dayValue.sizePolicy().hasHeightForWidth()) + self.dayValue.setSizePolicy(sizePolicy) + self.dayValue.setMinimumSize(QtCore.QSize(0, 0)) + self.dayValue.setMaximumSize(QtCore.QSize(40, 16777215)) + self.dayValue.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.dayValue.setReadOnly(True) + self.dayValue.setObjectName("dayValue") + self.gridLayout.addWidget(self.dayValue, 0, 2, 1, 1) + self.radioButton = QtWidgets.QRadioButton(parent=Dialog) + self.radioButton.setText("") + self.radioButton.setCheckable(True) + self.radioButton.setObjectName("radioButton") + self.gridLayout.addWidget(self.radioButton, 3, 2, 1, 1) + self.reportlink = QtWidgets.QLabel(parent=Dialog) + self.reportlink.setText("") + self.reportlink.setObjectName("reportlink") + self.gridLayout.addWidget(self.reportlink, 3, 1, 1, 1) + self.dayslider = QtWidgets.QSlider(parent=Dialog) + self.dayslider.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus) + self.dayslider.setMinimum(1) + self.dayslider.setMaximum(365) + self.dayslider.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.dayslider.setInvertedControls(True) + self.dayslider.setTickPosition(QtWidgets.QSlider.TickPosition.TicksAbove) + self.dayslider.setTickInterval(10) + self.dayslider.setObjectName("dayslider") + self.gridLayout.addWidget(self.dayslider, 0, 1, 1, 1) + self.frame = QtWidgets.QFrame(parent=Dialog) + self.frame.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) + self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Plain) + self.frame.setLineWidth(0) + self.frame.setObjectName("frame") + self.gridLayout_2 = QtWidgets.QGridLayout(self.frame) + self.gridLayout_2.setObjectName("gridLayout_2") + self.format_txt = QtWidgets.QRadioButton(parent=self.frame) + self.format_txt.setObjectName("format_txt") + self.gridLayout_2.addWidget(self.format_txt, 0, 0, 1, 1) + self.format_csv = QtWidgets.QRadioButton(parent=self.frame) + self.format_csv.setObjectName("format_csv") + self.gridLayout_2.addWidget(self.format_csv, 0, 1, 1, 1) + self.gridLayout.addWidget(self.frame, 2, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=Dialog) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) + self.verticalLayout.addLayout(self.gridLayout) + self.label_4 = QtWidgets.QLabel(parent=Dialog) + self.label_4.setObjectName("label_4") + self.verticalLayout.addWidget(self.label_4) + self.reportprogress = QtWidgets.QProgressBar(parent=Dialog) + self.reportprogress.setProperty("value", 24) + self.reportprogress.setTextVisible(True) + self.reportprogress.setInvertedAppearance(False) + self.reportprogress.setObjectName("reportprogress") + self.verticalLayout.addWidget(self.reportprogress) + self.generateReport = QtWidgets.QPushButton(parent=Dialog) + self.generateReport.setObjectName("generateReport") + self.verticalLayout.addWidget(self.generateReport) + + self.retranslateUi(Dialog) + QtCore.QMetaObject.connectSlotsByName(Dialog) + Dialog.setTabOrder(self.radio_week, self.radio_month) + Dialog.setTabOrder(self.radio_month, self.radio_year) + Dialog.setTabOrder(self.radio_year, self.generateReport) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label.setText(_translate("Dialog", "Wieviele Tage sollen im Bericht erfasst werden?")) + self.label_2.setText(_translate("Dialog", "Tage")) + self.radio_week.setText(_translate("Dialog", "Woche")) + self.radio_month.setText(_translate("Dialog", "Monat")) + self.radio_year.setText(_translate("Dialog", "Jahr")) + self.format_txt.setText(_translate("Dialog", "Text")) + self.format_csv.setText(_translate("Dialog", "Excel")) + self.label_3.setText(_translate("Dialog", "Dateiformat")) + self.label_4.setText(_translate("Dialog", "Fortschritt:")) + self.generateReport.setText(_translate("Dialog", " Bericht erstellen")) diff --git a/src/ui/sources/Ui_dialog_generateReport.ui.py b/src/ui/sources/Ui_dialog_generateReport.ui.py new file mode 100644 index 0000000..940cd4a --- /dev/null +++ b/src/ui/sources/Ui_dialog_generateReport.ui.py @@ -0,0 +1,8 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_generateReport.ui.nKmkjJ' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + diff --git a/src/ui/sources/Ui_dialog_settings.py b/src/ui/sources/Ui_dialog_settings.py new file mode 100644 index 0000000..ff5f87e --- /dev/null +++ b/src/ui/sources/Ui_dialog_settings.py @@ -0,0 +1,196 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_settings.ui' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(492, 445) + self.formLayout = QtWidgets.QFormLayout(Dialog) + self.formLayout.setObjectName("formLayout") + self.label = QtWidgets.QLabel(parent=Dialog) + self.label.setObjectName("label") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label) + self.institution_name = QtWidgets.QLineEdit(parent=Dialog) + self.institution_name.setObjectName("institution_name") + self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.institution_name) + self.label_2 = QtWidgets.QLabel(parent=Dialog) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.default_loan_duration = QtWidgets.QSpinBox(parent=Dialog) + self.default_loan_duration.setProperty("value", 7) + self.default_loan_duration.setObjectName("default_loan_duration") + self.horizontalLayout_2.addWidget(self.default_loan_duration) + self.label_13 = QtWidgets.QLabel(parent=Dialog) + self.label_13.setMaximumSize(QtCore.QSize(43, 16777215)) + self.label_13.setObjectName("label_13") + self.horizontalLayout_2.addWidget(self.label_13) + self.formLayout.setLayout(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout_2) + self.label_7 = QtWidgets.QLabel(parent=Dialog) + self.label_7.setObjectName("label_7") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_7) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.delete_inactive_user_duration = QtWidgets.QSpinBox(parent=Dialog) + self.delete_inactive_user_duration.setMaximum(9999) + self.delete_inactive_user_duration.setProperty("value", 365) + self.delete_inactive_user_duration.setObjectName("delete_inactive_user_duration") + self.horizontalLayout.addWidget(self.delete_inactive_user_duration) + self.label_12 = QtWidgets.QLabel(parent=Dialog) + self.label_12.setMaximumSize(QtCore.QSize(43, 16777215)) + self.label_12.setObjectName("label_12") + self.horizontalLayout.addWidget(self.label_12) + self.formLayout.setLayout(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.horizontalLayout) + self.returnMode = QtWidgets.QCheckBox(parent=Dialog) + self.returnMode.setTristate(False) + self.returnMode.setObjectName("returnMode") + self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.returnMode) + self.label_3 = QtWidgets.QLabel(parent=Dialog) + self.label_3.setObjectName("label_3") + self.formLayout.setWidget(5, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_3) + self.databasesettings = QtWidgets.QGridLayout() + self.databasesettings.setObjectName("databasesettings") + self.database_name = QtWidgets.QLineEdit(parent=Dialog) + self.database_name.setObjectName("database_name") + self.databasesettings.addWidget(self.database_name, 1, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(parent=Dialog) + self.label_4.setObjectName("label_4") + self.databasesettings.addWidget(self.label_4, 0, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(parent=Dialog) + self.label_6.setObjectName("label_6") + self.databasesettings.addWidget(self.label_6, 2, 0, 1, 1) + self.database_path = QtWidgets.QLineEdit(parent=Dialog) + self.database_path.setObjectName("database_path") + self.databasesettings.addWidget(self.database_path, 0, 1, 1, 1) + self.database_backupLocation = QtWidgets.QLineEdit(parent=Dialog) + self.database_backupLocation.setObjectName("database_backupLocation") + self.databasesettings.addWidget(self.database_backupLocation, 2, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(parent=Dialog) + self.label_5.setObjectName("label_5") + self.databasesettings.addWidget(self.label_5, 1, 0, 1, 1) + self.btn_select_database_path = QtWidgets.QToolButton(parent=Dialog) + self.btn_select_database_path.setObjectName("btn_select_database_path") + self.databasesettings.addWidget(self.btn_select_database_path, 0, 2, 1, 1) + self.btn_select_database_name = QtWidgets.QToolButton(parent=Dialog) + self.btn_select_database_name.setObjectName("btn_select_database_name") + self.databasesettings.addWidget(self.btn_select_database_name, 1, 2, 1, 1) + self.btn_select_database_backupLocation = QtWidgets.QToolButton(parent=Dialog) + self.btn_select_database_backupLocation.setObjectName("btn_select_database_backupLocation") + self.databasesettings.addWidget(self.btn_select_database_backupLocation, 2, 2, 1, 1) + self.formLayout.setLayout(5, QtWidgets.QFormLayout.ItemRole.FieldRole, self.databasesettings) + self.label_9 = QtWidgets.QLabel(parent=Dialog) + self.label_9.setObjectName("label_9") + self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_9) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.btn_select_report_path = QtWidgets.QToolButton(parent=Dialog) + self.btn_select_report_path.setObjectName("btn_select_report_path") + self.gridLayout.addWidget(self.btn_select_report_path, 2, 2, 1, 1) + self.label_10 = QtWidgets.QLabel(parent=Dialog) + self.label_10.setText("") + self.label_10.setObjectName("label_10") + self.gridLayout.addWidget(self.label_10, 1, 0, 1, 1) + self.check_generate_report = QtWidgets.QCheckBox(parent=Dialog) + self.check_generate_report.setObjectName("check_generate_report") + self.gridLayout.addWidget(self.check_generate_report, 1, 1, 1, 1) + self.report_path = QtWidgets.QLineEdit(parent=Dialog) + self.report_path.setObjectName("report_path") + self.gridLayout.addWidget(self.report_path, 2, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(parent=Dialog) + self.label_8.setObjectName("label_8") + self.gridLayout.addWidget(self.label_8, 2, 0, 1, 1) + self.label_11 = QtWidgets.QLabel(parent=Dialog) + self.label_11.setObjectName("label_11") + self.gridLayout.addWidget(self.label_11, 0, 0, 1, 1) + self.report_day = QtWidgets.QComboBox(parent=Dialog) + self.report_day.setObjectName("report_day") + self.report_day.addItem("") + self.report_day.addItem("") + self.report_day.addItem("") + self.report_day.addItem("") + self.report_day.addItem("") + self.gridLayout.addWidget(self.report_day, 0, 1, 1, 1) + self.formLayout.setLayout(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.gridLayout) + self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Discard|QtWidgets.QDialogButtonBox.StandardButton.Ok) + self.buttonBox.setObjectName("buttonBox") + self.formLayout.setWidget(9, QtWidgets.QFormLayout.ItemRole.FieldRole, self.buttonBox) + self.shortcutchanger = QtWidgets.QTableWidget(parent=Dialog) + self.shortcutchanger.setObjectName("shortcutchanger") + self.shortcutchanger.setColumnCount(3) + self.shortcutchanger.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.shortcutchanger.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.shortcutchanger.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.shortcutchanger.setHorizontalHeaderItem(2, item) + self.formLayout.setWidget(8, QtWidgets.QFormLayout.ItemRole.FieldRole, self.shortcutchanger) + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.label_14 = QtWidgets.QLabel(parent=Dialog) + self.label_14.setObjectName("label_14") + self.verticalLayout.addWidget(self.label_14) + self.label_15 = QtWidgets.QLabel(parent=Dialog) + self.label_15.setObjectName("label_15") + self.verticalLayout.addWidget(self.label_15) + self.formLayout.setLayout(8, QtWidgets.QFormLayout.ItemRole.LabelRole, self.verticalLayout) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) # type: ignore + self.buttonBox.rejected.connect(Dialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(Dialog) + Dialog.setTabOrder(self.institution_name, self.database_path) + Dialog.setTabOrder(self.database_path, self.database_name) + Dialog.setTabOrder(self.database_name, self.database_backupLocation) + Dialog.setTabOrder(self.database_backupLocation, self.btn_select_database_path) + Dialog.setTabOrder(self.btn_select_database_path, self.btn_select_database_name) + Dialog.setTabOrder(self.btn_select_database_name, self.btn_select_database_backupLocation) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label.setText(_translate("Dialog", "Name der Einrichtung")) + self.label_2.setText(_translate("Dialog", "Leihdauer")) + self.label_13.setText(_translate("Dialog", "Tage(n)")) + self.label_7.setText(_translate("Dialog", "Inaktive Nutzer\n" +"Löschen nach")) + self.label_12.setText(_translate("Dialog", "Tage(n)")) + self.returnMode.setToolTip(_translate("Dialog", "Wenn aktiv: Wenn ein Medium zurückgegeben wird, wird die nächste Aktion des Moduswechsels zum normalen Rückgabemodus führen")) + self.returnMode.setText(_translate("Dialog", "Erweiterter Rückgabemodus")) + self.label_3.setText(_translate("Dialog", "Datenbank")) + self.label_4.setText(_translate("Dialog", "Speicherort")) + self.label_6.setText(_translate("Dialog", "Sicherungspfad")) + self.label_5.setText(_translate("Dialog", "Datenbankname")) + self.btn_select_database_path.setText(_translate("Dialog", "...")) + self.btn_select_database_name.setText(_translate("Dialog", "...")) + self.btn_select_database_backupLocation.setText(_translate("Dialog", "...")) + self.label_9.setText(_translate("Dialog", "Bericht")) + self.btn_select_report_path.setText(_translate("Dialog", "...")) + self.check_generate_report.setText(_translate("Dialog", "Bericht erstellen")) + self.label_8.setText(_translate("Dialog", "Speicherpfad")) + self.label_11.setText(_translate("Dialog", "Tag")) + self.report_day.setItemText(0, _translate("Dialog", "Montag")) + self.report_day.setItemText(1, _translate("Dialog", "Dienstag")) + self.report_day.setItemText(2, _translate("Dialog", "Mittwoch")) + self.report_day.setItemText(3, _translate("Dialog", "Donnerstag")) + self.report_day.setItemText(4, _translate("Dialog", "Freitag")) + item = self.shortcutchanger.horizontalHeaderItem(0) + item.setText(_translate("Dialog", "Name")) + item = self.shortcutchanger.horizontalHeaderItem(1) + item.setText(_translate("Dialog", "Standard")) + item = self.shortcutchanger.horizontalHeaderItem(2) + item.setText(_translate("Dialog", "Aktuell")) + self.label_14.setText(_translate("Dialog", "Shortcuts")) + self.label_15.setText(_translate("Dialog", "(Erst nach Neustart\n" +"wirksam)")) diff --git a/src/ui/sources/Ui_dialog_settings.ui.py b/src/ui/sources/Ui_dialog_settings.ui.py new file mode 100644 index 0000000..42e0582 --- /dev/null +++ b/src/ui/sources/Ui_dialog_settings.ui.py @@ -0,0 +1,8 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\dialog_settings.ui.vqAAbY' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + diff --git a/src/ui/sources/Ui_main_Loans.py b/src/ui/sources/Ui_main_Loans.py new file mode 100644 index 0000000..4ef2f59 --- /dev/null +++ b/src/ui/sources/Ui_main_Loans.py @@ -0,0 +1,110 @@ +# Form implementation generated from reading ui file 'c:\Users\aky547\GitHub\LibrarySystem\src\ui\sources\main_Loans.ui' +# +# Created by: PyQt6 UI code generator 6.6.1 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(899, 658) + self.centralwidget = QtWidgets.QWidget(parent=MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.radio_all = QtWidgets.QRadioButton(parent=self.centralwidget) + self.radio_all.setChecked(True) + self.radio_all.setObjectName("radio_all") + self.horizontalLayout.addWidget(self.radio_all) + self.radio_current = QtWidgets.QRadioButton(parent=self.centralwidget) + self.radio_current.setObjectName("radio_current") + self.horizontalLayout.addWidget(self.radio_current) + self.radio_overdue = QtWidgets.QRadioButton(parent=self.centralwidget) + self.radio_overdue.setObjectName("radio_overdue") + self.horizontalLayout.addWidget(self.radio_overdue) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.verticalLayout.addLayout(self.horizontalLayout) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.searchbar = QtWidgets.QLineEdit(parent=self.centralwidget) + self.searchbar.setObjectName("searchbar") + self.horizontalLayout_2.addWidget(self.searchbar) + self.searchFields = QtWidgets.QComboBox(parent=self.centralwidget) + self.searchFields.setObjectName("searchFields") + self.searchFields.addItem("") + self.searchFields.addItem("") + self.searchFields.addItem("") + self.horizontalLayout_2.addWidget(self.searchFields) + self.verticalLayout.addLayout(self.horizontalLayout_2) + self.loanTable = QtWidgets.QTableWidget(parent=self.centralwidget) + self.loanTable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.loanTable.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) + self.loanTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) + self.loanTable.setObjectName("loanTable") + self.loanTable.setColumnCount(7) + self.loanTable.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(3, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(4, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(5, item) + item = QtWidgets.QTableWidgetItem() + self.loanTable.setHorizontalHeaderItem(6, item) + self.verticalLayout.addWidget(self.loanTable) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(parent=MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 899, 22)) + self.menubar.setObjectName("menubar") + self.menuDatei = QtWidgets.QMenu(parent=self.menubar) + self.menuDatei.setObjectName("menuDatei") + MainWindow.setMenuBar(self.menubar) + self.actionBeenden = QtGui.QAction(parent=MainWindow) + self.actionBeenden.setObjectName("actionBeenden") + self.menuDatei.addAction(self.actionBeenden) + self.menubar.addAction(self.menuDatei.menuAction()) + + self.retranslateUi(MainWindow) + self.actionBeenden.triggered.connect(MainWindow.close) # type: ignore + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.radio_all.setText(_translate("MainWindow", "Alle Ausleihen")) + self.radio_current.setText(_translate("MainWindow", "Aktuell Entliehene Medien")) + self.radio_overdue.setText(_translate("MainWindow", "Überzogene Medien")) + self.searchFields.setItemText(0, _translate("MainWindow", "Titel")) + self.searchFields.setItemText(1, _translate("MainWindow", "Signatur")) + self.searchFields.setItemText(2, _translate("MainWindow", "Nutzer")) + item = self.loanTable.horizontalHeaderItem(0) + item.setText(_translate("MainWindow", "ISBN")) + item = self.loanTable.horizontalHeaderItem(1) + item.setText(_translate("MainWindow", "Signatur")) + item = self.loanTable.horizontalHeaderItem(2) + item.setText(_translate("MainWindow", "Titel")) + item = self.loanTable.horizontalHeaderItem(3) + item.setText(_translate("MainWindow", "Nutzerkonto")) + item = self.loanTable.horizontalHeaderItem(4) + item.setText(_translate("MainWindow", "entliehen am")) + item = self.loanTable.horizontalHeaderItem(5) + item.setText(_translate("MainWindow", "entliehen bis")) + item = self.loanTable.horizontalHeaderItem(6) + item.setText(_translate("MainWindow", "Zurückgegeben am")) + self.menuDatei.setTitle(_translate("MainWindow", "Datei")) + self.actionBeenden.setText(_translate("MainWindow", "Beenden")) + self.actionBeenden.setShortcut(_translate("MainWindow", "Q")) diff --git a/src/ui/sources/Ui_main_UserInterface.py b/src/ui/sources/Ui_main_UserInterface.py index 450504c..e5f8621 100644 --- a/src/ui/sources/Ui_main_UserInterface.py +++ b/src/ui/sources/Ui_main_UserInterface.py @@ -19,15 +19,29 @@ class Ui_MainWindow(object): self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") - self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) - self.input_userno = QtWidgets.QLineEdit(parent=self.centralwidget) - self.input_userno.setObjectName("input_userno") - self.gridLayout.addWidget(self.input_userno, 1, 1, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.duedate = QtWidgets.QDateEdit(parent=self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.duedate.sizePolicy().hasHeightForWidth()) + self.duedate.setSizePolicy(sizePolicy) + self.duedate.setMinimumSize(QtCore.QSize(130, 0)) + self.duedate.setMaximumSize(QtCore.QSize(100, 16777215)) + self.duedate.setBaseSize(QtCore.QSize(70, 0)) self.duedate.setObjectName("duedate") - self.gridLayout.addWidget(self.duedate, 5, 1, 1, 1) + self.horizontalLayout_4.addWidget(self.duedate) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_4.addItem(spacerItem) + self.gridLayout.addLayout(self.horizontalLayout_4, 5, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_6.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) + self.label_6.setObjectName("label_6") + self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.label_5 = QtWidgets.QLabel(parent=self.centralwidget) @@ -37,19 +51,10 @@ class Ui_MainWindow(object): self.label_5.setFont(font) self.label_5.setObjectName("label_5") self.horizontalLayout.addWidget(self.label_5) - self.mode = QtWidgets.QLabel(parent=self.centralwidget) - self.mode.setFrameShape(QtWidgets.QFrame.Shape.Box) - self.mode.setLineWidth(2) + self.mode = QtWidgets.QPushButton(parent=self.centralwidget) self.mode.setObjectName("mode") self.horizontalLayout.addWidget(self.mode) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1) - self.label_6 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_6.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) - self.label_6.setObjectName("label_6") - self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1) self.label = QtWidgets.QLabel(parent=self.centralwidget) self.label.setObjectName("label") self.gridLayout.addWidget(self.label, 1, 0, 1, 1) @@ -59,6 +64,20 @@ class Ui_MainWindow(object): self.input_file_ident = QtWidgets.QLineEdit(parent=self.centralwidget) self.input_file_ident.setObjectName("input_file_ident") self.gridLayout.addWidget(self.input_file_ident, 3, 1, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout_3.addItem(spacerItem1) + self.btn_createNewUser = QtWidgets.QPushButton(parent=self.centralwidget) + self.btn_createNewUser.setObjectName("btn_createNewUser") + self.horizontalLayout_3.addWidget(self.btn_createNewUser) + self.gridLayout.addLayout(self.horizontalLayout_3, 0, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) + self.input_userno = QtWidgets.QLineEdit(parent=self.centralwidget) + self.input_userno.setObjectName("input_userno") + self.gridLayout.addWidget(self.input_userno, 1, 1, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget) self.groupBox.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) @@ -119,8 +138,13 @@ class Ui_MainWindow(object): self.menubar.setObjectName("menubar") self.menuDatei = QtWidgets.QMenu(parent=self.menubar) self.menuDatei.setObjectName("menuDatei") - self.menuShortkeys = QtWidgets.QMenu(parent=self.menubar) - self.menuShortkeys.setObjectName("menuShortkeys") + self.menuHotkeys = QtWidgets.QMenu(parent=self.menubar) + self.menuHotkeys.setObjectName("menuHotkeys") + self.menuFenster = QtWidgets.QMenu(parent=self.menubar) + self.menuFenster.setObjectName("menuFenster") + self.menuHilfe = QtWidgets.QMenu(parent=self.menubar) + self.menuHilfe.setGeometry(QtCore.QRect(2484, 209, 181, 94)) + self.menuHilfe.setObjectName("menuHilfe") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(parent=MainWindow) self.statusbar.setObjectName("statusbar") @@ -133,46 +157,68 @@ class Ui_MainWindow(object): self.actionRueckgabemodus.setObjectName("actionRueckgabemodus") self.actionNutzer = QtGui.QAction(parent=MainWindow) self.actionNutzer.setObjectName("actionNutzer") + self.actionAusleihhistorie = QtGui.QAction(parent=MainWindow) + self.actionAusleihhistorie.setObjectName("actionAusleihhistorie") + self.actionBericht_erstellen = QtGui.QAction(parent=MainWindow) + self.actionBericht_erstellen.setObjectName("actionBericht_erstellen") + self.actionDokumentation_ffnen = QtGui.QAction(parent=MainWindow) + self.actionDokumentation_ffnen.setObjectName("actionDokumentation_ffnen") + self.actionProblem_melden = QtGui.QAction(parent=MainWindow) + self.actionProblem_melden.setObjectName("actionProblem_melden") self.menuDatei.addAction(self.actionEinstellungen) self.menuDatei.addAction(self.actionBeenden) - self.menuShortkeys.addAction(self.actionRueckgabemodus) - self.menuShortkeys.addAction(self.actionNutzer) + self.menuHotkeys.addAction(self.actionRueckgabemodus) + self.menuFenster.addAction(self.actionNutzer) + self.menuFenster.addAction(self.actionAusleihhistorie) + self.menuFenster.addAction(self.actionBericht_erstellen) + self.menuHilfe.addAction(self.actionDokumentation_ffnen) + self.menuHilfe.addAction(self.actionProblem_melden) self.menubar.addAction(self.menuDatei.menuAction()) - self.menubar.addAction(self.menuShortkeys.menuAction()) + self.menubar.addAction(self.menuHotkeys.menuAction()) + self.menubar.addAction(self.menuFenster.menuAction()) + self.menubar.addAction(self.menuHilfe.menuAction()) self.retranslateUi(MainWindow) self.actionBeenden.triggered.connect(MainWindow.close) # type: ignore QtCore.QMetaObject.connectSlotsByName(MainWindow) + MainWindow.setTabOrder(self.btn_createNewUser, self.input_userno) MainWindow.setTabOrder(self.input_userno, self.input_username) MainWindow.setTabOrder(self.input_username, self.input_file_ident) - MainWindow.setTabOrder(self.input_file_ident, self.groupBox) - MainWindow.setTabOrder(self.groupBox, self.mediaOverview) - MainWindow.setTabOrder(self.mediaOverview, self.btn_show_lentmedia) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.label_2.setText(_translate("MainWindow", "Benutzername")) + self.label_3.setText(_translate("MainWindow", "Signatur")) + self.label_6.setText(_translate("MainWindow", "Ausleihe bis")) self.label_5.setText(_translate("MainWindow", "Modus")) self.mode.setText(_translate("MainWindow", "Rückgabe")) - self.label_3.setText(_translate("MainWindow", "Suchbegriff")) - self.label_6.setText(_translate("MainWindow", "Ausleihe bis")) self.label.setText(_translate("MainWindow", "Matrikelnummer")) + self.btn_createNewUser.setText(_translate("MainWindow", "Neuen Nutzer anlegen")) + self.label_2.setText(_translate("MainWindow", "Benutzername")) self.groupBox.setTitle(_translate("MainWindow", "Nutzerdaten")) self.groupBox_2.setTitle(_translate("MainWindow", "Ausleihdaten")) self.label_4.setText(_translate("MainWindow", "Anzahl Ausleihen")) self.label_7.setText(_translate("MainWindow", "Nächstes Rückgabedatum")) item = self.mediaOverview.horizontalHeaderItem(0) - item.setText(_translate("MainWindow", "ISBN")) + item.setText(_translate("MainWindow", "Signatur")) item = self.mediaOverview.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "Titel")) item = self.mediaOverview.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "Status")) self.menuDatei.setTitle(_translate("MainWindow", "Datei")) - self.menuShortkeys.setTitle(_translate("MainWindow", "Shortkeys")) + self.menuHotkeys.setTitle(_translate("MainWindow", "Hotkeys")) + self.menuFenster.setTitle(_translate("MainWindow", "Fenster")) + self.menuHilfe.setTitle(_translate("MainWindow", "Hilfe")) self.actionEinstellungen.setText(_translate("MainWindow", "Einstellungen")) self.actionBeenden.setText(_translate("MainWindow", "Beenden")) self.actionRueckgabemodus.setText(_translate("MainWindow", "Rückgabemodus")) self.actionRueckgabemodus.setShortcut(_translate("MainWindow", "F5")) self.actionNutzer.setText(_translate("MainWindow", "Nutzer")) - self.actionNutzer.setShortcut(_translate("MainWindow", "U")) + self.actionNutzer.setShortcut(_translate("MainWindow", "F6")) + self.actionAusleihhistorie.setText(_translate("MainWindow", "Ausleihhistorie")) + self.actionAusleihhistorie.setShortcut(_translate("MainWindow", "F8")) + self.actionBericht_erstellen.setText(_translate("MainWindow", "Bericht erstellen")) + self.actionBericht_erstellen.setShortcut(_translate("MainWindow", "F7")) + self.actionDokumentation_ffnen.setText(_translate("MainWindow", "Dokumentation öffnen")) + self.actionDokumentation_ffnen.setShortcut(_translate("MainWindow", "F1")) + self.actionProblem_melden.setText(_translate("MainWindow", "Problem melden")) diff --git a/src/ui/sources/Ui_main_userData.py b/src/ui/sources/Ui_main_userData.py index 9f058f3..c901d3f 100644 --- a/src/ui/sources/Ui_main_userData.py +++ b/src/ui/sources/Ui_main_userData.py @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) + MainWindow.setWindowModality(QtCore.Qt.WindowModality.WindowModal) MainWindow.resize(800, 600) self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget.setObjectName("centralwidget") @@ -20,24 +20,6 @@ class Ui_MainWindow(object): self.verticalLayout.setObjectName("verticalLayout") self.gridLayout = QtWidgets.QGridLayout() self.gridLayout.setObjectName("gridLayout") - self.name = QtWidgets.QLineEdit(parent=self.centralwidget) - self.name.setObjectName("name") - self.gridLayout.addWidget(self.name, 0, 1, 1, 1) - self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) - self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) - self.mail = QtWidgets.QLineEdit(parent=self.centralwidget) - self.mail.setObjectName("mail") - self.gridLayout.addWidget(self.mail, 2, 1, 1, 1) - self.label = QtWidgets.QLabel(parent=self.centralwidget) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - self.user_no = QtWidgets.QLineEdit(parent=self.centralwidget) - self.user_no.setObjectName("user_no") - self.gridLayout.addWidget(self.user_no, 1, 1, 1, 1) self.frame = QtWidgets.QFrame(parent=self.centralwidget) self.frame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) self.frame.setFrameShadow(QtWidgets.QFrame.Shadow.Raised) @@ -56,6 +38,37 @@ class Ui_MainWindow(object): spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.gridLayout.addWidget(self.frame, 3, 1, 1, 1) + self.label = QtWidgets.QLabel(parent=self.centralwidget) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.user_no = QtWidgets.QLineEdit(parent=self.centralwidget) + self.user_no.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) + self.user_no.setReadOnly(True) + self.user_no.setObjectName("user_no") + self.gridLayout.addWidget(self.user_no, 1, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) + self.name = QtWidgets.QLineEdit(parent=self.centralwidget) + self.name.setObjectName("name") + self.gridLayout.addWidget(self.name, 0, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.mail = QtWidgets.QLineEdit(parent=self.centralwidget) + self.mail.setObjectName("mail") + self.gridLayout.addWidget(self.mail, 2, 1, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.label_5 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_5.setObjectName("label_5") + self.horizontalLayout_4.addWidget(self.label_5) + self.deleteUser = QtWidgets.QToolButton(parent=self.centralwidget) + self.deleteUser.setMinimumSize(QtCore.QSize(30, 30)) + self.deleteUser.setText("") + self.deleteUser.setObjectName("deleteUser") + self.horizontalLayout_4.addWidget(self.deleteUser) + self.gridLayout.addLayout(self.horizontalLayout_4, 3, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.line = QtWidgets.QFrame(parent=self.centralwidget) self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) @@ -71,9 +84,11 @@ class Ui_MainWindow(object): self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.radio_allLoanedMedia = QtWidgets.QRadioButton(parent=self.centralwidget) + self.radio_allLoanedMedia.setChecked(False) self.radio_allLoanedMedia.setObjectName("radio_allLoanedMedia") self.horizontalLayout_2.addWidget(self.radio_allLoanedMedia) self.radio_currentlyLoaned = QtWidgets.QRadioButton(parent=self.centralwidget) + self.radio_currentlyLoaned.setChecked(True) self.radio_currentlyLoaned.setObjectName("radio_currentlyLoaned") self.horizontalLayout_2.addWidget(self.radio_currentlyLoaned) self.radio_overdueLoans = QtWidgets.QRadioButton(parent=self.centralwidget) @@ -87,9 +102,11 @@ class Ui_MainWindow(object): self.searchbox = QtWidgets.QLineEdit(parent=self.centralwidget) self.searchbox.setObjectName("searchbox") self.horizontalLayout_3.addWidget(self.searchbox) - self.btn_searchTableContent = QtWidgets.QPushButton(parent=self.centralwidget) - self.btn_searchTableContent.setObjectName("btn_searchTableContent") - self.horizontalLayout_3.addWidget(self.btn_searchTableContent) + self.searchfilter = QtWidgets.QComboBox(parent=self.centralwidget) + self.searchfilter.setObjectName("searchfilter") + self.searchfilter.addItem("") + self.searchfilter.addItem("") + self.horizontalLayout_3.addWidget(self.searchfilter) spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.horizontalLayout_3.addItem(spacerItem3) self.btn_extendSelectedMedia = QtWidgets.QPushButton(parent=self.centralwidget) @@ -99,6 +116,7 @@ class Ui_MainWindow(object): self.verticalLayout.addLayout(self.horizontalLayout_3) self.UserMediaTable = QtWidgets.QTableWidget(parent=self.centralwidget) self.UserMediaTable.setMouseTracking(True) + self.UserMediaTable.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) self.UserMediaTable.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.UserMediaTable.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) self.UserMediaTable.setAlternatingRowColors(True) @@ -120,6 +138,7 @@ class Ui_MainWindow(object): self.UserMediaTable.setHorizontalHeaderItem(5, item) self.UserMediaTable.horizontalHeader().setDefaultSectionSize(156) self.UserMediaTable.horizontalHeader().setMinimumSectionSize(43) + self.UserMediaTable.horizontalHeader().setSortIndicatorShown(True) self.UserMediaTable.verticalHeader().setDefaultSectionSize(31) self.UserMediaTable.verticalHeader().setMinimumSectionSize(25) self.verticalLayout.addWidget(self.UserMediaTable) @@ -135,28 +154,44 @@ class Ui_MainWindow(object): self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) + MainWindow.setTabOrder(self.name, self.mail) + MainWindow.setTabOrder(self.mail, self.btn_userChange_save) + MainWindow.setTabOrder(self.btn_userChange_save, self.btn_userchange_cancel) + MainWindow.setTabOrder(self.btn_userchange_cancel, self.radio_allLoanedMedia) + MainWindow.setTabOrder(self.radio_allLoanedMedia, self.radio_currentlyLoaned) + MainWindow.setTabOrder(self.radio_currentlyLoaned, self.radio_overdueLoans) + MainWindow.setTabOrder(self.radio_overdueLoans, self.searchbox) + MainWindow.setTabOrder(self.searchbox, self.searchfilter) + MainWindow.setTabOrder(self.searchfilter, self.btn_extendSelectedMedia) + MainWindow.setTabOrder(self.btn_extendSelectedMedia, self.UserMediaTable) + MainWindow.setTabOrder(self.UserMediaTable, self.user_no) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.label_3.setText(_translate("MainWindow", "Mail")) - self.label_2.setText(_translate("MainWindow", "Matrikelnummer")) - self.label.setText(_translate("MainWindow", "Name, Vorname")) self.btn_userChange_save.setText(_translate("MainWindow", "Speichern")) self.btn_userchange_cancel.setText(_translate("MainWindow", "Abbrechen")) + self.label.setText(_translate("MainWindow", "Name, Vorname")) + self.label_3.setText(_translate("MainWindow", "Mail")) + self.label_2.setText(_translate("MainWindow", "Matrikelnummer")) + self.label_5.setText(_translate("MainWindow", "Nutzer Löschen")) self.label_4.setText(_translate("MainWindow", "Medien")) self.radio_allLoanedMedia.setText(_translate("MainWindow", "Alle Ausleihen")) self.radio_currentlyLoaned.setText(_translate("MainWindow", "Aktuell entliehen")) self.radio_overdueLoans.setText(_translate("MainWindow", "Überzogen")) - self.btn_searchTableContent.setText(_translate("MainWindow", "Suchen")) + self.searchfilter.setItemText(0, _translate("MainWindow", "Titel")) + self.searchfilter.setItemText(1, _translate("MainWindow", "Signatur")) self.btn_extendSelectedMedia.setText(_translate("MainWindow", "Ausgewählte Verlängern")) - item = self.UserMediaTable.horizontalHeaderItem(1) + self.UserMediaTable.setSortingEnabled(True) + item = self.UserMediaTable.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "ISBN")) - item = self.UserMediaTable.horizontalHeaderItem(2) + item = self.UserMediaTable.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "Signatur")) - item = self.UserMediaTable.horizontalHeaderItem(3) + item = self.UserMediaTable.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "Titel")) - item = self.UserMediaTable.horizontalHeaderItem(4) + item = self.UserMediaTable.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "entliehen am")) - item = self.UserMediaTable.horizontalHeaderItem(5) + item = self.UserMediaTable.horizontalHeaderItem(4) item.setText(_translate("MainWindow", "entliehen bis")) + item = self.UserMediaTable.horizontalHeaderItem(5) + item.setText(_translate("MainWindow", "Rückgabe am")) diff --git a/src/ui/sources/dialog_addBook.ui b/src/ui/sources/dialog_addBook.ui new file mode 100644 index 0000000..cab6162 --- /dev/null +++ b/src/ui/sources/dialog_addBook.ui @@ -0,0 +1,71 @@ + + + Dialog + + + + 0 + 0 + 262 + 100 + + + + Dialog + + + + + + + + + Signatur + + + + + + + Titel + + + + + + + + + + + + Qt::ClickFocus + + + Speichern + + + + + + + Qt::ClickFocus + + + Abbrechen + + + + + + + + + book_title + book_signature + btn_save + btn_cancel + + + + diff --git a/src/ui/sources/dialog_createUser.ui b/src/ui/sources/dialog_createUser.ui index 60efe56..125ab67 100644 --- a/src/ui/sources/dialog_createUser.ui +++ b/src/ui/sources/dialog_createUser.ui @@ -22,6 +22,9 @@ Qt::ImhDigitsOnly + + 102888557 + @@ -39,7 +42,11 @@ - + + + email@ph-freiburg.de + + @@ -49,7 +56,11 @@ - + + + Nachname, Vorname + + diff --git a/src/ui/sources/dialog_extendLoanDuration.ui b/src/ui/sources/dialog_extendLoanDuration.ui index c4c4c1c..ce3374e 100644 --- a/src/ui/sources/dialog_extendLoanDuration.ui +++ b/src/ui/sources/dialog_extendLoanDuration.ui @@ -2,6 +2,9 @@ Dialog + + Qt::ApplicationModal + 0 diff --git a/src/ui/sources/dialog_generateReport.ui b/src/ui/sources/dialog_generateReport.ui new file mode 100644 index 0000000..e478a02 --- /dev/null +++ b/src/ui/sources/dialog_generateReport.ui @@ -0,0 +1,209 @@ + + + Dialog + + + + 0 + 0 + 375 + 245 + + + + + 40 + 0 + + + + Dialog + + + + + + Wieviele Tage sollen im Bericht erfasst werden? + + + + + + + + + Tage + + + + + + + + + Woche + + + + + + + Monat + + + + + + + Jahr + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 40 + 16777215 + + + + Qt::NoFocus + + + true + + + + + + + + + + true + + + + + + + + + + + + + + Qt::ClickFocus + + + 1 + + + 365 + + + Qt::Horizontal + + + true + + + QSlider::TicksAbove + + + 10 + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + + + Text + + + + + + + Excel + + + + + + + + + + Dateiformat + + + + + + + + + Fortschritt: + + + + + + + 24 + + + true + + + false + + + + + + + Bericht erstellen + + + + + + + radio_week + radio_month + radio_year + generateReport + + + + diff --git a/src/ui/sources/dialog_settings.ui b/src/ui/sources/dialog_settings.ui new file mode 100644 index 0000000..d94cce4 --- /dev/null +++ b/src/ui/sources/dialog_settings.ui @@ -0,0 +1,340 @@ + + + Dialog + + + + 0 + 0 + 492 + 445 + + + + Dialog + + + + + + Name der Einrichtung + + + + + + + + + + Leihdauer + + + + + + + + + 7 + + + + + + + + 43 + 16777215 + + + + Tage(n) + + + + + + + + + Inaktive Nutzer +Löschen nach + + + + + + + + + 9999 + + + 365 + + + + + + + + 43 + 16777215 + + + + Tage(n) + + + + + + + + + Wenn aktiv: Wenn ein Medium zurückgegeben wird, wird die nächste Aktion des Moduswechsels zum normalen Rückgabemodus führen + + + Erweiterter Rückgabemodus + + + false + + + + + + + Datenbank + + + + + + + + + + + + Speicherort + + + + + + + Sicherungspfad + + + + + + + + + + + + + Datenbankname + + + + + + + ... + + + + + + + ... + + + + + + + ... + + + + + + + + + Bericht + + + + + + + + + ... + + + + + + + + + + + + + + Bericht erstellen + + + + + + + + + + Speicherpfad + + + + + + + Tag + + + + + + + + Montag + + + + + Dienstag + + + + + Mittwoch + + + + + Donnerstag + + + + + Freitag + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Discard|QDialogButtonBox::Ok + + + + + + + + Name + + + + + Standard + + + + + Aktuell + + + + + + + + + + Shortcuts + + + + + + + (Erst nach Neustart +wirksam) + + + + + + + + + institution_name + database_path + database_name + database_backupLocation + btn_select_database_path + btn_select_database_name + btn_select_database_backupLocation + + + + + buttonBox + accepted() + Dialog + accept() + + + 379 + 174 + + + 157 + 183 + + + + + buttonBox + rejected() + Dialog + reject() + + + 426 + 174 + + + 286 + 183 + + + + + diff --git a/src/ui/sources/main_Loans.ui b/src/ui/sources/main_Loans.ui new file mode 100644 index 0000000..94aac73 --- /dev/null +++ b/src/ui/sources/main_Loans.ui @@ -0,0 +1,180 @@ + + + MainWindow + + + + 0 + 0 + 899 + 658 + + + + MainWindow + + + + + + + + + Alle Ausleihen + + + true + + + + + + + Aktuell Entliehene Medien + + + + + + + Überzogene Medien + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Titel + + + + + Signatur + + + + + Nutzer + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + ISBN + + + + + Signatur + + + + + Titel + + + + + Nutzerkonto + + + + + entliehen am + + + + + entliehen bis + + + + + Zurückgegeben am + + + + + + + + + + 0 + 0 + 899 + 22 + + + + + Datei + + + + + + + + Beenden + + + Q + + + + + + + actionBeenden + triggered() + MainWindow + close() + + + -1 + -1 + + + 449 + 328 + + + + + diff --git a/src/ui/sources/main_UserInterface.ui b/src/ui/sources/main_UserInterface.ui index a905129..46614e8 100644 --- a/src/ui/sources/main_UserInterface.ui +++ b/src/ui/sources/main_UserInterface.ui @@ -17,18 +17,67 @@ - - + + + + + + + 0 + 0 + + + + + 130 + 0 + + + + + 100 + 16777215 + + + + + 70 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - Benutzername + Signatur - - - - - + + + + Ausleihe bis + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + @@ -46,13 +95,7 @@ - - - QFrame::Box - - - 2 - + Rückgabe @@ -60,23 +103,6 @@ - - - - Suchbegriff - - - - - - - Ausleihe bis - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - @@ -90,6 +116,40 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Neuen Nutzer anlegen + + + + + + + + + Benutzername + + + + + + @@ -171,7 +231,7 @@ - ISBN + Signatur @@ -204,15 +264,39 @@ - + - Shortkeys + Hotkeys + + + + Fenster + + + + + + + + 2484 + 209 + 181 + 94 + + + + Hilfe + + + - + + + @@ -238,17 +322,44 @@ Nutzer - U + F6 + + + + + Ausleihhistorie + + + F8 + + + + + Bericht erstellen + + + F7 + + + + + Dokumentation öffnen + + + F1 + + + + + Problem melden + btn_createNewUser input_userno input_username input_file_ident - groupBox - mediaOverview - btn_show_lentmedia diff --git a/src/ui/sources/main_userData.ui b/src/ui/sources/main_userData.ui index 13e0cb6..d91d773 100644 --- a/src/ui/sources/main_userData.ui +++ b/src/ui/sources/main_userData.ui @@ -3,7 +3,7 @@ MainWindow - Qt::ApplicationModal + Qt::WindowModal @@ -20,36 +20,6 @@ - - - - - - - Mail - - - - - - - Matrikelnummer - - - - - - - - - - Name, Vorname - - - - - - @@ -105,6 +75,67 @@ + + + + Name, Vorname + + + + + + + Qt::NoFocus + + + true + + + + + + + Mail + + + + + + + + + + Matrikelnummer + + + + + + + + + + + + Nutzer Löschen + + + + + + + + 30 + 30 + + + + + + + + + @@ -133,6 +164,9 @@ Alle Ausleihen + + false + @@ -140,6 +174,9 @@ Aktuell entliehen + + true + @@ -170,10 +207,17 @@ - - - Suchen - + + + + Titel + + + + + Signatur + + @@ -206,6 +250,9 @@ true + + Qt::NoFocus + Qt::ScrollBarAlwaysOff @@ -218,23 +265,24 @@ QAbstractItemView::SelectRows + + true + 43 156 + + true + 25 31 - - - - - ISBN @@ -260,6 +308,11 @@ entliehen bis + + + Rückgabe am + + @@ -280,6 +333,20 @@ + + name + mail + btn_userChange_save + btn_userchange_cancel + radio_allLoanedMedia + radio_currentlyLoaned + radio_overdueLoans + searchbox + searchfilter + btn_extendSelectedMedia + UserMediaTable + user_no + diff --git a/src/ui/user.py b/src/ui/user.py index 2bdca97..bc3a7b3 100644 --- a/src/ui/user.py +++ b/src/ui/user.py @@ -2,30 +2,121 @@ from .sources.Ui_main_userData import Ui_MainWindow from PyQt6 import QtCore, QtGui, QtWidgets from src.logic import Database from src.schemas import User +from .extendLoan import ExtendLoan +from src.utils import stringToDate, Icon +from src.utils import debugMessage as dbg + +TABLETOFIELDTRANSLATE = { + "Titel": "title", + "Signatur": "signature", +} + + class UserUI(QtWidgets.QMainWindow, Ui_MainWindow): - def __init__(self, u_name, u_no, u_mail): + def __init__(self, user): super(UserUI, self).__init__() self.setupUi(self) + self.setWindowTitle("Nutzerdaten") + self.setWindowIcon(Icon("user").icon) self.db = Database() - self.username = u_name - self.userno = u_no - self.usermail = u_mail + self.username = user.username + self.userno = user.userid + self.usermail = user.email + self.user_id = user.id self.setFields() + self.userMedia = [] + self.loadMedia() # Buttons self.btn_userChange_save.clicked.connect(self.saveChanges) self.btn_userchange_cancel.clicked.connect(self.discardChanges) + self.btn_extendSelectedMedia.clicked.connect(self.extendLoan) + self.deleteUser.clicked.connect(self.userDelete) + self.deleteUser.setIcon(Icon("delete").overwriteColor("red")) + self.deleteUser.setEnabled(False) + self.deleteUser.setToolTip("Nutzer löschen nicht möglich, solange Medien ausgeliehen sind") + self.btn_extendSelectedMedia.setEnabled(False) + + # radioButtons + self.radio_allLoanedMedia.clicked.connect(self.loadMedia) + self.radio_currentlyLoaned.clicked.connect(self.loadMedia) + self.radio_overdueLoans.clicked.connect(self.loadMedia) # frames self.frame.hide() + if self.UserMediaTable.rowCount() == 0: + self.btn_extendSelectedMedia.setEnabled(False) + self.deleteUser.setEnabled( True) + else: self.btn_extendSelectedMedia.setEnabled(True) + + # table + self.UserMediaTable.horizontalHeader().setSectionResizeMode( + QtWidgets.QHeaderView.ResizeMode.Stretch + ) + # if one or more rows is selected, enable btn + self.UserMediaTable.itemSelectionChanged.connect(self.userTableAction) # LineEdits + self.searchbox.textChanged.connect(self.limitResults) # self.frame.hide() self.name.textChanged.connect(self.showFrame) self.user_no.textChanged.connect(self.showFrame) self.mail.textChanged.connect(self.showFrame) + self.show() + def userDelete(self): + self.db.deleteUser(self.user_id) + dialog = QtWidgets.QMessageBox() + dialog.setIcon(QtWidgets.QMessageBox.Icon.Information) + dialog.setWindowIcon(Icon("delete").icon) + dialog.setText("Nutzer wurde gelöscht") + dialog.setWindowTitle("Nutzer gelöscht") + dialog.exec() + self.close() + + def extendLoan(self): + extend = ExtendLoan(self.username, self.userMedia) + extend.exec() + if extend.result() == 1: + extendDate = extend.extendDate.toString("yyyy-MM-dd") + # #print columns of selected rows + for item in self.UserMediaTable.selectedItems(): + if item.column() == 1: + signature = item.text() + #print(signature) + self.db.extendLoanDuration(signature, extendDate) + self.userMedia = [] + self.UserMediaTable.setRowCount(0) + self.loadMedia() + return + + def limitResults(self): + limiter = self.searchbox.text().lower() + searchfield = self.searchfilter.currentText() + searchfield = TABLETOFIELDTRANSLATE[searchfield] + # dbg(limiter=limiter, search=searchfield) + + self.UserMediaTable.setRowCount(0) + for loan in self.userMedia: + #print("looping loans") + fielddata = eval(f"loan.{searchfield}") + if isinstance(fielddata, str): + fielddata = fielddata.lower() + if limiter in fielddata: + self.addBookToTable(loan) + + def userTableAction(self): + if self.UserMediaTable.selectedItems(): + # if any selected item has a value in column 5, disable btn + for item in self.UserMediaTable.selectedItems(): + if item.column() == 5 and item.text() != "": + self.btn_extendSelectedMedia.setEnabled(False) + return + self.btn_extendSelectedMedia.setEnabled(True) + else: + self.btn_extendSelectedMedia.setEnabled(False) + def showFrame(self): self.frame.show() @@ -35,8 +126,100 @@ class UserUI(QtWidgets.QMainWindow, Ui_MainWindow): self.mail.setText(self.usermail) def saveChanges(self): + username = self.name.text() + userno = int(self.user_no.text()) + usermail = self.mail.text() + self.db.updateUser(username, userno, usermail) + self.username = username + self.userno = userno + self.usermail = usermail + self.frame.hide() + self.discardChanges() pass def discardChanges(self): self.setFields() - self.frame.hide() \ No newline at end of file + self.frame.hide() + + def loadMedia(self): + mode = ( + "all" + if self.radio_allLoanedMedia.isChecked() + else "current" + if self.radio_currentlyLoaned.isChecked() + else "overdue" + ) + #print(mode) + if self.userMedia == []: + books = self.db.getAllMedia(self.user_id) + for book in books: + self.userMedia.append(book) + #print(self.userMedia) + self.UserMediaTable.setRowCount(0) + + for book in self.userMedia: + # fromdate = stringToDate(book.loan_from) + todate = stringToDate(book.loan_to) + if mode == "current": + # book not returned + if book.returned == 1: + continue + elif mode == "overdue": + # book not returned and todays date is greater than todate + dbg(book=book) + if book.returned_date is not None: + continue + # if todate is greater than current date, continue + if todate > QtCore.QDate.currentDate(): + continue + + self.addBookToTable(book) + #print(book.title) + def addBookToTable(self, book): + self.UserMediaTable.insertRow(0) + # item0 = isbn + # item1 = signature + # item2 = title + # item3 = loan date + # item4 = return date + # item5 = returned_date + self.UserMediaTable.setItem( + 0, + 0, + QtWidgets.QTableWidgetItem(book.isbn if book.isbn != "None" else ""), + ) + self.UserMediaTable.setItem(0, 1, QtWidgets.QTableWidgetItem(book.signature)) + self.UserMediaTable.setItem(0, 2, QtWidgets.QTableWidgetItem(book.title)) + self.UserMediaTable.setItem( + 0, + 3, + QtWidgets.QTableWidgetItem( + stringToDate(book.loan_from).toString("dd.MM.yyyy") + ), + ) + self.UserMediaTable.setItem( + 0, + 4, + QtWidgets.QTableWidgetItem( + stringToDate(book.loan_to).toString("dd.MM.yyyy") + ), + ) + self.UserMediaTable.setItem( + 0, + 5, + QtWidgets.QTableWidgetItem( + "" + if book.returned_date is None + else stringToDate(book.returned_date).toString("dd.MM.yyyy") + ), + ) + + +def launch(): + import sys + + app = QtWidgets.QApplication(sys.argv) + + window = UserUI("Test", "3613899476", "sdf@f.de") + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..d004bfe --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,5 @@ +from .log import Log +from .icon import Icon +from .debug import debugMessage +from .stringtodate import stringToDate +from .documentation import launch_documentation \ No newline at end of file diff --git a/src/utils/createReport.py b/src/utils/createReport.py new file mode 100644 index 0000000..8c914d8 --- /dev/null +++ b/src/utils/createReport.py @@ -0,0 +1,61 @@ +import os +from prettytable import PrettyTable +from src.logic import Database +from src.utils import stringToDate +import sqlite3 as sql +from PyQt6.QtCore import QDate +from src import config +import datetime + +# query all loans that happened in the last 7 days +def generate_report(): + '''Generate an excel report for all actions that happened in the last seven days + + Returns: + str: a string represeting the generated table + ''' + db = Database() + path = db.db_path + year = datetime.datetime.now().year + week = datetime.datetime.now().isocalendar()[1] + if not os.path.exists(config.report.path): + os.makedirs(config.report.path) + report_path = os.path.join(config.report.path, f"report_{year}_{week}.tsv") + day = QDate.currentDate().addDays(-7).toString("yyyy-MM-dd") + query = f"""SELECT * FROM loans WHERE loan_date >= '{day}';""" + #print(query) + + colnames = ["UserId", "Title", "Action", "Datum"] + table = PrettyTable(colnames) + table.align[colnames[0]] = "l" + table.align[colnames[1]] = "l" + table.align[colnames[2]] = "l" + + with sql.connect(path) as conn: + cursor = conn.cursor() + cursor.execute(query) + loans = cursor.fetchall() + + for loan in loans: + loan_action = "Ausleihe" if loan[5] == 0 else "Rückgabe" + loan_action_date = stringToDate( + loan[3] if loan[5] == 0 else loan[6] + ).toString("dd.MM.yyyy") + table.add_row( + [ + loan[1], + db.getMedia(loan[2]).title, + loan_action, + loan_action_date, + ] + ) + # #print(table) + # # wruitng the table to a file + # with open("report.txt", "w", encoding="utf-8") as f: + # f.write(str(table)) + + tsv_table = table.get_csv_string().replace(",", "\t") + # write the file + with open(report_path, "w", encoding="utf-8") as f: + f.write(tsv_table) + return table diff --git a/src/utils/debug.py b/src/utils/debug.py new file mode 100644 index 0000000..8616e92 --- /dev/null +++ b/src/utils/debug.py @@ -0,0 +1,18 @@ +from src import config +from icecream import ic +from src.utils import Log +log = Log("debugMessage") + +def debugMessage(*args, **kwargs): + startmessage = "Logging debug message" + # join args and kwargs to a string + message = " ".join(args) + for key, value in kwargs.items(): + message += f" {key}: {value}" + if config.debug: + if config.log_debug: + log.info(f"{startmessage}: {message}") + if config.ic_logging == True: + ic(message) + else: print(message) + return message \ No newline at end of file diff --git a/src/utils/documentation.py b/src/utils/documentation.py new file mode 100644 index 0000000..f4a0cc8 --- /dev/null +++ b/src/utils/documentation.py @@ -0,0 +1,22 @@ +from pyramid.config import Configurator +from wsgiref.simple_server import make_server +from src import docport +import os +def website(): + config = Configurator() + + # Set up static file serving from the 'site/' directory + config.add_static_view(name='/', path=os.path.join(os.getcwd(), 'site'), cache_max_age=3600) + + app = config.make_wsgi_app() + return app + +def launch_documentation(): + app = website() + server = make_server('localhost', 6543, app) + print("Serving MkDocs documentation on http://0.0.0.0:{}".format(docport)) + server.serve_forever() + +if __name__ == '__main__': + pass + \ No newline at end of file diff --git a/src/utils/icon.py b/src/utils/icon.py new file mode 100644 index 0000000..3b75378 --- /dev/null +++ b/src/utils/icon.py @@ -0,0 +1,80 @@ +from omegaconf import OmegaConf +from PyQt6 import QtGui +import re + +config = OmegaConf.load("icons/icons.yaml") + +path = "icons/" + + +class Icon: + def __init__(self, icon_type, widget=None): + self.icon = QtGui.QIcon() + self.icon_path = path + config["icons"][icon_type] + self.add_icon(self.icon_path) + if widget is not None: + widget.setIcon(self.icon) + + def add_icon(self, icon_path): + icon = self.changeColor(icon_path) + + # use icon bytes to create a pixmap + pixmap = QtGui.QPixmap() + pixmap.loadFromData(icon) + + self.icon.addPixmap( + pixmap, + QtGui.QIcon.Mode.Normal, + QtGui.QIcon.State.Off, + ) + + def overwriteColor(self, color): + # take the icon, read it as bytes and change the color in fill + icon = self.changeColor(self.icon_path) + cicon = str(icon) + fill = re.search(r"fill=\"(.*?)\"", cicon).group(1) + stroke = re.search(r"stroke=\"(.*?)\"", cicon) + if stroke: + stroke = stroke.group(1) + + if fill and stroke: + # replace stroke + newicon = icon.replace(stroke.encode(), color.encode()) + else: + newicon = icon.replace(fill.encode(), color.encode()) + + pixmap = QtGui.QPixmap() + pixmap.loadFromData(newicon) + + self.icon.addPixmap( + pixmap, + QtGui.QIcon.Mode.Normal, + QtGui.QIcon.State.Off, + ) + return self.icon + + @staticmethod + def changeColor(icon_path) -> bytes: + """change the color of the svg icon to the color set in the config file + + Args: + icon_path (str): the path to the icon, usually icons/[name].svg + + Returns: + icon: a byte representation of the icon with the new color + """ + color = config.color + with open(icon_path, "rb") as file: + icon = file.read() + cicon = str(icon) + try: + fill = re.search(r"fill=\"(.*?)\"", cicon).group(1) + except AttributeError: + fill = None + if fill: + icon = icon.replace(fill.encode(), config.color.encode()) + return icon + + +if __name__ == "__main__": + print("This is a module and can not be executed directly.") diff --git a/src/utils/log.py b/src/utils/log.py new file mode 100644 index 0000000..6ba3717 --- /dev/null +++ b/src/utils/log.py @@ -0,0 +1,26 @@ +import logging + + +class Log: + def __init__(self, name): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging.DEBUG) + self.formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + self.file_handler = logging.FileHandler("log.log") + self.file_handler.setLevel(logging.DEBUG) + self.file_handler.setFormatter(self.formatter) + self.logger.addHandler(self.file_handler) + + def info(self, message): + self.logger.info(message) + + def debug(self, message): + self.logger.debug(message) + + def error(self, message): + self.logger.error(message) + + def warning(self, message): + self.logger.warning(message) \ No newline at end of file diff --git a/src/utils/reportThread.py b/src/utils/reportThread.py new file mode 100644 index 0000000..5e3edba --- /dev/null +++ b/src/utils/reportThread.py @@ -0,0 +1,68 @@ +from PyQt6.QtCore import QThread, pyqtSignal, QDate +from prettytable import PrettyTable +from src.logic import Database +from src.utils import stringToDate +import sqlite3 as sql + + +class ReportThread(QThread): + report_signal = pyqtSignal(str) + report_progress_signal = pyqtSignal(int) + report_nums_signal = pyqtSignal(int) + + def __init__(self, parent=None): + super(ReportThread, self).__init__(parent) + self.days = None + self.format = "txt" + + def setFormat(self, format): + self.format = format + + def setDays(self, days): + self.days = days + + def run(self): + db = Database() + + path = db.db_path + day = QDate.currentDate().addDays(-self.days).toString("yyyy-MM-dd") + query = f"""SELECT * FROM loans WHERE loan_date >= '{day}';""" + colnames = ["UserId", "Title", "Action", "Datum"] + table = PrettyTable(colnames) + table.align[colnames[0]] = "l" + table.align[colnames[1]] = "l" + table.align[colnames[2]] = "l" + + with sql.connect(path) as conn: + cursor = conn.cursor() + cursor.execute(query) + loans = cursor.fetchall() + self.report_nums_signal.emit(len(loans)) + counter = 0 + for loan in loans: + counter += 1 + self.report_progress_signal.emit(counter) + loan_action = "Ausleihe" if loan[5] == 0 else "Rückgabe" + loan_action_date = stringToDate( + loan[3] if loan[5] == 0 else loan[6] + ).toString("dd.MM.yyyy") + table.add_row( + [ + loan[1], + db.getMedia(loan[2]).title, + loan_action, + loan_action_date, + ] + ) + # # print(table) + # # wruitng the table to a file + if self.format == "tsv": + table = table.get_csv_string() + tsv_table = table.replace(",", "\t")#.replace("Rückgabe", "Rückgabe") + # write the file + with open("report.tsv", "w") as f: + f.write(tsv_table) + else: + with open("report.txt", "w", encoding="utf-8") as f: + f.write(str(table)) + self.report_signal.emit("Report generated successfully.") diff --git a/src/utils/stringtodate.py b/src/utils/stringtodate.py index be623a8..98cabb3 100644 --- a/src/utils/stringtodate.py +++ b/src/utils/stringtodate.py @@ -1,7 +1,6 @@ # import qdate from PyQt6 import QtCore - - +from .debug import debugMessage def stringToDate(date: str) -> QtCore.QDate: """Takes an input string and returns a QDate object. @@ -11,21 +10,33 @@ def stringToDate(date: str) -> QtCore.QDate: Returns: QtCore.QDate: the QDate object in string format DD.MM.yyyy """ + debugMessage(date=date) + if not date: + return "" + if isinstance(date, QtCore.QDate): + return date + else: + datedata = date.split("-") + day = datedata[2] + month = datedata[1] + year = datedata[0] + return QtCore.QDate(int(year), int(month), int(day)) - datedata = date.split(" ")[1:] - month = datedata[0] - day = datedata[1] - year = datedata[2] - month = month.replace("Jan", "01") - month = month.replace("Feb", "02") - month = month.replace("Mar", "03") - month = month.replace("Apr", "04") - month = month.replace("May", "05") - month = month.replace("Jun", "06") - month = month.replace("Jul", "07") - month = month.replace("Aug", "08") - month = month.replace("Sep", "09") - month = month.replace("Oct", "10") - month = month.replace("Nov", "11") - month = month.replace("Dec", "12") - return QtCore.QDate(int(year), int(month), int(day)).toString("dd.MM.yyyy") + # else: + # datedata = date.split(" ")[1:] + # month = datedata[0] + # day = datedata[1] + # year = datedata[2] + # month = month.replace("Jan", "01") + # month = month.replace("Feb", "02") + # month = month.replace("Mar", "03") + # month = month.replace("Apr", "04") + # month = month.replace("May", "05") + # month = month.replace("Jun", "06") + # month = month.replace("Jul", "07") + # month = month.replace("Aug", "08") + # month = month.replace("Sep", "09") + # month = month.replace("Oct", "10") + # month = month.replace("Nov", "11") + # month = month.replace("Dec", "12") + # return QtCore.QDate(int(year), int(month), int(day)).toString("dd.MM.yyyy")