Files
gg c96ed6e97d feat(workflow): Tages-Workflow-UI für Telefon-Akquise (Liste + Maske)
Server: neuer Endpunkt /xhr/workflow/list aggregiert pro Kontaktadresse
die letzte Gesprächsnotiz, das effektiv gültige Wiedervorlage-Datum
(jüngste Notiz mit Wert), den Erledigt-Status und eine priorisierte
Sortierung (überfällig → ohne Notizen → Rest). Filter: kunden_id, status
(offen/erledigt), wiedervorlage (heute/3tage/ueberfaellig/woche).
Sichtbarkeit wie kontaktadressen über projekt_users.

CallLog erfasst den Ersteller serverseitig (user_id, Migration _db_V01_14,
kein FK wegen separater Auth-DB); to_dict ergänzt mitarbeiter_name.
calllog_list unterstützt kontaktadresse_id als Filter.

Client: TagesWorkflowListe (Route /tagesworkflow, Filterleiste) und
TagesWorkflowMaske (Route /tagesworkflowkontakt, lesende Historie,
Notiz-Formular mit "Speichern und weiter"), Routes + Menüpunkt erster
Eintrag. berechneWiedervorlageDatum aus der Call_Log-Maske nach
models/tag.ts ausgelagert (gemeinsame Nutzung).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-24 18:56:23 +02:00

144 lines
4.8 KiB
TypeScript

import m from "mithril"
/**
* Datenmodell für einen Tag (hierarchische Taxonomie).
* Die Feldnamen entsprechen den serverseitigen ORM-Spalten (snake_case).
* Tags koennen beliebig verschachtelt werden (parent_id -> tags.id).
*/
export interface iTag {
id: number
name: string
description: string
color: string
parent_id: number | null
is_parent: boolean
/** True = Kontakt gilt als erledigt, wenn dieser Tag zugewiesen wird. */
is_erledigend: boolean
/** True = Tag löst eine Wiedervorlage aus. */
loest_wiedervorlage_aus: boolean
/** Tage bis zur Wiedervorlage (nur relevant, wenn loest_wiedervorlage_aus True). */
wiedervorlage_abstand_tage: number | null
}
/**
* Tag mit verschachtelten Kindern für die Hierarchie-Darstellung.
* Das children-Array wird serverseitig im Hierarchie-Endpunkt befüllt.
*/
export interface iTagWithChildren extends iTag {
children: iTagWithChildren[]
}
/* ------------------------------------------------------------------ */
/* REST-API-Aufrufe (Client-Seite) */
/* ------------------------------------------------------------------ */
/* Die folgenden Funktionen kapseln die Kommunikation mit den */
/* serverseitigen REST-Endpunkten (/xhr/tag/*). */
/* ------------------------------------------------------------------ */
/**
* Basis-Pfad der Tag-REST-Endpunkte.
*/
const TAG_REST_BASE = "/xhr/tag"
/**
* Lädt alle Tags als flache Liste vom Server.
* Ist parentId gesetzt, werden nur die direkten Kinder dieses Tags
* geliefert (serverseitige Filterung).
*/
export function fetchTags(parentId?: number): Promise<iTag[]> {
const params: Record<string, any> = {}
if (parentId !== undefined) {
params.parent_id = parentId
}
return m.request({
method: "GET",
url: TAG_REST_BASE + "/list",
params: params
}).then((result) => result as iTag[])
}
/**
* Lädt die vollständige Tag-Hierarchie als verschachtelte Struktur vom Server.
* Jeder Tag enthält ein children-Array mit seinen rekursiv eingebetteten Kindern.
*/
export function fetchTagHierarchy(): Promise<iTagWithChildren[]> {
return m.request({
method: "GET",
url: TAG_REST_BASE + "/hierarchy"
}).then((result) => result as iTagWithChildren[])
}
/**
* Lädt einen einzelnen Tag anhand seiner ID vom Server.
*/
export function fetchTagById(id: number): Promise<iTag> {
return m.request({
method: "GET",
url: TAG_REST_BASE + "/get",
params: {id: id}
}).then((result) => result as iTag)
}
/**
* Legt einen neuen Tag serverseitig an. Die ID wird vom Server
* vergeben und im zurückgegebenen Tag-Objekt gesetzt.
*/
export function createTagREST(tag: iTag): Promise<iTag> {
return m.request({
method: "POST",
url: TAG_REST_BASE + "/create",
body: tag
}).then((result) => result as iTag)
}
/**
* Aktualisiert einen bestehenden Tag serverseitig.
*/
export function updateTagREST(tag: iTag): Promise<iTag> {
return m.request({
method: "PUT",
url: TAG_REST_BASE + "/update",
body: tag
}).then((result) => result as iTag)
}
/**
* Löscht einen Tag inkl. aller Nachfahren serverseitig anhand seiner ID.
*/
export function deleteTagREST(id: number): Promise<void> {
return m.request({
method: "DELETE",
url: TAG_REST_BASE + "/delete",
params: {id: id}
}).then(() => undefined)
}
/* ------------------------------------------------------------------ */
/* Fachliche Hilfsfunktionen */
/* ------------------------------------------------------------------ */
/**
* Berechnet das Wiedervorlage-Datum automatisch aus den ausgewählten
* Tags. Basis ist das übergebene Basisdatum (z.B. das Datum der Notiz
* oder heute). Aus allen ausgewählten Tags mit
* loest_wiedervorlage_aus=True wird das Minimum von
* wiedervorlage_abstand_tage ermittelt und zum Basisdatum addiert.
* Liefert einen leeren String, wenn kein Tag mit
* loest_wiedervorlage_aus=True ausgewählt ist.
*
* Wird von der Call_Log-Maske und der Tages-Workflow-Maske genutzt,
* damit die Berechnungslogik nur an einer Stelle gepflegt wird.
*/
export function berechneWiedervorlageDatum(tagIds: number[],
tagListe: iTag[],
basisdatum: string): string {
const wvTags = tagListe.filter((t: iTag) =>
tagIds.includes(t.id) && t.loest_wiedervorlage_aus && t.wiedervorlage_abstand_tage != null
)
if (wvTags.length === 0) return ""
const minAbstand = Math.min(...wvTags.map(t => t.wiedervorlage_abstand_tage as number))
const basisDate = new Date(basisdatum)
basisDate.setDate(basisDate.getDate() + minAbstand)
return basisDate.toISOString().split('T')[0]
}