Files
support_backend/prisma/migrations/20260903070436_add_sla_escalation/migration.sql
T
saqib mirandClaude Sonnet 5 9357f03e1d feat: implement SLA and escalation (008)
Populates platform/business-calendars, orchestration/sla, and
orchestration/escalation (all thin stubs until now) with the real engine:

- business-calendars: a luxon-based day-by-day calendar walk
  (addBusinessMinutes/isWithinWorkingHours) excluding non-working hours,
  weekends, and holidays — replacing the naive createdAt+hours stub FR-004
  explicitly forbids.
- sla: most-specific SLAPolicy resolution (product/category/problemType/
  priority, wildcard-or-exact-match, specificity-count + updatedAt
  tiebreak), SLARun creation on the first real publish of the
  long-unused TICKET_ASSIGNED domain event, durable pause/resume via an
  absolute-timestamp shift (no in-memory state, verified across a real
  buildApp() restart), and a repeatable BullMQ breach-detection sweep
  (src/jobs/sla, itself a previously-unregistered stub) that is directly
  callable for tests, not only reachable through a running worker.
- escalation: EscalationPolicy/Rule CRUD (all 10 doc05 trigger types
  storable, only resolution_breach/first_response_breach evaluated),
  breach-triggered and manual escalation both funnel through one EscalationEvent
  + scoped re-assignment path. AssignmentEngine (007) gains
  assignToSpecificNode — a new, explicitly node-scoped entry point,
  since escalation must never let 007's general resolution re-derive a
  different node than the one a rule or a caller targeted.

Two small pre-existing scaffold gaps were closed along the way:
CategoriesRepository had no findById, and TICKET_ASSIGNED/SLA_BREACHED/
ESCALATION_TRIGGERED were defined since earlier phases but never
published by any code.

Verified against throwaway Docker Postgres/Redis (typecheck, lint,
architecture-check all clean; 148/150 relevant tests pass — the 2
failures are pre-existing, MinIO-dependent, and unrelated to this
feature).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 13:02:05 +05:30

148 lines
5.1 KiB
SQL

-- CreateTable
CREATE TABLE "sla_policies" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"productId" TEXT,
"categoryId" TEXT,
"problemTypeId" TEXT,
"priority" TEXT,
"firstResponseMinutes" INTEGER NOT NULL,
"investigationMinutes" INTEGER,
"resolutionMinutes" INTEGER NOT NULL,
"customerResponseMinutes" INTEGER,
"businessCalendarId" TEXT,
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "sla_policies_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sla_runs" (
"id" TEXT NOT NULL,
"ticketId" TEXT NOT NULL,
"policyId" TEXT NOT NULL,
"firstResponseDueAt" TIMESTAMP(3),
"resolutionDueAt" TIMESTAMP(3),
"status" TEXT NOT NULL,
"pausedAt" TIMESTAMP(3),
"resumedAt" TIMESTAMP(3),
"breachedAt" TIMESTAMP(3),
"firstResponseBreachedAt" TIMESTAMP(3),
"completedAt" TIMESTAMP(3),
CONSTRAINT "sla_runs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "business_calendars" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"timezone" TEXT NOT NULL,
"workingHours" JSONB NOT NULL,
CONSTRAINT "business_calendars_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "holidays" (
"id" TEXT NOT NULL,
"calendarId" TEXT NOT NULL,
"date" TIMESTAMP(3) NOT NULL,
"description" TEXT,
CONSTRAINT "holidays_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "escalation_policies" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"productId" TEXT,
"active" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "escalation_policies_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "escalation_rules" (
"id" TEXT NOT NULL,
"policyId" TEXT NOT NULL,
"triggerType" TEXT NOT NULL,
"condition" JSONB NOT NULL,
"targetNodeId" TEXT NOT NULL,
"notify" JSONB NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "escalation_rules_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "escalation_events" (
"id" TEXT NOT NULL,
"ticketId" TEXT NOT NULL,
"ruleId" TEXT,
"fromNodeId" TEXT,
"toNodeId" TEXT,
"reason" TEXT NOT NULL,
"triggeredBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "escalation_events_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "sla_policies_productId_categoryId_active_idx" ON "sla_policies"("productId", "categoryId", "active");
-- CreateIndex
CREATE UNIQUE INDEX "sla_runs_ticketId_key" ON "sla_runs"("ticketId");
-- CreateIndex
CREATE INDEX "sla_runs_status_resolutionDueAt_idx" ON "sla_runs"("status", "resolutionDueAt");
-- CreateIndex
CREATE INDEX "sla_runs_status_firstResponseDueAt_idx" ON "sla_runs"("status", "firstResponseDueAt");
-- CreateIndex
CREATE INDEX "holidays_calendarId_date_idx" ON "holidays"("calendarId", "date");
-- CreateIndex
CREATE INDEX "escalation_policies_productId_active_idx" ON "escalation_policies"("productId", "active");
-- CreateIndex
CREATE INDEX "escalation_rules_policyId_triggerType_active_idx" ON "escalation_rules"("policyId", "triggerType", "active");
-- CreateIndex
CREATE INDEX "escalation_events_ticketId_createdAt_idx" ON "escalation_events"("ticketId", "createdAt");
-- AddForeignKey
ALTER TABLE "sla_policies" ADD CONSTRAINT "sla_policies_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sla_policies" ADD CONSTRAINT "sla_policies_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sla_policies" ADD CONSTRAINT "sla_policies_businessCalendarId_fkey" FOREIGN KEY ("businessCalendarId") REFERENCES "business_calendars"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sla_runs" ADD CONSTRAINT "sla_runs_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sla_runs" ADD CONSTRAINT "sla_runs_policyId_fkey" FOREIGN KEY ("policyId") REFERENCES "sla_policies"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "holidays" ADD CONSTRAINT "holidays_calendarId_fkey" FOREIGN KEY ("calendarId") REFERENCES "business_calendars"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "escalation_policies" ADD CONSTRAINT "escalation_policies_productId_fkey" FOREIGN KEY ("productId") REFERENCES "products"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "escalation_rules" ADD CONSTRAINT "escalation_rules_policyId_fkey" FOREIGN KEY ("policyId") REFERENCES "escalation_policies"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "escalation_rules" ADD CONSTRAINT "escalation_rules_targetNodeId_fkey" FOREIGN KEY ("targetNodeId") REFERENCES "hierarchy_nodes"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "escalation_events" ADD CONSTRAINT "escalation_events_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE RESTRICT ON UPDATE CASCADE;