Restrict agent ticket visibility to strictly their own assigned tickets

Previous behavior also showed unassigned tickets to every agent (so new
tickets stayed discoverable to pick up), but the actual want here is
stricter: an agent sees only what's assigned to them, period. New
tickets are now only visible to admins until explicitly assigned to an
agent — an assign-then-work model rather than self-service pickup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-08-05 18:05:01 +00:00
1 parent 69467239ed
commit ed636bdbd5
2 files changed
+6 -9

No files matched your search

+4 -7
View File
@@ -417,10 +417,9 @@ export async function assignTicket(ticketId: string, assigneeId: string | null)
* body in the thread — a plain LIKE search, not FTS5 (simpler and * body in the thread — a plain LIKE search, not FTS5 (simpler and
* sufficient at this ticket volume; revisit if it gets slow). * sufficient at this ticket volume; revisit if it gets slow).
* *
* `visibility` restricts the result to what an agent should see: tickets * `visibility` restricts the result to what an agent should see: only
* assigned to them, plus unassigned ones (so new/unclaimed tickets are * tickets assigned to them, not the whole board. Admins see everything,
* still discoverable to pick up) — not the whole board. Admins see * so pass `undefined`/role "admin" for that case.
* everything, so pass `undefined`/role "admin" for that case.
*/ */
export async function listTickets( export async function listTickets(
query?: string, query?: string,
@@ -458,9 +457,7 @@ export async function listTickets(
let filteredRows = idsToKeep ? rows.filter((r) => idsToKeep.has(r.ticket.id)) : rows; let filteredRows = idsToKeep ? rows.filter((r) => idsToKeep.has(r.ticket.id)) : rows;
if (visibility?.role === "agent") { if (visibility?.role === "agent") {
filteredRows = filteredRows.filter( filteredRows = filteredRows.filter((r) => r.ticket.assigneeId === visibility.id);
(r) => r.ticket.assigneeId === null || r.ticket.assigneeId === visibility.id,
);
} }
const tagsMap = await getTagsForTickets(filteredRows.map((r) => r.ticket.id)); const tagsMap = await getTagsForTickets(filteredRows.map((r) => r.ticket.id));
+2 -2
View File
@@ -1,10 +1,10 @@
import type { TicketDTO } from "./types"; import type { TicketDTO } from "./types";
/** Agents see tickets assigned to them plus unassigned ones (so new tickets are still discoverable to pick up); admins see everything. */ /** Agents see only tickets assigned to them, nothing else — admins see everything. */
export function isTicketVisibleTo( export function isTicketVisibleTo(
ticket: Pick<TicketDTO, "assigneeId">, ticket: Pick<TicketDTO, "assigneeId">,
user: { id: string; role: "admin" | "agent" }, user: { id: string; role: "admin" | "agent" },
): boolean { ): boolean {
if (user.role === "admin") return true; if (user.role === "admin") return true;
return ticket.assigneeId === null || ticket.assigneeId === user.id; return ticket.assigneeId === user.id;
} }