This commit is contained in:
michael committed 2026-09-23 07:52:15 +00:00
1 parent 040f1903e6
commit d6c8877ed7
16 files changed
+1540 -177

No files matched your search

+1
View File
@@ -3,6 +3,7 @@ APP_PORT=3910
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
DB_POOL_SIZE=10
COOKIE_SECURE=false
+1
View File
@@ -5,6 +5,7 @@ POSTGRES_DB=tickettracker
POSTGRES_USER=tickettracker
POSTGRES_PASSWORD=change-me-db-password
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
DB_POOL_SIZE=10
COOKIE_SECURE=false
+1
View File
@@ -1,5 +1,6 @@
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
APP_PORT=3910
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
COOKIE_SECURE=false
+1
View File
@@ -4,3 +4,4 @@ APP_PORT=3911
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
COOKIE_SECURE=false
DB_POOL_SIZE=10
ZAMMAD_EMAIL_WEBHOOK_TOKEN='sPXJc87R@ySW3uTFKjGFMrti8BcDkNv8mN2g71hRkdg='
+5 -2
View File
@@ -17,6 +17,7 @@ export type AuthUser = {
active: boolean;
zammad_user_id: string | null;
has_zammad_api_token: boolean;
zammad_email_session_webhook_enabled: boolean;
};
declare global {
@@ -77,7 +78,8 @@ function publicUser(user: AuthUser) {
role: user.role,
active: user.active,
zammad_user_id: user.zammad_user_id ?? null,
has_zammad_api_token: Boolean(user.has_zammad_api_token)
has_zammad_api_token: Boolean(user.has_zammad_api_token),
zammad_email_session_webhook_enabled: Boolean(user.zammad_email_session_webhook_enabled)
};
}
@@ -134,7 +136,8 @@ export async function requireAuth(req: Request, res: Response, next: NextFunctio
u.role,
u.active,
u.zammad_user_id::text AS zammad_user_id,
(u.zammad_api_token IS NOT NULL) AS has_zammad_api_token
(u.zammad_api_token IS NOT NULL) AS has_zammad_api_token,
u.zammad_email_session_webhook_enabled
FROM auth_sessions s
JOIN users u ON u.id = s.user_id
WHERE s.token_hash = $1
+725 -167
View File
File diff suppressed because it is too large. Load diff
+22
View File
@@ -86,6 +86,7 @@ export async function migrate() {
`);
await query("ALTER TABLE users ADD COLUMN IF NOT EXISTS zammad_api_token TEXT;");
await query("ALTER TABLE users ADD COLUMN IF NOT EXISTS zammad_user_id BIGINT;");
await query("ALTER TABLE users ADD COLUMN IF NOT EXISTS zammad_email_session_webhook_enabled BOOLEAN NOT NULL DEFAULT false;");
await query(`
CREATE TABLE IF NOT EXISTS auth_sessions (
@@ -220,6 +221,27 @@ export async function migrate() {
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS recurring_occurrence_date DATE;");
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS recurring_occurrence_key TEXT;");
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS project_teamspace_entry_id BIGINT;");
await query(`
CREATE TABLE IF NOT EXISTS zammad_email_session_events (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
zammad_article_id BIGINT,
delivery_id TEXT,
session_id BIGINT REFERENCES sessions(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT zammad_email_session_event_source CHECK (zammad_article_id IS NOT NULL OR delivery_id IS NOT NULL)
);
`);
await query(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_zammad_email_session_events_user_article
ON zammad_email_session_events(user_id, zammad_article_id)
WHERE zammad_article_id IS NOT NULL;
`);
await query(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_zammad_email_session_events_delivery
ON zammad_email_session_events(delivery_id)
WHERE delivery_id IS NOT NULL;
`);
if (adminId) {
await query("UPDATE sessions SET user_id = $1 WHERE user_id IS NULL;", [adminId]);
}
+1
View File
@@ -27,6 +27,7 @@ services:
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-tickettracker}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-tickettracker}
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
ZAMMAD_EMAIL_WEBHOOK_TOKEN: ${ZAMMAD_EMAIL_WEBHOOK_TOKEN:-}
PORT: 3000
BACKEND_PORT: 3001
API_PROXY_TARGET: http://127.0.0.1:3001
+1
View File
@@ -8,6 +8,7 @@ services:
environment:
DATABASE_URL: ${DATABASE_URL}
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
ZAMMAD_EMAIL_WEBHOOK_TOKEN: ${ZAMMAD_EMAIL_WEBHOOK_TOKEN:-}
PORT: 3000
BACKEND_PORT: 3001
API_PROXY_TARGET: http://127.0.0.1:3001
+3 -3
View File
@@ -528,9 +528,9 @@ function SidebarTimers({ timers, selectedTimerId, setSelectedTimerId, setTimers,
<span className="truncate text-xs font-medium">{timer.ticketNumber}</span>
</span>
{timer.organizationName ? (
<span className="block truncate text-[11px] leading-4 text-muted-foreground">
{timer.isProject ? "Projekt · " : ""}
{timer.projectName || timer.organizationName}
<span className="flex min-w-0 items-center gap-1 truncate text-[11px] leading-4 text-muted-foreground">
{timer.isProject ? <FolderGit className="size-3 shrink-0" aria-label="Projekt" /> : null}
<span className="truncate">{timer.organizationName}</span>
</span>
) : null}
<span className="block font-mono text-[11px] font-semibold leading-4 tracking-normal text-muted-foreground">
+1
View File
@@ -197,6 +197,7 @@ export function updateCurrentUser(payload: {
currentPassword?: string;
newPassword?: string;
zammadApiToken?: string;
zammadEmailSessionWebhookEnabled?: boolean;
}) {
return request<{ user: AuthUser }>("/api/auth/me", {
method: "PATCH",
+14
View File
@@ -12,6 +12,7 @@ export type AuthUser = {
active: boolean;
zammad_user_id: string | null;
has_zammad_api_token: boolean;
zammad_email_session_webhook_enabled: boolean;
};
export type DashboardWidgetType = "dayControl" | "today" | "month" | "openRatings" | "projectBudgets" | "todos" | "teamspace" | "favorites" | "divider" | "statistic" | "notes";
@@ -425,6 +426,18 @@ export type StatisticsCrmDay = {
acknowledged_at?: string | null;
};
export type StatisticsTeamspaceDay = {
ticket_id: string;
ticket_number: string;
organization_name: string;
day: string;
tracked_minutes: number;
crm_billed_minutes: number;
delta_minutes: number;
session_count: number;
project_teamspace_project: string | null;
};
export type StatisticsOverview = {
periodType: "month";
period: string;
@@ -459,4 +472,5 @@ export type StatisticsOverview = {
acknowledgedMissingCrmDays: StatisticsCrmDay[];
crmMismatches: StatisticsCrmDay[];
};
teamspaceDays: StatisticsTeamspaceDay[];
};
+55 -1
View File
@@ -22,7 +22,7 @@ import { CopyTicketButton } from "@/components/CopyTicketButton";
import { HelpLink } from "@/components/HelpLink";
import { acknowledgeMissingTicketDayBilling, closePeriod, getStatisticsOverview, reopenPeriod } from "../api";
import { currentMonth, formatDate, formatDateTime, formatMinutes } from "../format";
import type { StatisticsCrmDay, StatisticsOpenSession, StatisticsOverview } from "../types";
import type { StatisticsCrmDay, StatisticsOpenSession, StatisticsOverview, StatisticsTeamspaceDay } from "../types";
type MonthlyClosePageProps = {
onNavigate: (to: string) => void;
@@ -156,6 +156,36 @@ function CrmDayItem({
);
}
function TeamspaceDayItem({ item, onNavigate }: { item: StatisticsTeamspaceDay; onNavigate: (to: string) => void }) {
const deltaVariant = item.delta_minutes === 0 ? "outline" : item.delta_minutes > 0 ? "success" : "destructive";
return (
<div className="grid gap-3 rounded-md border bg-background px-3 py-2 lg:grid-cols-[minmax(8rem,0.85fr)_minmax(0,1.45fr)_minmax(15rem,1fr)_auto] lg:items-center">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline">{formatDate(`${item.day}T00:00:00`)}</Badge>
<Badge variant="info">{item.session_count} Session(s)</Badge>
</div>
<div className="min-w-0 space-y-1">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<span className="font-medium">{item.ticket_number}</span>
<CopyTicketButton ticketNumber={item.ticket_number} />
{item.project_teamspace_project ? <Badge variant="secondary">{item.project_teamspace_project}</Badge> : null}
</div>
<p className="truncate text-sm text-muted-foreground">{item.organization_name}</p>
</div>
<div className="flex flex-wrap gap-x-3 gap-y-1 text-sm">
<span className={`font-medium ${trackedTextClass}`}>Abgerechnet {formatMinutes(item.tracked_minutes)}</span>
<span className={`font-medium ${teamspaceTextClass}`}>Teamspace {formatMinutes(item.crm_billed_minutes)}</span>
<Badge variant={deltaVariant}>Diff. {formatTeamspaceDelta(item.delta_minutes)}</Badge>
</div>
<Button className="h-8 justify-self-start lg:justify-self-end" variant="secondary" size="sm" onClick={() => onNavigate(`/tickets/${item.ticket_id}?month=${item.day.slice(0, 7)}`)}>
Tag öffnen
<ExternalLink className="size-4" />
</Button>
</div>
);
}
export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
const [month, setMonth] = useState(currentMonth());
const [stats, setStats] = useState<StatisticsOverview | null>(null);
@@ -274,6 +304,7 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
const openSessions = stats?.attention.openSessions ?? [];
const missingCrmDays = stats?.attention.missingCrmDays ?? [];
const acknowledgedMissingCrmDays = stats?.attention.acknowledgedMissingCrmDays ?? [];
const teamspaceDays = stats?.teamspaceDays ?? [];
const openTicketGroups = useMemo(() => groupOpenSessions(openSessions), [openSessions]);
const visibleMissingCrmDays = useMemo(() => {
const days = showAcknowledgedMissingCrm ? [...missingCrmDays, ...acknowledgedMissingCrmDays] : [...missingCrmDays];
@@ -379,6 +410,29 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
</CardContent>
</Card>
<Card>
<CardHeader className="p-4">
<div className="flex items-start justify-between gap-3">
<div>
<CardTitle>Vergleich abgerechnete Zeiten Teamspace</CardTitle>
<CardDescription>Teamspace-Tageswerte je Ticket mit Datum der abgerechneten Session(s) und Projekt, sofern verfügbar.</CardDescription>
</div>
<Badge variant="outline">{teamspaceDays.length} Eintrag(e)</Badge>
</div>
</CardHeader>
<CardContent className="space-y-2 px-4 pb-4">
{teamspaceDays.length > 0 ? (
<div className="max-h-[28rem] space-y-2 overflow-y-auto pr-1">
{teamspaceDays.map((item) => (
<TeamspaceDayItem key={`${item.ticket_id}-${item.day}`} item={item} onNavigate={onNavigate} />
))}
</div>
) : (
<p className="rounded-md border bg-muted/20 p-3 text-sm text-muted-foreground">Für diesen Monat sind noch keine Teamspace-Werte zu abgerechneten Ticket-Tagen vorhanden.</p>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
+22 -2
View File
@@ -1,9 +1,10 @@
import { FormEvent, useEffect, useState } from "react";
import { KeyRound, Save, Unplug, UserCircle, X } from "lucide-react";
import { KeyRound, MailPlus, Save, Unplug, UserCircle, X } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { HelpLink } from "@/components/HelpLink";
import { updateCurrentUser } from "../api";
import type { AuthUser } from "../types";
@@ -21,6 +22,7 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
const [confirmPassword, setConfirmPassword] = useState("");
const [zammadApiToken, setZammadApiToken] = useState("");
const [zammadTokenDirty, setZammadTokenDirty] = useState(false);
const [zammadEmailSessionWebhookEnabled, setZammadEmailSessionWebhookEnabled] = useState(currentUser.zammad_email_session_webhook_enabled);
const [saving, setSaving] = useState(false);
useEffect(() => {
@@ -28,6 +30,7 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
setDisplayName(currentUser.display_name);
setZammadApiToken("");
setZammadTokenDirty(false);
setZammadEmailSessionWebhookEnabled(currentUser.zammad_email_session_webhook_enabled);
}, [currentUser]);
async function submit(event: FormEvent) {
@@ -54,7 +57,8 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
displayName,
currentPassword: wantsPasswordChange ? currentPassword : undefined,
newPassword: wantsPasswordChange ? newPassword : undefined,
...(currentUser.role === "user" && zammadTokenDirty ? { zammadApiToken } : {})
...(currentUser.role === "user" && zammadTokenDirty ? { zammadApiToken } : {}),
...(currentUser.role === "user" ? { zammadEmailSessionWebhookEnabled } : {})
};
try {
@@ -206,6 +210,22 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
) : null}
</div>
</div>
<div className="mt-4 flex items-center justify-between gap-3 rounded-md border bg-background p-3">
<div className="flex items-center gap-2">
<MailPlus className="size-4 text-muted-foreground" />
<div>
<label className="text-sm font-medium" htmlFor="profile-zammad-email-session-webhook">
eMail-Sessions aus Zammad
</label>
<p className="text-xs text-muted-foreground">Ausgehende Zammad-eMails automatisch als Session übernehmen.</p>
</div>
</div>
<Switch
id="profile-zammad-email-session-webhook"
checked={zammadEmailSessionWebhookEnabled}
onCheckedChange={setZammadEmailSessionWebhookEnabled}
/>
</div>
</div>
) : null}
+8 -2
View File
@@ -1,6 +1,6 @@
import { FormEvent, useEffect, useMemo, useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import { Pause, Play, Plus, RotateCcw, Search, Square, Upload } from "lucide-react";
import { FolderGit, Pause, Play, Plus, RotateCcw, Search, Square, Upload } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -610,7 +610,13 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
<CardDescription>
{selectedTimer
? selectedTimer.organizationName
? `${selectedTimer.isProject ? "Projekt · " : ""}${selectedTimer.projectName || selectedTimer.organizationName}${selectedTimer.workType ? ` · ${selectedTimer.workType === "support" ? "Support" : "Consulting"}` : ""}`
? (
<span className="inline-flex min-w-0 items-center gap-1.5">
{selectedTimer.isProject ? <FolderGit className="size-3.5 shrink-0" aria-label="Projekt" /> : null}
<span>{selectedTimer.organizationName}</span>
{selectedTimer.workType ? <span>· {selectedTimer.workType === "support" ? "Support" : "Consulting"}</span> : null}
</span>
)
: "Beim Umschalten wird dieser Timer aktiviert und alle anderen werden pausiert."
: "Starte einen Timer, um eine Session zu erfassen."}
</CardDescription>
+679
View File
@@ -0,0 +1,679 @@
Richtung out
URL
http://192.168.42.169:3911/api/zammad/email-session-webhook
Methode
POST
Status
0
Anfrage
accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
accept: */*
user-agent: Zammad User Agent
host: 192.168.42.169:3911
x-zammad-trigger: Tickettracker Test
x-zammad-delivery: ef364583-36b3-40f3-bacb-967a09e7ddae
content-type: application/json; charset=utf-8
Authorization: Bearer [FILTERED]@ySW3uTFKjGFMrti8BcDkNv8mN2g71...[TRUNCATED]
{
"ticket": {
"abgerechnet": "",
"ai_agent_running": false,
"ai_stored_result_ids": [],
"ai_stored_results": [],
"article_count": 13,
"article_ids": [
66138,
66151,
66152,
66139,
66141,
66145,
66144,
66146,
66130,
66149,
66131,
66150,
66133
],
"atc_version": "-",
"bemerkung": "",
"bestellnummer": "",
"checklist_id": null,
"close_at": null,
"close_diff_in_min": null,
"close_escalation_at": null,
"close_in_min": null,
"create_article_sender": "Customer",
"create_article_sender_id": 2,
"create_article_type": "phone",
"create_article_type_id": 5,
"created_at": "2026-09-22T14:56:23.929Z",
"created_by": {
"active": true,
"address": "",
"city": "",
"country": "",
"created_at": "2025-03-31T08:14:45.514Z",
"created_by": "marco.christener@aikava.de",
"created_by_id": 3,
"department": "Senior Software Consultant",
"email": "michael.boehmlaender@aikava.de",
"fax": "",
"firstname": "Michael",
"id": 1764,
"image": "facb8844175ddeb0a0fab286b9b68fbd",
"image_source": null,
"lastname": "Böhmländer",
"login": "michael.boehmlaender@aikava.de",
"mobile": "",
"mobile_2": "",
"note": "Geplante Abwesenheiten:",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": true,
"out_of_office_end_at": "2026-08-28",
"out_of_office_replacement": "marco.dirscherl@aikava.de",
"out_of_office_replacement_id": 77,
"out_of_office_start_at": "2026-08-17",
"overview_sorting_ids": [
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320
],
"phone": "",
"role_ids": [
2,
1
],
"roles": [
"Admin",
"Agent"
],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-21T05:56:12.741Z",
"updated_by": "michael.boehmlaender@aikava.de",
"updated_by_id": 1764,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"created_by_id": 1764,
"customer": {
"active": true,
"address": "testadresse",
"city": "",
"country": "",
"created_at": "2023-01-10T13:07:40.366Z",
"created_by": "thomas.roider@aikava.de",
"created_by_id": 4,
"department": "",
"email": "test@test.de",
"fax": "",
"firstname": "Testkundenbesitzer",
"id": 6,
"image": null,
"image_source": null,
"lastname": "Testnachname",
"login": "test@test.de",
"mobile": "",
"mobile_2": null,
"note": "testkunde",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": false,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"out_of_office_start_at": null,
"overview_sorting_ids": [],
"overview_sortings": [],
"phone": "+49123456789",
"role_ids": [],
"roles": [],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-22T14:56:23.989Z",
"updated_by": "thomas.roider@aikava.de",
"updated_by_id": 4,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"customer_id": 6,
"daily_event_lock_ids": [],
"daily_event_locks": [],
"escalation_at": null,
"first_response_at": "2026-09-22T15:00:35.799Z",
"first_response_diff_in_min": null,
"first_response_escalation_at": null,
"first_response_in_min": null,
"gesamtbudget": "",
"group": {
"active": true,
"assignment_timeout": null,
"created_at": "2023-01-10T12:33:22.977Z",
"created_by": "thomas.roider@aikava.de",
"created_by_id": 4,
"email_address": "Zammad Helpdesk Support",
"email_address_id": 6,
"follow_up_assignment": true,
"follow_up_possible": "yes",
"id": 3,
"name": "Support",
"name_last": "Support",
"note": "Support AIKAVA",
"parent_id": null,
"reopen_time_in_days": null,
"shared_drafts": true,
"signature": "AIKAVA - Abteilung als Berufsbezeichnung (Gruppe Support)",
"signature_id": 2,
"summary_generation": "global_default",
"updated_at": "2026-07-30T09:34:01.249Z",
"updated_by": "thomas.roider@aikava.de",
"updated_by_id": 4,
"user_ids": [
6,
2308,
2309,
251,
77,
4,
3
],
"users": [
"test@test.de",
"apiuser@aikava.de",
"hung.nguyen@aikava.de",
"corinna.breu@aikava.de",
"marco.dirscherl@aikava.de",
"thomas.roider@aikava.de",
"marco.christener@aikava.de"
]
},
"group_id": 3,
"id": 7035,
"last_close_at": null,
"last_contact_agent_at": "2026-09-23T06:47:38.553Z",
"last_contact_at": "2026-09-23T06:47:38.553Z",
"last_contact_customer_at": "2026-09-22T14:56:24.008Z",
"last_owner_update_at": "2026-09-23T06:47:38.566Z",
"netzwerkpfad": "",
"note": null,
"number": "947021",
"organization": {
"active": true,
"country": "",
"created_at": "2023-01-06T11:06:22.976Z",
"created_by": "-",
"created_by_id": 1,
"customer_owner": null,
"dauerticket_bestellnummer": "",
"dauerticket_url": "",
"domain": "aikava.de",
"domain_assignment": false,
"hardware": null,
"hosting_adress": null,
"hostingkunde": false,
"hotline_contract": null,
"id": 2,
"member_ids": [
297,
295,
251,
301,
6,
2309,
982,
479,
78,
1764,
77,
3,
4
],
"members": [
"auto-feb98421-3344-4f54-a650-dc54b010ba90",
"auto-b4d24e63-db3e-44c2-9fb9-b8f3b6a1a675",
"corinna.breu@aikava.de",
"auto-27ce025f-f458-4a90-b320-df18812c150b",
"test@test.de",
"hung.nguyen@aikava.de",
"marcel.reisch@aikava.de",
"cristian.dimcia@aikava.de",
"heidi.schmaderer@aikava.de",
"michael.boehmlaender@aikava.de",
"marco.dirscherl@aikava.de",
"marco.christener@aikava.de",
"thomas.roider@aikava.de"
],
"name": "AIKAVA GmbH",
"note": "",
"ort": "Cham",
"plz": "93413",
"rechnungsadresse": "",
"secondary_member_ids": [],
"shared": true,
"stock_customer": false,
"strasse": "Galgenfeld 7",
"support_project": null,
"updated_at": "2026-09-22T14:56:23.997Z",
"updated_by": "corinna.breu@aikava.de",
"updated_by_id": 251,
"vertrieb_atoss": [],
"vip": false
},
"organization_id": 2,
"owner": {
"active": true,
"address": "",
"city": "",
"country": "",
"created_at": "2025-03-31T08:14:45.514Z",
"created_by": "marco.christener@aikava.de",
"created_by_id": 3,
"department": "Senior Software Consultant",
"email": "michael.boehmlaender@aikava.de",
"fax": "",
"firstname": "Michael",
"id": 1764,
"image": "facb8844175ddeb0a0fab286b9b68fbd",
"image_source": null,
"lastname": "Böhmländer",
"login": "michael.boehmlaender@aikava.de",
"mobile": "",
"mobile_2": "",
"note": "Geplante Abwesenheiten:",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": true,
"out_of_office_end_at": "2026-08-28",
"out_of_office_replacement": "marco.dirscherl@aikava.de",
"out_of_office_replacement_id": 77,
"out_of_office_start_at": "2026-08-17",
"overview_sorting_ids": [
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320
],
"phone": "",
"role_ids": [
2,
1
],
"roles": [
"Admin",
"Agent"
],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-21T05:56:12.741Z",
"updated_by": "michael.boehmlaender@aikava.de",
"updated_by_id": 1764,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"owner_id": 1764,
"pending_time": null,
"preferences": {},
"priority": {
"active": true,
"created_at": "2023-01-06T10:59:49.163Z",
"created_by": "-",
"created_by_id": 1,
"default_create": true,
"id": 2,
"name": "2 normal",
"note": null,
"ui_color": null,
"ui_icon": null,
"updated_at": "2023-01-06T10:59:49.163Z",
"updated_by": "-",
"updated_by_id": 1
},
"priority_id": 2,
"project": "",
"referencing_checklist_ids": [],
"referencing_checklists": [],
"state": "open",
"state_id": 2,
"ticket_time_accounting_ids": [
16133,
16135,
16137,
16138,
16139,
16140,
16141,
16142,
16144,
16145,
16146,
16147
],
"time_unit": "2.75",
"title": "Bla",
"type": null,
"update_diff_in_min": null,
"update_escalation_at": null,
"update_in_min": null,
"updated_at": "2026-09-23T06:47:42.937Z",
"updated_by": {
"active": true,
"address": "",
"city": "",
"country": "",
"created_at": "2025-03-31T08:14:45.514Z",
"created_by": "marco.christener@aikava.de",
"created_by_id": 3,
"department": "Senior Software Consultant",
"email": "michael.boehmlaender@aikava.de",
"fax": "",
"firstname": "Michael",
"id": 1764,
"image": "facb8844175ddeb0a0fab286b9b68fbd",
"image_source": null,
"lastname": "Böhmländer",
"login": "michael.boehmlaender@aikava.de",
"mobile": "",
"mobile_2": "",
"note": "Geplante Abwesenheiten:",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": true,
"out_of_office_end_at": "2026-08-28",
"out_of_office_replacement": "marco.dirscherl@aikava.de",
"out_of_office_replacement_id": 77,
"out_of_office_start_at": "2026-08-17",
"overview_sorting_ids": [
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320
],
"phone": "",
"role_ids": [
2,
1
],
"roles": [
"Admin",
"Agent"
],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-21T05:56:12.741Z",
"updated_by": "michael.boehmlaender@aikava.de",
"updated_by_id": 1764,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"updated_by_id": 1764
},
"article": {
"ai_stored_result_ids": [],
"ai_stored_results": [],
"attachments": [],
"body": "test<br><br><div data-signature=\"true\" data-signature-id=\"2\"><div>
<div><b>Michael Böhmländer</b></div>
<div>Senior Software Consultant<br>Geplante Abwesenheiten:</div>
</div><div><br></div><div><b>AIKAVA GmbH</b></div><div>Galgenfeld 7 | 93413 Cham</div><div><b><br></b></div><div>T: +49 9971 88999 40</div><div>
<a href=\"mailto:support.atoss@aikava.de\" title=\"mailto:support.atoss@aikava.de\">support@aikava.de</a><br>
</div><div>
<a href=\"https://aikava.de/\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">aikava.de</a><br>
</div><div><br></div><div><p>Amtsgericht Regensburg HRB 19554<br></p></div><div>Geschäftsführung Marco Christener</div><div><i><br></i></div><div><i>This e-mail may contain confidential and/or privileged information. If you are not the intended recipient or have received this e-mail in error please notify the sender immediately and delete this e-mail.</i></div></div>",
"body_rendering_error": false,
"cc": "",
"content_type": "text/html",
"created_at": "2026-09-23T06:47:38.553Z",
"created_by": {
"active": true,
"address": "",
"city": "",
"country": "",
"created_at": "2025-03-31T08:14:45.514Z",
"created_by": "marco.christener@aikava.de",
"created_by_id": 3,
"department": "Senior Software Consultant",
"email": "michael.boehmlaender@aikava.de",
"fax": "",
"firstname": "Michael",
"id": 1764,
"image": "facb8844175ddeb0a0fab286b9b68fbd",
"image_source": null,
"lastname": "Böhmländer",
"login": "michael.boehmlaender@aikava.de",
"mobile": "",
"mobile_2": "",
"note": "Geplante Abwesenheiten:",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": true,
"out_of_office_end_at": "2026-08-28",
"out_of_office_replacement": "marco.dirscherl@aikava.de",
"out_of_office_replacement_id": 77,
"out_of_office_start_at": "2026-08-17",
"overview_sorting_ids": [
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320
],
"phone": "",
"role_ids": [
2,
1
],
"roles": [
"Admin",
"Agent"
],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-21T05:56:12.741Z",
"updated_by": "michael.boehmlaender@aikava.de",
"updated_by_id": 1764,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"created_by_id": 1764,
"detected_language": null,
"from": "\"Michael Böhmländer via Zammad Helpdesk Support\" <support@aikava.de>",
"id": 66152,
"in_reply_to": "",
"internal": false,
"message_id": "<20260923064738.7035.8a97ff10-62fc-4d7a-9d91-2d968ea53002@aikava.zammad.com>",
"message_id_md5": "4d6a39f57fc20c1834cc2e7b8c6e94cd",
"origin_by_id": null,
"preferences": {
"email_address_id": 6,
"delivery_retry": 1,
"delivery_channel_id": 23,
"delivery_status_message": null,
"delivery_status": "success",
"delivery_status_date": "2026-09-23T06:47:42.903Z"
},
"reply_to": null,
"sender": "Agent",
"sender_id": 1,
"subject": "test",
"ticket_id": 7035,
"time_unit": "0.0",
"to": "michael.boehmlaender@aikava.de",
"type": "email",
"type_id": 1,
"updated_at": "2026-09-23T06:47:42.905Z",
"updated_by": {
"active": true,
"address": "",
"city": "",
"country": "",
"created_at": "2025-03-31T08:14:45.514Z",
"created_by": "marco.christener@aikava.de",
"created_by_id": 3,
"department": "Senior Software Consultant",
"email": "michael.boehmlaender@aikava.de",
"fax": "",
"firstname": "Michael",
"id": 1764,
"image": "facb8844175ddeb0a0fab286b9b68fbd",
"image_source": null,
"lastname": "Böhmländer",
"login": "michael.boehmlaender@aikava.de",
"mobile": "",
"mobile_2": "",
"note": "Geplante Abwesenheiten:",
"organization": "AIKAVA GmbH",
"organization_id": 2,
"organization_ids": [],
"organizations": [],
"out_of_office": true,
"out_of_office_end_at": "2026-08-28",
"out_of_office_replacement": "marco.dirscherl@aikava.de",
"out_of_office_replacement_id": 77,
"out_of_office_start_at": "2026-08-17",
"overview_sorting_ids": [
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320
],
"phone": "",
"role_ids": [
2,
1
],
"roles": [
"Admin",
"Agent"
],
"source": null,
"street": "",
"two_factor_preference_ids": [],
"two_factor_preferences": [],
"updated_at": "2026-09-21T05:56:12.741Z",
"updated_by": "michael.boehmlaender@aikava.de",
"updated_by_id": 1764,
"verified": false,
"vip": false,
"web": "",
"zip": ""
},
"updated_by_id": 1764,
"accounted_time": 0
}
}
Antwort
Erstellt am 2026-09-23 08:48:13