diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index 8ad2161..fff4103 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -2,7 +2,7 @@ from __future__ import annotations import dataclasses as dc import datetime -from typing import Any, TypedDict, cast +from typing import Any, TypeAlias, TypedDict, cast import polars as pl import sqlalchemy as sql @@ -10,6 +10,8 @@ import sqlalchemy as sql from wce_crm import db from wce_crm.logging import logger_back as logger +InitRecId: TypeAlias = int + class CompanyInfo(TypedDict): ma_id: str @@ -151,43 +153,63 @@ def initrec_comp_contact_person_search_get_info( def initrec_insert_initial_recording( data: dict[str, Any], -) -> None: +) -> InitRecId: logger.debug("[Call backend] insert_initial_recording") - stmt = db.grunderfassung.insert().values(data) + stmt = db.grunderfassung.insert() with db.ENGINE.begin() as conn: - conn.execute(stmt) + ret = conn.execute(stmt, data) + + if ret.rowcount == 0: + raise IOError("Entry was not inserted correctly") + + prim_keys = ret.inserted_primary_key + assert prim_keys + + return prim_keys[0] def initrec_update_initial_recording( - id_: int, + id_: InitRecId, data: dict[str, Any], ) -> None: logger.debug("[Call backend] update_initial_recording") - stmt = ( - db.grunderfassung.update().where(db.grunderfassung.c.erfassung_id == id_).values(data) - ) + stmt = db.grunderfassung.update().where(db.grunderfassung.c.erfassung_id == id_) with db.ENGINE.begin() as conn: - conn.execute(stmt) + conn.execute(stmt, data) def initrec_get_initial_recording( - id_: int, + id_: InitRecId, ) -> dict[str, Any]: logger.debug("[Call backend] get_initial_recording") stmt = db.grunderfassung.select().where(db.grunderfassung.c.erfassung_id == id_) with db.ENGINE.begin() as conn: ret = conn.execute(stmt) - row = ret.fetchone() - if row is None: + if ret.rowcount == 0: raise KeyError(f"Database ID {id_} not found") + row = ret.fetchone() + assert row, "row was not obtained" + return row._asdict() +def initrec_delete_initial_recording( + id_: InitRecId, +) -> None: + logger.debug("[Call backend] delete_initial_recording") + stmt = db.grunderfassung.delete().where(db.grunderfassung.c.erfassung_id == id_) + with db.ENGINE.begin() as conn: + ret = conn.execute(stmt) + + if ret.rowcount == 0: + raise KeyError(f"Database ID {id_} not found for deletion") + + @dc.dataclass(slots=True) class FrontpageCompany: - erfassung_id: int + erfassung_id: InitRecId # ma_id: int name: str Metadaten_aktualisierung: datetime.datetime diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 454f452..b5ebef9 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -53,7 +53,7 @@ from PySide6.QtWidgets import ( ) import wce_crm.constants -from wce_crm.backend import backend as be_init_rec +from wce_crm.backend import backend from wce_crm.data_models import COLUMN_SEP, FlatBaseModel, Grunderfassung from wce_crm.form_defs import ( INITREC_COMP, @@ -226,7 +226,7 @@ class AutoFormInsert(Protocol): def __call__( self, data: dict[str, Any], - ) -> None: ... + ) -> backend.InitRecId: ... class AutoFormUpdate(Protocol): @@ -244,12 +244,20 @@ class AutoFormGet(Protocol): ) -> dict[str, Any]: ... +class AutoFormDelete(Protocol): + def __call__( + self, + id_: int, + ) -> None: ... + + @dc.dataclass(slots=True) class AutoFormConfig: model: type[FlatBaseModel] data_insert: AutoFormInsert data_update: AutoFormUpdate data_get: AutoFormGet + data_delete: AutoFormDelete form_fields: Sequence[FormField] ignored_keys: Iterable[str] = tuple() add_buttons: bool = True @@ -1001,7 +1009,7 @@ class Grunderfassung_SuchWidget(CustomWidget): def fill_out_company( self, - data: be_init_rec.CompanyInfo, + data: backend.CompanyInfo, ) -> None: for key, widget in self.company_widgets.items(): if key not in data: @@ -1015,7 +1023,7 @@ class Grunderfassung_SuchWidget(CustomWidget): def fill_out_person( self, - data: be_init_rec.ContactPersonInfo, + data: backend.ContactPersonInfo, ) -> None: for key, widget in self.person_widgets.items(): if key not in data: @@ -1042,7 +1050,7 @@ class Grunderfassung_SuchWidget(CustomWidget): def update_company_data(self) -> None: self.company_search_input.clear() self.company_search_input.addItem(DROPDOWN_DEFAULT, None) - search_choices = be_init_rec.initrec_comp_search_choices() + search_choices = backend.initrec_comp_search_choices() for item, db_index in search_choices: self.company_search_input.addItem(item, db_index) self.company_search_input.setCurrentIndex(-1) @@ -1053,7 +1061,7 @@ class Grunderfassung_SuchWidget(CustomWidget): ) -> None: self.person_search_input.clear() self.person_search_input.addItem(DROPDOWN_DEFAULT, None) - search_choices = be_init_rec.initrec_comp_contact_person_search_choices(ma_id, True) + search_choices = backend.initrec_comp_contact_person_search_choices(ma_id, True) for item, db_index in search_choices: self.person_search_input.addItem(item, db_index) self.person_search_input.setCurrentIndex(0) @@ -1066,7 +1074,7 @@ class Grunderfassung_SuchWidget(CustomWidget): if ma_id is None or index == (-1): self._clear_company_fields() return - data = be_init_rec.initrec_comp_search_get_info( + data = backend.initrec_comp_search_get_info( ma_id=ma_id, ) self.fill_out_company(data) @@ -1081,7 +1089,7 @@ class Grunderfassung_SuchWidget(CustomWidget): self._clear_person_fields() return - data = be_init_rec.initrec_comp_contact_person_search_get_info( + data = backend.initrec_comp_contact_person_search_get_info( an_id=an_id, ) self.fill_out_person(data) @@ -1159,7 +1167,7 @@ def search_widgets_by_key( class AutoForm(QWidget): """a widget, which is managed by a code-defined field definition collection""" - save_clicked_form = Signal() # formular saved (data changed for front page) + update_triggered = Signal() # formular saved (data changed for front page) def __init__( self, @@ -1273,6 +1281,7 @@ class AutoForm(QWidget): if self.add_buttons: self.layout_btn = QHBoxLayout() self.main_layout.addLayout(self.layout_btn) + # save self.save_btn_txt_enabled = "Speichern (Strg + S)" self.save_btn_txt_disabled = "Wird gespeichert..." self.save_btn = QPushButton(self.save_btn_txt_enabled) @@ -1283,6 +1292,7 @@ class AutoForm(QWidget): ) self.save_btn.clicked.connect(self.save_data) self.layout_btn.addWidget(self.save_btn) + # reset self.reset_btn = QPushButton("Zurücksetzen (Strg + Z)") self.reset_btn.setShortcut("Ctrl+Z") self.reset_btn.setFixedHeight(50) @@ -1291,6 +1301,15 @@ class AutoForm(QWidget): ) self.reset_btn.clicked.connect(self.reset_form) self.layout_btn.addWidget(self.reset_btn) + # delete + self.delete_btn = QPushButton("Eintrag löschen (Strg + L)") + self.delete_btn.setShortcut("Ctrl+L") + self.delete_btn.setFixedHeight(50) + self.delete_btn.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + self.delete_btn.clicked.connect(self.delete_data) + self.layout_btn.addWidget(self.delete_btn) self.current_id: int = -1 @@ -1322,6 +1341,18 @@ class AutoForm(QWidget): QTimer.singleShot(timeout + 1, lambda: self.save_btn.setShortcut("Ctrl+S")) self.save_btn.setText(self.save_btn_txt_enabled) + def _activation_delete(self) -> None: + if self.current_id > -1: + self.delete_btn.setEnabled(True) + else: + self.delete_btn.setEnabled(False) + + def delete_data(self) -> None: + assert self.current_id > -1, "deletion initialised despite no index set" + self.cfg.data_delete(self.current_id) + self.update_triggered.emit() + self.reset_form() + def load_data( self, lookup_id: int | None = None, @@ -1354,6 +1385,7 @@ class AutoForm(QWidget): logger_auto_form.debug("Form data:\n%s", pformat(form_data)) self.set_form_data(form_data) self.current_id = lookup_id + self._activation_delete() def save_data(self) -> None: self._disable_save() @@ -1417,13 +1449,16 @@ class AutoForm(QWidget): if self.current_id < 0: logger_auto_form.debug("Insert triggered") - self.cfg.data_insert(db_data) + init_rec_id = self.cfg.data_insert(db_data) + assert isinstance(init_rec_id, int) + self.current_id = init_rec_id else: logger_auto_form.debug("Update triggered") self.cfg.data_update(self.current_id, db_data) logger_auto_form.info("Data saved successfully") - self.save_clicked_form.emit() + self.update_triggered.emit() + self._activation_delete() # self.reset_form() # TODO check if this behaviour is expected finally: # always re-enable save, even if error occurred @@ -1435,6 +1470,7 @@ class AutoForm(QWidget): def reset_form(self) -> None: reset_form(self.widget_registry) self.current_id = -1 + self._activation_delete() def get_form_data(self) -> dict[str, Any]: form_data = get_form_data(self.widget_registry) @@ -1977,12 +2013,12 @@ class NoScrollFilter(QObject): class ClickableCell(QFrame): """cell in the table on the startup screen""" - clicked = Signal(be_init_rec.FrontpageCompany) + clicked = Signal(backend.FrontpageCompany) def __init__( self, text: str, - data_record: be_init_rec.FrontpageCompany, + data_record: backend.FrontpageCompany, ): super().__init__() self.data_record = data_record @@ -2068,9 +2104,10 @@ class NewEntrySelect_view(QWidget): CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[AutoFormConfig] = AutoFormConfig( model=Grunderfassung, - data_insert=be_init_rec.initrec_insert_initial_recording, - data_update=be_init_rec.initrec_update_initial_recording, - data_get=be_init_rec.initrec_get_initial_recording, + data_insert=backend.initrec_insert_initial_recording, + data_update=backend.initrec_update_initial_recording, + data_get=backend.initrec_get_initial_recording, + data_delete=backend.initrec_delete_initial_recording, ignored_keys=( "Metadaten_erstellung", "Metadaten_aktualisierung", @@ -2081,9 +2118,10 @@ CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[AutoFormConfig] = AutoFormConfig( CONFIG_GRUNDERFASSUNG_PERSONEN: Final[AutoFormConfig] = AutoFormConfig( model=Grunderfassung, - data_insert=be_init_rec.initrec_insert_initial_recording, - data_update=be_init_rec.initrec_update_initial_recording, - data_get=be_init_rec.initrec_get_initial_recording, + data_insert=backend.initrec_insert_initial_recording, + data_update=backend.initrec_update_initial_recording, + data_get=backend.initrec_get_initial_recording, + data_delete=backend.initrec_delete_initial_recording, ignored_keys=( "Metadaten_erstellung", "Metadaten_aktualisierung", @@ -2100,7 +2138,7 @@ CUSTOM_WIDGETS: Final[dict[str, type[CustomWidget]]] = { class Page_InitRecCompany(QWidget): back_main_requested = Signal() # back to main page back_requested = Signal() # back button - save_clicked_form = Signal() # form saved (data changed for front page) + update_triggered = Signal() # form saved (data changed for front page) def __init__(self): super().__init__() @@ -2164,7 +2202,7 @@ class Page_InitRecCompany(QWidget): container_layout.addSpacing(20) self.auto_form = AutoForm(cfg=CONFIG_GRUNDERFASSUNG_UNTERNEHMEN) container_layout.addWidget(self.auto_form) - self.auto_form.save_clicked_form.connect(lambda: self.save_clicked_form.emit()) + self.auto_form.update_triggered.connect(lambda: self.update_triggered.emit()) container_layout.addSpacing(15) @@ -2229,7 +2267,7 @@ class Page_InitRecCompany(QWidget): class Page_InitRecPerson(QWidget): back_main_requested = Signal() # back to main page back_requested = Signal() # back button - save_clicked_form = Signal() # form saved (data changed for front page) + update_triggered = Signal() # form saved (data changed for front page) def __init__(self): super().__init__() @@ -2293,7 +2331,7 @@ class Page_InitRecPerson(QWidget): container_layout.addSpacing(20) self.auto_form = AutoForm(cfg=CONFIG_GRUNDERFASSUNG_PERSONEN) container_layout.addWidget(self.auto_form) - self.auto_form.save_clicked_form.connect(lambda: self.save_clicked_form.emit()) + self.auto_form.update_triggered.connect(lambda: self.update_triggered.emit()) container_layout.addSpacing(15) @@ -2402,13 +2440,13 @@ class MainWindow(QMainWindow): self.initrec_company = Page_InitRecCompany() self.initrec_company.back_main_requested.connect(self.show_main_page) self.initrec_company.back_requested.connect(self.show_new_entry_select) - self.initrec_company.save_clicked_form.connect(self.update_grid) + self.initrec_company.update_triggered.connect(self.update_grid) self.stack.addWidget(self.initrec_company) # SITE: 'Grunderfassung Person' self.initrec_person = Page_InitRecPerson() self.initrec_person.back_main_requested.connect(self.show_main_page) self.initrec_person.back_requested.connect(self.show_new_entry_select) - self.initrec_person.save_clicked_form.connect(self.update_grid) + self.initrec_person.update_triggered.connect(self.update_grid) self.stack.addWidget(self.initrec_person) def setup_main_page(self): @@ -2505,14 +2543,14 @@ class MainWindow(QMainWindow): def update_grid(self) -> None: clear_layout(self.grid) - data = be_init_rec.front_get_company_list() + data = backend.front_get_company_list() for entry in data: self.add_row_to_grid(entry) def add_row_to_grid( self, - entry: be_init_rec.FrontpageCompany, + entry: backend.FrontpageCompany, ): row = self.current_row @@ -2546,7 +2584,7 @@ class MainWindow(QMainWindow): def goto_initial_recording( self, - data: be_init_rec.FrontpageCompany, + data: backend.FrontpageCompany, ): if data.is_company: self.initrec_company.auto_form.load_data(data.erfassung_id)