382 lines
9.2 KiB
Markdown
382 lines
9.2 KiB
Markdown
# Custom Task Status Reorder Endpoint
|
|
|
|
## Overview
|
|
|
|
This document describes the PATCH endpoint for reordering custom task statuses within a project. The endpoint allows coordinators and administrators to change the display order of custom task statuses.
|
|
|
|
## Endpoint
|
|
|
|
```
|
|
PATCH /projects/{project_id}/task-statuses/reorder
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Requires JWT authentication with coordinator or admin role.
|
|
|
|
## Request
|
|
|
|
### Path Parameters
|
|
|
|
- `project_id` (integer, required): The ID of the project
|
|
|
|
### Request Body
|
|
|
|
```json
|
|
{
|
|
"status_ids": ["custom_abc123", "custom_def456", "custom_ghi789"]
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
- `status_ids` (array of strings, required): Ordered list of status IDs in the desired sequence
|
|
- Must contain all existing custom status IDs for the project
|
|
- Cannot contain duplicates
|
|
- Cannot be empty
|
|
|
|
## Response
|
|
|
|
### Success Response (200 OK)
|
|
|
|
```json
|
|
{
|
|
"message": "Custom task statuses reordered successfully",
|
|
"status": null,
|
|
"all_statuses": {
|
|
"statuses": [
|
|
{
|
|
"id": "custom_abc123",
|
|
"name": "Review",
|
|
"color": "#9333EA",
|
|
"order": 0,
|
|
"is_default": false
|
|
},
|
|
{
|
|
"id": "custom_def456",
|
|
"name": "Blocked",
|
|
"color": "#DC2626",
|
|
"order": 1,
|
|
"is_default": true
|
|
},
|
|
{
|
|
"id": "custom_ghi789",
|
|
"name": "Ready for Delivery",
|
|
"color": "#059669",
|
|
"order": 2,
|
|
"is_default": false
|
|
}
|
|
],
|
|
"system_statuses": [
|
|
{
|
|
"id": "not_started",
|
|
"name": "Not Started",
|
|
"color": "#6B7280",
|
|
"is_system": true
|
|
},
|
|
{
|
|
"id": "in_progress",
|
|
"name": "In Progress",
|
|
"color": "#3B82F6",
|
|
"is_system": true
|
|
},
|
|
{
|
|
"id": "submitted",
|
|
"name": "Submitted",
|
|
"color": "#F59E0B",
|
|
"is_system": true
|
|
},
|
|
{
|
|
"id": "approved",
|
|
"name": "Approved",
|
|
"color": "#10B981",
|
|
"is_system": true
|
|
},
|
|
{
|
|
"id": "retake",
|
|
"name": "Retake",
|
|
"color": "#EF4444",
|
|
"is_system": true
|
|
}
|
|
],
|
|
"default_status_id": "custom_def456"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
#### 400 Bad Request - Missing Status IDs
|
|
|
|
```json
|
|
{
|
|
"detail": "Missing status IDs in reorder request: custom_xyz999"
|
|
}
|
|
```
|
|
|
|
Occurs when the request doesn't include all existing custom status IDs.
|
|
|
|
#### 400 Bad Request - Invalid Status IDs
|
|
|
|
```json
|
|
{
|
|
"detail": "Status IDs not found: invalid_id_12345"
|
|
}
|
|
```
|
|
|
|
Occurs when the request includes status IDs that don't exist in the project.
|
|
|
|
#### 403 Forbidden
|
|
|
|
```json
|
|
{
|
|
"detail": "Insufficient permissions"
|
|
}
|
|
```
|
|
|
|
Occurs when the user doesn't have coordinator or admin role.
|
|
|
|
#### 404 Not Found
|
|
|
|
```json
|
|
{
|
|
"detail": "Project not found"
|
|
}
|
|
```
|
|
|
|
Occurs when the specified project doesn't exist.
|
|
|
|
#### 422 Unprocessable Entity - Duplicate IDs
|
|
|
|
```json
|
|
{
|
|
"detail": "1 validation error for CustomTaskStatusReorder\nstatus_ids\n Value error, Status IDs list contains duplicates"
|
|
}
|
|
```
|
|
|
|
Occurs when the request contains duplicate status IDs.
|
|
|
|
#### 422 Unprocessable Entity - Empty List
|
|
|
|
```json
|
|
{
|
|
"detail": "1 validation error for CustomTaskStatusReorder\nstatus_ids\n Value error, Status IDs list cannot be empty"
|
|
}
|
|
```
|
|
|
|
Occurs when the request contains an empty status_ids array.
|
|
|
|
## Implementation Details
|
|
|
|
### Validation
|
|
|
|
1. **Project Existence**: Verifies the project exists
|
|
2. **Permission Check**: Ensures user has coordinator or admin role
|
|
3. **Complete List**: Validates that all existing custom status IDs are included
|
|
4. **No Missing IDs**: Ensures no status IDs are omitted
|
|
5. **No Invalid IDs**: Ensures all provided IDs exist in the project
|
|
6. **No Duplicates**: Validates the list contains no duplicate IDs (handled by Pydantic schema)
|
|
|
|
### Order Update Process
|
|
|
|
1. Parse and validate the reorder request
|
|
2. Retrieve existing custom statuses from the project
|
|
3. Create a mapping of status_id to status data
|
|
4. Reorder statuses according to the provided list
|
|
5. Update the `order` field for each status (0-indexed)
|
|
6. Save the reordered list to the database
|
|
7. Use `flag_modified()` to ensure JSON column changes are persisted
|
|
|
|
### Database Changes
|
|
|
|
- Updates the `custom_task_statuses` JSON column in the `projects` table
|
|
- Each status object's `order` field is updated to match its position in the new list
|
|
- Uses SQLAlchemy's `flag_modified()` to ensure JSON column changes are detected
|
|
|
|
## Usage Examples
|
|
|
|
### Python (requests)
|
|
|
|
```python
|
|
import requests
|
|
|
|
# Login and get token
|
|
response = requests.post(
|
|
"http://localhost:8000/auth/login",
|
|
json={"email": "admin@vfx.com", "password": "admin123"}
|
|
)
|
|
token = response.json()["access_token"]
|
|
|
|
# Reorder statuses
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
data = {
|
|
"status_ids": [
|
|
"custom_abc123",
|
|
"custom_def456",
|
|
"custom_ghi789"
|
|
]
|
|
}
|
|
|
|
response = requests.patch(
|
|
"http://localhost:8000/projects/1/task-statuses/reorder",
|
|
headers=headers,
|
|
json=data
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
print(f"✅ {result['message']}")
|
|
print(f"Reordered {len(result['all_statuses']['statuses'])} statuses")
|
|
else:
|
|
print(f"❌ Error: {response.json()['detail']}")
|
|
```
|
|
|
|
### JavaScript (fetch)
|
|
|
|
```javascript
|
|
// Assuming you have a token from login
|
|
const token = "your_jwt_token_here";
|
|
|
|
const reorderStatuses = async (projectId, statusIds) => {
|
|
const response = await fetch(
|
|
`http://localhost:8000/projects/${projectId}/task-statuses/reorder`,
|
|
{
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
status_ids: statusIds
|
|
})
|
|
}
|
|
);
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
console.log('✅', result.message);
|
|
return result.all_statuses;
|
|
} else {
|
|
const error = await response.json();
|
|
console.error('❌ Error:', error.detail);
|
|
throw new Error(error.detail);
|
|
}
|
|
};
|
|
|
|
// Usage
|
|
const statusIds = [
|
|
'custom_abc123',
|
|
'custom_def456',
|
|
'custom_ghi789'
|
|
];
|
|
|
|
reorderStatuses(1, statusIds)
|
|
.then(allStatuses => {
|
|
console.log('New order:', allStatuses.statuses);
|
|
})
|
|
.catch(error => {
|
|
console.error('Failed to reorder:', error);
|
|
});
|
|
```
|
|
|
|
## Frontend Integration
|
|
|
|
### Drag-and-Drop Implementation
|
|
|
|
The frontend should implement drag-and-drop functionality using a library like `vue-draggable-next`:
|
|
|
|
1. Display statuses in their current order
|
|
2. Allow users to drag statuses to reorder them
|
|
3. On drop, collect the new order of status IDs
|
|
4. Call the reorder endpoint with the new order
|
|
5. Update the UI optimistically or wait for the response
|
|
6. Handle errors by reverting to the previous order
|
|
|
|
### Example Vue Component
|
|
|
|
```vue
|
|
<template>
|
|
<draggable
|
|
v-model="statuses"
|
|
@end="handleReorder"
|
|
item-key="id"
|
|
>
|
|
<template #item="{ element }">
|
|
<div class="status-item">
|
|
<span class="drag-handle">⋮⋮</span>
|
|
<span :style="{ color: element.color }">
|
|
{{ element.name }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</draggable>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue';
|
|
import draggable from 'vuedraggable';
|
|
import { reorderTaskStatuses } from '@/services/customTaskStatus';
|
|
|
|
const props = defineProps({
|
|
projectId: Number,
|
|
initialStatuses: Array
|
|
});
|
|
|
|
const statuses = ref([...props.initialStatuses]);
|
|
|
|
const handleReorder = async () => {
|
|
const statusIds = statuses.value.map(s => s.id);
|
|
|
|
try {
|
|
const result = await reorderTaskStatuses(props.projectId, statusIds);
|
|
// Update with server response
|
|
statuses.value = result.all_statuses.statuses;
|
|
} catch (error) {
|
|
// Revert to original order on error
|
|
statuses.value = [...props.initialStatuses];
|
|
console.error('Failed to reorder:', error);
|
|
}
|
|
};
|
|
</script>
|
|
```
|
|
|
|
## Testing
|
|
|
|
A comprehensive test script is available at `backend/test_reorder_custom_task_status.py` that tests:
|
|
|
|
1. ✅ Successful reordering (reversing order)
|
|
2. ✅ Order field updates correctly
|
|
3. ✅ Rejection of incomplete status lists
|
|
4. ✅ Rejection of invalid status IDs
|
|
5. ✅ Rejection of duplicate status IDs
|
|
|
|
Run the test with:
|
|
|
|
```bash
|
|
cd backend
|
|
python test_reorder_custom_task_status.py
|
|
```
|
|
|
|
## Requirements Validation
|
|
|
|
This endpoint satisfies the following requirements from the custom task status specification:
|
|
|
|
- **Requirement 4.1**: ✅ Displays statuses in their defined order
|
|
- **Requirement 4.2**: ✅ Updates the order when user reorders statuses
|
|
- **Requirement 4.3**: ✅ Updates display order in all dropdowns and filters
|
|
- **Requirement 4.4**: ✅ Validates all status IDs are present
|
|
|
|
## Related Endpoints
|
|
|
|
- `GET /projects/{project_id}/task-statuses` - Get all task statuses
|
|
- `POST /projects/{project_id}/task-statuses` - Create a custom status
|
|
- `PUT /projects/{project_id}/task-statuses/{status_id}` - Update a custom status
|
|
- `DELETE /projects/{project_id}/task-statuses/{status_id}` - Delete a custom status
|
|
|
|
## Notes
|
|
|
|
- System statuses (not_started, in_progress, submitted, approved, retake) cannot be reordered
|
|
- Only custom statuses can be reordered
|
|
- The order field is 0-indexed
|
|
- Reordering does not affect the default status designation
|
|
- The endpoint uses `flag_modified()` to ensure JSON column changes are persisted to the database
|