Files
govoplan-tickets/webui/src/api/tickets.ts
T

206 lines
7.0 KiB
TypeScript

import {
apiFetch,
apiPath,
type ApiSettings
} from "@govoplan/core-webui";
export type TicketSubject = {
kind: string;
id: string;
label?: string | null;
role?: string | null;
};
export type TicketLink = {
link_id: string;
kind: string;
owner_module: string;
resource_type: string;
resource_id: string;
relation: string;
label?: string | null;
url?: string | null;
metadata: Record<string, unknown>;
};
export type TicketRecord = {
tenant_id: string;
ticket_id: string;
ticket_number: string;
revision: number;
ticket_type: "request" | "incident" | "problem" | "report";
priority: "low" | "normal" | "high" | "urgent";
status: string;
title: string;
description: string;
visibility: "tenant" | "restricted";
queue_ref?: string | null;
assignee?: TicketSubject | null;
reporter?: TicketSubject | null;
requester?: TicketSubject | null;
participants: TicketSubject[];
links: TicketLink[];
service_target_at?: string | null;
received_at: string;
recorded_at: string;
resolved_at?: string | null;
resolution_summary?: string | null;
deleted_at?: string | null;
change_reason: string;
metadata: Record<string, unknown>;
};
export type TicketListResponse = {
tickets: TicketRecord[];
total: number;
offset: number;
limit: number;
};
export type TicketAvailability = {
routing: { available: boolean; provider?: string | null };
case_escalation: { available: boolean; provider?: string | null };
modules: Record<string, boolean>;
consequences: Record<string, string>;
};
export type TicketHistoryEntry = {
revision: number;
event_type: string;
occurred_at: string;
actor_id?: string | null;
reason: string;
details: Record<string, unknown>;
};
export function listTickets(
settings: ApiSettings,
options: { statuses?: string[]; priorities?: string[]; ticketTypes?: string[]; queueRef?: string; query?: string; limit?: number },
signal?: AbortSignal
): Promise<TicketListResponse> {
return apiFetch(settings, apiPath("/api/v1/tickets", {
status: options.statuses,
priority: options.priorities,
ticket_type: options.ticketTypes,
queue_ref: options.queueRef,
query: options.query,
limit: options.limit
}), { signal });
}
export function getTicketAvailability(settings: ApiSettings, signal?: AbortSignal): Promise<TicketAvailability> {
return apiFetch(settings, "/api/v1/tickets/availability", { signal });
}
export function getTicket(settings: ApiSettings, ticketId: string, signal?: AbortSignal): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(ticketId)}`, { signal });
}
export function listTicketHistory(settings: ApiSettings, ticketId: string, signal?: AbortSignal): Promise<{ history: TicketHistoryEntry[] }> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(ticketId)}/history`, { signal });
}
export function createTicket(settings: ApiSettings, record: TicketRecord): Promise<TicketRecord> {
return apiFetch(settings, "/api/v1/tickets", {
method: "POST",
body: JSON.stringify({ record, idempotency_key: crypto.randomUUID() })
});
}
function mutation(record: TicketRecord, changeReason: string) {
return {
expected_revision: record.revision,
recorded_at: new Date().toISOString(),
change_reason: changeReason,
idempotency_key: crypto.randomUUID()
};
}
export function triageTicket(settings: ApiSettings, record: TicketRecord, changes: Record<string, unknown>, changeReason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/triage`, {
method: "PATCH",
body: JSON.stringify({ ...mutation(record, changeReason), changes })
});
}
export function assignTicket(settings: ApiSettings, record: TicketRecord, assignee: TicketSubject | null, changeReason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/assignment`, {
method: "POST",
body: JSON.stringify({ ...mutation(record, changeReason), assignee })
});
}
export function replaceTicketParticipants(settings: ApiSettings, record: TicketRecord, participants: TicketSubject[], changeReason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/participants`, {
method: "PUT",
body: JSON.stringify({ ...mutation(record, changeReason), participants })
});
}
export function addTicketLink(settings: ApiSettings, record: TicketRecord, link: TicketLink, changeReason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/links`, {
method: "POST",
body: JSON.stringify({ ...mutation(record, changeReason), link })
});
}
export function removeTicketLink(settings: ApiSettings, record: TicketRecord, linkId: string, changeReason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/links/${encodeURIComponent(linkId)}`, {
method: "DELETE",
body: JSON.stringify({ ...mutation(record, changeReason), changes: {} })
});
}
export function resolveTicket(
settings: ApiSettings,
record: TicketRecord,
targetStatus: string,
resolutionSummary: string | null,
changeReason: string
): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/resolution`, {
method: "POST",
body: JSON.stringify({ ...mutation(record, changeReason), target_status: targetStatus, resolution_summary: resolutionSummary })
});
}
export function addTicketComment(settings: ApiSettings, record: TicketRecord, body: string, visibility: "internal" | "external") {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/comments`, {
method: "POST",
body: JSON.stringify({
expected_revision: record.revision,
comment_id: crypto.randomUUID(),
body,
visibility,
recorded_at: new Date().toISOString(),
idempotency_key: crypto.randomUUID()
})
}) as Promise<{ ticket: TicketRecord; comment: Record<string, unknown> }>;
}
export function escalateTicket(settings: ApiSettings, record: TicketRecord, caseTypeKey: string, handoffNote: string) {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}/case-escalations`, {
method: "POST",
body: JSON.stringify({
expected_revision: record.revision,
case_type_key: caseTypeKey,
handoff_note: handoffNote || null,
occurred_at: new Date().toISOString(),
idempotency_key: crypto.randomUUID()
})
}) as Promise<{ ticket: TicketRecord; escalation: Record<string, unknown> }>;
}
export function deleteTicket(settings: ApiSettings, record: TicketRecord, reason: string): Promise<TicketRecord> {
return apiFetch(settings, `/api/v1/tickets/${encodeURIComponent(record.ticket_id)}`, {
method: "DELETE",
body: JSON.stringify({
expected_revision: record.revision,
occurred_at: new Date().toISOString(),
reason,
idempotency_key: crypto.randomUUID()
})
});
}