206 lines
7.0 KiB
TypeScript
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()
|
|
})
|
|
});
|
|
}
|