Files
LibrarySystem/src/ui/main_ui.py

666 lines
26 KiB
Python

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