Update
This commit is contained in:
1 parent
040f1903e6
commit
d6c8877ed7
16 files changed
+1540
-177
No files matched your search
@@ -3,6 +3,7 @@ APP_PORT=3910
|
|||||||
|
|
||||||
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
|
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
|
||||||
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
|
||||||
DB_POOL_SIZE=10
|
DB_POOL_SIZE=10
|
||||||
|
|
||||||
COOKIE_SECURE=false
|
COOKIE_SECURE=false
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ POSTGRES_DB=tickettracker
|
|||||||
POSTGRES_USER=tickettracker
|
POSTGRES_USER=tickettracker
|
||||||
POSTGRES_PASSWORD=change-me-db-password
|
POSTGRES_PASSWORD=change-me-db-password
|
||||||
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
|
||||||
DB_POOL_SIZE=10
|
DB_POOL_SIZE=10
|
||||||
|
|
||||||
COOKIE_SECURE=false
|
COOKIE_SECURE=false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
|
DATABASE_URL=postgresql://tickettracker:change-me@10.10.10.23:5432/tickettracker
|
||||||
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
SECRET_ENCRYPTION_KEY=change-me-secret-encryption-key
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN=change-me-zammad-webhook-token
|
||||||
APP_PORT=3910
|
APP_PORT=3910
|
||||||
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
|
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
|
||||||
COOKIE_SECURE=false
|
COOKIE_SECURE=false
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ APP_PORT=3911
|
|||||||
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
|
TICKETTRACKER_IMAGE=git.d-razz.de/michael/tickettracker:latest
|
||||||
COOKIE_SECURE=false
|
COOKIE_SECURE=false
|
||||||
DB_POOL_SIZE=10
|
DB_POOL_SIZE=10
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN='sPXJc87R@ySW3uTFKjGFMrti8BcDkNv8mN2g71hRkdg='
|
||||||
+5
-2
@@ -17,6 +17,7 @@ export type AuthUser = {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
zammad_user_id: string | null;
|
zammad_user_id: string | null;
|
||||||
has_zammad_api_token: boolean;
|
has_zammad_api_token: boolean;
|
||||||
|
zammad_email_session_webhook_enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -77,7 +78,8 @@ function publicUser(user: AuthUser) {
|
|||||||
role: user.role,
|
role: user.role,
|
||||||
active: user.active,
|
active: user.active,
|
||||||
zammad_user_id: user.zammad_user_id ?? null,
|
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.role,
|
||||||
u.active,
|
u.active,
|
||||||
u.zammad_user_id::text AS zammad_user_id,
|
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
|
FROM auth_sessions s
|
||||||
JOIN users u ON u.id = s.user_id
|
JOIN users u ON u.id = s.user_id
|
||||||
WHERE s.token_hash = $1
|
WHERE s.token_hash = $1
|
||||||
|
|||||||
+725
-167
File diff suppressed because it is too large.
Load diff
@@ -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_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_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(`
|
await query(`
|
||||||
CREATE TABLE IF NOT EXISTS auth_sessions (
|
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_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 recurring_occurrence_key TEXT;");
|
||||||
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS project_teamspace_entry_id BIGINT;");
|
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) {
|
if (adminId) {
|
||||||
await query("UPDATE sessions SET user_id = $1 WHERE user_id IS NULL;", [adminId]);
|
await query("UPDATE sessions SET user_id = $1 WHERE user_id IS NULL;", [adminId]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USER:-tickettracker}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-tickettracker}
|
DATABASE_URL: postgresql://${POSTGRES_USER:-tickettracker}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-tickettracker}
|
||||||
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
|
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN: ${ZAMMAD_EMAIL_WEBHOOK_TOKEN:-}
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
BACKEND_PORT: 3001
|
BACKEND_PORT: 3001
|
||||||
API_PROXY_TARGET: http://127.0.0.1:3001
|
API_PROXY_TARGET: http://127.0.0.1:3001
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_URL: ${DATABASE_URL}
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
|
SECRET_ENCRYPTION_KEY: ${SECRET_ENCRYPTION_KEY:-}
|
||||||
|
ZAMMAD_EMAIL_WEBHOOK_TOKEN: ${ZAMMAD_EMAIL_WEBHOOK_TOKEN:-}
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
BACKEND_PORT: 3001
|
BACKEND_PORT: 3001
|
||||||
API_PROXY_TARGET: http://127.0.0.1:3001
|
API_PROXY_TARGET: http://127.0.0.1:3001
|
||||||
|
|||||||
@@ -528,9 +528,9 @@ function SidebarTimers({ timers, selectedTimerId, setSelectedTimerId, setTimers,
|
|||||||
<span className="truncate text-xs font-medium">{timer.ticketNumber}</span>
|
<span className="truncate text-xs font-medium">{timer.ticketNumber}</span>
|
||||||
</span>
|
</span>
|
||||||
{timer.organizationName ? (
|
{timer.organizationName ? (
|
||||||
<span className="block truncate text-[11px] leading-4 text-muted-foreground">
|
<span className="flex min-w-0 items-center gap-1 truncate text-[11px] leading-4 text-muted-foreground">
|
||||||
{timer.isProject ? "Projekt · " : ""}
|
{timer.isProject ? <FolderGit className="size-3 shrink-0" aria-label="Projekt" /> : null}
|
||||||
{timer.projectName || timer.organizationName}
|
<span className="truncate">{timer.organizationName}</span>
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="block font-mono text-[11px] font-semibold leading-4 tracking-normal text-muted-foreground">
|
<span className="block font-mono text-[11px] font-semibold leading-4 tracking-normal text-muted-foreground">
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ export function updateCurrentUser(payload: {
|
|||||||
currentPassword?: string;
|
currentPassword?: string;
|
||||||
newPassword?: string;
|
newPassword?: string;
|
||||||
zammadApiToken?: string;
|
zammadApiToken?: string;
|
||||||
|
zammadEmailSessionWebhookEnabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return request<{ user: AuthUser }>("/api/auth/me", {
|
return request<{ user: AuthUser }>("/api/auth/me", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type AuthUser = {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
zammad_user_id: string | null;
|
zammad_user_id: string | null;
|
||||||
has_zammad_api_token: boolean;
|
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";
|
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;
|
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 = {
|
export type StatisticsOverview = {
|
||||||
periodType: "month";
|
periodType: "month";
|
||||||
period: string;
|
period: string;
|
||||||
@@ -459,4 +472,5 @@ export type StatisticsOverview = {
|
|||||||
acknowledgedMissingCrmDays: StatisticsCrmDay[];
|
acknowledgedMissingCrmDays: StatisticsCrmDay[];
|
||||||
crmMismatches: StatisticsCrmDay[];
|
crmMismatches: StatisticsCrmDay[];
|
||||||
};
|
};
|
||||||
|
teamspaceDays: StatisticsTeamspaceDay[];
|
||||||
};
|
};
|
||||||
@@ -22,7 +22,7 @@ import { CopyTicketButton } from "@/components/CopyTicketButton";
|
|||||||
import { HelpLink } from "@/components/HelpLink";
|
import { HelpLink } from "@/components/HelpLink";
|
||||||
import { acknowledgeMissingTicketDayBilling, closePeriod, getStatisticsOverview, reopenPeriod } from "../api";
|
import { acknowledgeMissingTicketDayBilling, closePeriod, getStatisticsOverview, reopenPeriod } from "../api";
|
||||||
import { currentMonth, formatDate, formatDateTime, formatMinutes } from "../format";
|
import { currentMonth, formatDate, formatDateTime, formatMinutes } from "../format";
|
||||||
import type { StatisticsCrmDay, StatisticsOpenSession, StatisticsOverview } from "../types";
|
import type { StatisticsCrmDay, StatisticsOpenSession, StatisticsOverview, StatisticsTeamspaceDay } from "../types";
|
||||||
|
|
||||||
type MonthlyClosePageProps = {
|
type MonthlyClosePageProps = {
|
||||||
onNavigate: (to: string) => void;
|
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) {
|
export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
||||||
const [month, setMonth] = useState(currentMonth());
|
const [month, setMonth] = useState(currentMonth());
|
||||||
const [stats, setStats] = useState<StatisticsOverview | null>(null);
|
const [stats, setStats] = useState<StatisticsOverview | null>(null);
|
||||||
@@ -274,6 +304,7 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
|||||||
const openSessions = stats?.attention.openSessions ?? [];
|
const openSessions = stats?.attention.openSessions ?? [];
|
||||||
const missingCrmDays = stats?.attention.missingCrmDays ?? [];
|
const missingCrmDays = stats?.attention.missingCrmDays ?? [];
|
||||||
const acknowledgedMissingCrmDays = stats?.attention.acknowledgedMissingCrmDays ?? [];
|
const acknowledgedMissingCrmDays = stats?.attention.acknowledgedMissingCrmDays ?? [];
|
||||||
|
const teamspaceDays = stats?.teamspaceDays ?? [];
|
||||||
const openTicketGroups = useMemo(() => groupOpenSessions(openSessions), [openSessions]);
|
const openTicketGroups = useMemo(() => groupOpenSessions(openSessions), [openSessions]);
|
||||||
const visibleMissingCrmDays = useMemo(() => {
|
const visibleMissingCrmDays = useMemo(() => {
|
||||||
const days = showAcknowledgedMissingCrm ? [...missingCrmDays, ...acknowledgedMissingCrmDays] : [...missingCrmDays];
|
const days = showAcknowledgedMissingCrm ? [...missingCrmDays, ...acknowledgedMissingCrmDays] : [...missingCrmDays];
|
||||||
@@ -379,6 +410,29 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
<Card>
|
||||||
<CardHeader className="p-4">
|
<CardHeader className="p-4">
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { FormEvent, useEffect, useState } from "react";
|
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 { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { HelpLink } from "@/components/HelpLink";
|
import { HelpLink } from "@/components/HelpLink";
|
||||||
import { updateCurrentUser } from "../api";
|
import { updateCurrentUser } from "../api";
|
||||||
import type { AuthUser } from "../types";
|
import type { AuthUser } from "../types";
|
||||||
@@ -21,6 +22,7 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
|
|||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
const [zammadApiToken, setZammadApiToken] = useState("");
|
const [zammadApiToken, setZammadApiToken] = useState("");
|
||||||
const [zammadTokenDirty, setZammadTokenDirty] = useState(false);
|
const [zammadTokenDirty, setZammadTokenDirty] = useState(false);
|
||||||
|
const [zammadEmailSessionWebhookEnabled, setZammadEmailSessionWebhookEnabled] = useState(currentUser.zammad_email_session_webhook_enabled);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,6 +30,7 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
|
|||||||
setDisplayName(currentUser.display_name);
|
setDisplayName(currentUser.display_name);
|
||||||
setZammadApiToken("");
|
setZammadApiToken("");
|
||||||
setZammadTokenDirty(false);
|
setZammadTokenDirty(false);
|
||||||
|
setZammadEmailSessionWebhookEnabled(currentUser.zammad_email_session_webhook_enabled);
|
||||||
}, [currentUser]);
|
}, [currentUser]);
|
||||||
|
|
||||||
async function submit(event: FormEvent) {
|
async function submit(event: FormEvent) {
|
||||||
@@ -54,7 +57,8 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
|
|||||||
displayName,
|
displayName,
|
||||||
currentPassword: wantsPasswordChange ? currentPassword : undefined,
|
currentPassword: wantsPasswordChange ? currentPassword : undefined,
|
||||||
newPassword: wantsPasswordChange ? newPassword : undefined,
|
newPassword: wantsPasswordChange ? newPassword : undefined,
|
||||||
...(currentUser.role === "user" && zammadTokenDirty ? { zammadApiToken } : {})
|
...(currentUser.role === "user" && zammadTokenDirty ? { zammadApiToken } : {}),
|
||||||
|
...(currentUser.role === "user" ? { zammadEmailSessionWebhookEnabled } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -206,6 +210,22 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
import { FormEvent, useEffect, useMemo, useState } from "react";
|
||||||
import type { Dispatch, SetStateAction } 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 { toast } from "sonner";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -610,7 +610,13 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
|||||||
<CardDescription>
|
<CardDescription>
|
||||||
{selectedTimer
|
{selectedTimer
|
||||||
? selectedTimer.organizationName
|
? 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."
|
: "Beim Umschalten wird dieser Timer aktiviert und alle anderen werden pausiert."
|
||||||
: "Starte einen Timer, um eine Session zu erfassen."}
|
: "Starte einen Timer, um eine Session zu erfassen."}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|||||||
+679
@@ -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
|
||||||
Reference in new issue
Block a user