424 lines
11 KiB
Markdown
424 lines
11 KiB
Markdown
|
|
# Enhanced Approval Actions - Practical Usage Guide
|
||
|
|
|
||
|
|
## 🎯 **Overview**
|
||
|
|
|
||
|
|
Panduan praktis untuk menggunakan Enhanced Approval Actions API yang mendukung 3 jenis action: **Approve**, **Revision**, dan **Reject**.
|
||
|
|
|
||
|
|
## 🔄 **Action Types & Behavior**
|
||
|
|
|
||
|
|
| Action | Behavior | Use Case | Result |
|
||
|
|
|--------|----------|----------|---------|
|
||
|
|
| **`approve`** | Naik ke step berikutnya | Content sudah sesuai | Workflow berlanjut |
|
||
|
|
| **`revision`** | Mundur 1 step | Content perlu diperbaiki | Workflow mundur 1 step |
|
||
|
|
| **`reject`** | Balik ke Draft | Content tidak sesuai | Workflow dihentikan, artikel jadi draft |
|
||
|
|
|
||
|
|
## 🧪 **Practical Examples**
|
||
|
|
|
||
|
|
### **Scenario 1: Normal Approval Flow**
|
||
|
|
|
||
|
|
#### **Step 1: Editor Review**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/10/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "approve",
|
||
|
|
"message": "Content is well-written and follows our guidelines"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"messages": ["Article successfully approve through active approval flow"],
|
||
|
|
"data": {
|
||
|
|
"article_id": 10,
|
||
|
|
"flow_id": 1,
|
||
|
|
"action": "approve",
|
||
|
|
"current_step": 2,
|
||
|
|
"current_branch": "Final_Approval",
|
||
|
|
"workflow_id": 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **Step 2: Senior Editor Review**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/10/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "approve",
|
||
|
|
"message": "Final approval granted. Ready for publication."
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:** Article published successfully! 🎉
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### **Scenario 2: Request Update Flow**
|
||
|
|
|
||
|
|
#### **Step 1: Editor Review**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "approve",
|
||
|
|
"message": "Content is good, moving to next level"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **Step 2: Senior Editor Review (Request Revision)**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "revision",
|
||
|
|
"message": "Please add more examples in section 3 and improve the conclusion"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"messages": ["Article successfully revision through active approval flow"],
|
||
|
|
"data": {
|
||
|
|
"article_id": 11,
|
||
|
|
"flow_id": 2,
|
||
|
|
"action": "revision",
|
||
|
|
"current_step": 1,
|
||
|
|
"current_branch": "Branch_A",
|
||
|
|
"workflow_id": 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:** Article kembali ke step 1 untuk revisi! 📝
|
||
|
|
|
||
|
|
#### **Step 3: After Revision, Approve Again**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "approve",
|
||
|
|
"message": "Revisions completed, content improved significantly"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### **Scenario 3: Reject Flow**
|
||
|
|
|
||
|
|
#### **Step 1: Editor Review (Reject)**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/12/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "reject",
|
||
|
|
"message": "Content does not meet our quality standards. Please rewrite with better research and clearer structure."
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": true,
|
||
|
|
"messages": ["Article successfully reject through active approval flow"],
|
||
|
|
"data": {
|
||
|
|
"article_id": 12,
|
||
|
|
"flow_id": 3,
|
||
|
|
"action": "reject",
|
||
|
|
"current_step": 1,
|
||
|
|
"current_branch": "Branch_A",
|
||
|
|
"workflow_id": 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:** Article dikembalikan ke status Draft! ❌
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 **Database State Changes**
|
||
|
|
|
||
|
|
### **After Approve:**
|
||
|
|
```sql
|
||
|
|
-- Article Approval Flows
|
||
|
|
UPDATE article_approval_flows
|
||
|
|
SET current_step = 2,
|
||
|
|
updated_at = NOW()
|
||
|
|
WHERE id = 1;
|
||
|
|
|
||
|
|
-- Article Approval Step Logs
|
||
|
|
INSERT INTO article_approval_step_logs
|
||
|
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||
|
|
VALUES (1, 1, 'approve', 5, 'Content is well-written', NOW());
|
||
|
|
```
|
||
|
|
|
||
|
|
### **After Revision:**
|
||
|
|
```sql
|
||
|
|
-- Article Approval Flows
|
||
|
|
UPDATE article_approval_flows
|
||
|
|
SET current_step = 1,
|
||
|
|
revision_requested = true,
|
||
|
|
revision_message = 'Please add more examples',
|
||
|
|
updated_at = NOW()
|
||
|
|
WHERE id = 2;
|
||
|
|
|
||
|
|
-- Article Approval Step Logs
|
||
|
|
INSERT INTO article_approval_step_logs
|
||
|
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||
|
|
VALUES (2, 2, 'revision', 6, 'Please add more examples', NOW());
|
||
|
|
```
|
||
|
|
|
||
|
|
### **After Reject:**
|
||
|
|
```sql
|
||
|
|
-- Article Approval Flows
|
||
|
|
UPDATE article_approval_flows
|
||
|
|
SET status_id = 3,
|
||
|
|
rejection_reason = 'Content does not meet quality standards',
|
||
|
|
completed_at = NOW(),
|
||
|
|
updated_at = NOW()
|
||
|
|
WHERE id = 3;
|
||
|
|
|
||
|
|
-- Articles
|
||
|
|
UPDATE articles
|
||
|
|
SET status_id = 1,
|
||
|
|
is_draft = true,
|
||
|
|
workflow_id = NULL,
|
||
|
|
current_approval_step = NULL,
|
||
|
|
updated_at = NOW()
|
||
|
|
WHERE id = 12;
|
||
|
|
|
||
|
|
-- Article Approval Step Logs
|
||
|
|
INSERT INTO article_approval_step_logs
|
||
|
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||
|
|
VALUES (3, 1, 'reject', 5, 'Content does not meet quality standards', NOW());
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🎯 **Frontend Integration Examples**
|
||
|
|
|
||
|
|
### **React Component:**
|
||
|
|
```jsx
|
||
|
|
import React, { useState } from 'react';
|
||
|
|
|
||
|
|
const ApprovalActions = ({ articleId, onActionComplete }) => {
|
||
|
|
const [action, setAction] = useState('approve');
|
||
|
|
const [message, setMessage] = useState('');
|
||
|
|
|
||
|
|
const handleSubmit = async () => {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/article-approval-flows/articles/${articleId}/approve`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${token}`
|
||
|
|
},
|
||
|
|
body: JSON.stringify({ action, message })
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await response.json();
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
onActionComplete(result.data);
|
||
|
|
alert(`Article ${action} successfully!`);
|
||
|
|
} else {
|
||
|
|
alert(`Error: ${result.messages[0]}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
alert('Failed to process approval action');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="approval-actions">
|
||
|
|
<h3>Approval Actions</h3>
|
||
|
|
|
||
|
|
<div className="action-buttons">
|
||
|
|
<button
|
||
|
|
className={action === 'approve' ? 'active' : ''}
|
||
|
|
onClick={() => setAction('approve')}
|
||
|
|
>
|
||
|
|
✅ Approve
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button
|
||
|
|
className={action === 'revision' ? 'active' : ''}
|
||
|
|
onClick={() => setAction('revision')}
|
||
|
|
>
|
||
|
|
📝 Revision
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button
|
||
|
|
className={action === 'reject' ? 'active' : ''}
|
||
|
|
onClick={() => setAction('reject')}
|
||
|
|
>
|
||
|
|
❌ Reject
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<textarea
|
||
|
|
placeholder="Enter your message..."
|
||
|
|
value={message}
|
||
|
|
onChange={(e) => setMessage(e.target.value)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<button onClick={handleSubmit}>
|
||
|
|
Submit {action}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### **Vue.js Component:**
|
||
|
|
```vue
|
||
|
|
<template>
|
||
|
|
<div class="approval-actions">
|
||
|
|
<h3>Approval Actions</h3>
|
||
|
|
|
||
|
|
<div class="action-buttons">
|
||
|
|
<button
|
||
|
|
v-for="actionType in actions"
|
||
|
|
:key="actionType.value"
|
||
|
|
:class="{ active: selectedAction === actionType.value }"
|
||
|
|
@click="selectedAction = actionType.value"
|
||
|
|
>
|
||
|
|
{{ actionType.icon }} {{ actionType.label }}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<textarea
|
||
|
|
v-model="message"
|
||
|
|
placeholder="Enter your message..."
|
||
|
|
/>
|
||
|
|
|
||
|
|
<button @click="submitAction" :disabled="!selectedAction">
|
||
|
|
Submit {{ selectedAction }}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
export default {
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
selectedAction: 'approve',
|
||
|
|
message: '',
|
||
|
|
actions: [
|
||
|
|
{ value: 'approve', label: 'Approve', icon: '✅' },
|
||
|
|
{ value: 'revision', label: 'Revision', icon: '📝' },
|
||
|
|
{ value: 'reject', label: 'Reject', icon: '❌' }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
async submitAction() {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/article-approval-flows/articles/${this.articleId}/approve`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${this.token}`
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
action: this.selectedAction,
|
||
|
|
message: this.message
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await response.json();
|
||
|
|
|
||
|
|
if (result.success) {
|
||
|
|
this.$emit('action-complete', result.data);
|
||
|
|
alert(`Article ${this.selectedAction} successfully!`);
|
||
|
|
} else {
|
||
|
|
alert(`Error: ${result.messages[0]}`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
alert('Failed to process approval action');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🚨 **Error Handling Examples**
|
||
|
|
|
||
|
|
### **Invalid Action:**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"action": "invalid_action",
|
||
|
|
"message": "Test"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"messages": ["Validation failed: [Action must be one of: approve, revision, reject]"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **Missing Action:**
|
||
|
|
```bash
|
||
|
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||
|
|
-d '{
|
||
|
|
"message": "Test"
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"success": false,
|
||
|
|
"messages": ["Validation failed: [Action is required]"]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 📊 **Testing Scenarios**
|
||
|
|
|
||
|
|
### **Test Case 1: Complete Approval Flow**
|
||
|
|
1. Create article dengan user level 5
|
||
|
|
2. Editor (level 3) approve → step 2
|
||
|
|
3. Senior Editor (level 2) approve → published
|
||
|
|
|
||
|
|
### **Test Case 2: Revision Flow**
|
||
|
|
1. Create article dengan user level 5
|
||
|
|
2. Editor (level 3) approve → step 2
|
||
|
|
3. Senior Editor (level 2) revision → step 1
|
||
|
|
4. Editor (level 3) approve again → step 2
|
||
|
|
5. Senior Editor (level 2) approve → published
|
||
|
|
|
||
|
|
### **Test Case 3: Reject Flow**
|
||
|
|
1. Create article dengan user level 5
|
||
|
|
2. Editor (level 3) reject → article jadi draft
|
||
|
|
3. Workflow dihentikan
|
||
|
|
|
||
|
|
## 🎉 **Summary**
|
||
|
|
|
||
|
|
Enhanced Approval Actions API memberikan:
|
||
|
|
|
||
|
|
- ✅ **3 Action Types**: Approve, Revision, Reject
|
||
|
|
- ✅ **Flexible Workflow**: Support untuk berbagai skenario
|
||
|
|
- ✅ **Better UX**: User bisa memberikan feedback yang spesifik
|
||
|
|
- ✅ **Quality Control**: Reject option untuk content yang tidak sesuai
|
||
|
|
- ✅ **Revision Support**: Revision untuk perbaikan tanpa menghentikan workflow
|
||
|
|
- ✅ **Audit Trail**: Semua action dicatat dalam logs
|
||
|
|
|
||
|
|
**Sekarang approver memiliki kontrol penuh atas workflow dengan 3 pilihan action yang berbeda!** 🚀
|