from __future__ import annotations import datetime from typing import TYPE_CHECKING, Any, cast import polars as pl import sqlalchemy as sql from wce_crm import db from wce_crm.constants import TIMEZONE_CEST from wce_crm.logging import logger_back as logger from wce_crm.types import CompanyInfo, ContactPersonInfo, InitRecType, MainPageEntry if TYPE_CHECKING: from wce_crm.types import ExtAnId, ExtMaId, RecId def _transform_for_gui_output( data: pl.DataFrame, ) -> pl.DataFrame: q = ( data.lazy() .with_columns( pl.col(pl.Datetime).dt.to_string("%d.%m.%Y"), pl.col(pl.Date).dt.to_string("%d.%m.%Y"), pl.when(pl.col(pl.Boolean)) .then(pl.lit("Ja")) .otherwise(pl.lit("Nein")) .name.keep(), ) .with_columns(pl.all().cast(pl.String)) ) return q.collect() def initrec_comp_search_choices() -> tuple[tuple[str, int], ...]: # TODO no reload functionality logger.debug("[Call backend] comp_search_choices") q = db.DF_CRM_MASTER.lazy() counter = pl.int_range(0, pl.len()).over(pl.col.ma_unternehmensname) q = q.with_columns( dedupl=pl.when(counter == 0) .then(pl.col.ma_unternehmensname) .otherwise(pl.format("{} ({})", pl.col.ma_unternehmensname, counter)) ) df = q.collect() return tuple(zip(df["dedupl"], df["ma_id"])) def initrec_comp_search_get_info( ma_id: ExtMaId, ) -> CompanyInfo: logger.debug("[Call backend] comp_search_get_info") df = db.DF_CRM_MASTER.filter(pl.col.ma_id == ma_id) if df.height > 1 or df.height == 0: raise ValueError(f"Größe des zurückgelieferten Datenpakets ungültig: {df.height}") df = _transform_for_gui_output(df) return cast(CompanyInfo, df.row(0, named=True)) def initrec_comp_contact_person_search_choices( ma_id: ExtMaId | None, use_both_names: bool, ) -> tuple[tuple[str, int], ...]: # TODO no reload functionality logger.debug("[Call backend] contact_person_search_choices") q = db.DF_CONTACT_PERSON.lazy() if ma_id is not None: q = q.filter(pl.col.ma_id == ma_id) dedupl_col = pl.col.an_nachname if use_both_names: q = q.with_columns( name_search=(pl.format("{}, {}", pl.col.an_nachname, pl.col.an_vorname)) ) dedupl_col = pl.col.name_search counter = pl.int_range(0, pl.len()).over(dedupl_col) q = q.with_columns( dedupl=pl.when(counter == 0) .then(dedupl_col) .otherwise(pl.format("{} ({})", dedupl_col, counter)) ) df = q.collect() return tuple(zip(df["dedupl"], df["an_id"])) def initrec_comp_contact_person_search_get_info( an_id: ExtAnId, ) -> ContactPersonInfo: logger.debug("[Call backend] contact_person_search_get_info") df = db.DF_CONTACT_PERSON.filter(pl.col.an_id == an_id) if df.height > 1 or df.height == 0: raise ValueError(f"Größe des zurückgelieferten Datenpakets ungültig: {df.height}") df = _transform_for_gui_output(df) return cast(ContactPersonInfo, df.row(0, named=True)) # // internals def initrec_company_insert_initial_recording( data: dict[str, Any], ) -> RecId: logger.debug("[Call backend] insert_initial_recording") stmt = db.grunderfassung_unternehmen.insert() with db.ENGINE.begin() as conn: 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_company_update_initial_recording( id_: RecId, data: dict[str, Any], ) -> None: logger.debug("[Call backend] update_initial_recording") stmt = db.grunderfassung_unternehmen.update().where( db.grunderfassung_unternehmen.c.un_id == id_ ) with db.ENGINE.begin() as conn: conn.execute(stmt, data) def initrec_company_get_initial_recording( id_: RecId, ) -> dict[str, Any]: logger.debug("[Call backend] get_initial_recording") stmt = db.grunderfassung_unternehmen.select().where( db.grunderfassung_unternehmen.c.un_id == id_ ) with db.ENGINE.begin() as conn: ret = conn.execute(stmt) 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_company_delete_initial_recording( id_: RecId, ) -> None: logger.debug("[Call backend] delete_initial_recording") stmt = db.grunderfassung_unternehmen.delete().where( db.grunderfassung_unternehmen.c.un_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") def initrec_person_insert_initial_recording( data: dict[str, Any], ) -> RecId: logger.debug("[Call backend] insert_initial_recording") stmt = db.grunderfassung_personen.insert() with db.ENGINE.begin() as conn: 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_person_update_initial_recording( id_: RecId, data: dict[str, Any], ) -> None: logger.debug("[Call backend] update_initial_recording") stmt = db.grunderfassung_personen.update().where( db.grunderfassung_personen.c.pers_id == id_ ) with db.ENGINE.begin() as conn: conn.execute(stmt, data) def initrec_person_get_initial_recording( id_: RecId, ) -> dict[str, Any]: logger.debug("[Call backend] get_initial_recording") stmt = db.grunderfassung_personen.select().where( db.grunderfassung_personen.c.pers_id == id_ ) with db.ENGINE.begin() as conn: ret = conn.execute(stmt) 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_person_delete_initial_recording( id_: RecId, ) -> None: logger.debug("[Call backend] delete_initial_recording") stmt = db.grunderfassung_personen.delete().where( db.grunderfassung_personen.c.pers_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") # // main page interaction def _main_page_get_company_list() -> list[MainPageEntry]: logger.debug("[Call backend] get_company_list") stmt = sql.select( db.grunderfassung_unternehmen.c.un_id, db.grunderfassung_unternehmen.c.Partnersuche__un_suche, db.grunderfassung_unternehmen.c.Metadaten_aktualisierung, ) with db.ENGINE.connect() as conn: res = conn.execute(stmt) main_page_companies: list[MainPageEntry] = [] for entry in res.mappings(): rec_id = entry["un_id"] assert rec_id, "no RecID defined" ma_id_external = entry["Partnersuche__un_suche"] assert ma_id_external is not None, "external MA ID is NULL" datetime_akt = cast(datetime.datetime, entry["Metadaten_aktualisierung"]) datetime_akt = datetime_akt.astimezone(TIMEZONE_CEST) comp_info = initrec_comp_search_get_info(ma_id_external) display_name = comp_info["ma_unternehmensname"] main_page_companies.append( MainPageEntry( rec_id=rec_id, display_name=display_name, Metadaten_aktualisierung=datetime_akt, type=InitRecType.COMPANY, ) ) return main_page_companies def _main_page_get_person_list() -> list[MainPageEntry]: logger.debug("[Call backend] get_company_list") stmt = sql.select( db.grunderfassung_personen.c.pers_id, db.grunderfassung_personen.c.Stammdaten__vorname, db.grunderfassung_personen.c.Stammdaten__name, db.grunderfassung_personen.c.Metadaten_aktualisierung, ) with db.ENGINE.connect() as conn: res = conn.execute(stmt) main_page_persons: list[MainPageEntry] = [] for entry in res.mappings(): rec_id = entry["pers_id"] assert rec_id, "no RecID defined" datetime_akt = cast(datetime.datetime, entry["Metadaten_aktualisierung"]) datetime_akt = datetime_akt.astimezone() surname: str = entry["Stammdaten__name"] first_name: str = entry["Stammdaten__vorname"] names_to_join = [n for n in (first_name, surname) if n] display_name = " ".join(names_to_join) main_page_persons.append( MainPageEntry( rec_id=rec_id, display_name=display_name, Metadaten_aktualisierung=datetime_akt, type=InitRecType.PERSON, ) ) return main_page_persons def main_page_get_entries() -> list[MainPageEntry]: companies = _main_page_get_company_list() persons = _main_page_get_person_list() all_entries = companies + persons all_entries.sort(key=lambda x: x.Metadaten_aktualisierung, reverse=True) return all_entries