diff --git a/app/app.vue b/app/app.vue index 12a3138..2589efa 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,23 +1,47 @@ + + diff --git a/app/pages/clients/appointment-schedule-requests.vue b/app/pages/clients/appointment-schedule-requests.vue new file mode 100644 index 0000000..7aef7d5 --- /dev/null +++ b/app/pages/clients/appointment-schedule-requests.vue @@ -0,0 +1,273 @@ + + + diff --git a/app/pages/clients/index.vue b/app/pages/clients/index.vue index ac4d720..f7dfd35 100644 --- a/app/pages/clients/index.vue +++ b/app/pages/clients/index.vue @@ -123,6 +123,17 @@ ) const pendingNoteRequestCount = computed(() => pendingNoteRequests.value?.length ?? 0) + type ScheduleQueue = { pending: unknown[]; history: unknown[] } + const { data: scheduleRequestQueue } = await useFetch( + '/api/appointment-schedule-requests', + { + getCachedData: () => undefined, + } + ) + const pendingScheduleRequestCount = computed( + () => scheduleRequestQueue.value?.pending?.length ?? 0 + ) + /** Per-client most-urgent pending records request (earliest expiry wins). */ const pendingByClient = computed(() => { const map = new Map() @@ -388,15 +399,26 @@ Browse clients by status. Manage status and therapy progress.

- - Records requests - - {{ pendingNoteRequestCount }} pending - - +
+ + Records requests + + {{ pendingNoteRequestCount }} pending + + + + Session time requests + + {{ pendingScheduleRequestCount }} pending + + +
diff --git a/prisma/migrations/20260127013856_init/migration.sql b/prisma/migrations/20260127013856_init/migration.sql deleted file mode 100644 index b547781..0000000 --- a/prisma/migrations/20260127013856_init/migration.sql +++ /dev/null @@ -1,66 +0,0 @@ --- CreateTable -CREATE TABLE "user" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "emailVerified" BOOLEAN NOT NULL DEFAULT false, - "image" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "session" ( - "id" TEXT NOT NULL PRIMARY KEY, - "expiresAt" DATETIME NOT NULL, - "token" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "ipAddress" TEXT, - "userAgent" TEXT, - "userId" TEXT NOT NULL, - CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "account" ( - "id" TEXT NOT NULL PRIMARY KEY, - "accountId" TEXT NOT NULL, - "providerId" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "accessToken" TEXT, - "refreshToken" TEXT, - "idToken" TEXT, - "accessTokenExpiresAt" DATETIME, - "refreshTokenExpiresAt" DATETIME, - "scope" TEXT, - "password" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "verification" ( - "id" TEXT NOT NULL PRIMARY KEY, - "identifier" TEXT NOT NULL, - "value" TEXT NOT NULL, - "expiresAt" DATETIME NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); - --- CreateIndex -CREATE INDEX "session_userId_idx" ON "session"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); - --- CreateIndex -CREATE INDEX "account_userId_idx" ON "account"("userId"); - --- CreateIndex -CREATE INDEX "verification_identifier_idx" ON "verification"("identifier"); diff --git a/prisma/migrations/20260129194131_init/migration.sql b/prisma/migrations/20260129194131_init/migration.sql deleted file mode 100644 index b547781..0000000 --- a/prisma/migrations/20260129194131_init/migration.sql +++ /dev/null @@ -1,66 +0,0 @@ --- CreateTable -CREATE TABLE "user" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "emailVerified" BOOLEAN NOT NULL DEFAULT false, - "image" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "session" ( - "id" TEXT NOT NULL PRIMARY KEY, - "expiresAt" DATETIME NOT NULL, - "token" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "ipAddress" TEXT, - "userAgent" TEXT, - "userId" TEXT NOT NULL, - CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "account" ( - "id" TEXT NOT NULL PRIMARY KEY, - "accountId" TEXT NOT NULL, - "providerId" TEXT NOT NULL, - "userId" TEXT NOT NULL, - "accessToken" TEXT, - "refreshToken" TEXT, - "idToken" TEXT, - "accessTokenExpiresAt" DATETIME, - "refreshTokenExpiresAt" DATETIME, - "scope" TEXT, - "password" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "verification" ( - "id" TEXT NOT NULL PRIMARY KEY, - "identifier" TEXT NOT NULL, - "value" TEXT NOT NULL, - "expiresAt" DATETIME NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); - --- CreateIndex -CREATE INDEX "session_userId_idx" ON "session"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); - --- CreateIndex -CREATE INDEX "account_userId_idx" ON "account"("userId"); - --- CreateIndex -CREATE INDEX "verification_identifier_idx" ON "verification"("identifier"); diff --git a/prisma/migrations/20260217005051_add_gad/migration.sql b/prisma/migrations/20260217005051_add_gad/migration.sql deleted file mode 100644 index 278fd8e..0000000 --- a/prisma/migrations/20260217005051_add_gad/migration.sql +++ /dev/null @@ -1,36 +0,0 @@ --- CreateTable -CREATE TABLE "GadForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "GadForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "GadQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "g01" INTEGER, - "g02" INTEGER, - "g03" INTEGER, - "g04" INTEGER, - "g05" INTEGER, - "g06" INTEGER, - "g07" INTEGER, - "g08" INTEGER, - CONSTRAINT "GadQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "GadForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "GadQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE INDEX "GadForm_userId_idx" ON "GadForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "GadQuestion_formId_key" ON "GadQuestion"("formId"); - --- CreateIndex -CREATE INDEX "GadQuestion_userId_idx" ON "GadQuestion"("userId"); diff --git a/prisma/migrations/20260226040142/migration.sql b/prisma/migrations/20260226040142/migration.sql deleted file mode 100644 index 38b3e29..0000000 --- a/prisma/migrations/20260226040142/migration.sql +++ /dev/null @@ -1,123 +0,0 @@ -/* - Warnings: - - - You are about to drop the `GadForm` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `GadQuestion` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the column `image` on the `user` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "GadForm_userId_idx"; - --- DropIndex -DROP INDEX "GadQuestion_userId_idx"; - --- DropIndex -DROP INDEX "GadQuestion_formId_key"; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "GadForm"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "GadQuestion"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "AppForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "submittedAt" DATETIME, - CONSTRAINT "AppForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AppQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q01" TEXT, - "q02" TEXT, - "q03" TEXT, - "q04" TEXT, - "q05" TEXT, - "q06" TEXT, - "q07" TEXT, - "q08" TEXT, - "q09" TEXT, - "q10" TEXT, - "q11" TEXT, - "q12" TEXT, - "q13" TEXT, - "q14" TEXT, - "q15" TEXT, - "q16" TEXT, - "q17" TEXT, - "q18" TEXT, - "q19" TEXT, - "q20" TEXT, - "q21" TEXT, - "q22" TEXT, - "q23" TEXT, - "q24" TEXT, - "q25" TEXT, - "q26" TEXT, - "q27" TEXT, - "q28" TEXT, - "q29" TEXT, - "q30" TEXT, - "q31" TEXT, - "q32" TEXT, - "q33" TEXT, - "q34" TEXT, - "q35" TEXT, - "q36" TEXT, - "q37" TEXT, - "q38" TEXT, - "q39" TEXT, - "q40" TEXT, - "q41" TEXT, - "q42" TEXT, - "q43" TEXT, - "q44" TEXT, - "q45" TEXT, - "q46" TEXT, - "q47" TEXT, - "q48" TEXT, - "q49" TEXT, - "q50" TEXT, - CONSTRAINT "AppQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "AppForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "AppQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_user" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "emailVerified" BOOLEAN NOT NULL DEFAULT false, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "role" TEXT NOT NULL DEFAULT 'CLIENT', - "phoneNumber" INTEGER -); -INSERT INTO "new_user" ("createdAt", "email", "emailVerified", "id", "name", "updatedAt") SELECT "createdAt", "email", "emailVerified", "id", "name", "updatedAt" FROM "user"; -DROP TABLE "user"; -ALTER TABLE "new_user" RENAME TO "user"; -CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE UNIQUE INDEX "AppForm_userId_key" ON "AppForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "AppQuestion_formId_key" ON "AppQuestion"("formId"); - --- CreateIndex -CREATE INDEX "AppQuestion_userId_idx" ON "AppQuestion"("userId"); diff --git a/prisma/migrations/20260226042411_add_image/migration.sql b/prisma/migrations/20260226042411_add_image/migration.sql deleted file mode 100644 index b34507c..0000000 --- a/prisma/migrations/20260226042411_add_image/migration.sql +++ /dev/null @@ -1,203 +0,0 @@ --- AlterTable -ALTER TABLE "user" ADD COLUMN "image" TEXT; - --- CreateTable -CREATE TABLE "form" ( - "id" TEXT NOT NULL PRIMARY KEY, - "title" TEXT NOT NULL, - "description" TEXT, - "slug" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "question" ( - "id" TEXT NOT NULL PRIMARY KEY, - "text" TEXT NOT NULL, - "type" TEXT NOT NULL, - "alias" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "form_question" ( - "id" TEXT NOT NULL PRIMARY KEY, - "formId" TEXT NOT NULL, - "questionId" TEXT NOT NULL, - "order" INTEGER NOT NULL, - CONSTRAINT "form_question_formId_fkey" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "form_question_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "question" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "form_assignment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "formId" TEXT NOT NULL, - "assignedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "completedAt" DATETIME, - CONSTRAINT "form_assignment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "form_assignment_formId_fkey" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ace_response" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "responses" TEXT NOT NULL, - "completedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "ace_response_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "GadForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "GadForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "GadQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "g01" INTEGER, - "g02" INTEGER, - "g03" INTEGER, - "g04" INTEGER, - "g05" INTEGER, - "g06" INTEGER, - "g07" INTEGER, - "g08" INTEGER, - CONSTRAINT "GadQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "GadForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "GadQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhqForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "submittedAt" DATETIME, - CONSTRAINT "PhqForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhqQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q1" INTEGER, - "q2" INTEGER, - "q3" INTEGER, - "q4" INTEGER, - "q5" INTEGER, - "q6" INTEGER, - "q7" INTEGER, - "q8" INTEGER, - "q9" INTEGER, - CONSTRAINT "PhqQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PhqForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "PhqQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PclForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "submittedAt" DATETIME, - CONSTRAINT "PclForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PclQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q01" INTEGER, - "q02" INTEGER, - "q03" INTEGER, - "q04" INTEGER, - "q05" INTEGER, - "q06" INTEGER, - "q07" INTEGER, - "q08" INTEGER, - "q09" INTEGER, - "q10" INTEGER, - "q11" INTEGER, - "q12" INTEGER, - "q13" INTEGER, - "q14" INTEGER, - "q15" INTEGER, - "q16" INTEGER, - "q17" INTEGER, - "q18" INTEGER, - "q19" INTEGER, - "q20" INTEGER, - CONSTRAINT "PclQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PclForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "PclQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "form_slug_key" ON "form"("slug"); - --- CreateIndex -CREATE UNIQUE INDEX "question_alias_key" ON "question"("alias"); - --- CreateIndex -CREATE INDEX "form_question_formId_idx" ON "form_question"("formId"); - --- CreateIndex -CREATE INDEX "form_question_questionId_idx" ON "form_question"("questionId"); - --- CreateIndex -CREATE UNIQUE INDEX "form_question_formId_questionId_key" ON "form_question"("formId", "questionId"); - --- CreateIndex -CREATE INDEX "form_assignment_userId_idx" ON "form_assignment"("userId"); - --- CreateIndex -CREATE INDEX "form_assignment_formId_idx" ON "form_assignment"("formId"); - --- CreateIndex -CREATE UNIQUE INDEX "form_assignment_userId_formId_key" ON "form_assignment"("userId", "formId"); - --- CreateIndex -CREATE INDEX "ace_response_userId_idx" ON "ace_response"("userId"); - --- CreateIndex -CREATE INDEX "GadForm_userId_idx" ON "GadForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "GadQuestion_formId_key" ON "GadQuestion"("formId"); - --- CreateIndex -CREATE INDEX "GadQuestion_userId_idx" ON "GadQuestion"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhqForm_userId_key" ON "PhqForm"("userId"); - --- CreateIndex -CREATE INDEX "PhqForm_userId_idx" ON "PhqForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhqQuestion_formId_key" ON "PhqQuestion"("formId"); - --- CreateIndex -CREATE INDEX "PhqQuestion_userId_idx" ON "PhqQuestion"("userId"); - --- CreateIndex -CREATE INDEX "PclForm_userId_idx" ON "PclForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PclQuestion_formId_key" ON "PclQuestion"("formId"); - --- CreateIndex -CREATE INDEX "PclQuestion_userId_idx" ON "PclQuestion"("userId"); diff --git a/prisma/migrations/20260226060301/migration.sql b/prisma/migrations/20260226060301/migration.sql deleted file mode 100644 index d40952a..0000000 --- a/prisma/migrations/20260226060301/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ --- CreateTable -CREATE TABLE "client" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'INCOMPLETE', - "therapyWeek" INTEGER, - CONSTRAINT "client_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "client_userId_key" ON "client"("userId"); - --- CreateIndex -CREATE INDEX "client_status_idx" ON "client"("status"); diff --git a/prisma/migrations/20260226072412_appointments/migration.sql b/prisma/migrations/20260226072412_appointments/migration.sql deleted file mode 100644 index e8d585f..0000000 --- a/prisma/migrations/20260226072412_appointments/migration.sql +++ /dev/null @@ -1,26 +0,0 @@ -/* - Warnings: - - - You are about to drop the `client` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "client"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "Appointment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "adminId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT, - "startTime" DATETIME NOT NULL, - "endTime" DATETIME NOT NULL, - "status" TEXT NOT NULL DEFAULT 'SCHEDULED', - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Appointment_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Appointment_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/prisma/migrations/20260314154429/migration.sql b/prisma/migrations/20260314154429/migration.sql deleted file mode 100644 index 57888dd..0000000 --- a/prisma/migrations/20260314154429/migration.sql +++ /dev/null @@ -1,374 +0,0 @@ --- CreateTable -CREATE TABLE "client" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'INCOMPLETE', - "therapyWeek" INTEGER, - "missedSessions" INTEGER NOT NULL DEFAULT 0, - CONSTRAINT "client_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "client_permission" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "canViewScores" BOOLEAN NOT NULL DEFAULT false, - "canViewNotes" BOOLEAN NOT NULL DEFAULT false, - "canViewPlan" BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT "client_permission_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "session_note" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "content" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "client_plan" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "content" TEXT NOT NULL DEFAULT '', - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "client_plan_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "form" ( - "id" TEXT NOT NULL PRIMARY KEY, - "title" TEXT NOT NULL, - "description" TEXT, - "slug" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "question" ( - "id" TEXT NOT NULL PRIMARY KEY, - "text" TEXT NOT NULL, - "type" TEXT NOT NULL, - "alias" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL -); - --- CreateTable -CREATE TABLE "form_question" ( - "id" TEXT NOT NULL PRIMARY KEY, - "formId" TEXT NOT NULL, - "questionId" TEXT NOT NULL, - "order" INTEGER NOT NULL, - CONSTRAINT "form_question_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "question" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "form_question_formId_fkey" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "form_assignment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "formId" TEXT NOT NULL, - "assignedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "completedAt" DATETIME, - CONSTRAINT "form_assignment_formId_fkey" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "form_assignment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ace_response" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "responses" TEXT NOT NULL, - "completedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "ace_response_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AppForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "submittedAt" DATETIME, - CONSTRAINT "AppForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AppQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q01" TEXT, - "q02" TEXT, - "q03" TEXT, - "q04" TEXT, - "q05" TEXT, - "q06" TEXT, - "q07" TEXT, - "q08" TEXT, - "q09" TEXT, - "q10" TEXT, - "q11" TEXT, - "q12" TEXT, - "q13" TEXT, - "q14" TEXT, - "q15" TEXT, - "q16" TEXT, - "q17" TEXT, - "q18" TEXT, - "q19" TEXT, - "q20" TEXT, - "q21" TEXT, - "q22" TEXT, - "q23" TEXT, - "q24" TEXT, - "q25" TEXT, - "q26" TEXT, - "q27" TEXT, - "q28" TEXT, - "q29" TEXT, - "q30" TEXT, - "q31" TEXT, - "q32" TEXT, - "q33" TEXT, - "q34" TEXT, - "q35" TEXT, - "q36" TEXT, - "q37" TEXT, - "q38" TEXT, - "q39" TEXT, - "q40" TEXT, - "q41" TEXT, - "q42" TEXT, - "q43" TEXT, - "q44" TEXT, - "q45" TEXT, - "q46" TEXT, - "q47" TEXT, - "q48" TEXT, - "q49" TEXT, - "q50" TEXT, - CONSTRAINT "AppQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "AppQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "AppForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "GadForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "GadForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "GadQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "g01" INTEGER, - "g02" INTEGER, - "g03" INTEGER, - "g04" INTEGER, - "g05" INTEGER, - "g06" INTEGER, - "g07" INTEGER, - "g08" INTEGER, - CONSTRAINT "GadQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "GadQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "GadForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhqForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "submittedAt" DATETIME, - "severity" TEXT, - CONSTRAINT "PhqForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhqQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q1" INTEGER, - "q2" INTEGER, - "q3" INTEGER, - "q4" INTEGER, - "q5" INTEGER, - "q6" INTEGER, - "q7" INTEGER, - "q8" INTEGER, - "q9" INTEGER, - "q10" INTEGER, - CONSTRAINT "PhqQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "PhqQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PhqForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PclForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "submittedAt" DATETIME, - "severity" TEXT, - "totalScore" INTEGER, - CONSTRAINT "PclForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PclQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "q01" INTEGER, - "q02" INTEGER, - "q03" INTEGER, - "q04" INTEGER, - "q05" INTEGER, - "q06" INTEGER, - "q07" INTEGER, - "q08" INTEGER, - "q09" INTEGER, - "q10" INTEGER, - "q11" INTEGER, - "q12" INTEGER, - "q13" INTEGER, - "q14" INTEGER, - "q15" INTEGER, - "q16" INTEGER, - "q17" INTEGER, - "q18" INTEGER, - "q19" INTEGER, - "q20" INTEGER, - CONSTRAINT "PclQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "PclQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PclForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "change_audit" ( - "id" TEXT NOT NULL PRIMARY KEY, - "entityType" TEXT NOT NULL, - "entityId" TEXT NOT NULL, - "oldValue" TEXT, - "newValue" TEXT, - "reasoning" TEXT, - "documentationBase64" TEXT, - "signatureData" TEXT NOT NULL, - "signedById" TEXT NOT NULL, - "signedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "change_audit_signedById_fkey" FOREIGN KEY ("signedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_user" ( - "id" TEXT NOT NULL PRIMARY KEY, - "name" TEXT NOT NULL, - "email" TEXT NOT NULL, - "emailVerified" BOOLEAN NOT NULL DEFAULT false, - "image" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - "role" TEXT NOT NULL DEFAULT 'CLIENT', - "phoneNumber" INTEGER -); -INSERT INTO "new_user" ("createdAt", "email", "emailVerified", "id", "image", "name", "updatedAt") SELECT "createdAt", "email", "emailVerified", "id", "image", "name", "updatedAt" FROM "user"; -DROP TABLE "user"; -ALTER TABLE "new_user" RENAME TO "user"; -CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE UNIQUE INDEX "client_userId_key" ON "client"("userId"); - --- CreateIndex -CREATE INDEX "client_status_idx" ON "client"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "client_permission_clientId_key" ON "client_permission"("clientId"); - --- CreateIndex -CREATE INDEX "session_note_clientId_idx" ON "session_note"("clientId"); - --- CreateIndex -CREATE UNIQUE INDEX "client_plan_clientId_key" ON "client_plan"("clientId"); - --- CreateIndex -CREATE UNIQUE INDEX "form_slug_key" ON "form"("slug"); - --- CreateIndex -CREATE UNIQUE INDEX "question_alias_key" ON "question"("alias"); - --- CreateIndex -CREATE INDEX "form_question_formId_idx" ON "form_question"("formId"); - --- CreateIndex -CREATE INDEX "form_question_questionId_idx" ON "form_question"("questionId"); - --- CreateIndex -CREATE UNIQUE INDEX "form_question_formId_questionId_key" ON "form_question"("formId", "questionId"); - --- CreateIndex -CREATE INDEX "form_assignment_userId_idx" ON "form_assignment"("userId"); - --- CreateIndex -CREATE INDEX "form_assignment_formId_idx" ON "form_assignment"("formId"); - --- CreateIndex -CREATE UNIQUE INDEX "form_assignment_userId_formId_key" ON "form_assignment"("userId", "formId"); - --- CreateIndex -CREATE INDEX "ace_response_userId_idx" ON "ace_response"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "AppForm_userId_key" ON "AppForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "AppQuestion_formId_key" ON "AppQuestion"("formId"); - --- CreateIndex -CREATE INDEX "AppQuestion_userId_idx" ON "AppQuestion"("userId"); - --- CreateIndex -CREATE INDEX "GadForm_userId_idx" ON "GadForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "GadQuestion_formId_key" ON "GadQuestion"("formId"); - --- CreateIndex -CREATE INDEX "GadQuestion_userId_idx" ON "GadQuestion"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhqForm_userId_key" ON "PhqForm"("userId"); - --- CreateIndex -CREATE INDEX "PhqForm_userId_idx" ON "PhqForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhqQuestion_formId_key" ON "PhqQuestion"("formId"); - --- CreateIndex -CREATE INDEX "PhqQuestion_userId_idx" ON "PhqQuestion"("userId"); - --- CreateIndex -CREATE INDEX "PclForm_userId_idx" ON "PclForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PclQuestion_formId_key" ON "PclQuestion"("formId"); - --- CreateIndex -CREATE INDEX "PclQuestion_userId_idx" ON "PclQuestion"("userId"); - --- CreateIndex -CREATE INDEX "change_audit_entityType_entityId_idx" ON "change_audit"("entityType", "entityId"); diff --git a/prisma/migrations/20260402034332_consolidate_notes_and_split_schemas/migration.sql b/prisma/migrations/20260402034332_consolidate_notes_and_split_schemas/migration.sql deleted file mode 100644 index e59221e..0000000 --- a/prisma/migrations/20260402034332_consolidate_notes_and_split_schemas/migration.sql +++ /dev/null @@ -1,147 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `documentationBase64` on the `change_audit` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "PclQuestion" ADD COLUMN "worstEvent" TEXT; - --- CreateTable -CREATE TABLE "Appointment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "adminId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT, - "startTime" DATETIME NOT NULL, - "endTime" DATETIME NOT NULL, - "status" TEXT NOT NULL DEFAULT 'SCHEDULED', - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Appointment_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Appointment_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhysicianStatementForm" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', - "originalFileName" TEXT, - "storedFileName" TEXT, - "mimeType" TEXT, - "uploadedAt" DATETIME, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "PhysicianStatementForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ReleaseOfInformationAuthorizationForm" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', - "originalFileName" TEXT, - "storedFileName" TEXT, - "mimeType" TEXT, - "uploadedAt" DATETIME, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "ReleaseOfInformationAuthorizationForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "session_note_edit" ( - "id" TEXT NOT NULL PRIMARY KEY, - "sessionNoteId" TEXT NOT NULL, - "originalContent" TEXT, - "reason" TEXT, - "signature" TEXT, - "editedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_edit_sessionNoteId_fkey" FOREIGN KEY ("sessionNoteId") REFERENCES "session_note" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "declaration_template" ( - "id" TEXT NOT NULL PRIMARY KEY, - "requestKind" TEXT NOT NULL, - "version" INTEGER NOT NULL, - "content" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- CreateTable -CREATE TABLE "session_notes_request" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "requestKind" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'PENDING', - "signatureData" TEXT NOT NULL, - "declarationTemplateId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "decidedAt" DATETIME, - "decidedByUserId" TEXT, - "rejectionReason" TEXT, - "approvedSummaryText" TEXT, - CONSTRAINT "session_notes_request_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "session_notes_request_declarationTemplateId_fkey" FOREIGN KEY ("declarationTemplateId") REFERENCES "declaration_template" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "session_notes_request_decidedByUserId_fkey" FOREIGN KEY ("decidedByUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_change_audit" ( - "id" TEXT NOT NULL PRIMARY KEY, - "entityType" TEXT NOT NULL, - "entityId" TEXT NOT NULL, - "oldValue" TEXT, - "newValue" TEXT, - "reasoning" TEXT, - "documentationPath" TEXT, - "documentationName" TEXT, - "signatureData" TEXT NOT NULL, - "signedById" TEXT NOT NULL, - "signedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "change_audit_signedById_fkey" FOREIGN KEY ("signedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); -INSERT INTO "new_change_audit" ("entityId", "entityType", "id", "newValue", "oldValue", "reasoning", "signatureData", "signedAt", "signedById") SELECT "entityId", "entityType", "id", "newValue", "oldValue", "reasoning", "signatureData", "signedAt", "signedById" FROM "change_audit"; -DROP TABLE "change_audit"; -ALTER TABLE "new_change_audit" RENAME TO "change_audit"; -CREATE INDEX "change_audit_entityType_entityId_idx" ON "change_audit"("entityType", "entityId"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE INDEX "Appointment_clientId_idx" ON "Appointment"("clientId"); - --- CreateIndex -CREATE INDEX "Appointment_adminId_idx" ON "Appointment"("adminId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhysicianStatementForm_userId_key" ON "PhysicianStatementForm"("userId"); - --- CreateIndex -CREATE INDEX "PhysicianStatementForm_status_idx" ON "PhysicianStatementForm"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "ReleaseOfInformationAuthorizationForm_userId_key" ON "ReleaseOfInformationAuthorizationForm"("userId"); - --- CreateIndex -CREATE INDEX "ReleaseOfInformationAuthorizationForm_status_idx" ON "ReleaseOfInformationAuthorizationForm"("status"); - --- CreateIndex -CREATE INDEX "session_note_edit_sessionNoteId_idx" ON "session_note_edit"("sessionNoteId"); - --- CreateIndex -CREATE UNIQUE INDEX "declaration_template_requestKind_version_key" ON "declaration_template"("requestKind", "version"); - --- CreateIndex -CREATE INDEX "session_notes_request_clientId_idx" ON "session_notes_request"("clientId"); - --- CreateIndex -CREATE INDEX "session_notes_request_status_idx" ON "session_notes_request"("status"); - --- CreateIndex -CREATE INDEX "session_notes_request_declarationTemplateId_idx" ON "session_notes_request"("declarationTemplateId"); diff --git a/prisma/migrations/20260402035909_remove_dynamic_forms/migration.sql b/prisma/migrations/20260402035909_remove_dynamic_forms/migration.sql deleted file mode 100644 index 21cc75c..0000000 --- a/prisma/migrations/20260402035909_remove_dynamic_forms/migration.sql +++ /dev/null @@ -1,28 +0,0 @@ -/* - Warnings: - - - You are about to drop the `form` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `form_assignment` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `form_question` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `question` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form_assignment"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form_question"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "question"; -PRAGMA foreign_keys=on; diff --git a/prisma/migrations/20260402041509_hardcode_ace_form/migration.sql b/prisma/migrations/20260402041509_hardcode_ace_form/migration.sql deleted file mode 100644 index 575593d..0000000 --- a/prisma/migrations/20260402041509_hardcode_ace_form/migration.sql +++ /dev/null @@ -1,52 +0,0 @@ -/* - Warnings: - - - You are about to drop the `ace_response` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "ace_response"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "ace_form" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "ace_form_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ace_question" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "a01" TEXT, - "a02" TEXT, - "a03" TEXT, - "a04" TEXT, - "a05" TEXT, - "a06" TEXT, - "a07" TEXT, - "a08" TEXT, - "a09" TEXT, - "a10" TEXT, - CONSTRAINT "ace_question_formId_fkey" FOREIGN KEY ("formId") REFERENCES "ace_form" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "ace_question_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "ace_form_userId_key" ON "ace_form"("userId"); - --- CreateIndex -CREATE INDEX "ace_form_userId_idx" ON "ace_form"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "ace_question_formId_key" ON "ace_question"("formId"); - --- CreateIndex -CREATE INDEX "ace_question_userId_idx" ON "ace_question"("userId"); diff --git a/prisma/migrations/20260409184455_add_recurring_fields/migration.sql b/prisma/migrations/20260409184455_add_recurring_fields/migration.sql deleted file mode 100644 index c2580e5..0000000 --- a/prisma/migrations/20260409184455_add_recurring_fields/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "Appointment" ADD COLUMN "recurrence" TEXT; -ALTER TABLE "Appointment" ADD COLUMN "seriesId" TEXT; diff --git a/prisma/migrations/20260409203356/migration.sql b/prisma/migrations/20260409203356/migration.sql deleted file mode 100644 index ffdd9af..0000000 --- a/prisma/migrations/20260409203356/migration.sql +++ /dev/null @@ -1,310 +0,0 @@ -/* - Warnings: - - - You are about to drop the `ace_response` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `form` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `form_assignment` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `form_question` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `question` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the column `recurrence` on the `Appointment` table. All the data in the column will be lost. - - You are about to drop the column `seriesId` on the `Appointment` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "ace_response_userId_idx"; - --- DropIndex -DROP INDEX "form_slug_key"; - --- DropIndex -DROP INDEX "form_assignment_userId_formId_key"; - --- DropIndex -DROP INDEX "form_assignment_formId_idx"; - --- DropIndex -DROP INDEX "form_assignment_userId_idx"; - --- DropIndex -DROP INDEX "form_question_formId_questionId_key"; - --- DropIndex -DROP INDEX "form_question_questionId_idx"; - --- DropIndex -DROP INDEX "form_question_formId_idx"; - --- DropIndex -DROP INDEX "question_alias_key"; - --- AlterTable -ALTER TABLE "PclForm" ADD COLUMN "severity" TEXT; -ALTER TABLE "PclForm" ADD COLUMN "totalScore" INTEGER; - --- AlterTable -ALTER TABLE "PclQuestion" ADD COLUMN "worstEvent" TEXT; - --- AlterTable -ALTER TABLE "PhqForm" ADD COLUMN "severity" TEXT; - --- AlterTable -ALTER TABLE "PhqQuestion" ADD COLUMN "q10" INTEGER; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "ace_response"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form_assignment"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "form_question"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "question"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "client" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'INCOMPLETE', - "therapyWeek" INTEGER, - "missedSessions" INTEGER NOT NULL DEFAULT 0, - CONSTRAINT "client_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "client_permission" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "canViewScores" BOOLEAN NOT NULL DEFAULT false, - "canViewNotes" BOOLEAN NOT NULL DEFAULT false, - "canViewPlan" BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT "client_permission_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "client_plan" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "content" TEXT NOT NULL DEFAULT '', - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "client_plan_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "change_audit" ( - "id" TEXT NOT NULL PRIMARY KEY, - "entityType" TEXT NOT NULL, - "entityId" TEXT NOT NULL, - "oldValue" TEXT, - "newValue" TEXT, - "reasoning" TEXT, - "documentationPath" TEXT, - "documentationName" TEXT, - "signatureData" TEXT NOT NULL, - "signedById" TEXT NOT NULL, - "signedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "change_audit_signedById_fkey" FOREIGN KEY ("signedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ace_form" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "ace_form_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ace_question" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "a01" TEXT, - "a02" TEXT, - "a03" TEXT, - "a04" TEXT, - "a05" TEXT, - "a06" TEXT, - "a07" TEXT, - "a08" TEXT, - "a09" TEXT, - "a10" TEXT, - CONSTRAINT "ace_question_formId_fkey" FOREIGN KEY ("formId") REFERENCES "ace_form" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "ace_question_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "PhysicianStatementForm" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', - "originalFileName" TEXT, - "storedFileName" TEXT, - "mimeType" TEXT, - "uploadedAt" DATETIME, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "PhysicianStatementForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "ReleaseOfInformationAuthorizationForm" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', - "originalFileName" TEXT, - "storedFileName" TEXT, - "mimeType" TEXT, - "uploadedAt" DATETIME, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "ReleaseOfInformationAuthorizationForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "session_note" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "content" TEXT NOT NULL, - "attended" BOOLEAN NOT NULL DEFAULT true, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "session_note_edit" ( - "id" TEXT NOT NULL PRIMARY KEY, - "sessionNoteId" TEXT NOT NULL, - "originalContent" TEXT, - "reason" TEXT, - "signature" TEXT, - "editedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_edit_sessionNoteId_fkey" FOREIGN KEY ("sessionNoteId") REFERENCES "session_note" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "declaration_template" ( - "id" TEXT NOT NULL PRIMARY KEY, - "requestKind" TEXT NOT NULL, - "version" INTEGER NOT NULL, - "content" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- CreateTable -CREATE TABLE "session_notes_request" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "requestKind" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'PENDING', - "signatureData" TEXT NOT NULL, - "declarationTemplateId" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "decidedAt" DATETIME, - "decidedByUserId" TEXT, - "rejectionReason" TEXT, - "approvedSummaryText" TEXT, - CONSTRAINT "session_notes_request_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "session_notes_request_declarationTemplateId_fkey" FOREIGN KEY ("declarationTemplateId") REFERENCES "declaration_template" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "session_notes_request_decidedByUserId_fkey" FOREIGN KEY ("decidedByUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Appointment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "adminId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "description" TEXT, - "startTime" DATETIME NOT NULL, - "endTime" DATETIME NOT NULL, - "status" TEXT NOT NULL DEFAULT 'SCHEDULED', - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Appointment_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Appointment_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Appointment" ("adminId", "clientId", "createdAt", "description", "endTime", "id", "startTime", "status", "title", "updatedAt") SELECT "adminId", "clientId", "createdAt", "description", "endTime", "id", "startTime", "status", "title", "updatedAt" FROM "Appointment"; -DROP TABLE "Appointment"; -ALTER TABLE "new_Appointment" RENAME TO "Appointment"; -CREATE INDEX "Appointment_clientId_idx" ON "Appointment"("clientId"); -CREATE INDEX "Appointment_adminId_idx" ON "Appointment"("adminId"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE UNIQUE INDEX "client_userId_key" ON "client"("userId"); - --- CreateIndex -CREATE INDEX "client_status_idx" ON "client"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "client_permission_clientId_key" ON "client_permission"("clientId"); - --- CreateIndex -CREATE UNIQUE INDEX "client_plan_clientId_key" ON "client_plan"("clientId"); - --- CreateIndex -CREATE INDEX "change_audit_entityType_entityId_idx" ON "change_audit"("entityType", "entityId"); - --- CreateIndex -CREATE UNIQUE INDEX "ace_form_userId_key" ON "ace_form"("userId"); - --- CreateIndex -CREATE INDEX "ace_form_userId_idx" ON "ace_form"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "ace_question_formId_key" ON "ace_question"("formId"); - --- CreateIndex -CREATE INDEX "ace_question_userId_idx" ON "ace_question"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "PhysicianStatementForm_userId_key" ON "PhysicianStatementForm"("userId"); - --- CreateIndex -CREATE INDEX "PhysicianStatementForm_status_idx" ON "PhysicianStatementForm"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "ReleaseOfInformationAuthorizationForm_userId_key" ON "ReleaseOfInformationAuthorizationForm"("userId"); - --- CreateIndex -CREATE INDEX "ReleaseOfInformationAuthorizationForm_status_idx" ON "ReleaseOfInformationAuthorizationForm"("status"); - --- CreateIndex -CREATE INDEX "session_note_clientId_idx" ON "session_note"("clientId"); - --- CreateIndex -CREATE INDEX "session_note_edit_sessionNoteId_idx" ON "session_note_edit"("sessionNoteId"); - --- CreateIndex -CREATE UNIQUE INDEX "declaration_template_requestKind_version_key" ON "declaration_template"("requestKind", "version"); - --- CreateIndex -CREATE INDEX "session_notes_request_clientId_idx" ON "session_notes_request"("clientId"); - --- CreateIndex -CREATE INDEX "session_notes_request_status_idx" ON "session_notes_request"("status"); - --- CreateIndex -CREATE INDEX "session_notes_request_declarationTemplateId_idx" ON "session_notes_request"("declarationTemplateId"); diff --git a/prisma/migrations/20260426215909_add_client_clinician_user_id/migration.sql b/prisma/migrations/20260426215909_add_client_clinician_user_id/migration.sql deleted file mode 100644 index edb850a..0000000 --- a/prisma/migrations/20260426215909_add_client_clinician_user_id/migration.sql +++ /dev/null @@ -1,194 +0,0 @@ -/* - Warnings: - - - You are about to drop the `ace_form` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `ace_question` table. If the table is not empty, all the data it contains will be lost. - - Added the required column `sessionName` to the `Appointment` table without a default value. This is not possible if the table is not empty. - - Added the required column `sessionNumber` to the `Appointment` table without a default value. This is not possible if the table is not empty. - - Added the required column `sessionName` to the `session_note` table without a default value. This is not possible if the table is not empty. - - Added the required column `sessionNumber` to the `session_note` table without a default value. This is not possible if the table is not empty. - -*/ --- DropIndex -DROP INDEX "PhqForm_userId_key"; - --- DropIndex -DROP INDEX "ace_form_userId_idx"; - --- DropIndex -DROP INDEX "ace_form_userId_key"; - --- DropIndex -DROP INDEX "ace_question_userId_idx"; - --- DropIndex -DROP INDEX "ace_question_formId_key"; - --- AlterTable -ALTER TABLE "session_notes_request" ADD COLUMN "approvalReason" TEXT; -ALTER TABLE "session_notes_request" ADD COLUMN "endDate" DATETIME; -ALTER TABLE "session_notes_request" ADD COLUMN "startDate" DATETIME; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "ace_form"; -PRAGMA foreign_keys=on; - --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "ace_question"; -PRAGMA foreign_keys=on; - --- CreateTable -CREATE TABLE "client_form_score_history" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "formKey" TEXT NOT NULL, - "score" INTEGER, - "severity" TEXT, - "recordedAt" DATETIME NOT NULL, - "answersJson" TEXT, - CONSTRAINT "client_form_score_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AceForm" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', - "totalScore" INTEGER, - "severity" TEXT, - "submittedAt" DATETIME, - CONSTRAINT "AceForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AceQuestion" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "formId" INTEGER NOT NULL, - "userId" TEXT NOT NULL, - "a01" TEXT, - "a02" TEXT, - "a03" TEXT, - "a04" TEXT, - "a05" TEXT, - "a06" TEXT, - "a07" TEXT, - "a08" TEXT, - "a09" TEXT, - "a10" TEXT, - CONSTRAINT "AceQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "AceForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "AceQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "notification" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "type" TEXT NOT NULL, - "title" TEXT NOT NULL, - "message" TEXT NOT NULL, - "sessionNoteId" TEXT, - "readAt" DATETIME, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "notification_sessionNoteId_fkey" FOREIGN KEY ("sessionNoteId") REFERENCES "session_note" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_Appointment" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "adminId" TEXT NOT NULL, - "title" TEXT NOT NULL, - "sessionName" TEXT NOT NULL, - "sessionNumber" INTEGER NOT NULL, - "description" TEXT, - "startTime" DATETIME NOT NULL, - "endTime" DATETIME NOT NULL, - "status" TEXT NOT NULL DEFAULT 'SCHEDULED', - "videoProvider" TEXT, - "videoJoinUrl" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Appointment_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "Appointment_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); -INSERT INTO "new_Appointment" ("adminId", "clientId", "createdAt", "description", "endTime", "id", "startTime", "status", "title", "updatedAt") SELECT "adminId", "clientId", "createdAt", "description", "endTime", "id", "startTime", "status", "title", "updatedAt" FROM "Appointment"; -DROP TABLE "Appointment"; -ALTER TABLE "new_Appointment" RENAME TO "Appointment"; -CREATE INDEX "Appointment_clientId_idx" ON "Appointment"("clientId"); -CREATE INDEX "Appointment_adminId_idx" ON "Appointment"("adminId"); -CREATE UNIQUE INDEX "Appointment_clientId_sessionNumber_key" ON "Appointment"("clientId", "sessionNumber"); -CREATE TABLE "new_client" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" TEXT NOT NULL, - "status" TEXT NOT NULL DEFAULT 'INCOMPLETE', - "waitlistedAt" DATETIME, - "archivedAt" DATETIME, - "therapyWeek" INTEGER, - "missedSessions" INTEGER NOT NULL DEFAULT 0, - "clinicianUserId" TEXT, - CONSTRAINT "client_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "client_clinicianUserId_fkey" FOREIGN KEY ("clinicianUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_client" ("id", "missedSessions", "status", "therapyWeek", "userId") SELECT "id", "missedSessions", "status", "therapyWeek", "userId" FROM "client"; -DROP TABLE "client"; -ALTER TABLE "new_client" RENAME TO "client"; -CREATE UNIQUE INDEX "client_userId_key" ON "client"("userId"); -CREATE INDEX "client_status_idx" ON "client"("status"); -CREATE INDEX "client_clinicianUserId_idx" ON "client"("clinicianUserId"); -CREATE TABLE "new_session_note" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "sessionName" TEXT NOT NULL, - "sessionNumber" INTEGER NOT NULL, - "appointmentId" TEXT, - "kind" TEXT NOT NULL DEFAULT 'PROGRESS', - "status" TEXT NOT NULL DEFAULT 'DRAFT', - "content" TEXT NOT NULL, - "attended" BOOLEAN NOT NULL DEFAULT true, - "signatureData" TEXT, - "clinicianSignedAt" DATETIME, - "clinicianSignedById" TEXT, - "clinicianSignatureData" TEXT, - "adminSignedAt" DATETIME, - "adminSignedById" TEXT, - "adminSignatureData" TEXT, - "adminApprovalNote" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "session_note_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment" ("id") ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT "session_note_clinicianSignedById_fkey" FOREIGN KEY ("clinicianSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT "session_note_adminSignedById_fkey" FOREIGN KEY ("adminSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_session_note" ("clientId", "content", "createdAt", "id") SELECT "clientId", "content", "createdAt", "id" FROM "session_note"; -DROP TABLE "session_note"; -ALTER TABLE "new_session_note" RENAME TO "session_note"; -CREATE INDEX "session_note_clientId_idx" ON "session_note"("clientId"); -CREATE INDEX "session_note_appointmentId_idx" ON "session_note"("appointmentId"); -CREATE INDEX "session_note_status_idx" ON "session_note"("status"); -CREATE INDEX "session_note_kind_idx" ON "session_note"("kind"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; - --- CreateIndex -CREATE INDEX "client_form_score_history_userId_recordedAt_idx" ON "client_form_score_history"("userId", "recordedAt"); - --- CreateIndex -CREATE INDEX "AceForm_userId_idx" ON "AceForm"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "AceQuestion_formId_key" ON "AceQuestion"("formId"); - --- CreateIndex -CREATE INDEX "AceQuestion_userId_idx" ON "AceQuestion"("userId"); - --- CreateIndex -CREATE INDEX "notification_userId_readAt_idx" ON "notification"("userId", "readAt"); - --- CreateIndex -CREATE INDEX "notification_userId_createdAt_idx" ON "notification"("userId", "createdAt"); diff --git a/prisma/migrations/20260427031935_add_attendance_status/migration.sql b/prisma/migrations/20260427031935_add_attendance_status/migration.sql deleted file mode 100644 index ad82aa1..0000000 --- a/prisma/migrations/20260427031935_add_attendance_status/migration.sql +++ /dev/null @@ -1,48 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `attended` on the `session_note` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "session_note_edit" ADD COLUMN "editedContent" TEXT; -ALTER TABLE "session_note_edit" ADD COLUMN "newAttendanceStatus" TEXT; -ALTER TABLE "session_note_edit" ADD COLUMN "oldAttendanceStatus" TEXT; - --- RedefineTables -PRAGMA defer_foreign_keys=ON; -PRAGMA foreign_keys=OFF; -CREATE TABLE "new_session_note" ( - "id" TEXT NOT NULL PRIMARY KEY, - "clientId" TEXT NOT NULL, - "content" TEXT NOT NULL, - "attendanceStatus" TEXT DEFAULT 'show', - "sessionName" TEXT NOT NULL, - "sessionNumber" INTEGER NOT NULL, - "appointmentId" TEXT, - "kind" TEXT NOT NULL DEFAULT 'PROGRESS', - "status" TEXT NOT NULL DEFAULT 'DRAFT', - "signatureData" TEXT, - "clinicianSignedAt" DATETIME, - "clinicianSignedById" TEXT, - "clinicianSignatureData" TEXT, - "adminSignedAt" DATETIME, - "adminSignedById" TEXT, - "adminSignatureData" TEXT, - "adminApprovalNote" TEXT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "session_note_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "session_note_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment" ("id") ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT "session_note_clinicianSignedById_fkey" FOREIGN KEY ("clinicianSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT "session_note_adminSignedById_fkey" FOREIGN KEY ("adminSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); -INSERT INTO "new_session_note" ("adminApprovalNote", "adminSignatureData", "adminSignedAt", "adminSignedById", "appointmentId", "clientId", "clinicianSignatureData", "clinicianSignedAt", "clinicianSignedById", "content", "createdAt", "id", "kind", "sessionName", "sessionNumber", "signatureData", "status", "updatedAt") SELECT "adminApprovalNote", "adminSignatureData", "adminSignedAt", "adminSignedById", "appointmentId", "clientId", "clinicianSignatureData", "clinicianSignedAt", "clinicianSignedById", "content", "createdAt", "id", "kind", "sessionName", "sessionNumber", "signatureData", "status", "updatedAt" FROM "session_note"; -DROP TABLE "session_note"; -ALTER TABLE "new_session_note" RENAME TO "session_note"; -CREATE INDEX "session_note_clientId_idx" ON "session_note"("clientId"); -CREATE INDEX "session_note_appointmentId_idx" ON "session_note"("appointmentId"); -CREATE INDEX "session_note_status_idx" ON "session_note"("status"); -CREATE INDEX "session_note_kind_idx" ON "session_note"("kind"); -PRAGMA foreign_keys=ON; -PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20260430214222_init/migration.sql b/prisma/migrations/20260430214222_init/migration.sql new file mode 100644 index 0000000..d291463 --- /dev/null +++ b/prisma/migrations/20260430214222_init/migration.sql @@ -0,0 +1,582 @@ +-- CreateTable +CREATE TABLE "Appointment" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "adminId" TEXT NOT NULL, + "title" TEXT NOT NULL, + "sessionName" TEXT NOT NULL, + "sessionNumber" INTEGER NOT NULL, + "description" TEXT, + "startTime" DATETIME NOT NULL, + "endTime" DATETIME NOT NULL, + "status" TEXT NOT NULL DEFAULT 'SCHEDULED', + "seriesId" TEXT, + "recurrence" TEXT NOT NULL, + "videoProvider" TEXT, + "videoJoinUrl" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Appointment_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Appointment_adminId_fkey" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "client" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'INCOMPLETE', + "waitlistedAt" DATETIME, + "archivedAt" DATETIME, + "therapyWeek" INTEGER, + "missedSessions" INTEGER NOT NULL DEFAULT 0, + "clinicianUserId" TEXT, + CONSTRAINT "client_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "client_clinicianUserId_fkey" FOREIGN KEY ("clinicianUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "client_permission" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "canViewScores" BOOLEAN NOT NULL DEFAULT false, + "canViewNotes" BOOLEAN NOT NULL DEFAULT false, + "canViewPlan" BOOLEAN NOT NULL DEFAULT false, + CONSTRAINT "client_permission_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "client_plan" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "content" TEXT NOT NULL DEFAULT '', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "client_plan_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "change_audit" ( + "id" TEXT NOT NULL PRIMARY KEY, + "entityType" TEXT NOT NULL, + "entityId" TEXT NOT NULL, + "oldValue" TEXT, + "newValue" TEXT, + "reasoning" TEXT, + "documentationPath" TEXT, + "documentationName" TEXT, + "signatureData" TEXT NOT NULL, + "signedById" TEXT NOT NULL, + "signedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "change_audit_signedById_fkey" FOREIGN KEY ("signedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "client_form_score_history" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "formKey" TEXT NOT NULL, + "score" INTEGER, + "severity" TEXT, + "recordedAt" DATETIME NOT NULL, + "answersJson" TEXT, + CONSTRAINT "client_form_score_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "AceForm" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', + "totalScore" INTEGER, + "severity" TEXT, + "submittedAt" DATETIME, + CONSTRAINT "AceForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "AceQuestion" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "formId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "a01" TEXT, + "a02" TEXT, + "a03" TEXT, + "a04" TEXT, + "a05" TEXT, + "a06" TEXT, + "a07" TEXT, + "a08" TEXT, + "a09" TEXT, + "a10" TEXT, + CONSTRAINT "AceQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "AceForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "AceQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "AppForm" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', + "submittedAt" DATETIME, + CONSTRAINT "AppForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "AppQuestion" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "formId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "q01" TEXT, + "q02" TEXT, + "q03" TEXT, + "q04" TEXT, + "q05" TEXT, + "q06" TEXT, + "q07" TEXT, + "q08" TEXT, + "q09" TEXT, + "q10" TEXT, + "q11" TEXT, + "q12" TEXT, + "q13" TEXT, + "q14" TEXT, + "q15" TEXT, + "q16" TEXT, + "q17" TEXT, + "q18" TEXT, + "q19" TEXT, + "q20" TEXT, + "q21" TEXT, + "q22" TEXT, + "q23" TEXT, + "q24" TEXT, + "q25" TEXT, + "q26" TEXT, + "q27" TEXT, + "q28" TEXT, + "q29" TEXT, + "q30" TEXT, + "q31" TEXT, + "q32" TEXT, + "q33" TEXT, + "q34" TEXT, + "q35" TEXT, + "q36" TEXT, + "q37" TEXT, + "q38" TEXT, + "q39" TEXT, + "q40" TEXT, + "q41" TEXT, + "q42" TEXT, + "q43" TEXT, + "q44" TEXT, + "q45" TEXT, + "q46" TEXT, + "q47" TEXT, + "q48" TEXT, + "q49" TEXT, + "q50" TEXT, + CONSTRAINT "AppQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "AppForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "AppQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "GadForm" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', + "totalScore" INTEGER, + "severity" TEXT, + "submittedAt" DATETIME, + CONSTRAINT "GadForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "GadQuestion" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "formId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "g01" INTEGER, + "g02" INTEGER, + "g03" INTEGER, + "g04" INTEGER, + "g05" INTEGER, + "g06" INTEGER, + "g07" INTEGER, + "g08" INTEGER, + CONSTRAINT "GadQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "GadForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "GadQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PhqForm" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', + "totalScore" INTEGER, + "severity" TEXT, + "submittedAt" DATETIME, + CONSTRAINT "PhqForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PhqQuestion" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "formId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "q1" INTEGER, + "q2" INTEGER, + "q3" INTEGER, + "q4" INTEGER, + "q5" INTEGER, + "q6" INTEGER, + "q7" INTEGER, + "q8" INTEGER, + "q9" INTEGER, + "q10" INTEGER, + CONSTRAINT "PhqQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PhqForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "PhqQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PclForm" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'IN_PROGRESS', + "totalScore" INTEGER, + "severity" TEXT, + "submittedAt" DATETIME, + CONSTRAINT "PclForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PclQuestion" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "formId" INTEGER NOT NULL, + "userId" TEXT NOT NULL, + "worstEvent" TEXT, + "q01" INTEGER, + "q02" INTEGER, + "q03" INTEGER, + "q04" INTEGER, + "q05" INTEGER, + "q06" INTEGER, + "q07" INTEGER, + "q08" INTEGER, + "q09" INTEGER, + "q10" INTEGER, + "q11" INTEGER, + "q12" INTEGER, + "q13" INTEGER, + "q14" INTEGER, + "q15" INTEGER, + "q16" INTEGER, + "q17" INTEGER, + "q18" INTEGER, + "q19" INTEGER, + "q20" INTEGER, + CONSTRAINT "PclQuestion_formId_fkey" FOREIGN KEY ("formId") REFERENCES "PclForm" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "PclQuestion_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "PhysicianStatementForm" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', + "originalFileName" TEXT, + "storedFileName" TEXT, + "mimeType" TEXT, + "uploadedAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "PhysicianStatementForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "ReleaseOfInformationAuthorizationForm" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'NOT_SUBMITTED', + "originalFileName" TEXT, + "storedFileName" TEXT, + "mimeType" TEXT, + "uploadedAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ReleaseOfInformationAuthorizationForm_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "session_note" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "attendanceStatus" TEXT DEFAULT 'show', + "sessionName" TEXT NOT NULL, + "sessionNumber" INTEGER NOT NULL, + "appointmentId" TEXT, + "kind" TEXT NOT NULL DEFAULT 'PROGRESS', + "status" TEXT NOT NULL DEFAULT 'DRAFT', + "signatureData" TEXT, + "clinicianSignedAt" DATETIME, + "clinicianSignedById" TEXT, + "clinicianSignatureData" TEXT, + "adminSignedAt" DATETIME, + "adminSignedById" TEXT, + "adminSignatureData" TEXT, + "adminApprovalNote" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "session_note_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "session_note_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "session_note_clinicianSignedById_fkey" FOREIGN KEY ("clinicianSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "session_note_adminSignedById_fkey" FOREIGN KEY ("adminSignedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "session_note_edit" ( + "id" TEXT NOT NULL PRIMARY KEY, + "sessionNoteId" TEXT NOT NULL, + "originalContent" TEXT, + "editedContent" TEXT, + "oldAttendanceStatus" TEXT, + "newAttendanceStatus" TEXT, + "reason" TEXT, + "signature" TEXT, + "editedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "session_note_edit_sessionNoteId_fkey" FOREIGN KEY ("sessionNoteId") REFERENCES "session_note" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "notification" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "title" TEXT NOT NULL, + "message" TEXT NOT NULL, + "sessionNoteId" TEXT, + "readAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "notification_sessionNoteId_fkey" FOREIGN KEY ("sessionNoteId") REFERENCES "session_note" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "declaration_template" ( + "id" TEXT NOT NULL PRIMARY KEY, + "requestKind" TEXT NOT NULL, + "version" INTEGER NOT NULL, + "content" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "session_notes_request" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "requestKind" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'PENDING', + "signatureData" TEXT NOT NULL, + "declarationTemplateId" TEXT NOT NULL, + "startDate" DATETIME, + "endDate" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "decidedAt" DATETIME, + "decidedByUserId" TEXT, + "approvalReason" TEXT, + "rejectionReason" TEXT, + "approvedSummaryText" TEXT, + CONSTRAINT "session_notes_request_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "client" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "session_notes_request_declarationTemplateId_fkey" FOREIGN KEY ("declarationTemplateId") REFERENCES "declaration_template" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "session_notes_request_decidedByUserId_fkey" FOREIGN KEY ("decidedByUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "user" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "image" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "role" TEXT NOT NULL DEFAULT 'CLIENT', + "phoneNumber" INTEGER +); + +-- CreateTable +CREATE TABLE "session" ( + "id" TEXT NOT NULL PRIMARY KEY, + "expiresAt" DATETIME NOT NULL, + "token" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "userId" TEXT NOT NULL, + CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "account" ( + "id" TEXT NOT NULL PRIMARY KEY, + "accountId" TEXT NOT NULL, + "providerId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "accessToken" TEXT, + "refreshToken" TEXT, + "idToken" TEXT, + "accessTokenExpiresAt" DATETIME, + "refreshTokenExpiresAt" DATETIME, + "scope" TEXT, + "password" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "verification" ( + "id" TEXT NOT NULL PRIMARY KEY, + "identifier" TEXT NOT NULL, + "value" TEXT NOT NULL, + "expiresAt" DATETIME NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateIndex +CREATE INDEX "Appointment_clientId_idx" ON "Appointment"("clientId"); + +-- CreateIndex +CREATE INDEX "Appointment_adminId_idx" ON "Appointment"("adminId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Appointment_clientId_sessionNumber_key" ON "Appointment"("clientId", "sessionNumber"); + +-- CreateIndex +CREATE UNIQUE INDEX "client_userId_key" ON "client"("userId"); + +-- CreateIndex +CREATE INDEX "client_status_idx" ON "client"("status"); + +-- CreateIndex +CREATE INDEX "client_clinicianUserId_idx" ON "client"("clinicianUserId"); + +-- CreateIndex +CREATE UNIQUE INDEX "client_permission_clientId_key" ON "client_permission"("clientId"); + +-- CreateIndex +CREATE UNIQUE INDEX "client_plan_clientId_key" ON "client_plan"("clientId"); + +-- CreateIndex +CREATE INDEX "change_audit_entityType_entityId_idx" ON "change_audit"("entityType", "entityId"); + +-- CreateIndex +CREATE INDEX "client_form_score_history_userId_recordedAt_idx" ON "client_form_score_history"("userId", "recordedAt"); + +-- CreateIndex +CREATE INDEX "AceForm_userId_idx" ON "AceForm"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AceQuestion_formId_key" ON "AceQuestion"("formId"); + +-- CreateIndex +CREATE INDEX "AceQuestion_userId_idx" ON "AceQuestion"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AppForm_userId_key" ON "AppForm"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "AppQuestion_formId_key" ON "AppQuestion"("formId"); + +-- CreateIndex +CREATE INDEX "AppQuestion_userId_idx" ON "AppQuestion"("userId"); + +-- CreateIndex +CREATE INDEX "GadForm_userId_idx" ON "GadForm"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "GadQuestion_formId_key" ON "GadQuestion"("formId"); + +-- CreateIndex +CREATE INDEX "GadQuestion_userId_idx" ON "GadQuestion"("userId"); + +-- CreateIndex +CREATE INDEX "PhqForm_userId_idx" ON "PhqForm"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PhqQuestion_formId_key" ON "PhqQuestion"("formId"); + +-- CreateIndex +CREATE INDEX "PhqQuestion_userId_idx" ON "PhqQuestion"("userId"); + +-- CreateIndex +CREATE INDEX "PclForm_userId_idx" ON "PclForm"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PclQuestion_formId_key" ON "PclQuestion"("formId"); + +-- CreateIndex +CREATE INDEX "PclQuestion_userId_idx" ON "PclQuestion"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "PhysicianStatementForm_userId_key" ON "PhysicianStatementForm"("userId"); + +-- CreateIndex +CREATE INDEX "PhysicianStatementForm_status_idx" ON "PhysicianStatementForm"("status"); + +-- CreateIndex +CREATE UNIQUE INDEX "ReleaseOfInformationAuthorizationForm_userId_key" ON "ReleaseOfInformationAuthorizationForm"("userId"); + +-- CreateIndex +CREATE INDEX "ReleaseOfInformationAuthorizationForm_status_idx" ON "ReleaseOfInformationAuthorizationForm"("status"); + +-- CreateIndex +CREATE INDEX "session_note_clientId_idx" ON "session_note"("clientId"); + +-- CreateIndex +CREATE INDEX "session_note_appointmentId_idx" ON "session_note"("appointmentId"); + +-- CreateIndex +CREATE INDEX "session_note_status_idx" ON "session_note"("status"); + +-- CreateIndex +CREATE INDEX "session_note_kind_idx" ON "session_note"("kind"); + +-- CreateIndex +CREATE INDEX "session_note_edit_sessionNoteId_idx" ON "session_note_edit"("sessionNoteId"); + +-- CreateIndex +CREATE INDEX "notification_userId_readAt_idx" ON "notification"("userId", "readAt"); + +-- CreateIndex +CREATE INDEX "notification_userId_createdAt_idx" ON "notification"("userId", "createdAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "declaration_template_requestKind_version_key" ON "declaration_template"("requestKind", "version"); + +-- CreateIndex +CREATE INDEX "session_notes_request_clientId_idx" ON "session_notes_request"("clientId"); + +-- CreateIndex +CREATE INDEX "session_notes_request_status_idx" ON "session_notes_request"("status"); + +-- CreateIndex +CREATE INDEX "session_notes_request_declarationTemplateId_idx" ON "session_notes_request"("declarationTemplateId"); + +-- CreateIndex +CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); + +-- CreateIndex +CREATE INDEX "session_userId_idx" ON "session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_token_key" ON "session"("token"); + +-- CreateIndex +CREATE INDEX "account_userId_idx" ON "account"("userId"); + +-- CreateIndex +CREATE INDEX "verification_identifier_idx" ON "verification"("identifier"); diff --git a/prisma/migrations/20260501033729_appointment_schedule_requests/migration.sql b/prisma/migrations/20260501033729_appointment_schedule_requests/migration.sql new file mode 100644 index 0000000..36f06fc --- /dev/null +++ b/prisma/migrations/20260501033729_appointment_schedule_requests/migration.sql @@ -0,0 +1,27 @@ +-- CreateTable +CREATE TABLE "appointment_schedule_request" ( + "id" TEXT NOT NULL PRIMARY KEY, + "clientId" TEXT NOT NULL, + "requestedStartTime" DATETIME NOT NULL, + "requestedEndTime" DATETIME NOT NULL, + "message" TEXT, + "status" TEXT NOT NULL DEFAULT 'PENDING', + "decidedAt" DATETIME, + "decidedByUserId" TEXT, + "staffResponseNote" TEXT, + "createdAppointmentId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "appointment_schedule_request_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "appointment_schedule_request_decidedByUserId_fkey" FOREIGN KEY ("decidedByUserId") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "appointment_schedule_request_createdAppointmentId_fkey" FOREIGN KEY ("createdAppointmentId") REFERENCES "Appointment" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "appointment_schedule_request_createdAppointmentId_key" ON "appointment_schedule_request"("createdAppointmentId"); + +-- CreateIndex +CREATE INDEX "appointment_schedule_request_clientId_idx" ON "appointment_schedule_request"("clientId"); + +-- CreateIndex +CREATE INDEX "appointment_schedule_request_status_idx" ON "appointment_schedule_request"("status"); diff --git a/prisma/schema/appointments.prisma b/prisma/schema/appointments.prisma index 01f34b6..2629e0f 100644 --- a/prisma/schema/appointments.prisma +++ b/prisma/schema/appointments.prisma @@ -5,6 +5,13 @@ enum VideoConferenceProvider { OTHER } +/** Client-requested session times (not booked until staff accepts). */ +enum AppointmentScheduleRequestStatus { + PENDING + ACCEPTED + DENIED +} + model Appointment { id String @id @default(cuid()) @@ -34,9 +41,34 @@ model Appointment { admin User @relation("AdminAppointments", fields: [adminId], references: [id], onDelete: Restrict) sessionNotes SessionNote[] @relation("AppointmentSessionNotes") + originScheduleRequest AppointmentScheduleRequest? @@index([clientId]) @@index([adminId]) @@unique([clientId, sessionNumber]) @@map("Appointment") } + +model AppointmentScheduleRequest { + id String @id @default(cuid()) + clientId String + requestedStartTime DateTime + requestedEndTime DateTime + message String? + status AppointmentScheduleRequestStatus @default(PENDING) + decidedAt DateTime? + decidedByUserId String? + staffResponseNote String? + createdAppointmentId String? @unique + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + client User @relation("ClientScheduleRequests", fields: [clientId], references: [id], onDelete: Cascade) + decidedBy User? @relation("StaffScheduleRequestDecisions", fields: [decidedByUserId], references: [id], onDelete: SetNull) + createdAppointment Appointment? @relation(fields: [createdAppointmentId], references: [id], onDelete: SetNull) + + @@index([clientId]) + @@index([status]) + @@map("appointment_schedule_request") +} diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma index 11e77c3..cfc7c05 100644 --- a/prisma/schema/user.prisma +++ b/prisma/schema/user.prisma @@ -36,6 +36,8 @@ model User { sessionNotesRequestsDecided SessionNotesRequest[] appointmentsAsClient Appointment[] @relation("ClientAppointments") appointmentsAsAdmin Appointment[] @relation("AdminAppointments") + clientScheduleRequests AppointmentScheduleRequest[] @relation("ClientScheduleRequests") + staffScheduleRequestDecisions AppointmentScheduleRequest[] @relation("StaffScheduleRequestDecisions") physicianStatementForm PhysicianStatementForm? releaseOfInformationAuthorizationForm ReleaseOfInformationAuthorizationForm? clientFormScoreHistory ClientFormScoreHistory[] diff --git a/server/api/admin/dashboard-stats.get.ts b/server/api/admin/dashboard-stats.get.ts index a883d66..9287a69 100644 --- a/server/api/admin/dashboard-stats.get.ts +++ b/server/api/admin/dashboard-stats.get.ts @@ -55,6 +55,16 @@ export default defineEventHandler(async (event) => { ? { status: 'PENDING' as const, client: { clinicianUserId: session.user.id } } : { status: 'PENDING' as const } + /** Schedule requests use clientId → User; scope by User.client (profile) → clinician. */ + const pendingScheduleRequestWhere = isClinicianViewer + ? { + status: 'PENDING' as const, + client: { + client: { clinicianUserId: session.user.id }, + }, + } + : { status: 'PENDING' as const } + // Pending note approvals: only admins can act on this queue, so clinicians // see 0 (their own CLINICIAN_SIGNED notes aren't "pending approval" to them). const pendingNoteApprovalsWhere = isClinicianViewer @@ -65,6 +75,7 @@ export default defineEventHandler(async (event) => { userCount, clientCount, pendingSessionNotesRequests, + pendingAppointmentScheduleRequests, pendingNoteApprovals, unreadNotifications, viewerClient, @@ -74,6 +85,7 @@ export default defineEventHandler(async (event) => { : prisma.user.count(), prisma.client.count({ where: clientWhere }), prisma.sessionNotesRequest.count({ where: pendingWhere }), + prisma.appointmentScheduleRequest.count({ where: pendingScheduleRequestWhere }), prisma.sessionNote.count({ where: pendingNoteApprovalsWhere }), prisma.notification.count({ where: { userId: session.user.id, readAt: null }, @@ -94,6 +106,7 @@ export default defineEventHandler(async (event) => { userCount, clientCount, pendingSessionNotesRequests, + pendingAppointmentScheduleRequests, pendingNoteApprovals, unreadNotifications, displayName, diff --git a/server/api/appointment-schedule-requests/[id].patch.ts b/server/api/appointment-schedule-requests/[id].patch.ts new file mode 100644 index 0000000..9fad917 --- /dev/null +++ b/server/api/appointment-schedule-requests/[id].patch.ts @@ -0,0 +1,164 @@ +import { requireStaff } from '../../utils/guard' +import { assertStaffCanAccessClient } from '../../utils/clinician-access' +import { createError, defineEventHandler, getRouterParam, readBody } from 'h3' +import { z } from 'zod' +import { prisma } from '../../utils/prisma' +import { sendAppEmail } from '../../utils/mail' +import { formatStoredUserNameInitials } from '../../utils/name' +import { createStaffAppointment } from '../../utils/create-staff-appointment' + +const bodySchema = z + .object({ + action: z.enum(['accept', 'deny']), + staffResponseNote: z.string().optional(), + }) + .superRefine((data, ctx) => { + if (data.action === 'deny') { + const r = String(data.staffResponseNote ?? '').trim() + if (r.length < 3) { + ctx.addIssue({ + code: 'custom', + message: 'Please explain why this request is denied (at least a few characters).', + path: ['staffResponseNote'], + }) + } + } + }) + +export default defineEventHandler(async (event) => { + const user = requireStaff(event) + const id = getRouterParam(event, 'id') + if (!id) { + throw createError({ statusCode: 400, statusMessage: 'Missing id' }) + } + + const parsed = bodySchema.safeParse(await readBody(event)) + if (!parsed.success) { + const msg = parsed.error.issues[0]?.message ?? 'Invalid request' + throw createError({ statusCode: 400, statusMessage: msg }) + } + + const req = await prisma.appointmentScheduleRequest.findUnique({ + where: { id }, + include: { + client: { select: { id: true, email: true, name: true } }, + }, + }) + + if (!req) { + throw createError({ statusCode: 404, statusMessage: 'Request not found' }) + } + + await assertStaffCanAccessClient(event, req.clientId) + + if (req.status !== 'PENDING') { + throw createError({ statusCode: 409, statusMessage: 'Request is no longer pending' }) + } + + const now = new Date() + const start = req.requestedStartTime + const end = req.requestedEndTime + + if (parsed.data.action === 'accept' && start < now) { + throw createError({ + statusCode: 400, + statusMessage: 'This requested start time is already in the past. Ask the client to update the request.', + }) + } + + const clientInitials = formatStoredUserNameInitials(req.client.name) + const greeting = + clientInitials.length > 0 ? `

Hello ${escapeHtml(clientInitials)},

` : '

Hello,

' + + const rangeHtml = `

Requested time: ${escapeHtml(formatRange(start, end))}

` + + if (parsed.data.action === 'deny') { + const note = String(parsed.data.staffResponseNote ?? '').trim() + await prisma.appointmentScheduleRequest.update({ + where: { id }, + data: { + status: 'DENIED', + decidedAt: now, + decidedByUserId: user.id, + staffResponseNote: note, + }, + }) + + await sendAppEmail({ + to: req.client.email, + subject: '[HCH] Session time request update', + html: ` + ${greeting} +

Your requested session time could not be scheduled.

+ ${rangeHtml} +

Message from the clinic:

+

${escapeHtml(note).replace(/\n/g, '
')}

+

You can submit a new request from your dashboard if you need a different time.

+ `, + }) + + return { id, status: 'DENIED' as const } + } + + const appointment = await prisma.$transaction(async (tx) => { + const appt = await createStaffAppointment(tx, { + staffUserId: user.id, + clientUserId: req.clientId, + startTime: start, + endTime: end, + description: req.message, + videoProvider: null, + videoJoinUrl: null, + }) + + await tx.appointmentScheduleRequest.update({ + where: { id }, + data: { + status: 'ACCEPTED', + decidedAt: now, + decidedByUserId: user.id, + staffResponseNote: null, + createdAppointmentId: appt.id, + }, + }) + + return appt + }) + + await sendAppEmail({ + to: req.client.email, + subject: '[HCH] Session time approved', + html: ` + ${greeting} +

Your requested session time has been approved and added to the calendar.

+ ${rangeHtml} +

Sign in to the client portal and open Calendar to see the confirmed appointment.

+ `, + }) + + return { + id, + status: 'ACCEPTED' as const, + appointmentId: appointment.id, + } +}) + +function escapeHtml(s: string): string { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') +} + +function formatRange(start: Date, end: Date): string { + const d = new Intl.DateTimeFormat('en-US', { + weekday: 'short', + month: 'short', + day: 'numeric', + year: 'numeric', + }).format(start) + const t0 = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit' }).format(start) + const t1 = new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit' }).format(end) + return `${d} · ${t0} – ${t1}` +} diff --git a/server/api/appointment-schedule-requests/index.get.ts b/server/api/appointment-schedule-requests/index.get.ts new file mode 100644 index 0000000..324cc0b --- /dev/null +++ b/server/api/appointment-schedule-requests/index.get.ts @@ -0,0 +1,76 @@ +import { requireStaff } from '../../utils/guard' +import { defineEventHandler } from 'h3' +import { prisma } from '../../utils/prisma' + +function mapRow(r: { + id: string + requestedStartTime: Date + requestedEndTime: Date + message: string | null + status: string + createdAt: Date + decidedAt: Date | null + staffResponseNote: string | null + createdAppointmentId: string | null + client: { id: string; name: string; email: string } +}) { + return { + id: r.id, + clientUserId: r.client.id, + clientName: r.client.name, + clientEmail: r.client.email, + requestedStartTime: r.requestedStartTime.toISOString(), + requestedEndTime: r.requestedEndTime.toISOString(), + message: r.message, + status: r.status, + createdAt: r.createdAt.toISOString(), + decidedAt: r.decidedAt?.toISOString() ?? null, + staffResponseNote: r.staffResponseNote, + createdAppointmentId: r.createdAppointmentId, + } +} + +export default defineEventHandler(async (event) => { + const user = requireStaff(event) + const isClinicianViewer = event.context.isClinician === true && !event.context.isAdmin + + /** Request.client is the client's User row; User.client is the Client profile (has clinicianUserId). */ + const assignedClinicianOnly = isClinicianViewer + ? { + client: { + client: { + clinicianUserId: user.id, + }, + }, + } + : {} + + const [pending, history] = await Promise.all([ + prisma.appointmentScheduleRequest.findMany({ + where: { + status: 'PENDING', + ...assignedClinicianOnly, + }, + include: { + client: { select: { id: true, name: true, email: true } }, + }, + orderBy: { createdAt: 'asc' }, + }), + prisma.appointmentScheduleRequest.findMany({ + where: { + status: { in: ['ACCEPTED', 'DENIED'] }, + ...assignedClinicianOnly, + }, + include: { + client: { select: { id: true, name: true, email: true } }, + }, + orderBy: { decidedAt: 'desc' }, + take: 40, + }), + ]) + + return { + pending: pending.map(mapRow), + history: history.map(mapRow), + } +}) diff --git a/server/api/appointments/index.post.ts b/server/api/appointments/index.post.ts index 1b398ce..d0199ce 100644 --- a/server/api/appointments/index.post.ts +++ b/server/api/appointments/index.post.ts @@ -4,28 +4,7 @@ import { prisma } from '../../utils/prisma' import { readBody, createError, defineEventHandler } from 'h3' import { normalizeVideoJoinUrl, parseVideoProviderInput } from '../../utils/video-conference' import type { VideoConferenceProvider } from '../../../prisma/generated/enums' - -function sanitizeNamePart(part: string | null | undefined) { - const normalized = (part ?? '').trim().replace(/\s+/g, '_') - return normalized.replace(/[^A-Za-z0-9_]/g, '') -} - -function deriveSessionName(fullName: string | null | undefined, sessionNumber: number) { - const raw = (fullName ?? '').trim() - const pieces = raw.split(/\s+/).filter(Boolean) - - const first = sanitizeNamePart(pieces[0] ?? 'Client') || 'Client' - const last = sanitizeNamePart(pieces.slice(1).join('_') || 'Unknown') || 'Unknown' - - return `${first}_${last}_${String(sessionNumber).padStart(2, '0')}` -} - -function nextAvailableNumber(used: number[]) { - const usedSet = new Set(used.filter((n) => Number.isInteger(n) && n > 0)) - let candidate = 1 - while (usedSet.has(candidate)) candidate += 1 - return candidate -} +import { createStaffAppointment } from '../../utils/create-staff-appointment' export default defineEventHandler(async (event) => { try { @@ -82,66 +61,25 @@ export default defineEventHandler(async (event) => { }) } - const [clientUser, existingAppointments] = await Promise.all([ - prisma.user.findUnique({ - where: { id: clientId }, - select: { name: true, role: true }, - }), - prisma.appointment.findMany({ - where: { clientId }, - select: { sessionNumber: true, status: true }, - }), - ]) - - if (!clientUser || clientUser.role !== 'CLIENT') { - throw createError({ - statusCode: 400, - statusMessage: 'Invalid client ID', - }) - } - - const activeSessionNumbers = existingAppointments - .filter((a) => { - const normalized = String(a.status ?? '').toUpperCase() - return normalized !== 'CANCELED' && normalized !== 'CANCELLED' - }) - .map((a) => a.sessionNumber) - - const sessionNumber = nextAvailableNumber(activeSessionNumbers) - const sessionName = deriveSessionName(clientUser.name, sessionNumber) - - const appointment = await prisma.appointment.create({ - data: { - clientId, - adminId, - title: sessionName, - sessionName, - sessionNumber, - description, + let appointment + try { + appointment = await createStaffAppointment(prisma, { + staffUserId: adminId, + clientUserId: clientId, startTime: start, endTime: end, - status: 'SCHEDULED', + description, videoProvider: parsedProvider ?? null, videoJoinUrl: normalizedJoin ?? null, - }, - }) - - const clientRow = await prisma.client.findUnique({ - where: { userId: clientId }, - select: { id: true }, - }) - - if (clientRow) { - await prisma.sessionNote.create({ - data: { - clientId: clientRow.id, - appointmentId: appointment.id, - sessionName, - sessionNumber, - content: '', - attendanceStatus: 'show', - }, }) + } catch (e: unknown) { + if (e instanceof Error && e.message === 'INVALID_CLIENT') { + throw createError({ + statusCode: 400, + statusMessage: 'Invalid client ID', + }) + } + throw e } return { diff --git a/server/api/client/schedule-requests.get.ts b/server/api/client/schedule-requests.get.ts new file mode 100644 index 0000000..736dfd9 --- /dev/null +++ b/server/api/client/schedule-requests.get.ts @@ -0,0 +1,45 @@ +import { createError, defineEventHandler } from 'h3' +import { prisma } from '../../utils/prisma' +import { requireClientUser } from '../../utils/guard' + +export default defineEventHandler(async (event) => { + const user = requireClientUser(event) + + const clientRow = await prisma.client.findUnique({ + where: { userId: user.id }, + select: { id: true }, + }) + if (!clientRow) { + throw createError({ statusCode: 403, statusMessage: 'Client profile not found' }) + } + + const rows = await prisma.appointmentScheduleRequest.findMany({ + where: { clientId: user.id }, + orderBy: { createdAt: 'desc' }, + select: { + id: true, + requestedStartTime: true, + requestedEndTime: true, + message: true, + status: true, + createdAt: true, + updatedAt: true, + decidedAt: true, + staffResponseNote: true, + createdAppointmentId: true, + }, + }) + + return rows.map((r) => ({ + id: r.id, + requestedStartTime: r.requestedStartTime.toISOString(), + requestedEndTime: r.requestedEndTime.toISOString(), + message: r.message, + status: r.status, + createdAt: r.createdAt.toISOString(), + updatedAt: r.updatedAt.toISOString(), + decidedAt: r.decidedAt?.toISOString() ?? null, + staffResponseNote: r.staffResponseNote, + createdAppointmentId: r.createdAppointmentId, + })) +}) diff --git a/server/api/client/schedule-requests.post.ts b/server/api/client/schedule-requests.post.ts new file mode 100644 index 0000000..97bdaf4 --- /dev/null +++ b/server/api/client/schedule-requests.post.ts @@ -0,0 +1,80 @@ +import { createError, defineEventHandler, readBody } from 'h3' +import { prisma } from '../../utils/prisma' +import { requireClientUser } from '../../utils/guard' + +function validateSlot(start: Date, end: Date) { + const now = new Date() + if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) { + return 'Invalid date or time.' + } + if (end <= start) { + return 'End time must be after start time.' + } + if (start < now) { + return 'Cannot request time slots in the past.' + } + return null +} + +export default defineEventHandler(async (event) => { + const user = requireClientUser(event) + + const clientRow = await prisma.client.findUnique({ + where: { userId: user.id }, + select: { id: true }, + }) + if (!clientRow) { + throw createError({ statusCode: 403, statusMessage: 'Client profile not found' }) + } + + const body = await readBody(event) + const { date, startTime, endTime, message } = body as { + date?: string + startTime?: string + endTime?: string + message?: string + } + + if (!date || !startTime || !endTime) { + throw createError({ + statusCode: 400, + statusMessage: 'Missing date, startTime, or endTime', + }) + } + + const start = new Date(`${date}T${startTime}`) + const end = new Date(`${date}T${endTime}`) + const slotErr = validateSlot(start, end) + if (slotErr) { + throw createError({ statusCode: 400, statusMessage: slotErr }) + } + + const msg = + typeof message === 'string' && message.trim().length > 0 ? message.trim().slice(0, 2000) : null + + const row = await prisma.appointmentScheduleRequest.create({ + data: { + clientId: user.id, + requestedStartTime: start, + requestedEndTime: end, + message: msg, + }, + select: { + id: true, + requestedStartTime: true, + requestedEndTime: true, + message: true, + status: true, + createdAt: true, + }, + }) + + return { + id: row.id, + requestedStartTime: row.requestedStartTime.toISOString(), + requestedEndTime: row.requestedEndTime.toISOString(), + message: row.message, + status: row.status, + createdAt: row.createdAt.toISOString(), + } +}) diff --git a/server/api/client/schedule-requests/[id].patch.ts b/server/api/client/schedule-requests/[id].patch.ts new file mode 100644 index 0000000..b12164e --- /dev/null +++ b/server/api/client/schedule-requests/[id].patch.ts @@ -0,0 +1,127 @@ +import { createError, defineEventHandler, getRouterParam, readBody } from 'h3' +import { prisma } from '../../../utils/prisma' +import { requireClientUser } from '../../../utils/guard' + +function validateSlot(start: Date, end: Date) { + const now = new Date() + if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) { + return 'Invalid date or time.' + } + if (end <= start) { + return 'End time must be after start time.' + } + if (start < now) { + return 'Cannot request time slots in the past.' + } + return null +} + +export default defineEventHandler(async (event) => { + const user = requireClientUser(event) + const id = getRouterParam(event, 'id') + if (!id) { + throw createError({ statusCode: 400, statusMessage: 'Missing id' }) + } + + const existing = await prisma.appointmentScheduleRequest.findFirst({ + where: { id, clientId: user.id }, + }) + if (!existing) { + throw createError({ statusCode: 404, statusMessage: 'Request not found' }) + } + if (existing.status !== 'PENDING') { + throw createError({ + statusCode: 409, + statusMessage: 'Only pending requests can be edited', + }) + } + + const body = await readBody(event) + const { date, startTime, endTime, message } = body as { + date?: string + startTime?: string + endTime?: string + message?: string | null + } + + const hasTimeFields = date !== undefined || startTime !== undefined || endTime !== undefined + if (hasTimeFields) { + if (!date || !startTime || !endTime) { + throw createError({ + statusCode: 400, + statusMessage: 'To change the time, provide date, startTime, and endTime together', + }) + } + const start = new Date(`${date}T${startTime}`) + const end = new Date(`${date}T${endTime}`) + const slotErr = validateSlot(start, end) + if (slotErr) { + throw createError({ statusCode: 400, statusMessage: slotErr }) + } + + const msgUpdate = + message !== undefined + ? typeof message === 'string' && message.trim().length > 0 + ? message.trim().slice(0, 2000) + : null + : undefined + + const updated = await prisma.appointmentScheduleRequest.update({ + where: { id }, + data: { + requestedStartTime: start, + requestedEndTime: end, + ...(msgUpdate !== undefined ? { message: msgUpdate } : {}), + }, + select: { + id: true, + requestedStartTime: true, + requestedEndTime: true, + message: true, + status: true, + updatedAt: true, + }, + }) + + return { + id: updated.id, + requestedStartTime: updated.requestedStartTime.toISOString(), + requestedEndTime: updated.requestedEndTime.toISOString(), + message: updated.message, + status: updated.status, + updatedAt: updated.updatedAt.toISOString(), + } + } + + if (message === undefined) { + throw createError({ + statusCode: 400, + statusMessage: 'No changes provided', + }) + } + + const msg = + typeof message === 'string' && message.trim().length > 0 ? message.trim().slice(0, 2000) : null + + const updated = await prisma.appointmentScheduleRequest.update({ + where: { id }, + data: { message: msg }, + select: { + id: true, + requestedStartTime: true, + requestedEndTime: true, + message: true, + status: true, + updatedAt: true, + }, + }) + + return { + id: updated.id, + requestedStartTime: updated.requestedStartTime.toISOString(), + requestedEndTime: updated.requestedEndTime.toISOString(), + message: updated.message, + status: updated.status, + updatedAt: updated.updatedAt.toISOString(), + } +}) diff --git a/server/utils/create-staff-appointment.ts b/server/utils/create-staff-appointment.ts new file mode 100644 index 0000000..f4787d6 --- /dev/null +++ b/server/utils/create-staff-appointment.ts @@ -0,0 +1,111 @@ +import type { PrismaClient } from '../../prisma/generated/client' +import type { VideoConferenceProvider } from '../../prisma/generated/enums' + +type AppointmentDb = Pick + +function sanitizeNamePart(part: string | null | undefined) { + const normalized = (part ?? '').trim().replace(/\s+/g, '_') + return normalized.replace(/[^A-Za-z0-9_]/g, '') +} + +function deriveSessionName(fullName: string | null | undefined, sessionNumber: number) { + const raw = (fullName ?? '').trim() + const pieces = raw.split(/\s+/).filter(Boolean) + + const first = sanitizeNamePart(pieces[0] ?? 'Client') || 'Client' + const last = sanitizeNamePart(pieces.slice(1).join('_') || 'Unknown') || 'Unknown' + + return `${first}_${last}_${String(sessionNumber).padStart(2, '0')}` +} + +function nextAvailableNumber(used: number[]) { + const usedSet = new Set(used.filter((n) => Number.isInteger(n) && n > 0)) + let candidate = 1 + while (usedSet.has(candidate)) candidate += 1 + return candidate +} + +export type CreateStaffAppointmentInput = { + staffUserId: string + clientUserId: string + startTime: Date + endTime: Date + description?: string | null + videoProvider?: VideoConferenceProvider | null + videoJoinUrl?: string | null +} + +/** + * Creates a scheduled appointment and placeholder session note (same rules as POST /api/appointments). + * Caller must enforce auth, client access, and time validation. + */ +export async function createStaffAppointment( + db: AppointmentDb, + input: CreateStaffAppointmentInput +) { + const { staffUserId, clientUserId, startTime, endTime, description } = input + const videoProvider = input.videoProvider ?? null + const videoJoinUrl = input.videoJoinUrl ?? null + + const [clientUser, existingAppointments] = await Promise.all([ + db.user.findUnique({ + where: { id: clientUserId }, + select: { name: true, role: true }, + }), + db.appointment.findMany({ + where: { clientId: clientUserId }, + select: { sessionNumber: true, status: true }, + }), + ]) + + if (!clientUser || clientUser.role !== 'CLIENT') { + throw new Error('INVALID_CLIENT') + } + + const activeSessionNumbers = existingAppointments + .filter((a) => { + const normalized = String(a.status ?? '').toUpperCase() + return normalized !== 'CANCELED' && normalized !== 'CANCELLED' + }) + .map((a) => a.sessionNumber) + + const sessionNumber = nextAvailableNumber(activeSessionNumbers) + const sessionName = deriveSessionName(clientUser.name, sessionNumber) + + const appointment = await db.appointment.create({ + data: { + clientId: clientUserId, + adminId: staffUserId, + title: sessionName, + sessionName, + sessionNumber, + description: description ?? null, + startTime, + endTime, + status: 'SCHEDULED', + recurrence: '', + videoProvider, + videoJoinUrl, + }, + }) + + const clientRow = await db.client.findUnique({ + where: { userId: clientUserId }, + select: { id: true }, + }) + + if (clientRow) { + await db.sessionNote.create({ + data: { + clientId: clientRow.id, + appointmentId: appointment.id, + sessionName, + sessionNumber, + content: '', + attendanceStatus: 'show', + }, + }) + } + + return appointment +} diff --git a/server/utils/guard.ts b/server/utils/guard.ts index 344e18a..12a9555 100644 --- a/server/utils/guard.ts +++ b/server/utils/guard.ts @@ -27,3 +27,15 @@ export function requireStaff(event: H3Event) { } return user } + +/** Authenticated portal user who is not staff (typical client account). */ +export function requireClientUser(event: H3Event) { + const user = requireUser(event) + if (event.context.isStaff) { + throw createError({ statusCode: 403, statusMessage: 'Forbidden' }) + } + if (user.role !== 'CLIENT') { + throw createError({ statusCode: 403, statusMessage: 'Forbidden' }) + } + return user +}