feat(service): P4.1 support tables (queue/team_member/ticket/ticket_thread_link/state_history/callback) + events
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,8 @@ export const IIOS_EVENTS = {
|
|||||||
messagePersisted: 'com.insignia.iios.message.persisted.v1',
|
messagePersisted: 'com.insignia.iios.message.persisted.v1',
|
||||||
inboxItemCreated: 'com.insignia.iios.inbox.item.created.v1',
|
inboxItemCreated: 'com.insignia.iios.inbox.item.created.v1',
|
||||||
ticketCreated: 'com.insignia.iios.support.ticket.created.v1',
|
ticketCreated: 'com.insignia.iios.support.ticket.created.v1',
|
||||||
|
ticketStateChanged: 'com.insignia.iios.support.ticket.state.changed.v1',
|
||||||
|
callbackRequested: 'com.insignia.iios.support.callback.requested.v1',
|
||||||
notificationQueued: 'com.insignia.iios.notification.queued.v1',
|
notificationQueued: 'com.insignia.iios.notification.queued.v1',
|
||||||
deliveryAttempted: 'com.insignia.iios.delivery.attempted.v1',
|
deliveryAttempted: 'com.insignia.iios.delivery.attempted.v1',
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosTicketState" AS ENUM ('NEW', 'OPEN', 'PENDING_CUSTOMER', 'PENDING_INTERNAL', 'RESOLVED', 'CLOSED', 'CANCELLED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosTicketPriority" AS ENUM ('P0', 'P1', 'P2', 'P3', 'P4');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosCallbackStatus" AS ENUM ('PENDING', 'SCHEDULED', 'COMPLETED', 'MISSED', 'CANCELLED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosSupportQueue" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"supportTier" TEXT NOT NULL DEFAULT 'REGULAR',
|
||||||
|
"assignmentPolicy" JSONB,
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosSupportQueue_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosSupportTeamMember" (
|
||||||
|
"queueId" TEXT NOT NULL,
|
||||||
|
"actorId" TEXT NOT NULL,
|
||||||
|
"role" TEXT NOT NULL DEFAULT 'AGENT',
|
||||||
|
"maxActive" INTEGER NOT NULL DEFAULT 3,
|
||||||
|
"activeCount" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"availabilityState" TEXT NOT NULL DEFAULT 'OFFLINE',
|
||||||
|
|
||||||
|
CONSTRAINT "IiosSupportTeamMember_pkey" PRIMARY KEY ("queueId","actorId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosTicket" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"queueId" TEXT,
|
||||||
|
"requesterActorId" TEXT,
|
||||||
|
"assignedActorId" TEXT,
|
||||||
|
"subject" TEXT NOT NULL,
|
||||||
|
"state" "IiosTicketState" NOT NULL DEFAULT 'NEW',
|
||||||
|
"priority" "IiosTicketPriority" NOT NULL DEFAULT 'P3',
|
||||||
|
"issueClass" TEXT,
|
||||||
|
"createdFromInteractionId" TEXT,
|
||||||
|
"traceId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"metadata" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosTicket_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosTicketThreadLink" (
|
||||||
|
"ticketId" TEXT NOT NULL,
|
||||||
|
"threadId" TEXT NOT NULL,
|
||||||
|
"relationKind" TEXT NOT NULL DEFAULT 'PRIMARY',
|
||||||
|
"linkedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosTicketThreadLink_pkey" PRIMARY KEY ("ticketId","threadId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosTicketStateHistory" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"ticketId" TEXT NOT NULL,
|
||||||
|
"fromState" "IiosTicketState",
|
||||||
|
"toState" "IiosTicketState" NOT NULL,
|
||||||
|
"actorId" TEXT,
|
||||||
|
"reasonCode" TEXT,
|
||||||
|
"at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosTicketStateHistory_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosCallbackRequest" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"ticketId" TEXT,
|
||||||
|
"requesterActorId" TEXT NOT NULL,
|
||||||
|
"preferChannel" TEXT NOT NULL,
|
||||||
|
"preferredTime" TIMESTAMP(3),
|
||||||
|
"notes" TEXT,
|
||||||
|
"status" "IiosCallbackStatus" NOT NULL DEFAULT 'PENDING',
|
||||||
|
"meetingRef" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosCallbackRequest_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosSupportQueue_scopeId_idx" ON "IiosSupportQueue"("scopeId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosSupportTeamMember_actorId_idx" ON "IiosSupportTeamMember"("actorId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosTicket_scopeId_state_idx" ON "IiosTicket"("scopeId", "state");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosTicket_assignedActorId_state_idx" ON "IiosTicket"("assignedActorId", "state");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosTicketThreadLink_threadId_idx" ON "IiosTicketThreadLink"("threadId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosTicketStateHistory_ticketId_idx" ON "IiosTicketStateHistory"("ticketId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosCallbackRequest_scopeId_status_idx" ON "IiosCallbackRequest"("scopeId", "status");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosSupportQueue" ADD CONSTRAINT "IiosSupportQueue_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosSupportTeamMember" ADD CONSTRAINT "IiosSupportTeamMember_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "IiosSupportQueue"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosSupportTeamMember" ADD CONSTRAINT "IiosSupportTeamMember_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "IiosActorRef"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicket" ADD CONSTRAINT "IiosTicket_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicket" ADD CONSTRAINT "IiosTicket_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "IiosSupportQueue"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicket" ADD CONSTRAINT "IiosTicket_requesterActorId_fkey" FOREIGN KEY ("requesterActorId") REFERENCES "IiosActorRef"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicket" ADD CONSTRAINT "IiosTicket_assignedActorId_fkey" FOREIGN KEY ("assignedActorId") REFERENCES "IiosActorRef"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicket" ADD CONSTRAINT "IiosTicket_createdFromInteractionId_fkey" FOREIGN KEY ("createdFromInteractionId") REFERENCES "IiosInteraction"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicketThreadLink" ADD CONSTRAINT "IiosTicketThreadLink_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "IiosTicket"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicketThreadLink" ADD CONSTRAINT "IiosTicketThreadLink_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "IiosThread"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosTicketStateHistory" ADD CONSTRAINT "IiosTicketStateHistory_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "IiosTicket"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosCallbackRequest" ADD CONSTRAINT "IiosCallbackRequest_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosCallbackRequest" ADD CONSTRAINT "IiosCallbackRequest_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "IiosTicket"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosCallbackRequest" ADD CONSTRAINT "IiosCallbackRequest_requesterActorId_fkey" FOREIGN KEY ("requesterActorId") REFERENCES "IiosActorRef"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -95,6 +95,32 @@ enum IiosInboxState {
|
|||||||
STALE
|
STALE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum IiosTicketState {
|
||||||
|
NEW
|
||||||
|
OPEN
|
||||||
|
PENDING_CUSTOMER
|
||||||
|
PENDING_INTERNAL
|
||||||
|
RESOLVED
|
||||||
|
CLOSED
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IiosTicketPriority {
|
||||||
|
P0
|
||||||
|
P1
|
||||||
|
P2
|
||||||
|
P3
|
||||||
|
P4
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IiosCallbackStatus {
|
||||||
|
PENDING
|
||||||
|
SCHEDULED
|
||||||
|
COMPLETED
|
||||||
|
MISSED
|
||||||
|
CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Kernel tables ────────────────────────────────────────────────
|
// ─── Kernel tables ────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null).
|
/// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null).
|
||||||
@@ -114,6 +140,9 @@ model IiosScope {
|
|||||||
threads IiosThread[]
|
threads IiosThread[]
|
||||||
interactions IiosInteraction[]
|
interactions IiosInteraction[]
|
||||||
inboxItems IiosInboxItem[]
|
inboxItems IiosInboxItem[]
|
||||||
|
supportQueues IiosSupportQueue[]
|
||||||
|
tickets IiosTicket[]
|
||||||
|
callbacks IiosCallbackRequest[]
|
||||||
|
|
||||||
@@index([orgId, appId, tenantId])
|
@@index([orgId, appId, tenantId])
|
||||||
}
|
}
|
||||||
@@ -157,6 +186,10 @@ model IiosActorRef {
|
|||||||
receipts IiosMessageReceipt[]
|
receipts IiosMessageReceipt[]
|
||||||
unreadCounters IiosUnreadCounter[]
|
unreadCounters IiosUnreadCounter[]
|
||||||
inboxItemsOwned IiosInboxItem[]
|
inboxItemsOwned IiosInboxItem[]
|
||||||
|
teamMemberships IiosSupportTeamMember[]
|
||||||
|
ticketsRequested IiosTicket[] @relation("TicketRequester")
|
||||||
|
ticketsAssigned IiosTicket[] @relation("TicketAssignee")
|
||||||
|
callbacksRequested IiosCallbackRequest[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A configured channel surface (PORTAL in P1).
|
/// A configured channel surface (PORTAL in P1).
|
||||||
@@ -200,6 +233,7 @@ model IiosThread {
|
|||||||
interactions IiosInteraction[]
|
interactions IiosInteraction[]
|
||||||
unreadCounters IiosUnreadCounter[]
|
unreadCounters IiosUnreadCounter[]
|
||||||
inboxItems IiosInboxItem[]
|
inboxItems IiosInboxItem[]
|
||||||
|
ticketLinks IiosTicketThreadLink[]
|
||||||
|
|
||||||
@@index([scopeId, status])
|
@@index([scopeId, status])
|
||||||
}
|
}
|
||||||
@@ -242,9 +276,10 @@ model IiosInteraction {
|
|||||||
traceId String?
|
traceId String?
|
||||||
metadata Json?
|
metadata Json?
|
||||||
|
|
||||||
parts IiosMessagePart[]
|
parts IiosMessagePart[]
|
||||||
receipts IiosMessageReceipt[]
|
receipts IiosMessageReceipt[]
|
||||||
inboxItems IiosInboxItem[]
|
inboxItems IiosInboxItem[]
|
||||||
|
ticketsCreatedFrom IiosTicket[]
|
||||||
|
|
||||||
@@unique([scopeId, idempotencyKey])
|
@@unique([scopeId, idempotencyKey])
|
||||||
@@index([threadId, occurredAt])
|
@@index([threadId, occurredAt])
|
||||||
@@ -369,3 +404,113 @@ model IiosInboxItemStateHistory {
|
|||||||
|
|
||||||
@@index([inboxItemId])
|
@@index([inboxItemId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── P4: Support (specialization over message + inbox) ────────────
|
||||||
|
|
||||||
|
/// A support queue; agents (team members) are assigned tickets from it.
|
||||||
|
model IiosSupportQueue {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
scopeId String
|
||||||
|
scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
supportTier String @default("REGULAR")
|
||||||
|
assignmentPolicy Json?
|
||||||
|
enabled Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
members IiosSupportTeamMember[]
|
||||||
|
tickets IiosTicket[]
|
||||||
|
|
||||||
|
@@index([scopeId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An agent's membership + capacity in a queue. Agent = an ActorRef that is a member.
|
||||||
|
model IiosSupportTeamMember {
|
||||||
|
queueId String
|
||||||
|
queue IiosSupportQueue @relation(fields: [queueId], references: [id], onDelete: Cascade)
|
||||||
|
actorId String
|
||||||
|
actor IiosActorRef @relation(fields: [actorId], references: [id])
|
||||||
|
role String @default("AGENT")
|
||||||
|
maxActive Int @default(3)
|
||||||
|
activeCount Int @default(0)
|
||||||
|
availabilityState String @default("OFFLINE")
|
||||||
|
|
||||||
|
@@id([queueId, actorId])
|
||||||
|
@@index([actorId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Support work object. Linked many-to-many to threads; the thread stays generic.
|
||||||
|
model IiosTicket {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
scopeId String
|
||||||
|
scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade)
|
||||||
|
queueId String?
|
||||||
|
queue IiosSupportQueue? @relation(fields: [queueId], references: [id])
|
||||||
|
requesterActorId String?
|
||||||
|
requesterActor IiosActorRef? @relation("TicketRequester", fields: [requesterActorId], references: [id])
|
||||||
|
assignedActorId String?
|
||||||
|
assignedActor IiosActorRef? @relation("TicketAssignee", fields: [assignedActorId], references: [id])
|
||||||
|
subject String
|
||||||
|
state IiosTicketState @default(NEW)
|
||||||
|
priority IiosTicketPriority @default(P3)
|
||||||
|
issueClass String?
|
||||||
|
createdFromInteractionId String?
|
||||||
|
createdFromInteraction IiosInteraction? @relation(fields: [createdFromInteractionId], references: [id])
|
||||||
|
traceId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
threadLinks IiosTicketThreadLink[]
|
||||||
|
stateHistory IiosTicketStateHistory[]
|
||||||
|
callbacks IiosCallbackRequest[]
|
||||||
|
|
||||||
|
@@index([scopeId, state])
|
||||||
|
@@index([assignedActorId, state])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// M:N link between tickets and kernel threads (one thread ↔ many tickets, and vice-versa).
|
||||||
|
model IiosTicketThreadLink {
|
||||||
|
ticketId String
|
||||||
|
ticket IiosTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||||
|
threadId String
|
||||||
|
thread IiosThread @relation(fields: [threadId], references: [id], onDelete: Cascade)
|
||||||
|
relationKind String @default("PRIMARY")
|
||||||
|
linkedAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@id([ticketId, threadId])
|
||||||
|
@@index([threadId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ticket state-transition audit.
|
||||||
|
model IiosTicketStateHistory {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
ticketId String
|
||||||
|
ticket IiosTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
|
||||||
|
fromState IiosTicketState?
|
||||||
|
toState IiosTicketState
|
||||||
|
actorId String?
|
||||||
|
reasonCode String?
|
||||||
|
at DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([ticketId])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback request placeholder (the Meeting module is P8; meetingRef is a stub).
|
||||||
|
model IiosCallbackRequest {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
scopeId String
|
||||||
|
scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade)
|
||||||
|
ticketId String?
|
||||||
|
ticket IiosTicket? @relation(fields: [ticketId], references: [id])
|
||||||
|
requesterActorId String
|
||||||
|
requesterActor IiosActorRef @relation(fields: [requesterActorId], references: [id])
|
||||||
|
preferChannel String
|
||||||
|
preferredTime DateTime?
|
||||||
|
notes String?
|
||||||
|
status IiosCallbackStatus @default(PENDING)
|
||||||
|
meetingRef String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([scopeId, status])
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user