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

+50
View File
@@ -275,6 +275,56 @@ def _db_V01_12():
session.commit()
def _db_V01_13():
"""
Fügt der Tabelle 'call_logs' die Spalte 'kontaktadresse_id' hinzu:
Jede Gesprächsnotiz gehört zu genau einer Kontaktadresse
(Fremdschlüssel -> kontaktadressen.id, Pflichtfeld).
Da SQLite kein nachträgliches NOT NULL auf einer bestehenden Spalte
setzen kann, wird die Spalte als nullable angelegt und anschließend
backgefüllt: Für bestehende CallLogs ohne Kontakt-Bezug wird die
erste Kontaktadresse des gleichen Projekts eingetragen. Das NOT NULL
ist im Modell deklariert und wird über die Server-Validierung in
crm.model.call_log durchgesetzt.
Idempotent: Bei Neuinstallation hat create_all die Spalte bereits
angelegt — in diesem Fall wird das ALTER TABLE übersprungen. Der
Backfill und der Index sind ebenfalls idempotent.
"""
from sqlalchemy import text
result = session.execute(text("PRAGMA table_info(call_logs)"))
spalten = {row[1] for row in result}
if 'kontaktadresse_id' not in spalten:
session.execute(text(
"ALTER TABLE call_logs ADD COLUMN kontaktadresse_id INTEGER"
))
# Backfill: erste Kontaktadresse des Projekts eintragen, falls
# noch keine kontaktadresse_id gesetzt ist.
session.execute(text(
"UPDATE call_logs SET kontaktadresse_id = ("
" SELECT ka.id FROM kontaktadressen ka"
" WHERE ka.projekt_id = call_logs.projekt_id"
" LIMIT 1"
") WHERE kontaktadresse_id IS NULL"
))
session.execute(text(
"CREATE INDEX IF NOT EXISTS ix_call_logs_kontaktadresse_id"
" ON call_logs (kontaktadresse_id)"
))
# Roh-SQL-Änderungen committen, BEVOR die Systeminfo aktualisiert wird
# (dasselbe Muster wie _db_V01_08): Das Backfill-UPDATE öffnet eine
# Schreib-Transaktion auf der Default-Bind-Verbindung —
# SQLALCHEMY_DATABASE_URI zeigt auf dieselbe Datei wie der appdb-Bind.
# Das ORM-Update der Systeminfo läuft über die appdb-Bind-Verbindung;
# ohne dieses Commit blockiert die offene Transaktion das UPDATE
# ('database is locked').
session.commit()
_updatesysteminfo('01.13')
session.commit()
def checkupdates():
"""
Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle