-
-
- Save
+
+
+
+ {{ isSaving ? 'Saving...' : 'Save' }}
diff --git a/pages/process-builder/manage.vue b/pages/process-builder/manage.vue
index 9822f9d..803cd27 100644
--- a/pages/process-builder/manage.vue
+++ b/pages/process-builder/manage.vue
@@ -16,24 +16,43 @@ definePageMeta({
const processStore = useProcessBuilderStore();
const router = useRouter();
-// Reference to the search input
+// State
const searchQuery = ref('');
-
-// Confirmation dialog state
+const loading = ref(false);
const showDeleteConfirm = ref(false);
const processToDelete = ref(null);
+const statusFilter = ref('');
+const sortBy = ref('processCreatedDate');
+const sortOrder = ref('desc');
+
+// Status options for filtering
+const statusOptions = [
+ { value: '', label: 'All Status' },
+ { value: 'draft', label: 'Draft' },
+ { value: 'published', label: 'Published' },
+ { value: 'archived', label: 'Archived' }
+];
// Filtered processes
const filteredProcesses = computed(() => {
- if (!searchQuery.value) {
- return processStore.processes;
+ let filtered = processStore.processes;
+
+ // Filter by search query
+ if (searchQuery.value) {
+ const query = searchQuery.value.toLowerCase();
+ filtered = filtered.filter(
+ process =>
+ process.name.toLowerCase().includes(query) ||
+ (process.description && process.description.toLowerCase().includes(query))
+ );
}
- const query = searchQuery.value.toLowerCase();
- return processStore.processes.filter(
- process => process.name.toLowerCase().includes(query) ||
- process.description.toLowerCase().includes(query)
- );
+ // Filter by status
+ if (statusFilter.value) {
+ filtered = filtered.filter(process => process.status === statusFilter.value);
+ }
+
+ return filtered;
});
// Format date for display
@@ -50,41 +69,100 @@ const formatDate = (isoString) => {
}).format(date);
};
+// Get status badge variant
+const getStatusVariant = (status) => {
+ switch (status) {
+ case 'published': return 'success';
+ case 'draft': return 'warning';
+ case 'archived': return 'secondary';
+ default: return 'primary';
+ }
+};
+
+// Load processes from API
+const loadProcesses = async () => {
+ loading.value = true;
+ try {
+ await processStore.fetchProcesses({
+ sortBy: sortBy.value,
+ sortOrder: sortOrder.value
+ });
+ } catch (error) {
+ console.error('Error loading processes:', error);
+ // TODO: Show error notification
+ } finally {
+ loading.value = false;
+ }
+};
+
// Edit a process
-const editProcess = (processId) => {
- processStore.setCurrentProcess(processId);
- router.push('/process-builder');
+const editProcess = async (processId) => {
+ try {
+ // Navigate to process builder with the process ID as a query parameter
+ router.push(`/process-builder?id=${processId}`);
+ } catch (error) {
+ console.error('Error navigating to process:', error);
+ // TODO: Show error notification
+ }
};
// Duplicate a process
-const duplicateProcess = (process) => {
- const newName = `${process.name} (Copy)`;
- const newProcess = processStore.createProcess(newName, process.description);
-
- // Copy nodes and edges
- process.nodes.forEach(node => {
- processStore.addNode({
- ...node,
- id: undefined // Let the store generate a new ID
- });
- });
-
- process.edges.forEach(edge => {
- processStore.addEdge({
- ...edge,
- id: undefined // Let the store generate a new ID
- });
- });
-
- processStore.saveProcess();
+const duplicateProcess = async (process) => {
+ try {
+ loading.value = true;
+ const newName = `${process.name} (Copy)`;
+ await processStore.duplicateProcess(process.id, newName);
+
+ // Refresh the process list
+ await loadProcesses();
+
+ // TODO: Show success notification
+ } catch (error) {
+ console.error('Error duplicating process:', error);
+ // TODO: Show error notification
+ } finally {
+ loading.value = false;
+ }
};
// Delete a process
-const deleteProcess = () => {
- if (processToDelete.value) {
- processStore.deleteProcess(processToDelete.value);
+const deleteProcess = async () => {
+ if (!processToDelete.value) return;
+
+ try {
+ loading.value = true;
+ await processStore.deleteProcess(processToDelete.value);
+
+ // Refresh the process list
+ await loadProcesses();
+
showDeleteConfirm.value = false;
processToDelete.value = null;
+
+ // TODO: Show success notification
+ } catch (error) {
+ console.error('Error deleting process:', error);
+ // TODO: Show error notification
+ } finally {
+ loading.value = false;
+ }
+};
+
+// Publish a process
+const publishProcess = async (processId) => {
+ try {
+ loading.value = true;
+ await processStore.publishProcess(processId);
+
+ // Refresh the process list
+ await loadProcesses();
+
+ // TODO: Show success notification
+ } catch (error) {
+ console.error('Error publishing process:', error);
+ // TODO: Show error notification
+ } finally {
+ loading.value = false;
}
};
@@ -103,7 +181,7 @@ const cancelDelete = () => {
// Create a new process
const createNewProcess = () => {
// Clear current process to start fresh
- processStore.currentProcess = null;
+ processStore.clearCurrentProcess();
router.push('/process-builder');
};
@@ -112,18 +190,9 @@ const goToBuilder = () => {
router.push('/');
};
-// Check if we have processes, if not create a demo one
-onMounted(() => {
- if (processStore.processes.length === 0) {
- // Create a demo process
- const process = processStore.createProcess(
- 'Demo Process',
- 'A demonstration process flow for testing purposes'
- );
-
- // Save it
- processStore.saveProcess();
- }
+// Load processes on component mount
+onMounted(async () => {
+ await loadProcesses();
});
@@ -137,7 +206,7 @@ onMounted(() => {

{
-
-
+
+
+
+
+
+
+
+
+ Refresh
+
-
+
+
+
+
+
Loading processes...
+
+
+
+
+
Name |
Description |
+ Status |
+ Category |
Created |
Last Updated |
Actions |
@@ -187,16 +282,41 @@ onMounted(() => {
-
- No processes found
+ |
+
+
+ No processes found
+
+ {{ searchQuery || statusFilter ? 'Try adjusting your filters' : 'Create your first process to get started' }}
+
+
+
+ Create New Process
+
+
|
{{ process.name }}
+
+ by {{ process.creator.userFullName || process.creator.userUsername }}
+
|
- {{ process.description }}
+
+ {{ process.description || 'No description' }}
+
+ |
+
+
+ {{ process.status || 'draft' }}
+
+ |
+
+
+ {{ process.category || '-' }}
+
|
{{ formatDate(process.createdAt) }}
@@ -205,25 +325,40 @@ onMounted(() => {
{{ formatDate(process.updatedAt) }}
|
-
+
+
+
+
+
@@ -233,24 +368,36 @@ onMounted(() => {
|
+
+
+
-
+
-
Are you sure you want to delete this process? This action cannot be undone.
+
Delete Process
+
+ Are you sure you want to delete this process? This action cannot be undone and will permanently remove all process data.
+
-
+
Cancel
-
+
+
Delete
@@ -260,5 +407,13 @@ onMounted(() => {
\ No newline at end of file
diff --git a/prisma/json/json-schema.json b/prisma/json/json-schema.json
index 4cdd83e..f26f529 100644
--- a/prisma/json/json-schema.json
+++ b/prisma/json/json-schema.json
@@ -1,73 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
- "audit": {
- "type": "object",
- "properties": {
- "auditID": {
- "type": "integer"
- },
- "auditIP": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditURL": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditURLMethod": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditURLPayload": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditCreatedDate": {
- "type": [
- "string",
- "null"
- ],
- "format": "date-time"
- },
- "auditAction": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditDetails": {
- "type": [
- "string",
- "null"
- ]
- },
- "auditUsername": {
- "type": [
- "string",
- "null"
- ]
- },
- "user": {
- "anyOf": [
- {
- "$ref": "#/definitions/user"
- },
- {
- "type": "null"
- }
- ]
- }
- }
- },
"user": {
"type": "object",
"properties": {
@@ -130,12 +63,6 @@
],
"format": "date-time"
},
- "audit": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/audit"
- }
- },
"forms": {
"type": "array",
"items": {
@@ -148,12 +75,6 @@
"$ref": "#/definitions/process"
}
},
- "assignedTasks": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/task"
- }
- },
"userrole": {
"type": "array",
"items": {
@@ -208,64 +129,6 @@
}
}
},
- "lookup": {
- "type": "object",
- "properties": {
- "lookupID": {
- "type": "integer"
- },
- "lookupOrder": {
- "type": [
- "integer",
- "null"
- ]
- },
- "lookupTitle": {
- "type": [
- "string",
- "null"
- ]
- },
- "lookupRefCode": {
- "type": [
- "string",
- "null"
- ]
- },
- "lookupValue": {
- "type": [
- "string",
- "null"
- ]
- },
- "lookupType": {
- "type": [
- "string",
- "null"
- ]
- },
- "lookupStatus": {
- "type": [
- "string",
- "null"
- ]
- },
- "lookupCreatedDate": {
- "type": [
- "string",
- "null"
- ],
- "format": "date-time"
- },
- "lookupModifiedDate": {
- "type": [
- "string",
- "null"
- ],
- "format": "date-time"
- }
- }
- },
"userrole": {
"type": "object",
"properties": {
@@ -312,13 +175,28 @@
"null"
]
},
- "customScript": {
+ "formStatus": {
+ "type": "string",
+ "default": "active"
+ },
+ "formCreatedDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "formModifiedDate": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "customCSS": {
"type": [
"string",
"null"
]
},
- "customCSS": {
+ "customScript": {
"type": [
"string",
"null"
@@ -341,21 +219,6 @@
],
"default": "safe"
},
- "formStatus": {
- "type": "string",
- "default": "active"
- },
- "formCreatedDate": {
- "type": "string",
- "format": "date-time"
- },
- "formModifiedDate": {
- "type": [
- "string",
- "null"
- ],
- "format": "date-time"
- },
"creator": {
"anyOf": [
{
@@ -365,12 +228,6 @@
"type": "null"
}
]
- },
- "formTasks": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/task"
- }
}
}
},
@@ -421,43 +278,23 @@
],
"format": "date-time"
},
- "creator": {
- "anyOf": [
- {
- "$ref": "#/definitions/user"
- },
- {
- "type": "null"
- }
+ "isTemplate": {
+ "type": "boolean",
+ "default": false
+ },
+ "processCategory": {
+ "type": [
+ "string",
+ "null"
]
},
- "tasks": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/task"
- }
- }
- }
- },
- "task": {
- "type": "object",
- "properties": {
- "taskID": {
- "type": "integer"
+ "processOwner": {
+ "type": [
+ "string",
+ "null"
+ ]
},
- "taskUUID": {
- "type": "string"
- },
- "taskNodeId": {
- "type": "string"
- },
- "taskName": {
- "type": "string"
- },
- "taskType": {
- "type": "string"
- },
- "taskData": {
+ "processPermissions": {
"type": [
"number",
"string",
@@ -467,22 +304,40 @@
"null"
]
},
- "taskStatus": {
- "type": "string",
- "default": "pending"
- },
- "taskCreatedDate": {
- "type": "string",
- "format": "date-time"
- },
- "taskModifiedDate": {
+ "processPriority": {
"type": [
"string",
"null"
],
- "format": "date-time"
+ "default": "normal"
},
- "assignee": {
+ "processSettings": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "processVariables": {
+ "type": [
+ "number",
+ "string",
+ "boolean",
+ "object",
+ "array",
+ "null"
+ ]
+ },
+ "templateCategory": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "creator": {
"anyOf": [
{
"$ref": "#/definitions/user"
@@ -491,37 +346,18 @@
"type": "null"
}
]
- },
- "form": {
- "anyOf": [
- {
- "$ref": "#/definitions/form"
- },
- {
- "type": "null"
- }
- ]
- },
- "process": {
- "$ref": "#/definitions/process"
}
}
}
},
"type": "object",
"properties": {
- "audit": {
- "$ref": "#/definitions/audit"
- },
"user": {
"$ref": "#/definitions/user"
},
"role": {
"$ref": "#/definitions/role"
},
- "lookup": {
- "$ref": "#/definitions/lookup"
- },
"userrole": {
"$ref": "#/definitions/userrole"
},
@@ -530,9 +366,6 @@
},
"process": {
"$ref": "#/definitions/process"
- },
- "task": {
- "$ref": "#/definitions/task"
}
}
}
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index b0f2db2..c155240 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -12,22 +12,6 @@ datasource db {
url = env("DATABASE_URL")
}
-model audit {
- auditID Int @id @default(autoincrement())
- auditIP String? @db.VarChar(255)
- auditURL String? @db.VarChar(255)
- auditURLMethod String? @db.VarChar(255)
- auditURLPayload String? @db.Text
- auditCreatedDate DateTime? @default(now()) @db.DateTime(0)
- auditAction String? @db.VarChar(255)
- auditDetails String? @db.Text
- auditUserID Int?
- auditUsername String? @db.VarChar(255)
- user user? @relation(fields: [auditUserID], references: [userID])
-
- @@index([auditUserID], map: "FK_audit_user")
-}
-
model user {
userID Int @id @default(autoincrement())
userSecretKey String? @db.VarChar(255)
@@ -39,10 +23,8 @@ model user {
userStatus String? @db.VarChar(255)
userCreatedDate DateTime? @db.DateTime(0)
userModifiedDate DateTime? @db.DateTime(0)
- audit audit[]
forms form[] @relation("FormCreator")
processes process[] @relation("ProcessCreator")
- assignedTasks task[] @relation("TaskAssignee")
userrole userrole[]
}
@@ -56,18 +38,6 @@ model role {
userrole userrole[]
}
-model lookup {
- lookupID Int @id @default(autoincrement())
- lookupOrder Int?
- lookupTitle String? @db.VarChar(255)
- lookupRefCode String? @db.VarChar(255)
- lookupValue String? @db.VarChar(255)
- lookupType String? @db.VarChar(255)
- lookupStatus String? @db.VarChar(255)
- lookupCreatedDate DateTime? @db.DateTime(0)
- lookupModifiedDate DateTime? @db.DateTime(0)
-}
-
model userrole {
userRoleID Int @id @default(autoincrement())
userRoleUserID Int @default(0)
@@ -86,16 +56,15 @@ model form {
formName String @db.VarChar(255)
formDescription String? @db.Text
formComponents Json
- customScript String? @db.LongText
- customCSS String? @db.Text
- formEvents Json?
- scriptMode String? @default("safe") @db.VarChar(20)
formStatus String @default("active") @db.VarChar(50)
formCreatedBy Int?
formCreatedDate DateTime @default(now()) @db.DateTime(0)
formModifiedDate DateTime? @updatedAt @db.DateTime(0)
+ customCSS String? @db.Text
+ customScript String? @db.LongText
+ formEvents Json?
+ scriptMode String? @default("safe") @db.VarChar(20)
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
- formTasks task[] @relation("FormTask")
@@index([formCreatedBy], map: "FK_form_creator")
}
@@ -111,30 +80,18 @@ model process {
processCreatedBy Int?
processCreatedDate DateTime @default(now()) @db.DateTime(0)
processModifiedDate DateTime? @updatedAt @db.DateTime(0)
+ isTemplate Boolean @default(false)
+ processCategory String? @db.VarChar(100)
+ processOwner String? @db.VarChar(255)
+ processPermissions Json?
+ processPriority String? @default("normal") @db.VarChar(50)
+ processSettings Json?
+ processVariables Json?
+ templateCategory String? @db.VarChar(100)
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
- tasks task[]
@@index([processCreatedBy], map: "FK_process_creator")
-}
-
-model task {
- taskID Int @id @default(autoincrement())
- taskUUID String @unique @db.VarChar(36)
- taskNodeId String @db.VarChar(255)
- taskName String @db.VarChar(255)
- taskType String @db.VarChar(50)
- taskData Json?
- taskProcessId Int
- taskFormId Int?
- taskAssigneeId Int?
- taskStatus String @default("pending") @db.VarChar(50)
- taskCreatedDate DateTime @default(now()) @db.DateTime(0)
- taskModifiedDate DateTime? @updatedAt @db.DateTime(0)
- assignee user? @relation("TaskAssignee", fields: [taskAssigneeId], references: [userID])
- form form? @relation("FormTask", fields: [taskFormId], references: [formID])
- process process @relation(fields: [taskProcessId], references: [processID])
-
- @@index([taskProcessId], map: "FK_task_process")
- @@index([taskFormId], map: "FK_task_form")
- @@index([taskAssigneeId], map: "FK_task_assignee")
+ @@index([processStatus], map: "IDX_process_status")
+ @@index([processCategory], map: "IDX_process_category")
+ @@index([isTemplate], map: "IDX_process_template")
}
diff --git a/server/api/process/[id].delete.js b/server/api/process/[id].delete.js
new file mode 100644
index 0000000..6f98bf0
--- /dev/null
+++ b/server/api/process/[id].delete.js
@@ -0,0 +1,76 @@
+import { PrismaClient } from '@prisma/client';
+
+// 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'
+ };
+ }
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // First, check if the process exists
+ const existingProcess = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ select: {
+ processID: true,
+ processName: true,
+ processStatus: true
+ }
+ });
+
+ if (!existingProcess) {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ // Optional: Prevent deletion of published processes
+ if (existingProcess.processStatus === 'published') {
+ return {
+ success: false,
+ error: 'Cannot delete published processes. Please set status to draft first.'
+ };
+ }
+
+ // Delete the process
+ await prisma.process.delete({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) }
+ });
+
+ return {
+ success: true,
+ message: `Process "${existingProcess.processName}" deleted successfully`
+ };
+ } catch (error) {
+ console.error('Error deleting process:', error);
+
+ // Handle specific Prisma errors
+ if (error.code === 'P2025') {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ return {
+ success: false,
+ error: 'Failed to delete process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/[id].get.js b/server/api/process/[id].get.js
new file mode 100644
index 0000000..3087be0
--- /dev/null
+++ b/server/api/process/[id].get.js
@@ -0,0 +1,57 @@
+import { PrismaClient } from '@prisma/client';
+
+// 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'
+ };
+ }
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // Find the process by UUID or ID
+ const process = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ if (!process) {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ return {
+ success: true,
+ process
+ };
+ } catch (error) {
+ console.error('Error fetching process:', error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/[id].put.js b/server/api/process/[id].put.js
new file mode 100644
index 0000000..d7a783c
--- /dev/null
+++ b/server/api/process/[id].put.js
@@ -0,0 +1,114 @@
+import { PrismaClient } from '@prisma/client';
+import { defineEventHandler, getRouterParam, readBody } from 'h3';
+
+// 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'
+ };
+ }
+
+ // Parse the request body
+ const body = await readBody(event);
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // Build update data
+ const updateData = {};
+
+ // Basic fields
+ if (body.processName !== undefined) updateData.processName = body.processName;
+ if (body.processDescription !== undefined) updateData.processDescription = body.processDescription;
+ if (body.processCategory !== undefined) updateData.processCategory = body.processCategory;
+ if (body.processPriority !== undefined) updateData.processPriority = body.processPriority;
+ if (body.processOwner !== undefined) updateData.processOwner = body.processOwner;
+ if (body.processStatus !== undefined) updateData.processStatus = body.processStatus;
+ if (body.isTemplate !== undefined) updateData.isTemplate = body.isTemplate;
+ if (body.templateCategory !== undefined) updateData.templateCategory = body.templateCategory;
+
+ // Process definition (nodes, edges, viewport)
+ if (body.nodes !== undefined || body.edges !== undefined || body.viewport !== undefined) {
+ updateData.processDefinition = {
+ nodes: body.nodes || [],
+ edges: body.edges || [],
+ viewport: body.viewport || { x: 0, y: 0, zoom: 1 }
+ };
+ }
+
+ // Process variables
+ if (body.variables !== undefined) {
+ updateData.processVariables = Object.keys(body.variables).length > 0 ? body.variables : null;
+ }
+
+ // Process settings
+ if (body.settings !== undefined) {
+ updateData.processSettings = Object.keys(body.settings).length > 0 ? body.settings : null;
+ }
+
+ // Process permissions
+ if (body.permissions !== undefined) {
+ updateData.processPermissions = Object.keys(body.permissions).length > 0 ? body.permissions : null;
+ }
+
+ // Version increment if major changes
+ if (body.incrementVersion === true) {
+ const currentProcess = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ select: { processVersion: true }
+ });
+
+ if (currentProcess) {
+ updateData.processVersion = currentProcess.processVersion + 1;
+ }
+ }
+
+ // Update the process
+ const updatedProcess = await prisma.process.update({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ data: updateData,
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ return {
+ success: true,
+ process: updatedProcess
+ };
+ } catch (error) {
+ console.error('Error updating process:', error);
+
+ // Handle specific Prisma errors
+ if (error.code === 'P2025') {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ return {
+ success: false,
+ error: 'Failed to update process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/[id]/duplicate.post.js b/server/api/process/[id]/duplicate.post.js
new file mode 100644
index 0000000..b753f92
--- /dev/null
+++ b/server/api/process/[id]/duplicate.post.js
@@ -0,0 +1,114 @@
+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'
+ };
+ }
+
+ // Parse the request body for optional parameters
+ const body = await readBody(event);
+ const { newName, asTemplate = false } = body;
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // Find the source process
+ const sourceProcess = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) }
+ });
+
+ if (!sourceProcess) {
+ return {
+ success: false,
+ error: 'Source process not found'
+ };
+ }
+
+ // Generate new IDs for all nodes and edges in the definition
+ const newDefinition = JSON.parse(JSON.stringify(sourceProcess.processDefinition));
+ const nodeIdMap = new Map();
+
+ // Update node IDs
+ if (newDefinition.nodes) {
+ newDefinition.nodes.forEach(node => {
+ const oldId = node.id;
+ const newId = uuidv4();
+ nodeIdMap.set(oldId, newId);
+ node.id = newId;
+ });
+ }
+
+ // Update edge IDs and references
+ if (newDefinition.edges) {
+ newDefinition.edges.forEach(edge => {
+ edge.id = uuidv4();
+ edge.source = nodeIdMap.get(edge.source) || edge.source;
+ edge.target = nodeIdMap.get(edge.target) || edge.target;
+ });
+ }
+
+ // Create the duplicate process
+ const duplicatedProcess = await prisma.process.create({
+ data: {
+ processUUID: uuidv4(),
+ processName: newName || `${sourceProcess.processName} (Copy)`,
+ processDescription: sourceProcess.processDescription,
+ processCategory: sourceProcess.processCategory,
+ processPriority: sourceProcess.processPriority,
+ processOwner: sourceProcess.processOwner,
+ processDefinition: newDefinition,
+ processVariables: sourceProcess.processVariables,
+ processSettings: sourceProcess.processSettings,
+ processPermissions: sourceProcess.processPermissions,
+ processStatus: 'draft', // Always start as draft
+ isTemplate: asTemplate,
+ templateCategory: asTemplate ? sourceProcess.templateCategory : null,
+ processCreatedBy: body.createdBy || sourceProcess.processCreatedBy
+ },
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Process duplicated successfully',
+ process: duplicatedProcess
+ };
+ } catch (error) {
+ console.error('Error duplicating process:', error);
+
+ // Handle specific Prisma errors
+ if (error.code === 'P2025') {
+ return {
+ success: false,
+ error: 'Source process not found'
+ };
+ }
+
+ return {
+ success: false,
+ error: 'Failed to duplicate process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/[id]/publish.post.js b/server/api/process/[id]/publish.post.js
new file mode 100644
index 0000000..94be9a0
--- /dev/null
+++ b/server/api/process/[id]/publish.post.js
@@ -0,0 +1,121 @@
+import { PrismaClient } from '@prisma/client';
+
+// 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'
+ };
+ }
+
+ // Check if the ID is a UUID or numeric ID
+ const isUUID = processId.length === 36 && processId.includes('-');
+
+ // First, get the current process to validate it can be published
+ const currentProcess = await prisma.process.findFirst({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) }
+ });
+
+ if (!currentProcess) {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ // Validate the process has required elements for publishing
+ const definition = currentProcess.processDefinition;
+
+ // Check if process has at least one start node
+ const hasStartNode = definition.nodes?.some(node => node.type === 'start');
+ if (!hasStartNode) {
+ return {
+ success: false,
+ error: 'Process must have at least one start node to be published'
+ };
+ }
+
+ // Check if process has at least one end node
+ const hasEndNode = definition.nodes?.some(node => node.type === 'end');
+ if (!hasEndNode) {
+ return {
+ success: false,
+ error: 'Process must have at least one end node to be published'
+ };
+ }
+
+ // Check if all nodes are properly connected (basic validation)
+ const nodeIds = new Set(definition.nodes?.map(node => node.id) || []);
+ const connectedNodes = new Set();
+
+ definition.edges?.forEach(edge => {
+ connectedNodes.add(edge.source);
+ connectedNodes.add(edge.target);
+ });
+
+ // Ensure all non-start/end nodes are connected
+ const unconnectedNodes = definition.nodes?.filter(node =>
+ node.type !== 'start' &&
+ node.type !== 'end' &&
+ !connectedNodes.has(node.id)
+ ) || [];
+
+ if (unconnectedNodes.length > 0) {
+ return {
+ success: false,
+ error: `Process has unconnected nodes: ${unconnectedNodes.map(n => n.label || n.id).join(', ')}`
+ };
+ }
+
+ // Update the process status to published and increment version
+ const publishedProcess = await prisma.process.update({
+ where: isUUID
+ ? { processUUID: processId }
+ : { processID: parseInt(processId) },
+ data: {
+ processStatus: 'published',
+ processVersion: currentProcess.processVersion + 1
+ },
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ return {
+ success: true,
+ message: 'Process published successfully',
+ process: publishedProcess
+ };
+ } catch (error) {
+ console.error('Error publishing process:', error);
+
+ // Handle specific Prisma errors
+ if (error.code === 'P2025') {
+ return {
+ success: false,
+ error: 'Process not found'
+ };
+ }
+
+ return {
+ success: false,
+ error: 'Failed to publish process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/create.post.js b/server/api/process/create.post.js
new file mode 100644
index 0000000..f082a3c
--- /dev/null
+++ b/server/api/process/create.post.js
@@ -0,0 +1,78 @@
+import { PrismaClient } from '@prisma/client';
+import { v4 as uuidv4 } from 'uuid';
+
+// Initialize Prisma client
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ try {
+ // Parse the request body
+ const body = await readBody(event);
+
+ // Validate required fields
+ if (!body.processName) {
+ return {
+ success: false,
+ error: 'Process name is required'
+ };
+ }
+
+ // Prepare process definition
+ const processDefinition = {
+ nodes: body.nodes || [],
+ edges: body.edges || [],
+ viewport: body.viewport || { x: 0, y: 0, zoom: 1 }
+ };
+
+ // Prepare process variables (if any)
+ const processVariables = body.variables || {};
+
+ // Prepare process settings (if any)
+ const processSettings = body.settings || {};
+
+ // Prepare process permissions (if any)
+ const processPermissions = body.permissions || {};
+
+ // Create a new process in the database
+ const process = await prisma.process.create({
+ data: {
+ processUUID: uuidv4(),
+ processName: body.processName,
+ processDescription: body.processDescription || null,
+ processCategory: body.processCategory || null,
+ processPriority: body.processPriority || 'normal',
+ processOwner: body.processOwner || null,
+ processDefinition: processDefinition,
+ processVariables: Object.keys(processVariables).length > 0 ? processVariables : null,
+ processSettings: Object.keys(processSettings).length > 0 ? processSettings : null,
+ processPermissions: Object.keys(processPermissions).length > 0 ? processPermissions : null,
+ processStatus: body.processStatus || 'draft',
+ isTemplate: body.isTemplate || false,
+ templateCategory: body.templateCategory || null,
+ processCreatedBy: body.createdBy || null // In a real app, this would come from the authenticated user
+ },
+ include: {
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ return {
+ success: true,
+ process
+ };
+ } catch (error) {
+ console.error('Error creating process:', error);
+
+ return {
+ success: false,
+ error: 'Failed to create process',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/index.get.js b/server/api/process/index.get.js
new file mode 100644
index 0000000..e676d29
--- /dev/null
+++ b/server/api/process/index.get.js
@@ -0,0 +1,113 @@
+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 {
+ page = 1,
+ limit = 20,
+ status,
+ category,
+ search,
+ isTemplate,
+ sortBy = 'processCreatedDate',
+ sortOrder = 'desc'
+ } = query;
+
+ // Build where clause
+ const where = {};
+
+ if (status) {
+ where.processStatus = status;
+ }
+
+ if (category) {
+ where.processCategory = category;
+ }
+
+ if (isTemplate !== undefined) {
+ where.isTemplate = isTemplate === 'true';
+ }
+
+ if (search) {
+ where.OR = [
+ { processName: { contains: search, mode: 'insensitive' } },
+ { processDescription: { contains: search, mode: 'insensitive' } }
+ ];
+ }
+
+ // Calculate pagination
+ const skip = (parseInt(page) - 1) * parseInt(limit);
+ const take = parseInt(limit);
+
+ // Build orderBy clause
+ const orderBy = {};
+ orderBy[sortBy] = sortOrder;
+
+ // Get processes with pagination
+ const [processes, totalCount] = await Promise.all([
+ prisma.process.findMany({
+ where,
+ orderBy,
+ skip,
+ take,
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true,
+ processDescription: true,
+ processCategory: true,
+ processPriority: true,
+ processOwner: true,
+ processVersion: true,
+ processStatus: true,
+ isTemplate: true,
+ templateCategory: true,
+ processCreatedDate: true,
+ processModifiedDate: true,
+ // Don't include the full definition data to keep response size small
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ }),
+ prisma.process.count({ where })
+ ]);
+
+ // Calculate pagination info
+ const totalPages = Math.ceil(totalCount / take);
+ const hasNextPage = parseInt(page) < totalPages;
+ const hasPrevPage = parseInt(page) > 1;
+
+ return {
+ success: true,
+ data: {
+ processes,
+ pagination: {
+ currentPage: parseInt(page),
+ totalPages,
+ totalCount,
+ limit: take,
+ hasNextPage,
+ hasPrevPage
+ }
+ }
+ };
+ } catch (error) {
+ console.error('Error fetching processes:', error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch processes',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/process/templates.get.js b/server/api/process/templates.get.js
new file mode 100644
index 0000000..5311e6b
--- /dev/null
+++ b/server/api/process/templates.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 {
+ category,
+ search,
+ sortBy = 'processCreatedDate',
+ sortOrder = 'desc'
+ } = query;
+
+ // Build where clause for templates
+ const where = {
+ isTemplate: true
+ };
+
+ if (category) {
+ where.templateCategory = category;
+ }
+
+ if (search) {
+ where.OR = [
+ { processName: { contains: search, mode: 'insensitive' } },
+ { processDescription: { contains: search, mode: 'insensitive' } }
+ ];
+ }
+
+ // Build orderBy clause
+ const orderBy = {};
+ orderBy[sortBy] = sortOrder;
+
+ // Get templates
+ const templates = await prisma.process.findMany({
+ where,
+ orderBy,
+ select: {
+ processID: true,
+ processUUID: true,
+ processName: true,
+ processDescription: true,
+ processCategory: true,
+ templateCategory: true,
+ processDefinition: true,
+ processVariables: true,
+ processSettings: true,
+ processCreatedDate: true,
+ processModifiedDate: true,
+ creator: {
+ select: {
+ userID: true,
+ userFullName: true,
+ userUsername: true
+ }
+ }
+ }
+ });
+
+ // Get template categories for filtering
+ const categories = await prisma.process.findMany({
+ where: {
+ isTemplate: true,
+ templateCategory: { not: null }
+ },
+ select: {
+ templateCategory: true
+ },
+ distinct: ['templateCategory']
+ });
+
+ const uniqueCategories = categories
+ .map(c => c.templateCategory)
+ .filter(Boolean)
+ .sort();
+
+ return {
+ success: true,
+ data: {
+ templates,
+ categories: uniqueCategories
+ }
+ };
+ } catch (error) {
+ console.error('Error fetching process templates:', error);
+
+ return {
+ success: false,
+ error: 'Failed to fetch process templates',
+ details: process.env.NODE_ENV === 'development' ? error.message : undefined
+ };
+ }
+});
\ No newline at end of file
diff --git a/server/api/processes/[id].put.js b/server/api/processes/[id].put.js
deleted file mode 100644
index 94c59fd..0000000
--- a/server/api/processes/[id].put.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { PrismaClient } from '@prisma/client';
-
-// Initialize Prisma client
-const prisma = new PrismaClient();
-
-export default defineEventHandler(async (event) => {
- // Get the process ID from the route params
- const id = event.context.params.id;
-
- try {
- // Parse the request body
- const body = await readBody(event);
-
- // Validate required fields
- if (!body.processName) {
- return {
- success: false,
- error: 'Process name is required'
- };
- }
-
- // Prepare update data
- const updateData = {
- processName: body.processName,
- processModifiedDate: new Date()
- };
-
- // Add optional fields if provided
- if (body.processDescription !== undefined) {
- updateData.processDescription = body.processDescription;
- }
-
- if (body.definition !== undefined) {
- updateData.processDefinition = body.definition;
- }
-
- if (body.processStatus !== undefined) {
- updateData.processStatus = body.processStatus;
- }
-
- if (body.processVersion !== undefined) {
- updateData.processVersion = body.processVersion;
- }
-
- // Try to update by UUID first
- let process;
- try {
- process = await prisma.process.update({
- where: { processUUID: id },
- data: updateData
- });
- } catch (e) {
- // If UUID not found, try numeric ID
- if (!isNaN(parseInt(id))) {
- process = await prisma.process.update({
- where: { processID: parseInt(id) },
- data: updateData
- });
- } else {
- throw e;
- }
- }
-
- return {
- success: true,
- process
- };
- } catch (error) {
- console.error(`Error updating process ${id}:`, error);
-
- // Handle specific errors
- if (error.code === 'P2025') {
- return {
- success: false,
- error: 'Process not found'
- };
- }
-
- return {
- success: false,
- error: 'Failed to update process',
- details: process.env.NODE_ENV === 'development' ? error.message : undefined
- };
- }
-});
\ No newline at end of file
diff --git a/server/api/processes/[id]/tasks/create.post.js b/server/api/processes/[id]/tasks/create.post.js
deleted file mode 100644
index b96aff6..0000000
--- a/server/api/processes/[id]/tasks/create.post.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { PrismaClient } from '@prisma/client';
-import { v4 as uuidv4 } from 'uuid';
-
-// Initialize Prisma client
-const prisma = new PrismaClient();
-
-export default defineEventHandler(async (event) => {
- // Get the process ID from the route params
- const processId = event.context.params.id;
-
- try {
- // Parse the request body
- const body = await readBody(event);
-
- // Validate required fields
- if (!body.taskName || !body.taskType || !body.taskNodeId) {
- return {
- success: false,
- error: 'Task name, type, and node ID are required'
- };
- }
-
- // Find the process
- let process;
-
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(processId)) {
- // If it looks like a UUID
- process = await prisma.process.findUnique({
- where: { processUUID: processId }
- });
- } else if (!isNaN(parseInt(processId))) {
- // If it's a numeric ID
- process = await prisma.process.findUnique({
- where: { processID: parseInt(processId) }
- });
- }
-
- if (!process) {
- return {
- success: false,
- error: 'Process not found'
- };
- }
-
- // Create a new task
- const task = await prisma.task.create({
- data: {
- taskUUID: uuidv4(),
- taskNodeId: body.taskNodeId,
- taskName: body.taskName,
- taskType: body.taskType,
- taskData: body.taskData || {},
- taskProcessId: process.processID,
- taskAssigneeId: body.assigneeId || null
- }
- });
-
- return {
- success: true,
- task
- };
- } catch (error) {
- console.error(`Error creating task for process ${processId}:`, error);
-
- return {
- success: false,
- error: 'Failed to create task',
- details: process.env.NODE_ENV === 'development' ? error.message : undefined
- };
- }
-});
\ No newline at end of file
diff --git a/server/api/processes/create.post.js b/server/api/processes/create.post.js
deleted file mode 100644
index b6d9e10..0000000
--- a/server/api/processes/create.post.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { PrismaClient } from '@prisma/client';
-import { v4 as uuidv4 } from 'uuid';
-
-// Initialize Prisma client
-const prisma = new PrismaClient();
-
-export default defineEventHandler(async (event) => {
- try {
- // Parse the request body
- const body = await readBody(event);
-
- // Validate required fields
- if (!body.processName) {
- return {
- success: false,
- error: 'Process name is required'
- };
- }
-
- // Create a new process in the database
- const process = await prisma.process.create({
- data: {
- processUUID: uuidv4(),
- processName: body.processName,
- processDescription: body.processDescription || null,
- processDefinition: body.definition || { nodes: [], edges: [] },
- processVersion: 1,
- processStatus: body.status || 'draft',
- processCreatedBy: body.createdBy || null // In a real app, this would come from the authenticated user
- }
- });
-
- return {
- success: true,
- process
- };
- } catch (error) {
- console.error('Error creating process:', error);
-
- return {
- success: false,
- error: 'Failed to create process',
- details: process.env.NODE_ENV === 'development' ? error.message : undefined
- };
- }
-});
\ No newline at end of file
diff --git a/stores/processBuilder.js b/stores/processBuilder.js
index e34a53b..b4e16b0 100644
--- a/stores/processBuilder.js
+++ b/stores/processBuilder.js
@@ -4,7 +4,7 @@ import { useVariableStore } from './variableStore';
export const useProcessBuilderStore = defineStore('processBuilder', {
state: () => ({
- processes: [],
+ processes: [], // Only populated from database via fetchProcesses()
currentProcess: null,
selectedNodeId: null,
selectedEdgeId: null,
@@ -63,26 +63,55 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
/**
* Create a new process
*/
- createProcess(name, description = '') {
- const process = {
- id: crypto.randomUUID(),
- name,
- description,
- nodes: [],
- edges: [],
- variables: {},
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString()
- };
+ async createProcess(name, description = '') {
+ try {
+ const processData = {
+ processName: name,
+ processDescription: description,
+ nodes: [],
+ edges: [],
+ viewport: { x: 0, y: 0, zoom: 1 },
+ variables: {},
+ settings: {},
+ permissions: {},
+ createdBy: 1 // TODO: Get from auth store
+ };
- this.processes.push(process);
- this.currentProcess = process;
- this.unsavedChanges = true;
+ const response = await $fetch('/api/process/create', {
+ method: 'POST',
+ body: processData
+ });
- // Clear any existing variables
- useVariableStore().clearProcessVariables();
+ if (response.success) {
+ const process = {
+ id: response.process.processUUID,
+ name: response.process.processName,
+ description: response.process.processDescription,
+ nodes: response.process.processDefinition.nodes || [],
+ edges: response.process.processDefinition.edges || [],
+ variables: response.process.processVariables || {},
+ settings: response.process.processSettings || {},
+ permissions: response.process.processPermissions || {},
+ createdAt: response.process.processCreatedDate,
+ updatedAt: response.process.processModifiedDate
+ };
- return process;
+ // Set as current process but DON'T add to processes array
+ // The processes array should only be populated from fetchProcesses()
+ this.currentProcess = process;
+ this.unsavedChanges = false;
+
+ // Clear any existing variables
+ useVariableStore().clearProcessVariables();
+
+ return process;
+ } else {
+ throw new Error(response.error || 'Failed to create process');
+ }
+ } catch (error) {
+ console.error('Error creating process:', error);
+ throw error;
+ }
},
/**
@@ -90,10 +119,71 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
*/
async loadProcess(processId) {
try {
- // TODO: Implement API call to load process
- // For now, just load from local state
- const process = this.processes.find(p => p.id === processId);
- if (process) {
+ const response = await $fetch(`/api/process/${processId}`);
+
+ if (response.success) {
+ const apiProcess = response.process;
+ const definition = apiProcess.processDefinition;
+
+ let nodes = definition.nodes || [];
+ let edges = definition.edges || [];
+
+ // If nodes array is empty but edges contain node data, extract nodes from edges
+ if (nodes.length === 0 && edges.length > 0) {
+ const nodeMap = new Map();
+
+ // Extract unique nodes from edge sourceNode and targetNode
+ edges.forEach(edge => {
+ if (edge.sourceNode) {
+ nodeMap.set(edge.sourceNode.id, {
+ id: edge.sourceNode.id,
+ type: edge.sourceNode.type,
+ label: edge.sourceNode.data?.label || edge.sourceNode.label,
+ position: edge.sourceNode.position,
+ data: edge.sourceNode.data || {}
+ });
+ }
+
+ if (edge.targetNode) {
+ nodeMap.set(edge.targetNode.id, {
+ id: edge.targetNode.id,
+ type: edge.targetNode.type,
+ label: edge.targetNode.data?.label || edge.targetNode.label,
+ position: edge.targetNode.position,
+ data: edge.targetNode.data || {}
+ });
+ }
+ });
+
+ // Convert to array
+ nodes = Array.from(nodeMap.values());
+
+ // Clean up edges to remove embedded node data (Vue Flow doesn't need it)
+ edges = edges.map(edge => ({
+ id: edge.id,
+ source: edge.source,
+ target: edge.target,
+ label: edge.label || '',
+ type: edge.type || 'smoothstep',
+ animated: edge.animated !== undefined ? edge.animated : true,
+ data: edge.data || {}
+ }));
+ }
+
+ const process = {
+ id: apiProcess.processUUID,
+ name: apiProcess.processName,
+ description: apiProcess.processDescription,
+ nodes: nodes,
+ edges: edges,
+ viewport: definition.viewport || { x: 0, y: 0, zoom: 1 },
+ variables: apiProcess.processVariables || {},
+ settings: apiProcess.processSettings || {},
+ permissions: apiProcess.processPermissions || {},
+ createdAt: apiProcess.processCreatedDate,
+ updatedAt: apiProcess.processModifiedDate
+ };
+
this.currentProcess = process;
// Load variables into variable store
@@ -104,17 +194,22 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
});
}
- return true;
+ this.unsavedChanges = false;
+ return { success: true, process };
+ } else {
+ const errorMessage = response.error || 'Failed to load process';
+ console.error('Load process failed:', errorMessage);
+ return { success: false, error: errorMessage };
}
- return false;
} catch (error) {
- console.error('Error loading process:', error);
- return false;
+ const errorMessage = error.data?.error || error.message || 'Network error occurred';
+ console.error('Error loading process:', errorMessage);
+ return { success: false, error: errorMessage };
}
},
/**
- * Set the current process
+ * Set the current process from the processes list
*/
setCurrentProcess(processId) {
const process = this.processes.find(p => p.id === processId);
@@ -147,26 +242,41 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
* Save the current process
*/
async saveProcess() {
- if (!this.currentProcess) return;
+ if (!this.currentProcess) return false;
try {
- // Save process data
const processData = {
- ...this.currentProcess,
- variables: useVariableStore().getAllVariables.process
+ processName: this.currentProcess.name,
+ processDescription: this.currentProcess.description,
+ nodes: this.currentProcess.nodes,
+ edges: this.currentProcess.edges,
+ viewport: this.currentProcess.viewport || { x: 0, y: 0, zoom: 1 },
+ variables: useVariableStore().getAllVariables.process || {},
+ settings: this.currentProcess.settings || {},
+ permissions: this.currentProcess.permissions || {}
};
- // TODO: Implement API call to save process
- // For now, just update local state
- const index = this.processes.findIndex(p => p.id === this.currentProcess.id);
- if (index !== -1) {
- this.processes[index] = processData;
- } else {
- this.processes.push(processData);
- }
+ const response = await $fetch(`/api/process/${this.currentProcess.id}`, {
+ method: 'PUT',
+ body: processData
+ });
- this.unsavedChanges = false;
- return true;
+ if (response.success) {
+ // Update local state with server response
+ const apiProcess = response.process;
+ this.currentProcess.updatedAt = apiProcess.processModifiedDate;
+
+ // Update in processes array if it exists there
+ const index = this.processes.findIndex(p => p.id === this.currentProcess.id);
+ if (index !== -1) {
+ this.processes[index] = { ...this.currentProcess };
+ }
+
+ this.unsavedChanges = false;
+ return true;
+ } else {
+ throw new Error(response.error || 'Failed to save process');
+ }
} catch (error) {
console.error('Error saving process:', error);
return false;
@@ -176,19 +286,177 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
/**
* Delete a process
*/
- deleteProcess(processId) {
- const index = this.processes.findIndex(p => p.id === processId);
- if (index !== -1) {
- this.processes.splice(index, 1);
- if (this.currentProcess && this.currentProcess.id === processId) {
- this.currentProcess = null;
- this.selectedNodeId = null;
- this.selectedEdgeId = null;
- this.clearHistory();
+ async deleteProcess(processId) {
+ try {
+ const response = await $fetch(`/api/process/${processId}`, {
+ method: 'DELETE'
+ });
+
+ if (response.success) {
+ // Remove from local processes array
+ const index = this.processes.findIndex(p => p.id === processId);
+ if (index !== -1) {
+ this.processes.splice(index, 1);
+ }
+
+ // Clear current process if it's the one being deleted
+ if (this.currentProcess && this.currentProcess.id === processId) {
+ this.currentProcess = null;
+ this.selectedNodeId = null;
+ this.selectedEdgeId = null;
+ this.clearHistory();
+ }
+
+ return true;
+ } else {
+ throw new Error(response.error || 'Failed to delete process');
}
+ } catch (error) {
+ console.error('Error deleting process:', error);
+ return false;
}
},
+ /**
+ * Fetch all processes from database
+ */
+ async fetchProcesses(options = {}) {
+ try {
+ const queryParams = new URLSearchParams();
+
+ if (options.page) queryParams.append('page', options.page);
+ if (options.limit) queryParams.append('limit', options.limit);
+ if (options.status) queryParams.append('status', options.status);
+ if (options.category) queryParams.append('category', options.category);
+ if (options.search) queryParams.append('search', options.search);
+ if (options.isTemplate !== undefined) queryParams.append('isTemplate', options.isTemplate);
+ if (options.sortBy) queryParams.append('sortBy', options.sortBy);
+ if (options.sortOrder) queryParams.append('sortOrder', options.sortOrder);
+
+ const url = `/api/process${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
+ const response = await $fetch(url);
+
+ if (response.success) {
+ // Replace the entire processes array with fresh data from database
+ this.processes = response.data.processes.map(apiProcess => ({
+ id: apiProcess.processUUID,
+ name: apiProcess.processName,
+ description: apiProcess.processDescription,
+ category: apiProcess.processCategory,
+ priority: apiProcess.processPriority,
+ owner: apiProcess.processOwner,
+ status: apiProcess.processStatus,
+ isTemplate: apiProcess.isTemplate,
+ templateCategory: apiProcess.templateCategory,
+ createdAt: apiProcess.processCreatedDate,
+ updatedAt: apiProcess.processModifiedDate,
+ creator: apiProcess.creator
+ }));
+
+ return response.data;
+ } else {
+ throw new Error(response.error || 'Failed to fetch processes');
+ }
+ } catch (error) {
+ console.error('Error fetching processes:', error);
+ throw error;
+ }
+ },
+
+ /**
+ * Publish a process
+ */
+ async publishProcess(processId) {
+ try {
+ const response = await $fetch(`/api/process/${processId}/publish`, {
+ method: 'POST'
+ });
+
+ if (response.success) {
+ // Update local state if process exists in the array
+ const process = this.processes.find(p => p.id === processId);
+ if (process) {
+ process.status = 'published';
+ process.updatedAt = response.process.processModifiedDate;
+ }
+
+ // Update current process if it's the same one
+ if (this.currentProcess && this.currentProcess.id === processId) {
+ this.currentProcess.status = 'published';
+ this.currentProcess.updatedAt = response.process.processModifiedDate;
+ }
+
+ return true;
+ } else {
+ throw new Error(response.error || 'Failed to publish process');
+ }
+ } catch (error) {
+ console.error('Error publishing process:', error);
+ throw error;
+ }
+ },
+
+ /**
+ * Duplicate a process
+ */
+ async duplicateProcess(processId, newName = null, asTemplate = false) {
+ try {
+ const response = await $fetch(`/api/process/${processId}/duplicate`, {
+ method: 'POST',
+ body: {
+ newName,
+ asTemplate,
+ createdBy: 1 // TODO: Get from auth store
+ }
+ });
+
+ if (response.success) {
+ const apiProcess = response.process;
+ const newProcess = {
+ id: apiProcess.processUUID,
+ name: apiProcess.processName,
+ description: apiProcess.processDescription,
+ category: apiProcess.processCategory,
+ priority: apiProcess.processPriority,
+ owner: apiProcess.processOwner,
+ status: apiProcess.processStatus,
+ isTemplate: apiProcess.isTemplate,
+ templateCategory: apiProcess.templateCategory,
+ createdAt: apiProcess.processCreatedDate,
+ updatedAt: apiProcess.processModifiedDate,
+ creator: apiProcess.creator
+ };
+
+ // DON'T add to processes array - let fetchProcesses() handle that
+ // The manage page should call fetchProcesses() after duplication
+ return newProcess;
+ } else {
+ throw new Error(response.error || 'Failed to duplicate process');
+ }
+ } catch (error) {
+ console.error('Error duplicating process:', error);
+ throw error;
+ }
+ },
+
+ /**
+ * Clear the processes list (useful when switching contexts)
+ */
+ clearProcesses() {
+ this.processes = [];
+ },
+
+ /**
+ * Clear the current process (useful when starting fresh)
+ */
+ clearCurrentProcess() {
+ this.currentProcess = null;
+ this.selectedNodeId = null;
+ this.selectedEdgeId = null;
+ this.clearHistory();
+ this.unsavedChanges = false;
+ },
+
/**
* Add a node to the current process
*/