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

+14 -2
View File
@@ -189,6 +189,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
let currentLog: {
id: number
projekt_id: number
kontaktadresse_id: number
projekt_name: string
kunden_name: string
datum: string
@@ -203,6 +204,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
} = {
id: -1,
projekt_id: -1,
kontaktadresse_id: -1,
projekt_name: "",
kunden_name: "",
datum: "",
@@ -315,7 +317,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
fetchCallLogById(id).then((result) => {
currentLog = result
projektVoreingestellt = true
ansprechpartnerVoreingestellt = currentLog.name_ansprechpartner !== ""
ansprechpartnerVoreingestellt = currentLog.kontaktadresse_id > 0
selectedTagIds = (result.tags || []).map((t: iTag) => t.id)
}).catch((e) => showServerError(e))
} else {
@@ -332,7 +334,10 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
currentLog.notiz = routeParams && routeParams.notiz ? routeParams.notiz : ""
currentLog.wiedervorlage_datum = routeParams && routeParams.wiedervorlage_datum ? routeParams.wiedervorlage_datum : ""
currentLog.erledigt = routeParams && (routeParams.erledigt === true || routeParams.erledigt === "true")
ansprechpartnerVoreingestellt = currentLog.name_ansprechpartner !== ""
// Kontaktadresse-ID aus den Route-Params übernehmen, wenn die
// Maske aus der Kontaktadressenliste geöffnet wurde.
currentLog.kontaktadresse_id = routeParams && routeParams.kontaktadresse_id ? parseInt(routeParams.kontaktadresse_id) : -1
ansprechpartnerVoreingestellt = currentLog.kontaktadresse_id > 0
// Bei Neuanlage Startzeit merken und Dauer-Timer starten.
startZeit = new Date()
timerId = window.setInterval(() => {
@@ -386,6 +391,12 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
projekt_id: projektVoreingestellt
? currentLog.projekt_id
: parseInt((formData.get("projekt_id_select") as string) || "-1"),
// Pflichtfeld: voreingestellt aus dem currentLog, sonst
// aus dem Select-Widget (kontaktadresse_id ist im Server
// ein Pflichtfeld, ohne wird Bad Request geliefert).
kontaktadresse_id: ansprechpartnerVoreingestellt
? currentLog.kontaktadresse_id
: parseInt((formData.get("kontaktadresse_id") as string) || "-1"),
projekt_name: "",
kunden_name: "",
datum: currentLog.datum || "",
@@ -501,6 +512,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
name: "kontaktadresse_id",
value: -1,
options: kontaktadressenOptionen,
required: true,
onchanged: (kontaktadresseId: number) => {
const ka = kontaktadressenListe.find((k: iKontaktadresse) => k.id === kontaktadresseId)
if (ka) {