+
+
+

+
+
+ {{ error.message }}
+
+
+
+
+
+
Invalid URL Format
+
+ It looks like you're trying to access a page with a placeholder in the URL.
+ Dynamic routes like [id]
need to be
+ replaced with actual values.
+
+
+
+
+
+
+
+
+ Instead of using /execution/form/[id], you should use a specific ID, like
+ /execution/form/123 or navigate from the case list page.
+
+
+
-
-
-
-
-
-

-
-
Oops, something went wrong.
-
-
- Please try again later or contact us if the problem persists.
-
-
+
+
+
+
+
diff --git a/pages/execution/form/[id].vue b/pages/execution/form/[id].vue
new file mode 100644
index 0000000..d939b62
--- /dev/null
+++ b/pages/execution/form/[id].vue
@@ -0,0 +1,360 @@
+
+
+
{{ caseInstance.caseName }}
+
+
Loading...
+
{{ error }}
+
+
+
+
+
+
+
+
+
+
+
{{ form.formName || `Form ${index + 1}` }}
+
{{ form.description || 'Please complete this form step' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ component.props?.value || 'Heading Text' }}
+
+
+
+
+
+
{{ component.props?.value || 'Paragraph text goes here' }}
+
+
+
+
+
+
+
+
+
+
+
{{ component.props?.title || 'Information' }}
+
+
+ {{ field.label }}:
+ {{ field.value }}
+
+
+
+
+
+
+
+
+ {{ component.props?.label || 'Button' }}
+
+
+
+
+
+ No form components found.
+
+
+
+
+
+
+ Submitting form...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/execution/new-case.vue b/pages/execution/new-case.vue
index 5386f9f..9221835 100644
--- a/pages/execution/new-case.vue
+++ b/pages/execution/new-case.vue
@@ -26,14 +26,24 @@
+
+
+
+
+
+
+
+ {{ error }}
+
+
-
+
-
+
@@ -57,14 +67,14 @@
class="text-base mr-1"
name="material-symbols:schedule"
>
- Average duration: {{ process.duration }}
+ Created: {{ formatDate(process.createdAt) }}
- {{ process.steps }} steps
+ Status: {{ process.status }}
@@ -79,11 +89,21 @@
+
+
+
+
+
No processes found
+
Try selecting a different category or search term
+
diff --git a/plugins/route-guard.js b/plugins/route-guard.js
new file mode 100644
index 0000000..e4095f2
--- /dev/null
+++ b/plugins/route-guard.js
@@ -0,0 +1,28 @@
+export default defineNuxtPlugin((nuxtApp) => {
+ nuxtApp.hook('app:created', () => {
+ // Check if we're on the client side
+ if (process.client) {
+ const router = useRouter();
+
+ // Add global navigation guard
+ router.beforeEach((to, from, next) => {
+ // Check if the route path contains literal square brackets
+ // This indicates a user is trying to navigate to a route with [param] directly
+ if (to.fullPath.includes('/[') || to.fullPath.includes(']')) {
+ console.warn('Invalid route detected with literal brackets:', to.fullPath);
+
+ // Extract the route pattern without the brackets
+ const baseRoute = to.fullPath.split('/').slice(0, -1).join('/');
+
+ // Redirect to a more appropriate page
+ return next({
+ path: baseRoute || '/',
+ query: { error: 'invalid_route_format' }
+ });
+ }
+
+ next();
+ });
+ }
+ });
+});
\ No newline at end of file
diff --git a/prisma/json/json-schema.json b/prisma/json/json-schema.json
index 492b4a9..321d8ec 100644
--- a/prisma/json/json-schema.json
+++ b/prisma/json/json-schema.json
@@ -92,6 +92,24 @@
"items": {
"$ref": "#/definitions/userrole"
}
+ },
+ "startedCases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/caseInstance"
+ }
+ },
+ "assignedTasks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/task"
+ }
+ },
+ "caseTimelineEntries": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/caseTimeline"
+ }
}
}
},
@@ -246,6 +264,12 @@
"items": {
"$ref": "#/definitions/formHistory"
}
+ },
+ "tasks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/task"
+ }
}
}
},
@@ -464,6 +488,12 @@
"items": {
"$ref": "#/definitions/processHistory"
}
+ },
+ "cases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/caseInstance"
+ }
}
}
},
@@ -582,6 +612,203 @@
]
}
}
+ },
+ "caseInstance": {
+ "type": "object",
+ "properties": {
+ "caseID": {
+ "type": "integer"
+ },
+ "caseUUID": {
+ "type": "string"
+ },
+ "caseName": {
+ "type": "string"
+ },
+ "caseStatus": {
+ "type": "string",
+ "default": "active"
+ },
+ "caseVariables": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "caseSettings": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "caseDefinition": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "caseCreatedDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "caseModifiedDate": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "caseCompletedDate": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "process": {
+ "$ref": "#/definitions/process"
+ },
+ "startedBy": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/user"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "tasks": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/task"
+ }
+ },
+ "timeline": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/caseTimeline"
+ }
+ }
+ }
+ },
+ "task": {
+ "type": "object",
+ "properties": {
+ "taskID": {
+ "type": "integer"
+ },
+ "taskUUID": {
+ "type": "string"
+ },
+ "taskName": {
+ "type": "string"
+ },
+ "taskType": {
+ "type": "string"
+ },
+ "taskStatus": {
+ "type": "string",
+ "default": "pending"
+ },
+ "taskData": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "taskCreatedDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "taskModifiedDate": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "taskCompletedDate": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "case": {
+ "$ref": "#/definitions/caseInstance"
+ },
+ "assignedTo": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/user"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "form": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/form"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ },
+ "caseTimeline": {
+ "type": "object",
+ "properties": {
+ "timelineID": {
+ "type": "integer"
+ },
+ "timelineType": {
+ "type": "string"
+ },
+ "timelineDescription": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "timelineDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "case": {
+ "$ref": "#/definitions/caseInstance"
+ },
+ "createdBy": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/user"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
}
},
"type": "object",
@@ -606,6 +833,15 @@
},
"processHistory": {
"$ref": "#/definitions/processHistory"
+ },
+ "caseInstance": {
+ "$ref": "#/definitions/caseInstance"
+ },
+ "task": {
+ "$ref": "#/definitions/task"
+ },
+ "caseTimeline": {
+ "$ref": "#/definitions/caseTimeline"
}
}
}
\ No newline at end of file
diff --git a/prisma/migrations/20230808013656_initialize/migration.sql b/prisma/migrations/20230808013656_initialize/migration.sql
deleted file mode 100644
index c611a65..0000000
--- a/prisma/migrations/20230808013656_initialize/migration.sql
+++ /dev/null
@@ -1,72 +0,0 @@
--- CreateTable
-CREATE TABLE `audit` (
- `auditID` INTEGER NOT NULL AUTO_INCREMENT,
- `auditIP` VARCHAR(255) NULL,
- `auditURL` VARCHAR(255) NULL,
- `auditURLMethod` VARCHAR(255) NULL,
- `auditURLPayload` VARCHAR(255) NULL,
- `auditCreatedDate` DATETIME(0) NULL,
-
- PRIMARY KEY (`auditID`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `user` (
- `userID` INTEGER NOT NULL AUTO_INCREMENT,
- `userSecretKey` VARCHAR(255) NULL,
- `userUsername` VARCHAR(255) NULL,
- `userPassword` VARCHAR(255) NULL,
- `userFullName` VARCHAR(255) NULL,
- `userEmail` VARCHAR(255) NULL,
- `userPhone` VARCHAR(255) NULL,
- `userStatus` VARCHAR(255) NULL,
- `userCreatedDate` DATETIME(0) NULL,
- `userModifiedDate` DATETIME(0) NULL,
-
- PRIMARY KEY (`userID`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `role` (
- `roleID` INTEGER NOT NULL AUTO_INCREMENT,
- `roleName` VARCHAR(255) NULL,
- `roleDescription` VARCHAR(255) NULL,
- `roleStatus` VARCHAR(255) NULL,
- `roleCreatedDate` DATETIME(0) NULL,
- `roleModifiedDate` DATETIME(0) NULL,
-
- PRIMARY KEY (`roleID`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `lookup` (
- `lookupID` INTEGER NOT NULL AUTO_INCREMENT,
- `lookupOrder` INTEGER NULL,
- `lookupTitle` VARCHAR(255) NULL,
- `lookupRefCode` VARCHAR(255) NULL,
- `lookupValue` VARCHAR(255) NULL,
- `lookupType` VARCHAR(255) NULL,
- `lookupStatus` VARCHAR(255) NULL,
- `lookupCreatedDate` DATETIME(0) NULL,
- `lookupModifiedDate` DATETIME(0) NULL,
-
- PRIMARY KEY (`lookupID`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- CreateTable
-CREATE TABLE `userrole` (
- `userRoleID` INTEGER NOT NULL AUTO_INCREMENT,
- `userRoleUserID` INTEGER NOT NULL DEFAULT 0,
- `userRoleRoleID` INTEGER NOT NULL DEFAULT 0,
- `userRoleCreatedDate` DATETIME(0) NOT NULL,
-
- INDEX `FK_userrole_role`(`userRoleRoleID`),
- INDEX `FK_userrole_user`(`userRoleUserID`),
- PRIMARY KEY (`userRoleID`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
--- AddForeignKey
-ALTER TABLE `userrole` ADD CONSTRAINT `FK_userrole_role` FOREIGN KEY (`userRoleRoleID`) REFERENCES `role`(`roleID`) ON DELETE NO ACTION ON UPDATE NO ACTION;
-
--- AddForeignKey
-ALTER TABLE `userrole` ADD CONSTRAINT `FK_userrole_user` FOREIGN KEY (`userRoleUserID`) REFERENCES `user`(`userID`) ON DELETE NO ACTION ON UPDATE NO ACTION;
diff --git a/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.sql b/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.sql
new file mode 100644
index 0000000..9da3ad9
--- /dev/null
+++ b/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.sql
@@ -0,0 +1,56 @@
+-- CreateTable
+CREATE TABLE `caseInstance` (
+ `caseID` INTEGER NOT NULL AUTO_INCREMENT,
+ `caseUUID` VARCHAR(36) NOT NULL,
+ `processID` INTEGER NOT NULL,
+ `caseName` VARCHAR(255) NOT NULL,
+ `caseStatus` VARCHAR(50) NOT NULL DEFAULT 'active',
+ `caseStartedBy` INTEGER NULL,
+ `caseVariables` JSON NULL,
+ `caseSettings` JSON NULL,
+ `caseDefinition` JSON NULL,
+ `caseCreatedDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
+ `caseModifiedDate` DATETIME(0) NULL,
+ `caseCompletedDate` DATETIME(0) NULL,
+
+ UNIQUE INDEX `caseInstance_caseUUID_key`(`caseUUID`),
+ INDEX `FK_case_process`(`processID`),
+ INDEX `FK_case_startedBy`(`caseStartedBy`),
+ INDEX `IDX_case_status`(`caseStatus`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `task` (
+ `taskID` INTEGER NOT NULL AUTO_INCREMENT,
+ `taskUUID` VARCHAR(36) NOT NULL,
+ `caseID` INTEGER NOT NULL,
+ `taskName` VARCHAR(255) NOT NULL,
+ `taskType` VARCHAR(50) NOT NULL,
+ `taskStatus` VARCHAR(50) NOT NULL DEFAULT 'pending',
+ `taskAssignedTo` INTEGER NULL,
+ `taskFormID` INTEGER NULL,
+ `taskData` JSON NULL,
+ `taskCreatedDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
+ `taskModifiedDate` DATETIME(0) NULL,
+ `taskCompletedDate` DATETIME(0) NULL,
+
+ UNIQUE INDEX `task_taskUUID_key`(`taskUUID`),
+ INDEX `FK_task_case`(`caseID`),
+ INDEX `FK_task_assignedTo`(`taskAssignedTo`),
+ INDEX `FK_task_form`(`taskFormID`),
+ INDEX `IDX_task_status`(`taskStatus`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `caseTimeline` (
+ `timelineID` INTEGER NOT NULL AUTO_INCREMENT,
+ `caseID` INTEGER NOT NULL,
+ `timelineType` VARCHAR(50) NOT NULL,
+ `timelineDescription` TEXT NULL,
+ `timelineDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
+ `timelineCreatedBy` INTEGER NULL,
+
+ INDEX `FK_caseTimeline_case`(`caseID`),
+ INDEX `FK_caseTimeline_createdBy`(`timelineCreatedBy`),
+ INDEX `IDX_caseTimeline_date`(`timelineDate`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
\ No newline at end of file
diff --git a/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.toml b/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.toml
new file mode 100644
index 0000000..008dd21
--- /dev/null
+++ b/prisma/migrations/20240321000001_add_case_instance_and_task_tables_v2/migration.toml
@@ -0,0 +1,4 @@
+# This is an empty migration.
+
+migration_name = "20240321000001_add_case_instance_and_task_tables_v2"
+migration_hash = "20240321000001_add_case_instance_and_task_tables_v2"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 3da984a..d26580d 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -28,6 +28,9 @@ model user {
processes process[] @relation("ProcessCreator")
processHistoryEntries processHistory[]
userrole userrole[]
+ startedCases caseInstance[] @relation("CaseStartedBy")
+ assignedTasks task[] @relation("TaskAssignedTo")
+ caseTimelineEntries caseTimeline[]
}
model role {
@@ -68,6 +71,7 @@ model form {
scriptMode String? @default("safe") @db.VarChar(20)
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
history formHistory[] @relation("FormHistoryEntries")
+ tasks task[]
@@index([formCreatedBy], map: "FK_form_creator")
}
@@ -119,6 +123,7 @@ model process {
processDeletedDate DateTime? @db.DateTime(0)
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
history processHistory[] @relation("ProcessHistoryEntries")
+ cases caseInstance[]
@@index([processCreatedBy], map: "FK_process_creator")
@@index([processStatus], map: "IDX_process_status")
@@ -154,3 +159,64 @@ model processHistory {
@@index([processUUID], map: "IDX_processHistory_uuid")
@@index([savedDate], map: "IDX_processHistory_date")
}
+
+model caseInstance {
+ caseID Int @id @default(autoincrement())
+ caseUUID String @unique @db.VarChar(36)
+ processID Int
+ caseName String @db.VarChar(255)
+ caseStatus String @default("active") @db.VarChar(50)
+ caseStartedBy Int?
+ caseVariables Json?
+ caseSettings Json?
+ caseDefinition Json?
+ caseCreatedDate DateTime @default(now()) @db.DateTime(0)
+ caseModifiedDate DateTime? @updatedAt @db.DateTime(0)
+ caseCompletedDate DateTime? @db.DateTime(0)
+ process process @relation(fields: [processID], references: [processID])
+ startedBy user? @relation("CaseStartedBy", fields: [caseStartedBy], references: [userID])
+ tasks task[]
+ timeline caseTimeline[]
+
+ @@index([processID], map: "FK_case_process")
+ @@index([caseStartedBy], map: "FK_case_startedBy")
+ @@index([caseStatus], map: "IDX_case_status")
+}
+
+model task {
+ taskID Int @id @default(autoincrement())
+ taskUUID String @unique @db.VarChar(36)
+ caseID Int
+ taskName String @db.VarChar(255)
+ taskType String @db.VarChar(50)
+ taskStatus String @default("pending") @db.VarChar(50)
+ taskAssignedTo Int?
+ taskFormID Int?
+ taskData Json?
+ taskCreatedDate DateTime @default(now()) @db.DateTime(0)
+ taskModifiedDate DateTime? @updatedAt @db.DateTime(0)
+ taskCompletedDate DateTime? @db.DateTime(0)
+ case caseInstance @relation(fields: [caseID], references: [caseID])
+ assignedTo user? @relation("TaskAssignedTo", fields: [taskAssignedTo], references: [userID])
+ form form? @relation(fields: [taskFormID], references: [formID])
+
+ @@index([caseID], map: "FK_task_case")
+ @@index([taskAssignedTo], map: "FK_task_assignedTo")
+ @@index([taskFormID], map: "FK_task_form")
+ @@index([taskStatus], map: "IDX_task_status")
+}
+
+model caseTimeline {
+ timelineID Int @id @default(autoincrement())
+ caseID Int
+ timelineType String @db.VarChar(50)
+ timelineDescription String? @db.Text
+ timelineDate DateTime @default(now()) @db.DateTime(0)
+ timelineCreatedBy Int?
+ case caseInstance @relation(fields: [caseID], references: [caseID])
+ createdBy user? @relation(fields: [timelineCreatedBy], references: [userID])
+
+ @@index([caseID], map: "FK_caseTimeline_case")
+ @@index([timelineCreatedBy], map: "FK_caseTimeline_createdBy")
+ @@index([timelineDate], map: "IDX_caseTimeline_date")
+}
diff --git a/server/api/cases/[id]/forms.get.js b/server/api/cases/[id]/forms.get.js
new file mode 100644
index 0000000..7f0de23
--- /dev/null
+++ b/server/api/cases/[id]/forms.get.js
@@ -0,0 +1,184 @@
+import { PrismaClient } from '@prisma/client';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Get the case ID from the route parameter
+ const caseId = getRouterParam(event, 'id');
+
+ if (!caseId) {
+ return {
+ success: false,
+ error: 'Case ID is required'
+ };
+ }
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = caseId.length === 36 && caseId.includes('-');
+
+ // Find the case instance by UUID or ID
+ const caseInstance = await prisma.caseInstance.findFirst({
+ where: isUUID
+ ? { caseUUID: caseId }
+ : { caseID: parseInt(caseId) },
+ include: {
+ process: {
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true,
+ processDescription: true
+ }
+ },
+ startedBy: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ },
+ tasks: {
+ include: {
+ form: {
+ select: {
+ formID: true,
+ formUUID: true,
+ formName: true,
+ formDescription: true,
+ formComponents: true,
+ formStatus: true,
+ customCSS: true,
+ customScript: true,
+ formEvents: true,
+ scriptMode: true
+ }
+ },
+ assignedTo: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ },
+ timeline: {
+ orderBy: {
+ timelineDate: 'desc'
+ },
+ include: {
+ createdBy: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ }
+ }
+ });
+
+ if (!caseInstance) {
+ return {
+ success: false,
+ error: 'Case instance not found'
+ };
+ }
+
+ // Extract forms from tasks and remove duplicates
+ const forms = [];
+ const formIds = new Set();
+
+ for (const task of caseInstance.tasks) {
+ if (task.form) {
+ // Make sure formComponents is properly structured
+ let formComponents = [];
+ try {
+ if (task.form.formComponents) {
+ // Check if formComponents is already an array or needs to be extracted from a structure
+ if (Array.isArray(task.form.formComponents)) {
+ formComponents = task.form.formComponents;
+ } else if (task.form.formComponents.components && Array.isArray(task.form.formComponents.components)) {
+ formComponents = task.form.formComponents.components;
+ } else {
+ // Try to parse if it's a stringified JSON
+ const parsedComponents = typeof task.form.formComponents === 'string'
+ ? JSON.parse(task.form.formComponents)
+ : task.form.formComponents;
+
+ if (Array.isArray(parsedComponents)) {
+ formComponents = parsedComponents;
+ } else if (parsedComponents.components && Array.isArray(parsedComponents.components)) {
+ formComponents = parsedComponents.components;
+ }
+ }
+ }
+ } catch (err) {
+ console.error('Error parsing form components:', err);
+ }
+
+ // Extract form data from taskData if it exists
+ let formData = null;
+ if (task.taskData && task.taskData.formData) {
+ formData = task.taskData.formData;
+ }
+
+ forms.push({
+ ...task.form,
+ formComponents: formComponents,
+ taskId: task.taskID,
+ taskUUID: task.taskUUID,
+ taskStatus: task.taskStatus,
+ taskName: task.taskName,
+ taskData: task.taskData,
+ formData: formData,
+ submittedAt: task.taskData?.submittedAt || null,
+ completedDate: task.taskCompletedDate
+ });
+ }
+ }
+
+ // Format the response
+ const response = {
+ caseInstance: {
+ caseID: caseInstance.caseID,
+ caseUUID: caseInstance.caseUUID,
+ caseName: caseInstance.caseName,
+ caseStatus: caseInstance.caseStatus,
+ caseCreatedDate: caseInstance.caseCreatedDate,
+ caseModifiedDate: caseInstance.caseModifiedDate,
+ caseCompletedDate: caseInstance.caseCompletedDate,
+ caseVariables: caseInstance.caseVariables,
+ process: {
+ processID: caseInstance.process.processID,
+ processUUID: caseInstance.process.processUUID,
+ processName: caseInstance.process.processName,
+ processDescription: caseInstance.process.processDescription
+ },
+ startedBy: caseInstance.startedBy ? {
+ userID: caseInstance.startedBy.userID,
+ userFullName: caseInstance.startedBy.userFullName,
+ userUsername: caseInstance.startedBy.userUsername
+ } : null
+ },
+ forms: forms,
+ timeline: caseInstance.timeline
+ };
+
+ return {
+ success: true,
+ ...response
+ };
+ } catch (error) {
+ console.error('Error fetching case instance and forms:', error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch case instance and forms',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/[id]/start.post.js b/server/api/process/[id]/start.post.js
new file mode 100644
index 0000000..1a2030c
--- /dev/null
+++ b/server/api/process/[id]/start.post.js
@@ -0,0 +1,174 @@
+import { PrismaClient } from '@prisma/client';
+import { v4 as uuidv4 } from 'uuid';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Get the process ID from the route parameter
+ const processId = getRouterParam(event, 'id');
+
+ if (!processId) {
+ return {
+ success: false,
+ error: 'Process ID is required'
+ };
+ }
+
+ console.log('Starting process with ID:', processId);
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // Find the process
+ console.log('Finding process...');
+ const process = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ if (!process) {
+ console.log('Process not found');
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ console.log('Process found:', process.processName);
+
+ // Check if process is published
+ if (process.processStatus !== 'published') {
+ console.log('Process is not published:', process.processStatus);
+ return {
+ success: false,
+ error: 'Cannot start an unpublished process'
+ };
+ }
+
+ // Get the current user (in a real app, this would come from the authenticated user)
+ const currentUser = {
+ userID: 1, // This would be the actual user ID in a real app
+ userFullName: 'John Doe',
+ userUsername: 'johndoe'
+ };
+
+ console.log('Creating case instance...');
+ // Create a new case instance
+ const caseInstance = await prisma.caseInstance.create({
+ data: {
+ caseUUID: uuidv4(),
+ processID: process.processID,
+ caseName: `${process.processName} - ${new Date().toLocaleDateString()}`,
+ caseStatus: 'active',
+ caseStartedBy: currentUser.userID,
+ caseVariables: process.processVariables || {},
+ caseSettings: process.processSettings || {},
+ caseDefinition: process.processDefinition || {},
+ caseCreatedDate: new Date(),
+ caseModifiedDate: new Date()
+ }
+ });
+
+ console.log('Case instance created:', caseInstance.caseUUID);
+
+ // Get the process definition
+ const processDefinition = process.processDefinition || {};
+ const nodes = processDefinition.nodes || [];
+ const edges = processDefinition.edges || [];
+
+ // Find all form nodes
+ const formNodes = nodes.filter(node => node.type === 'form');
+
+ if (formNodes.length === 0) {
+ console.log('No form nodes found in process');
+ return {
+ success: false,
+ error: 'Process does not contain any forms'
+ };
+ }
+
+ console.log(`Found ${formNodes.length} form nodes`);
+
+ // Create tasks for all forms
+ const tasks = [];
+ for (const formNode of formNodes) {
+ console.log('Creating task for form:', formNode.data?.label);
+ const task = await prisma.task.create({
+ data: {
+ taskUUID: uuidv4(),
+ caseID: caseInstance.caseID,
+ taskName: formNode.data?.label || 'Complete Form',
+ taskType: 'form',
+ taskStatus: 'pending',
+ taskAssignedTo: currentUser.userID,
+ taskFormID: formNode.data?.formId,
+ taskCreatedDate: new Date(),
+ taskModifiedDate: new Date()
+ }
+ });
+ tasks.push(task);
+ console.log('Task created:', task.taskUUID);
+ }
+
+ // Add to case timeline
+ console.log('Adding to case timeline...');
+ await prisma.caseTimeline.create({
+ data: {
+ caseID: caseInstance.caseID,
+ timelineType: 'start',
+ timelineDescription: `Process started by ${currentUser.userFullName}`,
+ timelineDate: new Date(),
+ timelineCreatedBy: currentUser.userID
+ }
+ });
+
+ console.log('Process started successfully');
+ return {
+ success: true,
+ data: {
+ case: {
+ id: caseInstance.caseUUID,
+ name: caseInstance.caseName,
+ status: caseInstance.caseStatus,
+ startedAt: caseInstance.caseCreatedDate
+ },
+ tasks: tasks.map(task => ({
+ id: task.taskUUID,
+ name: task.taskName,
+ type: task.taskType,
+ formId: task.taskFormID,
+ status: task.taskStatus
+ }))
+ }
+ };
+ } catch (error) {
+ console.error('Error starting process:', error);
+ console.error('Error details:', {
+ name: error.name,
+ message: error.message,
+ stack: error.stack,
+ code: error.code
+ });
+
+ return {
+ success: false,
+ error: 'Failed to start process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ } finally {
+ // Close the Prisma client connection
+ await prisma.$disconnect();
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/pending.get.js b/server/api/process/pending.get.js
new file mode 100644
index 0000000..759ddc8
--- /dev/null
+++ b/server/api/process/pending.get.js
@@ -0,0 +1,41 @@
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Get all published processes that haven't been started
+ const processes = await prisma.process.findMany({
+ where: {
+ status: 'published',
+ cases: {
+ none: {
+ status: 'active'
+ }
+ }
+ },
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ status: true,
+ createdAt: true,
+ updatedAt: true
+ },
+ orderBy: {
+ updatedAt: 'desc'
+ }
+ });
+
+ return {
+ success: true,
+ data: processes
+ };
+ } catch (error) {
+ console.error('Error fetching pending processes:', error);
+ return {
+ success: false,
+ error: 'Failed to fetch pending processes'
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/tasks/[id].get.js b/server/api/tasks/[id].get.js
new file mode 100644
index 0000000..8e54ac9
--- /dev/null
+++ b/server/api/tasks/[id].get.js
@@ -0,0 +1,122 @@
+import { PrismaClient } from '@prisma/client';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ // Get the task ID from the route params
+ const taskId = event.context.params.id;
+
+ try {
+ // Find the task
+ let task;
+
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
+ // If it looks like a UUID
+ task = await prisma.task.findUnique({
+ where: { taskUUID: taskId },
+ include: {
+ case: {
+ select: {
+ caseID: true,
+ caseUUID: true,
+ caseName: true,
+ caseStatus: true,
+ process: {
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true
+ }
+ }
+ }
+ },
+ assignedTo: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ },
+ form: {
+ select: {
+ formID: true,
+ formUUID: true,
+ formName: true,
+ formDescription: true,
+ formComponents: true,
+ formStatus: true,
+ customCSS: true,
+ customScript: true,
+ formEvents: true,
+ scriptMode: true
+ }
+ }
+ }
+ });
+ } else if (!isNaN(parseInt(taskId))) {
+ // If it's a numeric ID
+ task = await prisma.task.findUnique({
+ where: { taskID: parseInt(taskId) },
+ include: {
+ case: {
+ select: {
+ caseID: true,
+ caseUUID: true,
+ caseName: true,
+ caseStatus: true,
+ process: {
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true
+ }
+ }
+ }
+ },
+ assignedTo: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ },
+ form: {
+ select: {
+ formID: true,
+ formUUID: true,
+ formName: true,
+ formDescription: true,
+ formComponents: true,
+ formStatus: true,
+ customCSS: true,
+ customScript: true,
+ formEvents: true,
+ scriptMode: true
+ }
+ }
+ }
+ });
+ }
+
+ if (!task) {
+ return {
+ success: false,
+ error: 'Task not found'
+ };
+ }
+
+ return {
+ success: true,
+ task
+ };
+ } catch (error) {
+ console.error(`Error fetching task ${taskId}:`, error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch task',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/tasks/[id]/save-draft.post.js b/server/api/tasks/[id]/save-draft.post.js
new file mode 100644
index 0000000..ba99801
--- /dev/null
+++ b/server/api/tasks/[id]/save-draft.post.js
@@ -0,0 +1,72 @@
+import { PrismaClient } from '@prisma/client';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ // Get the task ID from the route params
+ const taskId = event.context.params.id;
+
+ try {
+ // Parse the request body
+ const body = await readBody(event);
+
+ // Validate required fields
+ if (!body.formData) {
+ return {
+ success: false,
+ error: 'Form data is required'
+ };
+ }
+
+ // Find the task
+ let task;
+
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
+ // If it looks like a UUID
+ task = await prisma.task.findUnique({
+ where: { taskUUID: taskId }
+ });
+ } else if (!isNaN(parseInt(taskId))) {
+ // If it's a numeric ID
+ task = await prisma.task.findUnique({
+ where: { taskID: parseInt(taskId) }
+ });
+ }
+
+ if (!task) {
+ return {
+ success: false,
+ error: 'Task not found'
+ };
+ }
+
+ // Update the task with the draft form data
+ const updatedTask = await prisma.task.update({
+ where: {
+ taskID: task.taskID
+ },
+ data: {
+ taskData: {
+ ...task.taskData,
+ formData: body.formData,
+ lastSaved: new Date().toISOString(),
+ isDraft: true
+ }
+ }
+ });
+
+ return {
+ success: true,
+ task: updatedTask
+ };
+ } catch (error) {
+ console.error(`Error saving draft for task ${taskId}:`, error);
+
+ return {
+ success: false,
+ error: 'Failed to save draft',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/tasks/[id]/submit.post.js b/server/api/tasks/[id]/submit.post.js
new file mode 100644
index 0000000..7ceb0dd
--- /dev/null
+++ b/server/api/tasks/[id]/submit.post.js
@@ -0,0 +1,122 @@
+import { PrismaClient } from '@prisma/client';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ // Get the task ID from the route params
+ const taskId = event.context.params.id;
+
+ try {
+ // Parse the request body
+ const body = await readBody(event);
+
+ // Validate required fields
+ if (!body.formData) {
+ return {
+ success: false,
+ error: 'Form data is required'
+ };
+ }
+
+ // Find the task
+ let task;
+
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
+ // If it looks like a UUID
+ task = await prisma.task.findUnique({
+ where: { taskUUID: taskId },
+ include: {
+ case: true
+ }
+ });
+ } else if (!isNaN(parseInt(taskId))) {
+ // If it's a numeric ID
+ task = await prisma.task.findUnique({
+ where: { taskID: parseInt(taskId) },
+ include: {
+ case: true
+ }
+ });
+ }
+
+ if (!task) {
+ return {
+ success: false,
+ error: 'Task not found'
+ };
+ }
+
+ // Update the task with the submitted form data
+ const updatedTask = await prisma.task.update({
+ where: {
+ taskID: task.taskID
+ },
+ data: {
+ taskStatus: 'completed',
+ taskCompletedDate: new Date(),
+ taskData: {
+ ...task.taskData,
+ formData: body.formData,
+ submittedAt: new Date().toISOString()
+ }
+ }
+ });
+
+ // Add an entry to the case timeline
+ await prisma.caseTimeline.create({
+ data: {
+ caseID: task.caseID,
+ timelineType: 'task_completed',
+ timelineDescription: `Task "${task.taskName}" was completed`,
+ timelineCreatedBy: task.taskAssignedTo
+ }
+ });
+
+ // Check if all tasks for the case are completed
+ const remainingTasks = await prisma.task.count({
+ where: {
+ caseID: task.caseID,
+ taskStatus: {
+ not: 'completed'
+ }
+ }
+ });
+
+ // If all tasks are completed, update the case status
+ if (remainingTasks === 0) {
+ await prisma.caseInstance.update({
+ where: {
+ caseID: task.caseID
+ },
+ data: {
+ caseStatus: 'completed',
+ caseCompletedDate: new Date()
+ }
+ });
+
+ // Add an entry to the case timeline
+ await prisma.caseTimeline.create({
+ data: {
+ caseID: task.caseID,
+ timelineType: 'case_completed',
+ timelineDescription: `Case "${task.case.caseName}" was completed`,
+ timelineCreatedBy: task.taskAssignedTo
+ }
+ });
+ }
+
+ return {
+ success: true,
+ task: updatedTask
+ };
+ } catch (error) {
+ console.error(`Error submitting form for task ${taskId}:`, error);
+
+ return {
+ success: false,
+ error: 'Failed to submit form',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/tasks/index.get.js b/server/api/tasks/index.get.js
new file mode 100644
index 0000000..759a0a4
--- /dev/null
+++ b/server/api/tasks/index.get.js
@@ -0,0 +1,96 @@
+import { PrismaClient } from '@prisma/client';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Get query parameters
+ const query = getQuery(event);
+ const caseId = query.caseId;
+ const status = query.status;
+ const assignedTo = query.assignedTo;
+ const page = parseInt(query.page) || 1;
+ const limit = parseInt(query.limit) || 10;
+ const skip = (page - 1) * limit;
+
+ // Build where clause
+ const where = {};
+
+ if (caseId) {
+ where.caseID = parseInt(caseId);
+ }
+
+ if (status) {
+ where.taskStatus = status;
+ }
+
+ if (assignedTo) {
+ where.taskAssignedTo = parseInt(assignedTo);
+ }
+
+ // Fetch tasks
+ const tasks = await prisma.task.findMany({
+ where,
+ include: {
+ case: {
+ select: {
+ caseID: true,
+ caseUUID: true,
+ caseName: true,
+ caseStatus: true,
+ process: {
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true
+ }
+ }
+ }
+ },
+ assignedTo: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ },
+ form: {
+ select: {
+ formID: true,
+ formUUID: true,
+ formName: true,
+ formDescription: true
+ }
+ }
+ },
+ orderBy: {
+ taskCreatedDate: 'desc'
+ },
+ skip,
+ take: limit
+ });
+
+ // Count total tasks for pagination
+ const totalTasks = await prisma.task.count({ where });
+
+ return {
+ success: true,
+ tasks,
+ pagination: {
+ page,
+ limit,
+ totalItems: totalTasks,
+ totalPages: Math.ceil(totalTasks / limit)
+ }
+ };
+ } catch (error) {
+ console.error('Error fetching tasks:', error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch tasks',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/middleware/dynamic-routes.js b/server/middleware/dynamic-routes.js
new file mode 100644
index 0000000..dde552e
--- /dev/null
+++ b/server/middleware/dynamic-routes.js
@@ -0,0 +1,9 @@
+export default defineEventHandler((event) => {
+ const url = event.node.req.url;
+
+ // Check if the URL contains literal [id] which should be a parameter
+ if (url && url.includes('/execution/form/[id]')) {
+ // Redirect to a more appropriate error page or handle differently
+ return sendRedirect(event, '/execution', 302);
+ }
+});
\ No newline at end of file