Init Repo
This commit is contained in:
@@ -0,0 +1,381 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user