import os import sys import atexit import datetime import webbrowser from PyQt6 import QtCore, QtWidgets from omegaconf import OmegaConf from src import config, __contacts__, docport, log, restore_config from src.logic import Database, Catalogue, Backup, DocumentationThread from src.utils import stringToDate, Icon 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 .settings import Settings from .newBook import NewBook from .loans import LoanWindow from .reportUi import ReportUi from .addBook import addBook backup = Backup() cat = 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.setWindowTitle(f"Handbibliotheksleihsystem {config.institution_name}") self.setWindowIcon(Icon("main").icon) self.label_7.hide() self.nextReturnDate.hide() # add default loan duration to current date # hotkeys self.actionRueckgabemodus.triggered.connect(self.changeMode) self.actionNutzer.triggered.connect(self.showUser) 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:{__contacts__}") 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) self.addBook.clicked.connect(self.addBookAction) # LineEdits self.input_userno.returnPressed.connect( 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.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_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") # set Action Icons Icon("settings", self.actionEinstellungen) Icon("user", self.actionNutzer) Icon("quit", self.actionBeenden) Icon("report", self.actionBericht_erstellen) Icon("history", self.actionAusleihhistorie) Icon("help", self.actionDokumentation_ffnen) Icon("support", self.actionProblem_melden) Icon("add", self.addBook) self.show() def addBookAction(self): add = addBook() add.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) add.exec() result = add.result() book_id = add.book_id if result == 1: if self.activeUser: self.loanMedia(self.activeUser.id, book_id) # log.debug(f"UserID: {self.activeUser.id}, BookID: {book_id}") 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.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) 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 and settings.restart_required: # 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("restart").icon) dialog.setText( "Einstellungen wurden geändert\nDas Programm muss neu gestartet werden?" ) 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 log.info("Restarting Program") import os python_executable = sys.executable args = sys.argv[:] args.insert(0, sys.executable) os.execvp(python_executable, args) def changeMode(self): log.info("Changing Mode, current mode is {}", 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): log.info("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): log.info("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") self.input_file_ident.setFocus() def showUser(self): 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) # self.user_ui.setFields("John Doe", "123456789", "test@mail.com") self.user_ui.show() def setUserData(self): 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.setWindowModality( QtCore.Qt.WindowModality.ApplicationModal ) # Block MainUI fom being accessed user.exec() userid = user.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): 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: 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: self.activeUser = user[0] log.debug("User: {}", self.activeUser) if self.activeUser is not None: log.debug(self.activeUser.__dict__) self.setUserData() self.input_file_ident.setFocus() self.mode.setText("Ausleihe") loans = self.db.getActiveLoans(self.activeUser.id) log.debug("Active Loans", loans) self.btn_show_lentmedia.setText(loans) retdate = self.db.selectClosestReturnDate(self.activeUser.id) if retdate: date = stringToDate(retdate).toString("dd.MM.yyyy") today = QtCore.QDate.currentDate().toString("yyyy-MM-dd") # if retdate is in past, set nextReturnDate color to red if retdate < today: self.nextReturnDate.setStyleSheet("color: red") else: self.nextReturnDate.setStyleSheet("color: black") 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): log.info("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) log.debug(f"Book ID: {book_id}, User ID: {user_id}", media=media) if not book_id: log.info("Book not found, searching catalogue") if config.catalogue: 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: # TODO: implement book selection dialog raise NotImplementedError("Multiple books found") 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.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) log.debug( "Inserting duplicated book {} with id {} to user {}".format( book, entry, user_id ) ) self.loanMedia(user_id, [entry]) log.info("inserted duplicated book into database") else: log.info("No new entry created") self.input_file_ident.setEnabled(True) return else: # print("Book not loaned, loaning now") self.loanMedia(user_id, book_id) 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) # 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) if book_id: # check if book is already loaned loaned = self.db.checkLoanState(book_id[0]) if loaned: # 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], self.currentDate.toString("yyyy-MM-dd") ) self.mediaOverview.insertRow(0) 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") ) self.input_file_ident.clear() self.btn_show_lentmedia.setText( self.db.getActiveLoans(self.activeUser.id) ) else: # print("Book not loaned") self.setStatusTipMessage("Buch nicht entliehen") self.input_file_ident.clear() else: log.error("Book not found, identifier", identifier) # print("Book not found") # self.input_file_ident.setPlaceholderText(f"Buch {identifier} nicht gefunden") 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(): log.info( "Exiting, creating backup, renaming inactive users, creating report if day matches" ) restore_config() QtWidgets.QApplication(sys.argv) # print(backup.backup) # generate report if monday if datetime.datetime.now().weekday() == config.report.report_day: log.info("Generating Report") generate_report() Database().renameInactiveUsers() if config.database.do_backup: state = backup.createBackup() # create dialog to show state if state: return else: dialog = QtWidgets.QMessageBox() # set icon dialog.setWindowIcon(Icon("backup").icon) dialog.setWindowTitle("Backup") errormsg = "Backup konnte nicht erstellt werden\nGrund: {}" files = os.listdir(config.database.backupLocation) if ".backup" in files: errormsg = errormsg.format("Normaler speicherort nicht gefunden") else: errormsg = errormsg.format("Backuppfad nicht gefunden") dialog.setText(errormsg) dialog.exec() log.info("Exiting, backup:", 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() log.info("Exiting") sys.exit() def launch(*argv): options = sys.argv if argv: options += [arg for arg in argv] options += [arg for arg in options if arg.startswith("--")] # add options to sys.argv # print(options) # print("Launching Main UI") QtCore.QLocale().setDefault( QtCore.QLocale(QtCore.QLocale.Language.German, QtCore.QLocale.Country.Germany) ) SYSTEM_LANGUAGE = QtCore.QLocale().system().name() # Load base QT translations from the normal place app = QtWidgets.QApplication([]) 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()) # 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() # Load base QT translations from the normal place app = QtWidgets.QApplication([]) 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())