feat(calllog): kontaktadresse_id als Pflicht-FK + is_erledigt()-Methoden

- Migration V01_13: Spalte kontaktadresse_id auf call_logs mit Backfill
  über die erste Kontaktadresse des Projekts; Commit vor dem
  Systeminfo-Update (Muster wie V01_08) gegen 'database is locked'.
- CallLog gehört zu genau einer Kontaktadresse (FK, Pflichtfeld);
  create/update validieren Existenz und Projekt-Zugehörigkeit.
- is_erledigt() auf Kontaktadresse (mind. ein erledigend-Tag reicht)
  und Projekt (False ohne Kontakte, sonst alle erledigt); beide in
  to_dict() serialisiert.
- Client: kontaktadresse_id in Notizmaske/Submit (required), Interfaces
  um is_erledigt erweitert.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-22 20:12:02 +02:00
1 parent 24b492c5c1
commit 977b64292c
9 files changed
+174 -18

No files matched your search

+31 -4
View File
@@ -12,7 +12,7 @@ dort über den Decorator authorisation_required.
from datetime import date
from db import dbapp
from crm.model import CallLog, CallLogTag, Projekt, ProjektUser, Tag
from crm.model import CallLog, CallLogTag, Kontaktadresse, Projekt, ProjektUser, Tag
from auth.model.errors import ApplicationError, HtmlStatusCode
@@ -71,6 +71,29 @@ def _set_call_log_tags(call_log: CallLog, tag_ids: list) -> None:
def _get_validierte_kontaktadresse_id(data: dict, projekt_id) -> int:
"""
Liest kontaktadresse_id aus dem data-Dict und validiert sie:
- Pflichtfeld: fehlt sie oder ist sie leer, wird Bad Request geliefert.
- Die Kontaktadresse muss existieren und zum selben Projekt gehören.
Liefert die geprüfte ID als int zurück.
"""
kontaktadresse_id = data.get("kontaktadresse_id")
if not kontaktadresse_id:
raise ApplicationError(
"Es muss eine Kontaktadresse ausgewählt werden.",
htmlstatus=HtmlStatusCode.Bad_Request,
)
kontaktadresse = Kontaktadresse.query.get(int(kontaktadresse_id))
if kontaktadresse is None or kontaktadresse.projekt_id != int(projekt_id):
raise ApplicationError(
"Die ausgewählte Kontaktadresse existiert nicht oder gehört nicht zum Projekt.",
htmlstatus=HtmlStatusCode.Bad_Request,
)
return kontaktadresse.id
def get_call_logs(projekt_id: int | None = None, user_id: int | None = None):
"""
Liefert Gesprächsnotizen, sortiert nach Datum absteigend.
@@ -130,9 +153,9 @@ def create_call_log(data: dict) -> CallLog:
Legt eine neue Gesprächsnotiz an. Die vom Client übergebene ID wird
ignoriert; die ID wird serverseitig per Auto-Increment vergeben.
- data: dict mit den Feldern projekt_id, datum, uhrzeit_beginn, dauer,
betreff, name_ansprechpartner, telefon, notiz,
wiedervorlage_datum, erledigt
- data: dict mit den Feldern projekt_id, kontaktadresse_id, datum,
uhrzeit_beginn, dauer, betreff, name_ansprechpartner, telefon,
notiz, wiedervorlage_datum, erledigt, tag_ids
"""
projekt_id = data.get("projekt_id")
if projekt_id is None:
@@ -146,8 +169,10 @@ def create_call_log(data: dict) -> CallLog:
"Das ausgewählte Projekt existiert nicht.",
htmlstatus=HtmlStatusCode.Bad_Request,
)
kontaktadresse_id = _get_validierte_kontaktadresse_id(data, projekt_id)
call_log = CallLog()
call_log.projekt_id = int(projekt_id)
call_log.kontaktadresse_id = kontaktadresse_id
call_log.datum = _parse_date(data.get("datum"))
call_log.uhrzeit_beginn = (data.get("uhrzeit_beginn") or "").strip() or None
call_log.dauer = (data.get("dauer") or "").strip() or None
@@ -181,7 +206,9 @@ def update_call_log(data: dict) -> CallLog:
"Das ausgewählte Projekt existiert nicht.",
htmlstatus=HtmlStatusCode.Bad_Request,
)
kontaktadresse_id = _get_validierte_kontaktadresse_id(data, projekt_id)
call_log.projekt_id = int(projekt_id)
call_log.kontaktadresse_id = kontaktadresse_id
call_log.datum = _parse_date(data.get("datum"))
call_log.uhrzeit_beginn = (data.get("uhrzeit_beginn") or "").strip() or None
call_log.dauer = (data.get("dauer") or "").strip() or None