add user validation in staing proccess and form
This commit is contained in:
parent
b5ee79339a
commit
aa01c212ff
223
docs/process-execution/ACCESS_CONTROL.md
Normal file
223
docs/process-execution/ACCESS_CONTROL.md
Normal file
@ -0,0 +1,223 @@
|
||||
# Form Access Control
|
||||
|
||||
## Overview
|
||||
|
||||
The form execution system now includes comprehensive access control that determines whether users can edit or only view forms based on their assignments and roles.
|
||||
|
||||
## How It Works
|
||||
|
||||
### Access Validation
|
||||
|
||||
When a user accesses a form in a process execution, the system checks:
|
||||
|
||||
1. **Direct Task Assignment**: If the task is directly assigned to the current user
|
||||
2. **Process Definition Assignment**: If the form node in the process has specific user/role assignments
|
||||
3. **Default Access**: If no specific assignment is found, defaults to public access
|
||||
|
||||
### Assignment Types
|
||||
|
||||
The system supports the following assignment types for forms:
|
||||
|
||||
#### 1. Public Assignment (`assignmentType: 'public'`)
|
||||
- **Access**: Anyone can edit the form
|
||||
- **Behavior**: Full edit access for all users
|
||||
|
||||
#### 2. User Assignment (`assignmentType: 'users'`)
|
||||
- **Access**: Only specific users can edit the form
|
||||
- **Check**: Current user ID or email must be in `assignedUsers` array
|
||||
- **Example**:
|
||||
```json
|
||||
{
|
||||
"assignmentType": "users",
|
||||
"assignedUsers": [
|
||||
{
|
||||
"value": "123",
|
||||
"label": "John Doe (john.doe)",
|
||||
"username": "john.doe"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Role Assignment (`assignmentType: 'roles'`)
|
||||
- **Access**: Only users with specific roles can edit the form
|
||||
- **Check**: Current user's roles must match `assignedRoles` array
|
||||
- **Example**:
|
||||
```json
|
||||
{
|
||||
"assignmentType": "roles",
|
||||
"assignedRoles": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Variable Assignment (`assignmentType: 'variable'`)
|
||||
- **Access**: Dynamic assignment based on process variables
|
||||
- **Behavior**: Currently allows access (future enhancement needed)
|
||||
|
||||
## User Experience
|
||||
|
||||
### Edit Access
|
||||
Users with edit access see:
|
||||
- Green "Edit Access" badge
|
||||
- Fully functional form inputs
|
||||
- Submit buttons enabled
|
||||
- Conditional logic active
|
||||
|
||||
### Read-only Access
|
||||
Users without edit access see:
|
||||
- Yellow "Read-only Access" badge with warning icon
|
||||
- Disabled form inputs with gray styling
|
||||
- Submit buttons disabled
|
||||
- Clear explanation of why access is restricted
|
||||
- Form data is visible but not editable
|
||||
|
||||
### Visual Indicators
|
||||
|
||||
#### Tab Navigation
|
||||
- Warning icon next to form names for read-only forms
|
||||
- Visual distinction between editable and read-only forms
|
||||
|
||||
#### Form Header
|
||||
- Access status badges (Edit Access / Read-only Access)
|
||||
- Detailed explanation for read-only access
|
||||
- Color-coded indicators (green for edit, yellow for read-only)
|
||||
|
||||
#### Form Fields
|
||||
- Disabled styling for read-only inputs
|
||||
- Reduced opacity for entire form when disabled
|
||||
- Cursor changes to "not-allowed" for disabled fields
|
||||
|
||||
## API Changes
|
||||
|
||||
### Enhanced Response
|
||||
The `/api/cases/[id]/forms` endpoint now returns additional access control information:
|
||||
|
||||
```json
|
||||
{
|
||||
"forms": [
|
||||
{
|
||||
"formID": 123,
|
||||
"formName": "Example Form",
|
||||
"hasEditAccess": true,
|
||||
"accessReason": "user_assigned",
|
||||
"assignmentType": "users"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Access Control Fields
|
||||
- `hasEditAccess`: Boolean indicating if user can edit the form
|
||||
- `accessReason`: String explaining the access decision
|
||||
- `assignmentType`: The type of assignment configured for the form
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Authentication Required
|
||||
- All form access requires valid authentication
|
||||
- User context is validated on every request
|
||||
|
||||
### Role-based Validation
|
||||
- User roles are fetched from database
|
||||
- Role assignments are validated against current user's roles
|
||||
|
||||
### Assignment Validation
|
||||
- Direct task assignments are checked first
|
||||
- Process definition assignments are validated
|
||||
- Fallback to public access if no assignment found
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Backend Changes
|
||||
- Enhanced `/api/cases/[id]/forms` endpoint with access validation
|
||||
- User role fetching and validation
|
||||
- Assignment type checking logic
|
||||
|
||||
### Frontend Changes
|
||||
- Readonly mode for forms without edit access
|
||||
- Visual indicators for access status
|
||||
- Disabled form submission for read-only forms
|
||||
- Conditional logic disabled for read-only forms
|
||||
|
||||
### Form Behavior
|
||||
- FormKit forms are disabled when user lacks edit access
|
||||
- All form inputs are set to readonly/disabled
|
||||
- Submit buttons are disabled
|
||||
- Conditional logic scripts are not executed
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting Up Form Assignments
|
||||
|
||||
1. **Open Process Builder** - Navigate to the process you want to configure
|
||||
2. **Select Form Node** - Click on the form node in your process
|
||||
3. **Configure Assignment** - In the form configuration modal:
|
||||
- Choose assignment type (Public, Users, Roles, or Variable)
|
||||
- Select specific users or roles as needed
|
||||
- Save the configuration
|
||||
|
||||
### Example Process Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "form-1",
|
||||
"type": "form",
|
||||
"data": {
|
||||
"label": "Manager Approval Form",
|
||||
"formId": "123",
|
||||
"assignmentType": "roles",
|
||||
"assignedRoles": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Manager"
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "Supervisor"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Access to Forms
|
||||
|
||||
If a user can't edit forms:
|
||||
|
||||
1. **Check User Roles** - Verify the user has the correct roles assigned
|
||||
2. **Check Form Assignment** - Ensure the form node has proper assignment configuration
|
||||
3. **Check Process Status** - Process must be published and not deleted
|
||||
4. **Check Assignment Type** - Verify the assignment type is configured correctly
|
||||
|
||||
### Debug Information
|
||||
|
||||
The API endpoint includes console logging for debugging:
|
||||
|
||||
```javascript
|
||||
// User information
|
||||
console.log('Current user ID:', currentUser.userID);
|
||||
console.log('User roles:', userRoleNames);
|
||||
|
||||
// Assignment checks
|
||||
console.log('Checking form access:', {...});
|
||||
console.log('Access result:', accessCheck);
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Variable-based assignment evaluation
|
||||
- Time-based access control
|
||||
- Conditional access based on form data
|
||||
- Audit logging for access attempts
|
||||
- Advanced permission inheritance
|
146
docs/process-execution/ASSIGNED_PROCESSES.md
Normal file
146
docs/process-execution/ASSIGNED_PROCESSES.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Assigned Processes Feature
|
||||
|
||||
## Overview
|
||||
|
||||
The "Start New Case" page now displays only processes where the current user is assigned to complete the first form task. This ensures users only see processes they have permission to start.
|
||||
|
||||
## How It Works
|
||||
|
||||
### API Endpoint
|
||||
|
||||
A new API endpoint `/api/process/assigned` has been created that:
|
||||
|
||||
1. **Authenticates the user** - Gets the current user from the request context
|
||||
2. **Fetches user roles** - Retrieves all roles assigned to the current user
|
||||
3. **Filters processes** - Only returns processes where the user is assigned to the first form
|
||||
|
||||
### Assignment Types
|
||||
|
||||
The system checks the assignment configuration of the first form node in each process:
|
||||
|
||||
#### 1. Public Assignment (`assignmentType: 'public'`)
|
||||
- **Access**: Anyone can start the process
|
||||
- **Behavior**: Process is included for all users
|
||||
|
||||
#### 2. User Assignment (`assignmentType: 'users'`)
|
||||
- **Access**: Only specific users can start the process
|
||||
- **Check**: Current user ID or email must be in `assignedUsers` array
|
||||
- **Example**:
|
||||
```json
|
||||
{
|
||||
"assignmentType": "users",
|
||||
"assignedUsers": [
|
||||
{
|
||||
"value": "123",
|
||||
"label": "John Doe (john.doe)",
|
||||
"username": "john.doe"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Role Assignment (`assignmentType: 'roles'`)
|
||||
- **Access**: Only users with specific roles can start the process
|
||||
- **Check**: Current user's roles must match `assignedRoles` array
|
||||
- **Example**:
|
||||
```json
|
||||
{
|
||||
"assignmentType": "roles",
|
||||
"assignedRoles": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Variable Assignment (`assignmentType: 'variable'`)
|
||||
- **Access**: Dynamic assignment based on process variables
|
||||
- **Behavior**: Currently includes all processes (future enhancement needed)
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
The `/execution/new-case` page has been updated to:
|
||||
|
||||
1. **Use the new API endpoint** - Calls `/api/process/assigned` instead of `/api/process`
|
||||
2. **Updated UI** - Shows "My Assigned Processes" header and assignment indicators
|
||||
3. **Better messaging** - Clear indication when no processes are assigned
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting Up Process Assignments
|
||||
|
||||
1. **Open Process Builder** - Navigate to the process you want to configure
|
||||
2. **Select First Form Node** - Click on the first form node in your process
|
||||
3. **Configure Assignment** - In the form configuration modal:
|
||||
- Choose assignment type (Public, Users, Roles, or Variable)
|
||||
- Select specific users or roles as needed
|
||||
- Save the configuration
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "form-1",
|
||||
"type": "form",
|
||||
"data": {
|
||||
"label": "Initial Request Form",
|
||||
"formId": "123",
|
||||
"assignmentType": "roles",
|
||||
"assignedRoles": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "Manager"
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "Supervisor"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Authentication Required**: All requests must be authenticated
|
||||
- **Role-based Access**: Users can only see processes they're assigned to
|
||||
- **Audit Trail**: Process starts are logged with user information
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Processes Showing
|
||||
|
||||
If a user doesn't see any processes:
|
||||
|
||||
1. **Check User Roles** - Verify the user has the correct roles assigned
|
||||
2. **Check Process Assignment** - Ensure the first form node has proper assignment configuration
|
||||
3. **Check Process Status** - Process must be published and not deleted
|
||||
4. **Check Assignment Type** - Verify the assignment type is configured correctly
|
||||
|
||||
### Debug Information
|
||||
|
||||
The API endpoint includes console logging for debugging:
|
||||
|
||||
```javascript
|
||||
// User information
|
||||
console.log('Current user ID:', currentUser.userID);
|
||||
console.log('User roles:', userRoleNames);
|
||||
|
||||
// Assignment checks
|
||||
console.log('Checking user assignment:', {...});
|
||||
console.log('Checking role assignment:', {...});
|
||||
console.log('Process assignment result:', isAssigned);
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Variable Evaluation** - Implement proper variable-based assignment
|
||||
2. **Multiple Form Support** - Check assignments across multiple form nodes
|
||||
3. **Permission Inheritance** - Support for inherited permissions from parent processes
|
||||
4. **Bulk Assignment** - Tools for bulk assigning processes to users/roles
|
@ -25,7 +25,18 @@
|
||||
:class="{ 'bg-primary text-white': activeTabIndex === index }">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
{{ form.formName || `Form ${index + 1}` }}
|
||||
<div class="flex items-center">
|
||||
{{ form.formName || `Form ${index + 1}` }}
|
||||
<!-- Access indicator -->
|
||||
<div v-if="!form.hasEditAccess" class="ml-2">
|
||||
<div class="w-4 h-4 rounded-full bg-yellow-100 flex items-center justify-center"
|
||||
title="Read-only access">
|
||||
<svg class="w-3 h-3 text-yellow-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</nav>
|
||||
@ -34,8 +45,49 @@
|
||||
<!-- Tab Content -->
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div v-for="(form, index) in forms" :key="`content-${index}`" v-show="activeTabIndex === index">
|
||||
<h2 class="text-xl font-semibold mb-4">{{ form.formName || `Form ${index + 1}` }}</h2>
|
||||
<p class="text-gray-600 mb-1">{{ form.description || 'Please complete this form step' }}</p>
|
||||
<!-- Form Header with Access Status -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h2 class="text-xl font-semibold">{{ form.formName || `Form ${index + 1}` }}</h2>
|
||||
<!-- Access Status Badge -->
|
||||
<div v-if="!form.hasEditAccess" class="flex items-center">
|
||||
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800 border border-yellow-200">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Read-only Access
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center">
|
||||
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800 border border-green-200">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Edit Access
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 mb-1">{{ form.description || 'Please complete this form step' }}</p>
|
||||
|
||||
<!-- Access Information -->
|
||||
<div v-if="!form.hasEditAccess" class="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<div class="flex items-start">
|
||||
<svg class="w-5 h-5 text-yellow-600 mr-2 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-yellow-800">Read-only Access</p>
|
||||
<p class="text-sm text-yellow-700 mt-1">
|
||||
You can view this form but cannot make changes.
|
||||
<span v-if="form.accessReason === 'user_not_assigned'">This form is assigned to specific users.</span>
|
||||
<span v-else-if="form.accessReason === 'role_not_assigned'">This form is assigned to specific roles.</span>
|
||||
<span v-else>You don't have permission to edit this form.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Logic Engine -->
|
||||
<ConditionalLogicEngine
|
||||
@ -53,6 +105,7 @@
|
||||
:actions="false"
|
||||
:incomplete-message="false"
|
||||
validation-visibility="submit"
|
||||
:disabled="!form.hasEditAccess"
|
||||
>
|
||||
<div class="grid-preview-container">
|
||||
<template v-if="form.formComponents && form.formComponents.length > 0">
|
||||
@ -76,6 +129,8 @@
|
||||
:value="component.props?.value"
|
||||
:class="component.props?.width ? `w-${component.props.width}` : 'w-full'"
|
||||
class="mb-4"
|
||||
:disabled="!form.hasEditAccess"
|
||||
:readonly="!form.hasEditAccess"
|
||||
/>
|
||||
|
||||
<!-- Heading -->
|
||||
@ -136,7 +191,7 @@
|
||||
:type="component.props?.buttonType || 'button'"
|
||||
:variant="component.props?.variant || 'primary'"
|
||||
:size="component.props?.size || 'md'"
|
||||
:disabled="component.props?.disabled || false"
|
||||
:disabled="component.props?.disabled || !form.hasEditAccess"
|
||||
>
|
||||
{{ component.props?.label || 'Button' }}
|
||||
</RsButton>
|
||||
@ -153,7 +208,7 @@
|
||||
v-if="!hasSubmitButton(form)"
|
||||
type="submit"
|
||||
label="Submit"
|
||||
:disabled="submitting"
|
||||
:disabled="submitting || !form.hasEditAccess"
|
||||
:classes="{
|
||||
input: submitting ? 'opacity-75 cursor-wait' : ''
|
||||
}"
|
||||
@ -253,6 +308,9 @@
|
||||
}
|
||||
|
||||
const showField = (fieldName) => {
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't modify fields if readonly
|
||||
|
||||
const field = document.querySelector(`[name="${fieldName}"]`)
|
||||
if (field) {
|
||||
const outerDiv = field.closest('.formkit-outer')
|
||||
@ -264,6 +322,9 @@
|
||||
}
|
||||
|
||||
const hideField = (fieldName) => {
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't modify fields if readonly
|
||||
|
||||
const field = document.querySelector(`[name="${fieldName}"]`)
|
||||
if (field) {
|
||||
const outerDiv = field.closest('.formkit-outer')
|
||||
@ -275,6 +336,9 @@
|
||||
}
|
||||
|
||||
const enableField = (fieldName) => {
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't modify fields if readonly
|
||||
|
||||
const field = document.querySelector(`[name="${fieldName}"]`)
|
||||
if (field) {
|
||||
field.removeAttribute('disabled')
|
||||
@ -286,6 +350,9 @@
|
||||
}
|
||||
|
||||
const disableField = (fieldName) => {
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't modify fields if readonly
|
||||
|
||||
const field = document.querySelector(`[name="${fieldName}"]`)
|
||||
if (field) {
|
||||
field.setAttribute('disabled', 'disabled')
|
||||
@ -297,6 +364,9 @@
|
||||
}
|
||||
|
||||
const onFieldChange = (fieldName, callback) => {
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't add listeners if readonly
|
||||
|
||||
const field = document.querySelector(`[name="${fieldName}"]`)
|
||||
if (field) {
|
||||
// Remove existing listeners first to prevent duplicates
|
||||
@ -313,6 +383,9 @@
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.formComponents) return
|
||||
|
||||
// Don't apply conditional logic if user doesn't have edit access
|
||||
if (!currentForm.hasEditAccess) return
|
||||
|
||||
currentForm.formComponents.forEach(component => {
|
||||
if (component.props?.conditionalLogic?.enabled) {
|
||||
const { conditions, action, operator = 'and' } = component.props.conditionalLogic
|
||||
@ -392,6 +465,9 @@
|
||||
const handleScriptGenerated = (script) => {
|
||||
if (!script) return
|
||||
|
||||
const currentForm = forms.value[activeTabIndex.value]
|
||||
if (!currentForm?.hasEditAccess) return // Don't execute scripts if readonly
|
||||
|
||||
try {
|
||||
// Create a function with access to our utility functions
|
||||
const evalScript = new Function(
|
||||
@ -421,6 +497,13 @@
|
||||
// Handle form submission
|
||||
const handleSubmit = async (formIndex) => {
|
||||
try {
|
||||
// Check if user has edit access to this form
|
||||
const currentForm = forms.value[formIndex];
|
||||
if (!currentForm.hasEditAccess) {
|
||||
error.value = 'You do not have permission to submit this form';
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
console.log(`Form ${formIndex + 1} submitted:`, formData.value[formIndex])
|
||||
|
||||
@ -550,4 +633,40 @@
|
||||
color: #6b7280;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Readonly form styling */
|
||||
:deep(.formkit-form[disabled]) {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.formkit-form[disabled] .formkit-input) {
|
||||
background-color: #f9fafb;
|
||||
border-color: #e5e7eb;
|
||||
color: #6b7280;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:deep(.formkit-form[disabled] .formkit-label) {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
:deep(.formkit-form[disabled] .formkit-help) {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Readonly input styling */
|
||||
:deep(.formkit-input[readonly]) {
|
||||
background-color: #f9fafb;
|
||||
border-color: #e5e7eb;
|
||||
color: #6b7280;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:deep(.formkit-input[disabled]) {
|
||||
background-color: #f9fafb;
|
||||
border-color: #e5e7eb;
|
||||
color: #6b7280;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
@ -2,6 +2,19 @@
|
||||
<div>
|
||||
<LayoutsBreadcrumb />
|
||||
|
||||
<!-- Page Header -->
|
||||
<rs-card class="mb-6">
|
||||
<div class="p-5">
|
||||
<div class="flex items-center mb-4">
|
||||
<Icon name="material-symbols:assignment" class="text-blue-600 w-6 h-6 mr-3" />
|
||||
<h1 class="text-xl font-semibold text-gray-900">My Assigned Processes</h1>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">
|
||||
Start a new case from processes where you are assigned to complete the first form task.
|
||||
</p>
|
||||
</div>
|
||||
</rs-card>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<rs-card>
|
||||
<div class="p-5 flex flex-wrap gap-4">
|
||||
@ -9,7 +22,7 @@
|
||||
<FormKit
|
||||
type="text"
|
||||
name="search"
|
||||
placeholder="Search processes..."
|
||||
placeholder="Search assigned processes..."
|
||||
prefix-icon="search"
|
||||
v-model="searchQuery"
|
||||
/>
|
||||
@ -69,13 +82,20 @@
|
||||
></Icon>
|
||||
<span>Created: {{ formatDate(process.createdAt) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center mb-1">
|
||||
<Icon
|
||||
class="text-base mr-1"
|
||||
name="material-symbols:sync"
|
||||
></Icon>
|
||||
<span>Status: {{ process.status }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Icon
|
||||
class="text-base mr-1"
|
||||
name="material-symbols:assignment"
|
||||
></Icon>
|
||||
<span class="text-blue-600 font-medium">Assigned to you</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<rs-button
|
||||
@ -92,9 +112,9 @@
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredProcesses.length === 0 && !loading" class="col-span-3 flex flex-col items-center justify-center py-12 text-gray-500">
|
||||
<Icon name="material-symbols:category-outline" class="w-16 h-16 mb-4 text-gray-300" />
|
||||
<p class="text-base font-medium">No processes found</p>
|
||||
<p class="text-sm mt-1">Try selecting a different category or search term</p>
|
||||
<Icon name="material-symbols:assignment-outline" class="w-16 h-16 mb-4 text-gray-300" />
|
||||
<p class="text-base font-medium">No assigned processes found</p>
|
||||
<p class="text-sm mt-1">You don't have any processes assigned to you. Contact your administrator to get access to processes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -105,7 +125,7 @@ import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
definePageMeta({
|
||||
title: "Start New Case",
|
||||
title: "Start New Case - Assigned Processes",
|
||||
layout: "default",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
@ -127,8 +147,8 @@ const fetchProcesses = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
// Only fetch published processes that are not templates
|
||||
const response = await $fetch('/api/process', {
|
||||
// Fetch processes that the current user is assigned to in the first form
|
||||
const response = await $fetch('/api/process/assigned', {
|
||||
params: {
|
||||
status: 'published',
|
||||
isTemplate: false
|
||||
|
@ -15,6 +15,16 @@ export default defineEventHandler(async (event) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Get the current authenticated user from the request context
|
||||
const currentUser = event.context.user;
|
||||
|
||||
if (!currentUser || !currentUser.userID) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if the ID is a UUID or numeric ID
|
||||
const isUUID = caseId.length === 36 && caseId.includes('-');
|
||||
|
||||
@ -29,7 +39,8 @@ export default defineEventHandler(async (event) => {
|
||||
processID: true,
|
||||
processUUID: true,
|
||||
processName: true,
|
||||
processDescription: true
|
||||
processDescription: true,
|
||||
processDefinition: true
|
||||
}
|
||||
},
|
||||
startedBy: {
|
||||
@ -88,12 +99,95 @@ export default defineEventHandler(async (event) => {
|
||||
};
|
||||
}
|
||||
|
||||
// Get current user's roles for access validation
|
||||
const userRoles = await prisma.userrole.findMany({
|
||||
where: {
|
||||
userRoleUserID: parseInt(currentUser.userID)
|
||||
},
|
||||
select: {
|
||||
role: {
|
||||
select: {
|
||||
roleID: true,
|
||||
roleName: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const userRoleIds = userRoles.map(ur => ur.role.roleID);
|
||||
const userRoleNames = userRoles.map(ur => ur.role.roleName);
|
||||
|
||||
// Function to check if user has access to a form
|
||||
const checkFormAccess = (task, processDefinition) => {
|
||||
// If task is directly assigned to current user, they have access
|
||||
if (task.assignedTo && task.assignedTo.userID === parseInt(currentUser.userID)) {
|
||||
return { hasAccess: true, reason: 'directly_assigned' };
|
||||
}
|
||||
|
||||
// Check process definition for form node assignment
|
||||
const nodes = processDefinition?.nodes || [];
|
||||
const formNodes = nodes.filter(node => node.type === 'form');
|
||||
|
||||
// Find the form node that corresponds to this task
|
||||
const formNode = formNodes.find(node => {
|
||||
const nodeData = node.data || {};
|
||||
return nodeData.formId === task.form?.formID || nodeData.formUuid === task.form?.formUUID;
|
||||
});
|
||||
|
||||
if (!formNode) {
|
||||
// If no form node found, default to public access
|
||||
return { hasAccess: true, reason: 'public_default' };
|
||||
}
|
||||
|
||||
const formData = formNode.data || {};
|
||||
const assignmentType = formData.assignmentType || 'public';
|
||||
|
||||
// Check assignment type
|
||||
if (assignmentType === 'public') {
|
||||
return { hasAccess: true, reason: 'public_assignment' };
|
||||
} else if (assignmentType === 'users') {
|
||||
const assignedUsers = formData.assignedUsers || [];
|
||||
const currentUserIdStr = String(currentUser.userID);
|
||||
|
||||
const hasUserAccess = assignedUsers.some(user =>
|
||||
String(user.value) === currentUserIdStr ||
|
||||
user.username === currentUser.email
|
||||
);
|
||||
|
||||
return {
|
||||
hasAccess: hasUserAccess,
|
||||
reason: hasUserAccess ? 'user_assigned' : 'user_not_assigned'
|
||||
};
|
||||
} else if (assignmentType === 'roles') {
|
||||
const assignedRoles = formData.assignedRoles || [];
|
||||
|
||||
const hasRoleAccess = assignedRoles.some(role =>
|
||||
userRoleIds.includes(parseInt(role.value)) ||
|
||||
userRoleNames.includes(role.label)
|
||||
);
|
||||
|
||||
return {
|
||||
hasAccess: hasRoleAccess,
|
||||
reason: hasRoleAccess ? 'role_assigned' : 'role_not_assigned'
|
||||
};
|
||||
} else if (assignmentType === 'variable') {
|
||||
// For variable-based assignment, we'll allow access for now
|
||||
// In a real implementation, you might want to evaluate the variable
|
||||
return { hasAccess: true, reason: 'variable_assignment' };
|
||||
}
|
||||
|
||||
return { hasAccess: false, reason: 'no_access' };
|
||||
};
|
||||
|
||||
// Extract forms from tasks and remove duplicates
|
||||
const forms = [];
|
||||
const formIds = new Set();
|
||||
|
||||
for (const task of caseInstance.tasks) {
|
||||
if (task.form) {
|
||||
// Check if user has access to this form
|
||||
const accessCheck = checkFormAccess(task, caseInstance.process.processDefinition);
|
||||
|
||||
// Make sure formComponents is properly structured
|
||||
let formComponents = [];
|
||||
try {
|
||||
@ -136,7 +230,14 @@ export default defineEventHandler(async (event) => {
|
||||
taskData: task.taskData,
|
||||
formData: formData,
|
||||
submittedAt: task.taskData?.submittedAt || null,
|
||||
completedDate: task.taskCompletedDate
|
||||
completedDate: task.taskCompletedDate,
|
||||
// Add access control information
|
||||
hasEditAccess: accessCheck.hasAccess,
|
||||
accessReason: accessCheck.reason,
|
||||
assignmentType: accessCheck.reason === 'public_assignment' ? 'public' :
|
||||
accessCheck.reason === 'user_assigned' ? 'users' :
|
||||
accessCheck.reason === 'role_assigned' ? 'roles' :
|
||||
accessCheck.reason === 'variable_assignment' ? 'variable' : 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -57,12 +57,36 @@ export default defineEventHandler(async (event) => {
|
||||
};
|
||||
}
|
||||
|
||||
// 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'
|
||||
};
|
||||
// Get the current authenticated user from the request context
|
||||
const currentUser = event.context.user;
|
||||
|
||||
if (!currentUser || !currentUser.userID) {
|
||||
console.log('User not authenticated');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
};
|
||||
}
|
||||
|
||||
// Get full user details from database
|
||||
const userDetails = await prisma.user.findFirst({
|
||||
where: {
|
||||
userID: parseInt(currentUser.userID)
|
||||
},
|
||||
select: {
|
||||
userID: true,
|
||||
userFullName: true,
|
||||
userUsername: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!userDetails) {
|
||||
console.log('User details not found');
|
||||
return {
|
||||
success: false,
|
||||
error: 'User not found'
|
||||
};
|
||||
}
|
||||
|
||||
console.log('Creating case instance...');
|
||||
// Create a new case instance
|
||||
@ -72,7 +96,7 @@ export default defineEventHandler(async (event) => {
|
||||
processID: process.processID,
|
||||
caseName: `${process.processName} - ${new Date().toLocaleDateString()}`,
|
||||
caseStatus: 'active',
|
||||
caseStartedBy: currentUser.userID,
|
||||
caseStartedBy: userDetails.userID,
|
||||
caseVariables: process.processVariables || {},
|
||||
caseSettings: process.processSettings || {},
|
||||
caseDefinition: process.processDefinition || {},
|
||||
@ -112,7 +136,7 @@ export default defineEventHandler(async (event) => {
|
||||
taskName: formNode.data?.label || 'Complete Form',
|
||||
taskType: 'form',
|
||||
taskStatus: 'pending',
|
||||
taskAssignedTo: currentUser.userID,
|
||||
taskAssignedTo: userDetails.userID,
|
||||
taskFormID: formNode.data?.formId,
|
||||
taskCreatedDate: new Date(),
|
||||
taskModifiedDate: new Date()
|
||||
@ -128,9 +152,9 @@ export default defineEventHandler(async (event) => {
|
||||
data: {
|
||||
caseID: caseInstance.caseID,
|
||||
timelineType: 'start',
|
||||
timelineDescription: `Process started by ${currentUser.userFullName}`,
|
||||
timelineDescription: `Process started by ${userDetails.userFullName}`,
|
||||
timelineDate: new Date(),
|
||||
timelineCreatedBy: currentUser.userID
|
||||
timelineCreatedBy: userDetails.userID
|
||||
}
|
||||
});
|
||||
|
||||
|
228
server/api/process/assigned.get.js
Normal file
228
server/api/process/assigned.get.js
Normal file
@ -0,0 +1,228 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// Initialize Prisma client
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Get the current authenticated user from the request context
|
||||
const currentUser = event.context.user;
|
||||
|
||||
if (!currentUser || !currentUser.userID) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Authentication required'
|
||||
};
|
||||
}
|
||||
|
||||
// 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 = {};
|
||||
|
||||
// Exclude deleted processes by default unless explicitly requested
|
||||
if (query.includeDeleted !== 'true') {
|
||||
where.processStatus = { not: 'deleted' };
|
||||
}
|
||||
|
||||
if (status && status !== 'deleted') {
|
||||
// If status filter is provided and it's not 'deleted', filter by that status
|
||||
// and still exclude deleted processes
|
||||
where.processStatus = status;
|
||||
} else if (status === 'deleted') {
|
||||
// If specifically requesting deleted processes, only show those
|
||||
where.processStatus = 'deleted';
|
||||
}
|
||||
|
||||
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 all processes first
|
||||
const allProcesses = await 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,
|
||||
processDefinition: true,
|
||||
creator: {
|
||||
select: {
|
||||
userID: true,
|
||||
userFullName: true,
|
||||
userUsername: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get current user's roles
|
||||
const userRoles = await prisma.userrole.findMany({
|
||||
where: {
|
||||
userRoleUserID: parseInt(currentUser.userID)
|
||||
},
|
||||
select: {
|
||||
role: {
|
||||
select: {
|
||||
roleID: true,
|
||||
roleName: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const userRoleIds = userRoles.map(ur => ur.role.roleID);
|
||||
const userRoleNames = userRoles.map(ur => ur.role.roleName);
|
||||
|
||||
// Filter processes based on user assignments in the first form
|
||||
const filteredProcesses = [];
|
||||
|
||||
for (const process of allProcesses) {
|
||||
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) {
|
||||
// If no form nodes, skip this process
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the first form node (assuming it's the starting form)
|
||||
const firstFormNode = formNodes[0];
|
||||
const firstFormData = firstFormNode.data || {};
|
||||
|
||||
// Check if the user is assigned to this form
|
||||
let isAssigned = false;
|
||||
|
||||
// Check assignment type
|
||||
const assignmentType = firstFormData.assignmentType || 'public';
|
||||
|
||||
if (assignmentType === 'public') {
|
||||
// Public assignment - anyone can access
|
||||
isAssigned = true;
|
||||
} else if (assignmentType === 'users') {
|
||||
// Check if current user is in assigned users
|
||||
const assignedUsers = firstFormData.assignedUsers || [];
|
||||
const currentUserIdStr = String(currentUser.userID);
|
||||
|
||||
|
||||
|
||||
isAssigned = assignedUsers.some(user =>
|
||||
String(user.value) === currentUserIdStr ||
|
||||
user.username === currentUser.email
|
||||
);
|
||||
} else if (assignmentType === 'roles') {
|
||||
// Check if current user's roles are in assigned roles
|
||||
const assignedRoles = firstFormData.assignedRoles || [];
|
||||
|
||||
|
||||
|
||||
isAssigned = assignedRoles.some(role =>
|
||||
userRoleIds.includes(parseInt(role.value)) ||
|
||||
userRoleNames.includes(role.label)
|
||||
);
|
||||
} else if (assignmentType === 'variable') {
|
||||
// For variable-based assignment, we'll include it for now
|
||||
// In a real implementation, you might want to evaluate the variable
|
||||
isAssigned = true;
|
||||
}
|
||||
|
||||
if (isAssigned) {
|
||||
filteredProcesses.push(process);
|
||||
}
|
||||
}
|
||||
|
||||
// Count total processes for pagination (we need to get all processes to filter)
|
||||
const totalProcesses = await prisma.process.count({ where });
|
||||
|
||||
// Calculate pagination info
|
||||
const totalPages = Math.ceil(totalProcesses / take);
|
||||
const hasNextPage = parseInt(page) < totalPages;
|
||||
const hasPrevPage = parseInt(page) > 1;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
processes: filteredProcesses.map(process => ({
|
||||
processID: process.processID,
|
||||
processUUID: process.processUUID,
|
||||
processName: process.processName,
|
||||
processDescription: process.processDescription,
|
||||
processCategory: process.processCategory,
|
||||
processPriority: process.processPriority,
|
||||
processOwner: process.processOwner,
|
||||
processVersion: process.processVersion,
|
||||
processStatus: process.processStatus,
|
||||
isTemplate: process.isTemplate,
|
||||
templateCategory: process.templateCategory,
|
||||
processCreatedDate: process.processCreatedDate,
|
||||
processModifiedDate: process.processModifiedDate,
|
||||
creator: process.creator
|
||||
})),
|
||||
pagination: {
|
||||
currentPage: parseInt(page),
|
||||
totalPages,
|
||||
totalCount: totalProcesses,
|
||||
filteredCount: filteredProcesses.length,
|
||||
limit: take,
|
||||
hasNextPage,
|
||||
hasPrevPage
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching assigned processes:', error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to fetch assigned processes',
|
||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
};
|
||||
}
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user