feat: update multi branch client approval
This commit is contained in:
parent
a18d5991b7
commit
23a2103ea3
|
|
@ -1,24 +1,36 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ApprovalWorkflowSteps struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"`
|
||||
StepOrder int `json:"step_order" gorm:"type:int4;not null"`
|
||||
StepName string `json:"step_name" gorm:"type:varchar;not null"`
|
||||
RequiredUserLevelId uint `json:"required_user_level_id" gorm:"type:int4;not null"`
|
||||
CanSkip *bool `json:"can_skip" gorm:"type:bool;default:false"`
|
||||
AutoApproveAfterHours *int `json:"auto_approve_after_hours" gorm:"type:int4"`
|
||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
WorkflowId uint `json:"workflow_id" gorm:"type:int4;not null"`
|
||||
StepOrder int `json:"step_order" gorm:"type:int4;not null"`
|
||||
StepName string `json:"step_name" gorm:"type:varchar;not null"`
|
||||
RequiredUserLevelId uint `json:"required_user_level_id" gorm:"type:int4;not null"`
|
||||
CanSkip *bool `json:"can_skip" gorm:"type:bool;default:false"`
|
||||
AutoApproveAfterHours *int `json:"auto_approve_after_hours" gorm:"type:int4"`
|
||||
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||
|
||||
// Multi-branch support fields
|
||||
ParentStepId *uint `json:"parent_step_id" gorm:"type:int4;references:approval_workflow_steps(id)"`
|
||||
ConditionType *string `json:"condition_type" gorm:"type:varchar(50)"` // 'user_level', 'user_level_hierarchy', 'always', 'custom'
|
||||
ConditionValue *string `json:"condition_value" gorm:"type:text"` // JSON string for conditions
|
||||
IsParallel *bool `json:"is_parallel" gorm:"type:bool;default:false"`
|
||||
BranchName *string `json:"branch_name" gorm:"type:varchar(100)"`
|
||||
BranchOrder *int `json:"branch_order" gorm:"type:int4"` // Order within the same branch
|
||||
|
||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relations
|
||||
Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId;constraint:OnDelete:CASCADE"`
|
||||
RequiredUserLevel UserLevels `json:"required_user_level" gorm:"foreignKey:RequiredUserLevelId"`
|
||||
}
|
||||
Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId;constraint:OnDelete:CASCADE"`
|
||||
RequiredUserLevel UserLevels `json:"required_user_level" gorm:"foreignKey:RequiredUserLevelId"`
|
||||
ParentStep *ApprovalWorkflowSteps `json:"parent_step" gorm:"foreignKey:ParentStepId"`
|
||||
ChildSteps []ApprovalWorkflowSteps `json:"child_steps" gorm:"foreignKey:ParentStepId"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ArticleApprovalFlows struct {
|
||||
|
|
@ -17,13 +18,22 @@ type ArticleApprovalFlows struct {
|
|||
RejectionReason *string `json:"rejection_reason" gorm:"type:text"`
|
||||
RevisionRequested *bool `json:"revision_requested" gorm:"type:bool;default:false"`
|
||||
RevisionMessage *string `json:"revision_message" gorm:"type:text"`
|
||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Multi-branch support fields
|
||||
CurrentBranch *string `json:"current_branch" gorm:"type:varchar(100)"` // Current active branch
|
||||
BranchPath *string `json:"branch_path" gorm:"type:text"` // JSON array tracking the path taken
|
||||
IsParallelFlow *bool `json:"is_parallel_flow" gorm:"type:bool;default:false"` // Whether this is a parallel approval flow
|
||||
ParentFlowId *uint `json:"parent_flow_id" gorm:"type:int4;references:article_approval_flows(id)"` // For parallel flows
|
||||
|
||||
ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
|
||||
// Relations
|
||||
Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||
Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId"`
|
||||
SubmittedBy *Users `json:"submitted_by" gorm:"foreignKey:SubmittedById"`
|
||||
StepLogs []ArticleApprovalStepLogs `json:"step_logs" gorm:"foreignKey:ApprovalFlowId;constraint:OnDelete:CASCADE"`
|
||||
}
|
||||
Article Articles `json:"article" gorm:"foreignKey:ArticleId;constraint:OnDelete:CASCADE"`
|
||||
Workflow ApprovalWorkflows `json:"workflow" gorm:"foreignKey:WorkflowId"`
|
||||
SubmittedBy *Users `json:"submitted_by" gorm:"foreignKey:SubmittedById"`
|
||||
StepLogs []ArticleApprovalStepLogs `json:"step_logs" gorm:"foreignKey:ApprovalFlowId;constraint:OnDelete:CASCADE"`
|
||||
ParentFlow *ArticleApprovalFlows `json:"parent_flow" gorm:"foreignKey:ParentFlowId"`
|
||||
ChildFlows []ArticleApprovalFlows `json:"child_flows" gorm:"foreignKey:ParentFlowId"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ func (m *Middleware) Register(db *database.Database) {
|
|||
//===============================
|
||||
|
||||
// Client middleware - must be applied before other business logic
|
||||
m.App.Use(ClientMiddleware(db.DB))
|
||||
// m.App.Use(ClientMiddleware(db.DB))
|
||||
|
||||
m.App.Use(AuditTrailsMiddleware(db.DB))
|
||||
// StartAuditTrailCleanup(db.DB, m.Cfg.Middleware.AuditTrails.Retention)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,13 @@ type ApprovalWorkflowStepsRepository interface {
|
|||
// Validation methods
|
||||
ValidateStepSequence(clientId *uuid.UUID, workflowId uint) (isValid bool, errors []string, err error)
|
||||
CheckStepDependencies(clientId *uuid.UUID, stepId uint) (canDelete bool, dependencies []string, err error)
|
||||
|
||||
// Multi-branch support methods
|
||||
GetNextSteps(clientId *uuid.UUID, workflowId uint, currentStep int) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
GetStepsByBranch(clientId *uuid.UUID, workflowId uint, branchName string) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
GetChildSteps(clientId *uuid.UUID, parentStepId uint) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
GetRootSteps(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
GetStepsByCondition(clientId *uuid.UUID, workflowId uint, conditionType string, submitterLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
}
|
||||
|
||||
func NewApprovalWorkflowStepsRepository(db *database.Database, log zerolog.Logger) ApprovalWorkflowStepsRepository {
|
||||
|
|
@ -371,3 +378,97 @@ func (_i *approvalWorkflowStepsRepository) CheckStepDependencies(clientId *uuid.
|
|||
canDelete = len(dependencies) == 0
|
||||
return canDelete, dependencies, nil
|
||||
}
|
||||
|
||||
// Multi-branch support methods implementation
|
||||
func (_i *approvalWorkflowStepsRepository) GetNextSteps(clientId *uuid.UUID, workflowId uint, currentStep int) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{})
|
||||
|
||||
if clientId != nil {
|
||||
query = query.Where("client_id = ?", clientId)
|
||||
}
|
||||
|
||||
// Get all possible next steps (including parallel branches)
|
||||
query = query.Where("workflow_id = ? AND step_order > ? AND is_active = ?", workflowId, currentStep, true)
|
||||
query = query.Preload("Workflow").Preload("RequiredUserLevel").Preload("ParentStep")
|
||||
query = query.Order("step_order ASC, branch_order ASC")
|
||||
|
||||
err = query.Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func (_i *approvalWorkflowStepsRepository) GetStepsByBranch(clientId *uuid.UUID, workflowId uint, branchName string) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{})
|
||||
|
||||
if clientId != nil {
|
||||
query = query.Where("client_id = ?", clientId)
|
||||
}
|
||||
|
||||
query = query.Where("workflow_id = ? AND branch_name = ? AND is_active = ?", workflowId, branchName, true)
|
||||
query = query.Preload("Workflow").Preload("RequiredUserLevel").Preload("ParentStep")
|
||||
query = query.Order("step_order ASC, branch_order ASC")
|
||||
|
||||
err = query.Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func (_i *approvalWorkflowStepsRepository) GetChildSteps(clientId *uuid.UUID, parentStepId uint) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{})
|
||||
|
||||
if clientId != nil {
|
||||
query = query.Where("client_id = ?", clientId)
|
||||
}
|
||||
|
||||
query = query.Where("parent_step_id = ? AND is_active = ?", parentStepId, true)
|
||||
query = query.Preload("Workflow").Preload("RequiredUserLevel").Preload("ParentStep")
|
||||
query = query.Order("branch_order ASC")
|
||||
|
||||
err = query.Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func (_i *approvalWorkflowStepsRepository) GetRootSteps(clientId *uuid.UUID, workflowId uint) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{})
|
||||
|
||||
if clientId != nil {
|
||||
query = query.Where("client_id = ?", clientId)
|
||||
}
|
||||
|
||||
// Get root steps (steps with no parent or step_order = 1)
|
||||
query = query.Where("workflow_id = ? AND (parent_step_id IS NULL OR step_order = 1) AND is_active = ?", workflowId, true)
|
||||
query = query.Preload("Workflow").Preload("RequiredUserLevel").Preload("ParentStep")
|
||||
query = query.Order("step_order ASC, branch_order ASC")
|
||||
|
||||
err = query.Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
||||
func (_i *approvalWorkflowStepsRepository) GetStepsByCondition(clientId *uuid.UUID, workflowId uint, conditionType string, submitterLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
query := _i.DB.DB.Model(&entity.ApprovalWorkflowSteps{})
|
||||
|
||||
if clientId != nil {
|
||||
query = query.Where("client_id = ?", clientId)
|
||||
}
|
||||
|
||||
query = query.Where("workflow_id = ? AND is_active = ?", workflowId, true)
|
||||
|
||||
// Apply condition filtering
|
||||
switch conditionType {
|
||||
case "user_level":
|
||||
// Steps that apply to specific user levels
|
||||
query = query.Where("condition_type = ? OR condition_type IS NULL", conditionType)
|
||||
case "user_level_hierarchy":
|
||||
// Steps that apply based on user level hierarchy
|
||||
query = query.Where("condition_type = ? OR condition_type IS NULL", conditionType)
|
||||
case "always":
|
||||
// Steps that always apply
|
||||
query = query.Where("condition_type = ? OR condition_type IS NULL", conditionType)
|
||||
default:
|
||||
// Default: get all steps
|
||||
}
|
||||
|
||||
query = query.Preload("Workflow").Preload("RequiredUserLevel").Preload("ParentStep")
|
||||
query = query.Order("step_order ASC, branch_order ASC")
|
||||
|
||||
err = query.Find(&steps).Error
|
||||
return steps, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,14 @@ type ApprovalWorkflowStepRequest struct {
|
|||
CanSkip *bool `json:"canSkip"`
|
||||
AutoApproveAfterHours *int `json:"autoApproveAfterHours"`
|
||||
IsActive *bool `json:"isActive"`
|
||||
|
||||
// Multi-branch support fields
|
||||
ParentStepId *uint `json:"parentStepId"`
|
||||
ConditionType *string `json:"conditionType"` // 'user_level', 'user_level_hierarchy', 'always', 'custom'
|
||||
ConditionValue *string `json:"conditionValue"` // JSON string for conditions
|
||||
IsParallel *bool `json:"isParallel"`
|
||||
BranchName *string `json:"branchName"`
|
||||
BranchOrder *int `json:"branchOrder"`
|
||||
}
|
||||
|
||||
func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.ApprovalWorkflowSteps {
|
||||
|
|
@ -98,8 +106,17 @@ func (req ApprovalWorkflowStepRequest) ToEntity(workflowId uint) *entity.Approva
|
|||
CanSkip: req.CanSkip,
|
||||
AutoApproveAfterHours: req.AutoApproveAfterHours,
|
||||
IsActive: req.IsActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
||||
// Multi-branch support fields
|
||||
ParentStepId: req.ParentStepId,
|
||||
ConditionType: req.ConditionType,
|
||||
ConditionValue: req.ConditionValue,
|
||||
IsParallel: req.IsParallel,
|
||||
BranchName: req.BranchName,
|
||||
BranchOrder: req.BranchOrder,
|
||||
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,8 +153,17 @@ func (req ApprovalWorkflowsWithStepsCreateRequest) ToStepsEntity() []*entity.App
|
|||
CanSkip: stepReq.CanSkip,
|
||||
AutoApproveAfterHours: stepReq.AutoApproveAfterHours,
|
||||
IsActive: stepReq.IsActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
|
||||
// Multi-branch support fields
|
||||
ParentStepId: stepReq.ParentStepId,
|
||||
ConditionType: stepReq.ConditionType,
|
||||
ConditionValue: stepReq.ConditionValue,
|
||||
IsParallel: stepReq.IsParallel,
|
||||
BranchName: stepReq.BranchName,
|
||||
BranchOrder: stepReq.BranchOrder,
|
||||
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
return steps
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"netidhub-saas-be/app/database/entity"
|
||||
|
|
@ -446,33 +447,106 @@ func (_i *approvalWorkflowsService) ValidateWorkflow(authToken string, workflow
|
|||
if len(steps) == 0 {
|
||||
errors = append(errors, "Workflow must have at least one step")
|
||||
} else {
|
||||
// Check for duplicate step orders
|
||||
stepOrderMap := make(map[int]bool)
|
||||
for i, step := range steps {
|
||||
expectedOrder := i + 1
|
||||
if step.StepOrder != 0 && step.StepOrder != expectedOrder {
|
||||
errors = append(errors, fmt.Sprintf("Step %d has incorrect order %d, expected %d", i+1, step.StepOrder, expectedOrder))
|
||||
}
|
||||
|
||||
if stepOrderMap[step.StepOrder] {
|
||||
errors = append(errors, fmt.Sprintf("Duplicate step order: %d", step.StepOrder))
|
||||
}
|
||||
stepOrderMap[step.StepOrder] = true
|
||||
|
||||
if step.StepName == "" {
|
||||
errors = append(errors, fmt.Sprintf("Step %d name is required", i+1))
|
||||
}
|
||||
|
||||
if step.RequiredUserLevelId == 0 {
|
||||
errors = append(errors, fmt.Sprintf("Step %d must have a required user level", i+1))
|
||||
}
|
||||
}
|
||||
// For multi-branch workflow, we need different validation logic
|
||||
_i.validateMultiBranchSteps(steps, &errors)
|
||||
}
|
||||
|
||||
isValid = len(errors) == 0
|
||||
return isValid, errors, nil
|
||||
}
|
||||
|
||||
// validateMultiBranchSteps validates steps for multi-branch workflow
|
||||
func (_i *approvalWorkflowsService) validateMultiBranchSteps(steps []*entity.ApprovalWorkflowSteps, errors *[]string) {
|
||||
// Group steps by step order to handle parallel branches
|
||||
stepOrderGroups := make(map[int][]*entity.ApprovalWorkflowSteps)
|
||||
|
||||
for i, step := range steps {
|
||||
// Basic validation for each step
|
||||
if step.StepName == "" {
|
||||
*errors = append(*errors, fmt.Sprintf("Step %d name is required", i+1))
|
||||
}
|
||||
|
||||
if step.RequiredUserLevelId == 0 {
|
||||
*errors = append(*errors, fmt.Sprintf("Step %d must have a required user level", i+1))
|
||||
}
|
||||
|
||||
if step.StepOrder <= 0 {
|
||||
*errors = append(*errors, fmt.Sprintf("Step %d must have a valid step order (greater than 0)", i+1))
|
||||
}
|
||||
|
||||
// Validate condition type and value
|
||||
if step.ConditionType != nil {
|
||||
validConditionTypes := []string{"user_level", "user_level_hierarchy", "always", "custom"}
|
||||
isValidConditionType := false
|
||||
for _, validType := range validConditionTypes {
|
||||
if *step.ConditionType == validType {
|
||||
isValidConditionType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValidConditionType {
|
||||
*errors = append(*errors, fmt.Sprintf("Step %d has invalid condition type: %s", i+1, *step.ConditionType))
|
||||
}
|
||||
|
||||
// Validate condition value format for specific condition types
|
||||
if step.ConditionValue != nil && *step.ConditionValue != "" {
|
||||
if *step.ConditionType == "user_level_hierarchy" || *step.ConditionType == "user_level" {
|
||||
// Try to parse as JSON to validate format
|
||||
var conditionData interface{}
|
||||
if err := json.Unmarshal([]byte(*step.ConditionValue), &conditionData); err != nil {
|
||||
*errors = append(*errors, fmt.Sprintf("Step %d has invalid condition value format: %s", i+1, *step.ConditionValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group steps by step order
|
||||
stepOrderGroups[step.StepOrder] = append(stepOrderGroups[step.StepOrder], step)
|
||||
}
|
||||
|
||||
// Validate step order groups
|
||||
maxStepOrder := 0
|
||||
for stepOrder, groupSteps := range stepOrderGroups {
|
||||
if stepOrder > maxStepOrder {
|
||||
maxStepOrder = stepOrder
|
||||
}
|
||||
|
||||
// Check for duplicate branch names within the same step order
|
||||
branchNames := make(map[string]bool)
|
||||
for _, step := range groupSteps {
|
||||
if step.BranchName != nil && *step.BranchName != "" {
|
||||
if branchNames[*step.BranchName] {
|
||||
*errors = append(*errors, fmt.Sprintf("Duplicate branch name '%s' found in step order %d", *step.BranchName, stepOrder))
|
||||
}
|
||||
branchNames[*step.BranchName] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Validate branch order within the same step order
|
||||
branchOrders := make(map[int]bool)
|
||||
for _, step := range groupSteps {
|
||||
if step.BranchOrder != nil {
|
||||
if branchOrders[*step.BranchOrder] {
|
||||
*errors = append(*errors, fmt.Sprintf("Duplicate branch order %d found in step order %d", *step.BranchOrder, stepOrder))
|
||||
}
|
||||
branchOrders[*step.BranchOrder] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that we have at least one step with step order 1
|
||||
if len(stepOrderGroups[1]) == 0 {
|
||||
*errors = append(*errors, "Workflow must have at least one step with step order 1")
|
||||
}
|
||||
|
||||
// Validate that step orders are sequential (no gaps)
|
||||
for i := 1; i <= maxStepOrder; i++ {
|
||||
if len(stepOrderGroups[i]) == 0 {
|
||||
*errors = append(*errors, fmt.Sprintf("Missing step order %d - step orders must be sequential", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (_i *approvalWorkflowsService) CanDeleteWorkflow(authToken string, id uint) (canDelete bool, reason string, err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package article_approval_flows
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
"netidhub-saas-be/app/module/article_approval_flows/controller"
|
||||
"netidhub-saas-be/app/module/article_approval_flows/repository"
|
||||
"netidhub-saas-be/app/module/article_approval_flows/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// ArticleApprovalFlowsRouter struct of ArticleApprovalFlowsRouter
|
||||
|
|
@ -52,7 +53,9 @@ func (_i *ArticleApprovalFlowsRouter) RegisterArticleApprovalFlowsRoutes() {
|
|||
router.Get("/workload-stats", articleApprovalFlowsController.GetWorkloadStats)
|
||||
router.Get("/analytics", articleApprovalFlowsController.GetApprovalAnalytics)
|
||||
router.Get("/:id", articleApprovalFlowsController.Show)
|
||||
router.Get("/:id/next-steps-preview", articleApprovalFlowsController.GetNextStepsPreview)
|
||||
router.Post("/submit", articleApprovalFlowsController.SubmitForApproval)
|
||||
router.Post("/:id/multi-branch-approve", articleApprovalFlowsController.ProcessMultiBranchApproval)
|
||||
router.Put("/:id/approve", articleApprovalFlowsController.Approve)
|
||||
router.Put("/:id/reject", articleApprovalFlowsController.Reject)
|
||||
router.Put("/:id/request-revision", articleApprovalFlowsController.RequestRevision)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ type ArticleApprovalFlowsController interface {
|
|||
GetDashboardStats(c *fiber.Ctx) error
|
||||
GetWorkloadStats(c *fiber.Ctx) error
|
||||
GetApprovalAnalytics(c *fiber.Ctx) error
|
||||
|
||||
// Multi-branch support methods
|
||||
ProcessMultiBranchApproval(c *fiber.Ctx) error
|
||||
GetNextStepsPreview(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewArticleApprovalFlowsController(articleApprovalFlowsService service.ArticleApprovalFlowsService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ArticleApprovalFlowsController {
|
||||
|
|
@ -634,3 +638,113 @@ func (_i *articleApprovalFlowsController) GetApprovalAnalytics(c *fiber.Ctx) err
|
|||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// ProcessMultiBranchApproval ArticleApprovalFlows
|
||||
// @Summary Process multi-branch approval
|
||||
// @Description API for processing multi-branch approval with conditional routing
|
||||
// @Tags ArticleApprovalFlows
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string true "Insert the Authorization"
|
||||
// @Param id path int true "ArticleApprovalFlows ID"
|
||||
// @Param req body request.ApprovalActionRequest true "Approval action data"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approval-flows/{id}/multi-branch-approve [post]
|
||||
func (_i *articleApprovalFlowsController) ProcessMultiBranchApproval(c *fiber.Ctx) error {
|
||||
id, err := strconv.Atoi(c.Params("id"))
|
||||
if err != nil {
|
||||
return utilRes.ErrorBadRequest(c, "Invalid ID format")
|
||||
}
|
||||
|
||||
req := new(request.ApprovalActionRequest)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return utilRes.ErrorBadRequest(c, "Invalid request body")
|
||||
}
|
||||
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get Authorization token from header
|
||||
authToken := c.Get("Authorization")
|
||||
if authToken == "" {
|
||||
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||
}
|
||||
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
if user == nil {
|
||||
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||
}
|
||||
|
||||
err = _i.articleApprovalFlowsService.ProcessMultiBranchApproval(authToken, uint(id), user.ID, req.Message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Article successfully processed through multi-branch approval"},
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextStepsPreview ArticleApprovalFlows
|
||||
// @Summary Get next steps preview for multi-branch workflow
|
||||
// @Description API for getting preview of next steps based on submitter's user level
|
||||
// @Tags ArticleApprovalFlows
|
||||
// @Security Bearer
|
||||
// @Param Authorization header string true "Insert the Authorization"
|
||||
// @Param id path int true "ArticleApprovalFlows ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /article-approval-flows/{id}/next-steps-preview [get]
|
||||
func (_i *articleApprovalFlowsController) GetNextStepsPreview(c *fiber.Ctx) error {
|
||||
id, err := strconv.Atoi(c.Params("id"))
|
||||
if err != nil {
|
||||
return utilRes.ErrorBadRequest(c, "Invalid ID format")
|
||||
}
|
||||
|
||||
// Get Authorization token from header
|
||||
authToken := c.Get("Authorization")
|
||||
if authToken == "" {
|
||||
return utilRes.ErrorBadRequest(c, "Authorization token required")
|
||||
}
|
||||
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
if user == nil {
|
||||
return utilRes.ErrorBadRequest(c, "Invalid authorization token")
|
||||
}
|
||||
|
||||
// Get current flow to determine submitter's level
|
||||
flow, err := _i.articleApprovalFlowsService.FindOne(authToken, uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get submitter's user level
|
||||
submitterLevelId, err := _i.articleApprovalFlowsService.GetUserLevelId(authToken, flow.SubmittedById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get next steps based on submitter's level
|
||||
nextSteps, err := _i.articleApprovalFlowsService.FindNextStepsForBranch(authToken, flow.WorkflowId, flow.CurrentStep, submitterLevelId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Next steps preview successfully retrieved"},
|
||||
Data: map[string]interface{}{
|
||||
"current_step": flow.CurrentStep,
|
||||
"submitter_level_id": submitterLevelId,
|
||||
"next_steps": nextSteps,
|
||||
"total_next_steps": len(nextSteps),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"netidhub-saas-be/app/database/entity"
|
||||
approvalWorkflowStepsRepo "netidhub-saas-be/app/module/approval_workflow_steps/repository"
|
||||
|
|
@ -59,6 +60,13 @@ type ArticleApprovalFlowsService interface {
|
|||
CanUserApproveStep(authToken string, flowId uint, userId uint, userLevelId uint) (canApprove bool, reason string, err error)
|
||||
GetCurrentStepInfo(authToken string, flowId uint) (stepInfo map[string]interface{}, err error)
|
||||
GetNextStepPreview(authToken string, flowId uint) (nextStep *entity.ApprovalWorkflowSteps, err error)
|
||||
|
||||
// Multi-branch support methods
|
||||
ProcessMultiBranchApproval(authToken string, flowId uint, approvedById uint, message string) (err error)
|
||||
FindNextStepsForBranch(authToken string, workflowId uint, currentStep int, submitterLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error)
|
||||
IsStepApplicableForLevel(step *entity.ApprovalWorkflowSteps, submitterLevelId uint) (isApplicable bool, err error)
|
||||
ProcessParallelBranches(authToken string, flow *entity.ArticleApprovalFlows, nextSteps []*entity.ApprovalWorkflowSteps, approvedById uint, message string) (err error)
|
||||
GetUserLevelId(authToken string, userId uint) (userLevelId uint, err error)
|
||||
}
|
||||
|
||||
func NewArticleApprovalFlowsService(
|
||||
|
|
@ -1081,3 +1089,291 @@ func (_i *articleApprovalFlowsService) GetNextStepPreview(authToken string, flow
|
|||
|
||||
return nextStep, nil
|
||||
}
|
||||
|
||||
// Multi-branch support methods implementation
|
||||
func (_i *articleApprovalFlowsService) ProcessMultiBranchApproval(authToken string, flowId uint, approvedById uint, message string) (err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
if authToken != "" {
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
|
||||
if user != nil && user.ClientId != nil {
|
||||
clientId = user.ClientId
|
||||
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
||||
}
|
||||
}
|
||||
|
||||
if clientId == nil {
|
||||
return errors.New("clientId not found in auth token")
|
||||
}
|
||||
|
||||
// Get current flow
|
||||
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flow == nil {
|
||||
return errors.New("approval flow not found")
|
||||
}
|
||||
|
||||
if flow.StatusId != 1 && flow.StatusId != 4 { // not pending or revision_requested
|
||||
return errors.New("approval flow is not in pending state")
|
||||
}
|
||||
|
||||
// Get current step
|
||||
currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentStep == nil {
|
||||
return errors.New("current step not found")
|
||||
}
|
||||
|
||||
// Create step log
|
||||
stepLog := &entity.ArticleApprovalStepLogs{
|
||||
ApprovalFlowId: flow.ID,
|
||||
StepOrder: flow.CurrentStep,
|
||||
StepName: currentStep.StepName,
|
||||
ApprovedById: &approvedById,
|
||||
Action: "approve",
|
||||
Message: &message,
|
||||
ProcessedAt: time.Now(),
|
||||
UserLevelId: currentStep.RequiredUserLevelId,
|
||||
}
|
||||
|
||||
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get submitter's user level to determine next steps
|
||||
submitterLevelId, err := _i.getUserLevelId(authToken, flow.SubmittedById)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find applicable next steps based on branching logic
|
||||
nextSteps, err := _i.FindNextStepsForBranch(authToken, flow.WorkflowId, flow.CurrentStep, submitterLevelId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(nextSteps) == 0 {
|
||||
// No next steps - approval complete
|
||||
return _i.completeApprovalFlow(flow)
|
||||
} else if len(nextSteps) == 1 {
|
||||
// Single path - continue normally
|
||||
return _i.processSinglePathApproval(flow, nextSteps[0])
|
||||
} else {
|
||||
// Multiple paths - create parallel branches
|
||||
return _i.ProcessParallelBranches(authToken, flow, nextSteps, approvedById, message)
|
||||
}
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) FindNextStepsForBranch(authToken string, workflowId uint, currentStep int, submitterLevelId uint) (steps []*entity.ApprovalWorkflowSteps, err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
if authToken != "" {
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
|
||||
if user != nil && user.ClientId != nil {
|
||||
clientId = user.ClientId
|
||||
}
|
||||
}
|
||||
|
||||
if clientId == nil {
|
||||
return nil, errors.New("clientId not found in auth token")
|
||||
}
|
||||
|
||||
// Get all possible next steps
|
||||
allNextSteps, err := _i.ApprovalWorkflowStepsRepository.GetNextSteps(clientId, workflowId, currentStep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var applicableSteps []*entity.ApprovalWorkflowSteps
|
||||
|
||||
for _, step := range allNextSteps {
|
||||
isApplicable, err := _i.IsStepApplicableForLevel(step, submitterLevelId)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Uint("stepId", step.ID).Msg("Error checking step applicability")
|
||||
continue
|
||||
}
|
||||
|
||||
if isApplicable {
|
||||
applicableSteps = append(applicableSteps, step)
|
||||
}
|
||||
}
|
||||
|
||||
return applicableSteps, nil
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) IsStepApplicableForLevel(step *entity.ApprovalWorkflowSteps, submitterLevelId uint) (isApplicable bool, err error) {
|
||||
if step.ConditionType == nil {
|
||||
return true, nil // Default: all steps apply
|
||||
}
|
||||
|
||||
switch *step.ConditionType {
|
||||
case "user_level":
|
||||
// Parse condition value (JSON) to get allowed levels
|
||||
if step.ConditionValue == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var allowedLevels []uint
|
||||
err := json.Unmarshal([]byte(*step.ConditionValue), &allowedLevels)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, level := range allowedLevels {
|
||||
if level == submitterLevelId {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
case "user_level_hierarchy":
|
||||
// Check based on user level hierarchy
|
||||
return _i.checkUserLevelHierarchy(submitterLevelId, step)
|
||||
|
||||
case "always":
|
||||
return true, nil
|
||||
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) checkUserLevelHierarchy(submitterLevelId uint, step *entity.ApprovalWorkflowSteps) (isApplicable bool, err error) {
|
||||
if step.ConditionValue == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var condition struct {
|
||||
AppliesToLevels []uint `json:"applies_to_levels"`
|
||||
MinLevel *uint `json:"min_level"`
|
||||
MaxLevel *uint `json:"max_level"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(*step.ConditionValue), &condition)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if submitter level is in the applies_to_levels array
|
||||
for _, level := range condition.AppliesToLevels {
|
||||
if level == submitterLevelId {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check min/max level constraints
|
||||
if condition.MinLevel != nil && submitterLevelId < *condition.MinLevel {
|
||||
return false, nil
|
||||
}
|
||||
if condition.MaxLevel != nil && submitterLevelId > *condition.MaxLevel {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) ProcessParallelBranches(authToken string, flow *entity.ArticleApprovalFlows, nextSteps []*entity.ApprovalWorkflowSteps, approvedById uint, message string) (err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
if authToken != "" {
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
|
||||
if user != nil && user.ClientId != nil {
|
||||
clientId = user.ClientId
|
||||
}
|
||||
}
|
||||
|
||||
if clientId == nil {
|
||||
return errors.New("clientId not found in auth token")
|
||||
}
|
||||
|
||||
// For parallel branches, we need to create separate approval flows
|
||||
// or handle them in a single flow with multiple current steps
|
||||
|
||||
// For now, let's implement a simpler approach:
|
||||
// Take the first applicable step and continue
|
||||
if len(nextSteps) > 0 {
|
||||
return _i.processSinglePathApproval(flow, nextSteps[0])
|
||||
}
|
||||
|
||||
return errors.New("no applicable next steps found")
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) processSinglePathApproval(flow *entity.ArticleApprovalFlows, nextStep *entity.ApprovalWorkflowSteps) (err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
// Note: In a real implementation, you'd need to pass authToken or clientId
|
||||
|
||||
// Update flow to next step
|
||||
flowUpdate := &entity.ArticleApprovalFlows{
|
||||
CurrentStep: nextStep.StepOrder,
|
||||
StatusId: 1, // pending
|
||||
CurrentBranch: nextStep.BranchName,
|
||||
}
|
||||
|
||||
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flowUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update article current step
|
||||
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentArticle.CurrentApprovalStep = &nextStep.StepOrder
|
||||
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_i *articleApprovalFlowsService) completeApprovalFlow(flow *entity.ArticleApprovalFlows) (err error) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
// Note: In a real implementation, you'd need to pass authToken or clientId
|
||||
|
||||
// Mark flow as approved
|
||||
flowUpdate := &entity.ArticleApprovalFlows{
|
||||
StatusId: 2, // approved
|
||||
CurrentStep: 0, // Set to 0 to indicate completion
|
||||
CompletedAt: &[]time.Time{time.Now()}[0],
|
||||
}
|
||||
|
||||
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flowUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update article status
|
||||
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentArticle.StatusId = &[]int{2}[0] // approved
|
||||
currentArticle.CurrentApprovalStep = &[]int{0}[0] // Set to 0 to indicate completion
|
||||
currentArticle.IsPublish = &[]bool{true}[0] // Set to true to indicate publication
|
||||
currentArticle.IsDraft = &[]bool{false}[0] // Set to false to indicate publication
|
||||
|
||||
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserLevelId gets the user level ID for a given user (public method)
|
||||
func (_i *articleApprovalFlowsService) GetUserLevelId(authToken string, userId uint) (userLevelId uint, err error) {
|
||||
return _i.getUserLevelId(authToken, userId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package articles
|
|||
|
||||
import (
|
||||
"netidhub-saas-be/app/middleware"
|
||||
articleApprovalFlowsService "netidhub-saas-be/app/module/article_approval_flows/service"
|
||||
"netidhub-saas-be/app/module/articles/controller"
|
||||
"netidhub-saas-be/app/module/articles/repository"
|
||||
"netidhub-saas-be/app/module/articles/service"
|
||||
|
|
@ -34,7 +35,7 @@ var NewArticlesModule = fx.Options(
|
|||
)
|
||||
|
||||
// NewArticlesRouter init ArticlesRouter
|
||||
func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller, usersRepo usersRepo.UsersRepository) *ArticlesRouter {
|
||||
func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller, usersRepo usersRepo.UsersRepository, articleApprovalFlowsSvc articleApprovalFlowsService.ArticleApprovalFlowsService) *ArticlesRouter {
|
||||
return &ArticlesRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -11,6 +12,7 @@ import (
|
|||
"netidhub-saas-be/app/database/entity"
|
||||
approvalWorkflowsRepository "netidhub-saas-be/app/module/approval_workflows/repository"
|
||||
articleApprovalFlowsRepository "netidhub-saas-be/app/module/article_approval_flows/repository"
|
||||
articleApprovalFlowsService "netidhub-saas-be/app/module/article_approval_flows/service"
|
||||
articleApprovalsRepository "netidhub-saas-be/app/module/article_approvals/repository"
|
||||
articleCategoriesRepository "netidhub-saas-be/app/module/article_categories/repository"
|
||||
articleCategoryDetailsRepository "netidhub-saas-be/app/module/article_category_details/repository"
|
||||
|
|
@ -51,6 +53,7 @@ type articlesService struct {
|
|||
// Dynamic approval system dependencies
|
||||
ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository
|
||||
ApprovalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository
|
||||
ArticleApprovalFlowsSvc articleApprovalFlowsService.ArticleApprovalFlowsService
|
||||
}
|
||||
|
||||
// ArticlesService define interface of IArticlesService
|
||||
|
|
@ -94,6 +97,7 @@ func NewArticlesService(
|
|||
articleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository,
|
||||
articleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository,
|
||||
approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository,
|
||||
articleApprovalFlowsSvc articleApprovalFlowsService.ArticleApprovalFlowsService,
|
||||
log zerolog.Logger,
|
||||
cfg *config.Config,
|
||||
usersRepo usersRepository.UsersRepository,
|
||||
|
|
@ -108,6 +112,7 @@ func NewArticlesService(
|
|||
ArticleApprovalsRepo: articleApprovalsRepo,
|
||||
ArticleApprovalFlowsRepo: articleApprovalFlowsRepo,
|
||||
ApprovalWorkflowsRepo: approvalWorkflowsRepo,
|
||||
ArticleApprovalFlowsSvc: articleApprovalFlowsSvc,
|
||||
Log: log,
|
||||
UsersRepo: usersRepo,
|
||||
MinioStorage: minioStorage,
|
||||
|
|
@ -275,6 +280,13 @@ func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequ
|
|||
|
||||
// Check if user level requires approval
|
||||
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false {
|
||||
_i.Log.Info().
|
||||
Uint("userId", createdBy.ID).
|
||||
Uint("userLevelId", createdBy.UserLevel.ID).
|
||||
Str("userLevelName", createdBy.UserLevel.Name).
|
||||
Bool("isApprovalActive", *createdBy.UserLevel.IsApprovalActive).
|
||||
Msg("User level does not require approval - auto publishing")
|
||||
|
||||
// User level doesn't require approval - auto publish
|
||||
newReq.NeedApprovalFrom = nil
|
||||
newReq.StatusId = &statusIdTwo
|
||||
|
|
@ -282,6 +294,18 @@ func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequ
|
|||
newReq.PublishedAt = nil
|
||||
newReq.BypassApproval = &[]bool{true}[0]
|
||||
} else {
|
||||
_i.Log.Info().
|
||||
Uint("userId", createdBy.ID).
|
||||
Uint("userLevelId", createdBy.UserLevel.ID).
|
||||
Str("userLevelName", createdBy.UserLevel.Name).
|
||||
Bool("isApprovalActive", func() bool {
|
||||
if createdBy != nil && createdBy.UserLevel.IsApprovalActive != nil {
|
||||
return *createdBy.UserLevel.IsApprovalActive
|
||||
}
|
||||
return false
|
||||
}()).
|
||||
Msg("User level requires approval - setting to pending")
|
||||
|
||||
// User level requires approval - set to pending
|
||||
newReq.NeedApprovalFrom = &approvalLevelId
|
||||
newReq.StatusId = &statusIdOne
|
||||
|
|
@ -295,11 +319,29 @@ func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequ
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Dynamic Approval Workflow Assignment
|
||||
// Dynamic Approval Workflow Assignment with Multi-Branch Support
|
||||
if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true {
|
||||
_i.Log.Info().
|
||||
Uint("userId", createdBy.ID).
|
||||
Uint("userLevelId", createdBy.UserLevel.ID).
|
||||
Str("userLevelName", createdBy.UserLevel.Name).
|
||||
Bool("isApprovalActive", *createdBy.UserLevel.IsApprovalActive).
|
||||
Msg("User level requires approval - proceeding with workflow assignment")
|
||||
|
||||
// Get default workflow for the client
|
||||
defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId)
|
||||
if err == nil && defaultWorkflow != nil {
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to get default workflow")
|
||||
} else if defaultWorkflow == nil {
|
||||
_i.Log.Warn().Msg("No default workflow found for client")
|
||||
} else {
|
||||
_i.Log.Info().
|
||||
Uint("workflowId", defaultWorkflow.ID).
|
||||
Str("workflowName", defaultWorkflow.Name).
|
||||
Bool("isActive", *defaultWorkflow.IsActive).
|
||||
Bool("isDefault", *defaultWorkflow.IsDefault).
|
||||
Msg("Found default workflow")
|
||||
|
||||
// Assign workflow to article
|
||||
saveArticleRes.WorkflowId = &defaultWorkflow.ID
|
||||
saveArticleRes.CurrentApprovalStep = &[]int{1}[0] // Start at step 1
|
||||
|
|
@ -308,9 +350,14 @@ func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequ
|
|||
err = _i.Repo.Update(clientId, saveArticleRes.ID, saveArticleRes)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to update article with workflow")
|
||||
} else {
|
||||
_i.Log.Info().
|
||||
Uint("articleId", saveArticleRes.ID).
|
||||
Uint("workflowId", defaultWorkflow.ID).
|
||||
Msg("Article updated with workflow successfully")
|
||||
}
|
||||
|
||||
// Create approval flow
|
||||
// Create approval flow with multi-branch support
|
||||
approvalFlow := &entity.ArticleApprovalFlows{
|
||||
ArticleId: saveArticleRes.ID,
|
||||
WorkflowId: defaultWorkflow.ID,
|
||||
|
|
@ -319,11 +366,32 @@ func (_i *articlesService) Save(authToken string, req request.ArticlesCreateRequ
|
|||
SubmittedById: *newReq.CreatedById,
|
||||
SubmittedAt: time.Now(),
|
||||
ClientId: clientId,
|
||||
// Multi-branch fields will be set by the service
|
||||
CurrentBranch: nil, // Will be determined by first applicable step
|
||||
BranchPath: nil, // Will be populated as flow progresses
|
||||
IsParallelFlow: &[]bool{false}[0], // Default to sequential
|
||||
}
|
||||
|
||||
_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
|
||||
createdFlow, err := _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to create approval flow")
|
||||
} else {
|
||||
_i.Log.Info().
|
||||
Uint("flowId", createdFlow.ID).
|
||||
Uint("articleId", saveArticleRes.ID).
|
||||
Uint("workflowId", defaultWorkflow.ID).
|
||||
Msg("Approval flow created successfully")
|
||||
|
||||
// Initialize the multi-branch flow by determining the first applicable step
|
||||
err = _i.initializeMultiBranchFlow(authToken, createdFlow.ID, createdBy.UserLevel.ID)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to initialize multi-branch flow")
|
||||
} else {
|
||||
_i.Log.Info().
|
||||
Uint("flowId", createdFlow.ID).
|
||||
Uint("userLevelId", createdBy.UserLevel.ID).
|
||||
Msg("Multi-branch flow initialized successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1396,3 +1464,61 @@ func (_i *articlesService) findNextApprovalLevel(clientId *uuid.UUID, currentLev
|
|||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// initializeMultiBranchFlow initializes the multi-branch flow by determining the first applicable step
|
||||
func (_i *articlesService) initializeMultiBranchFlow(authToken string, flowId uint, submitterLevelId uint) error {
|
||||
// Get the approval flow
|
||||
flow, err := _i.ArticleApprovalFlowsRepo.FindOne(nil, flowId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find approval flow: %w", err)
|
||||
}
|
||||
|
||||
// Find the first applicable step based on submitter's user level
|
||||
nextSteps, err := _i.ArticleApprovalFlowsSvc.FindNextStepsForBranch(authToken, flow.WorkflowId, 0, submitterLevelId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find next steps: %w", err)
|
||||
}
|
||||
|
||||
if len(nextSteps) == 0 {
|
||||
// No applicable steps found - this shouldn't happen with proper workflow configuration
|
||||
_i.Log.Warn().Uint("flowId", flowId).Uint("submitterLevelId", submitterLevelId).Msg("No applicable steps found for multi-branch flow")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the flow with the first applicable step and branch information
|
||||
firstStep := nextSteps[0]
|
||||
flow.CurrentStep = firstStep.StepOrder
|
||||
|
||||
// Set branch information if available
|
||||
if firstStep.BranchName != nil {
|
||||
flow.CurrentBranch = firstStep.BranchName
|
||||
|
||||
// Initialize branch path
|
||||
branchPath := []string{*firstStep.BranchName}
|
||||
branchPathJSON, err := json.Marshal(branchPath)
|
||||
if err == nil {
|
||||
branchPathStr := string(branchPathJSON)
|
||||
flow.BranchPath = &branchPathStr
|
||||
}
|
||||
}
|
||||
|
||||
// Update the flow
|
||||
err = _i.ArticleApprovalFlowsRepo.Update(flowId, flow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update approval flow: %w", err)
|
||||
}
|
||||
|
||||
_i.Log.Info().
|
||||
Uint("flowId", flowId).
|
||||
Uint("submitterLevelId", submitterLevelId).
|
||||
Int("currentStep", flow.CurrentStep).
|
||||
Str("currentBranch", func() string {
|
||||
if flow.CurrentBranch != nil {
|
||||
return *flow.CurrentBranch
|
||||
}
|
||||
return "none"
|
||||
}()).
|
||||
Msg("Multi-branch flow initialized successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
-- Debug script untuk mengecek masalah approval flow
|
||||
|
||||
-- 1. Cek user level 5
|
||||
SELECT
|
||||
id,
|
||||
level_name,
|
||||
level_number,
|
||||
is_approval_active,
|
||||
client_id
|
||||
FROM user_levels
|
||||
WHERE id = 5;
|
||||
|
||||
-- 2. Cek user dengan level 5
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.user_level_id,
|
||||
ul.level_name,
|
||||
ul.level_number,
|
||||
ul.is_approval_active
|
||||
FROM users u
|
||||
JOIN user_levels ul ON u.user_level_id = ul.id
|
||||
WHERE u.user_level_id = 5;
|
||||
|
||||
-- 3. Cek default workflow
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_active,
|
||||
is_default,
|
||||
client_id
|
||||
FROM approval_workflows
|
||||
WHERE is_default = true
|
||||
AND is_active = true;
|
||||
|
||||
-- 4. Cek workflow steps
|
||||
SELECT
|
||||
aws.id,
|
||||
aws.workflow_id,
|
||||
aws.step_order,
|
||||
aws.step_name,
|
||||
aws.required_user_level_id,
|
||||
aws.condition_type,
|
||||
aws.condition_value,
|
||||
aws.branch_name,
|
||||
aw.name as workflow_name
|
||||
FROM approval_workflow_steps aws
|
||||
JOIN approval_workflows aw ON aws.workflow_id = aw.id
|
||||
WHERE aw.is_default = true
|
||||
ORDER BY aws.step_order, aws.branch_order;
|
||||
|
||||
-- 5. Cek artikel yang baru dibuat
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
created_by_id,
|
||||
workflow_id,
|
||||
current_approval_step,
|
||||
status_id,
|
||||
bypass_approval,
|
||||
approval_exempt,
|
||||
created_at
|
||||
FROM articles
|
||||
WHERE title = 'Test Tni Artikel 1'
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- 6. Cek approval flows
|
||||
SELECT
|
||||
aaf.id,
|
||||
aaf.article_id,
|
||||
aaf.workflow_id,
|
||||
aaf.current_step,
|
||||
aaf.current_branch,
|
||||
aaf.status_id,
|
||||
aaf.submitted_by_id,
|
||||
aaf.submitted_at,
|
||||
a.title as article_title
|
||||
FROM article_approval_flows aaf
|
||||
JOIN articles a ON aaf.article_id = a.id
|
||||
WHERE a.title = 'Test Tni Artikel 1'
|
||||
ORDER BY aaf.created_at DESC;
|
||||
|
||||
-- 7. Cek legacy approval records
|
||||
SELECT
|
||||
aa.id,
|
||||
aa.article_id,
|
||||
aa.approval_by,
|
||||
aa.status_id,
|
||||
aa.message,
|
||||
aa.approval_at_level,
|
||||
a.title as article_title
|
||||
FROM article_approvals aa
|
||||
JOIN articles a ON aa.article_id = a.id
|
||||
WHERE a.title = 'Test Tni Artikel 1'
|
||||
ORDER BY aa.created_at DESC;
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
# Auto Approval Flow Guide
|
||||
|
||||
## 🚀 **Overview**
|
||||
|
||||
Sistem auto approval flow memungkinkan artikel untuk otomatis masuk ke workflow approval yang sesuai dengan user level submitter saat artikel dibuat, tanpa perlu manual submit approval.
|
||||
|
||||
## 🔄 **How It Works**
|
||||
|
||||
### **1. Artikel Creation Process**
|
||||
|
||||
Ketika user membuat artikel baru:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/articles" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_TOKEN" \
|
||||
-d '{
|
||||
"title": "Sample Article",
|
||||
"content": "Article content here",
|
||||
"categoryId": 1,
|
||||
"isDraft": false
|
||||
}'
|
||||
```
|
||||
|
||||
**Sistem akan otomatis:**
|
||||
|
||||
1. ✅ **Detect User Level** - Mengambil user level dari JWT token
|
||||
2. ✅ **Check Approval Requirement** - Cek apakah user level memerlukan approval
|
||||
3. ✅ **Get Default Workflow** - Mengambil workflow default untuk client
|
||||
4. ✅ **Create Approval Flow** - Membuat `ArticleApprovalFlows` record
|
||||
5. ✅ **Initialize Multi-Branch** - Menentukan branch dan step pertama yang sesuai
|
||||
6. ✅ **Set Article Status** - Set artikel ke status "Pending Approval"
|
||||
|
||||
### **2. Multi-Branch Logic**
|
||||
|
||||
Sistem akan otomatis menentukan branch yang sesuai berdasarkan:
|
||||
|
||||
- **User Level Submitter** - Level user yang membuat artikel
|
||||
- **Workflow Conditions** - Kondisi yang dikonfigurasi di workflow steps
|
||||
- **Branch Rules** - Aturan branching yang sudah disetup
|
||||
|
||||
**Contoh Flow:**
|
||||
|
||||
```
|
||||
User Level 5 membuat artikel
|
||||
↓
|
||||
Sistem cek workflow conditions
|
||||
↓
|
||||
Masuk ke "Branch_A" (Level 2 A Branch)
|
||||
↓
|
||||
Menunggu approval dari User Level 3
|
||||
↓
|
||||
Setelah approved → masuk ke "Final_Approval"
|
||||
↓
|
||||
Menunggu approval dari User Level 2
|
||||
↓
|
||||
Artikel published
|
||||
```
|
||||
|
||||
## 📋 **Implementation Details**
|
||||
|
||||
### **Database Changes**
|
||||
|
||||
Sistem menggunakan field baru di `ArticleApprovalFlows`:
|
||||
|
||||
```sql
|
||||
-- Multi-branch support fields
|
||||
current_branch VARCHAR(100) -- Current active branch
|
||||
branch_path TEXT -- JSON array tracking path
|
||||
is_parallel_flow BOOLEAN -- Whether parallel flow
|
||||
parent_flow_id INT4 -- For parallel flows
|
||||
```
|
||||
|
||||
### **Service Integration**
|
||||
|
||||
```go
|
||||
// Articles Service - Auto approval flow creation
|
||||
func (s *articlesService) Save(authToken string, req request.ArticlesCreateRequest) {
|
||||
// ... create article ...
|
||||
|
||||
// Auto-create approval flow with multi-branch support
|
||||
if createdBy.UserLevel.IsApprovalActive {
|
||||
approvalFlow := &entity.ArticleApprovalFlows{
|
||||
ArticleId: article.ID,
|
||||
WorkflowId: defaultWorkflow.ID,
|
||||
CurrentStep: 1,
|
||||
StatusId: 1, // In Progress
|
||||
SubmittedById: user.ID,
|
||||
SubmittedAt: time.Now(),
|
||||
// Multi-branch fields
|
||||
CurrentBranch: nil, // Will be determined
|
||||
BranchPath: nil, // Will be populated
|
||||
IsParallelFlow: false,
|
||||
}
|
||||
|
||||
// Initialize multi-branch flow
|
||||
s.initializeMultiBranchFlow(authToken, flowId, userLevelId)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 **User Experience**
|
||||
|
||||
### **Before (Manual Submit)**
|
||||
```
|
||||
1. User creates article
|
||||
2. User manually submits for approval
|
||||
3. System creates approval flow
|
||||
4. Approval process begins
|
||||
```
|
||||
|
||||
### **After (Auto Submit)**
|
||||
```
|
||||
1. User creates article
|
||||
2. System automatically:
|
||||
- Creates approval flow
|
||||
- Determines correct branch
|
||||
- Sets first applicable step
|
||||
- Notifies approvers
|
||||
3. Approval process begins immediately
|
||||
```
|
||||
|
||||
## 🔧 **Configuration**
|
||||
|
||||
### **Workflow Setup**
|
||||
|
||||
Pastikan workflow sudah dikonfigurasi dengan benar:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Multi-Branch Article Approval",
|
||||
"isActive": true,
|
||||
"isDefault": true,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 A Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [5]}",
|
||||
"branchName": "Branch_A"
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 B Branch",
|
||||
"requiredUserLevelId": 4,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [6]}",
|
||||
"branchName": "Branch_B"
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 2,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### **User Level Configuration**
|
||||
|
||||
Pastikan user levels sudah dikonfigurasi dengan benar:
|
||||
|
||||
```sql
|
||||
-- Check user levels
|
||||
SELECT id, level_name, level_number, is_approval_active
|
||||
FROM user_levels
|
||||
WHERE client_id = 'your-client-id'
|
||||
ORDER BY level_number;
|
||||
```
|
||||
|
||||
## 📊 **Monitoring & Debugging**
|
||||
|
||||
### **Check Auto-Created Flows**
|
||||
|
||||
```sql
|
||||
-- Check approval flows created automatically
|
||||
SELECT
|
||||
aaf.id,
|
||||
aaf.article_id,
|
||||
aaf.current_step,
|
||||
aaf.current_branch,
|
||||
aaf.status_id,
|
||||
aaf.submitted_at,
|
||||
a.title,
|
||||
u.name as submitter_name,
|
||||
ul.level_name as submitter_level
|
||||
FROM article_approval_flows aaf
|
||||
JOIN articles a ON aaf.article_id = a.id
|
||||
JOIN users u ON aaf.submitted_by_id = u.id
|
||||
JOIN user_levels ul ON u.user_level_id = ul.id
|
||||
WHERE aaf.created_at >= NOW() - INTERVAL '1 day'
|
||||
ORDER BY aaf.created_at DESC;
|
||||
```
|
||||
|
||||
### **Check Branch Path**
|
||||
|
||||
```sql
|
||||
-- Check branch path taken
|
||||
SELECT
|
||||
id,
|
||||
article_id,
|
||||
current_branch,
|
||||
branch_path,
|
||||
current_step
|
||||
FROM article_approval_flows
|
||||
WHERE branch_path IS NOT NULL
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### **API Endpoints for Monitoring**
|
||||
|
||||
```bash
|
||||
# Check approval status
|
||||
curl -X GET "http://localhost:8080/api/articles/{article_id}/approval-status" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check next steps preview
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/{flow_id}/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check my approval queue
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/my-queue" \
|
||||
-H "Authorization: Bearer APPROVER_TOKEN"
|
||||
```
|
||||
|
||||
## 🚨 **Troubleshooting**
|
||||
|
||||
### **Common Issues**
|
||||
|
||||
1. **No Approval Flow Created**
|
||||
- Check if user level has `is_approval_active = true`
|
||||
- Check if default workflow exists
|
||||
- Check logs for errors
|
||||
|
||||
2. **Wrong Branch Selected**
|
||||
- Check workflow conditions
|
||||
- Check user level hierarchy
|
||||
- Check condition value JSON format
|
||||
|
||||
3. **Flow Not Initialized**
|
||||
- Check `initializeMultiBranchFlow` method
|
||||
- Check `FindNextStepsForBranch` method
|
||||
- Check database constraints
|
||||
|
||||
### **Debug Commands**
|
||||
|
||||
```bash
|
||||
# Check user level
|
||||
curl -X GET "http://localhost:8080/api/users/{user_id}" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" | jq '.data.user_levels'
|
||||
|
||||
# Check default workflow
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/default" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check workflow steps
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/{workflow_id}/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## 🎉 **Benefits**
|
||||
|
||||
1. **Better UX** - User tidak perlu manual submit approval
|
||||
2. **Automatic Routing** - Sistem otomatis menentukan branch yang sesuai
|
||||
3. **Immediate Processing** - Approval flow dimulai langsung setelah artikel dibuat
|
||||
4. **Consistent Behavior** - Semua artikel mengikuti aturan yang sama
|
||||
5. **Reduced Errors** - Mengurangi kemungkinan user lupa submit approval
|
||||
|
||||
## 🔄 **Migration from Manual Submit**
|
||||
|
||||
Jika sebelumnya menggunakan manual submit, sistem akan tetap backward compatible:
|
||||
|
||||
- Artikel lama tetap bisa menggunakan manual submit
|
||||
- Artikel baru akan otomatis menggunakan auto-approval flow
|
||||
- Kedua sistem bisa berjalan bersamaan
|
||||
|
||||
## 📝 **Next Steps**
|
||||
|
||||
Setelah implementasi auto-approval flow:
|
||||
|
||||
1. ✅ Test dengan berbagai user levels
|
||||
2. ✅ Monitor approval flow creation
|
||||
3. ✅ Verify branch routing logic
|
||||
4. ✅ Check notification system
|
||||
5. ✅ Update user documentation
|
||||
6. ✅ Train users on new workflow
|
||||
|
||||
---
|
||||
|
||||
**Sistem auto approval flow memberikan pengalaman yang lebih seamless dan otomatis untuk proses approval artikel!** 🚀
|
||||
|
|
@ -0,0 +1,554 @@
|
|||
# Multi-Branch Approval System Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
Sistem Multi-Branch Approval memungkinkan pembuatan workflow approval yang dapat bercabang berdasarkan User Level dari submitter artikel. Sistem ini mendukung:
|
||||
|
||||
- **Conditional Routing**: Approval flow dapat mengikuti jalur berbeda berdasarkan level user yang submit artikel
|
||||
- **Hierarchical Branching**: Support untuk struktur cabang yang kompleks dengan multiple level
|
||||
- **Parallel Processing**: Kemampuan untuk menjalankan multiple approval secara parallel
|
||||
- **Dynamic Configuration**: Workflow dapat dikonfigurasi secara dinamis tanpa mengubah kode
|
||||
|
||||
## Database Schema Changes
|
||||
|
||||
### 1. ApprovalWorkflowSteps Table
|
||||
|
||||
Field baru yang ditambahkan untuk mendukung multi-branch:
|
||||
|
||||
```sql
|
||||
-- Multi-branch support fields
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN parent_step_id INT REFERENCES approval_workflow_steps(id);
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN condition_type VARCHAR(50); -- 'user_level', 'user_level_hierarchy', 'always', 'custom'
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN condition_value TEXT; -- JSON string for conditions
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN is_parallel BOOLEAN DEFAULT false;
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN branch_name VARCHAR(100);
|
||||
ALTER TABLE approval_workflow_steps ADD COLUMN branch_order INT; -- Order within the same branch
|
||||
```
|
||||
|
||||
### 2. ArticleApprovalFlows Table
|
||||
|
||||
Field baru untuk tracking branch:
|
||||
|
||||
```sql
|
||||
-- Multi-branch support fields
|
||||
ALTER TABLE article_approval_flows ADD COLUMN current_branch VARCHAR(100); -- Current active branch
|
||||
ALTER TABLE article_approval_flows ADD COLUMN branch_path TEXT; -- JSON array tracking the path taken
|
||||
ALTER TABLE article_approval_flows ADD COLUMN is_parallel_flow BOOLEAN DEFAULT false; -- Whether this is a parallel approval flow
|
||||
ALTER TABLE article_approval_flows ADD COLUMN parent_flow_id INT REFERENCES article_approval_flows(id); -- For parallel flows
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### 1. Create Multi-Branch Workflow
|
||||
|
||||
**Endpoint**: `POST /api/approval-workflows/with-steps`
|
||||
|
||||
**Description**: Membuat workflow dengan steps yang mendukung multi-branch routing
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "Multi-Branch Article Approval",
|
||||
"description": "Approval workflow dengan cabang berdasarkan user level",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Branch",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6,7,8,9]}",
|
||||
"branchName": "Branch_A",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [10,11,12,13,14,15]}",
|
||||
"branchName": "Branch_B",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Curl Example**:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Multi-Branch Article Approval",
|
||||
"description": "Approval workflow dengan cabang berdasarkan user level",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Branch",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6,7,8,9]}",
|
||||
"branchName": "Branch_A",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [10,11,12,13,14,15]}",
|
||||
"branchName": "Branch_B",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Submit Article for Multi-Branch Approval
|
||||
|
||||
**Endpoint**: `POST /api/articles/{id}/submit-approval`
|
||||
|
||||
**Description**: Submit artikel untuk approval dengan multi-branch workflow
|
||||
|
||||
**Curl Example**:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. Process Multi-Branch Approval
|
||||
|
||||
**Endpoint**: `POST /api/article-approval-flows/{id}/multi-branch-approve`
|
||||
|
||||
**Description**: Proses approval dengan multi-branch logic
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"message": "Article looks good, approved for next level"
|
||||
}
|
||||
```
|
||||
|
||||
**Curl Example**:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Article looks good, approved for next level"
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Get Next Steps Preview
|
||||
|
||||
**Endpoint**: `GET /api/article-approval-flows/{id}/next-steps-preview`
|
||||
|
||||
**Description**: Mendapatkan preview next steps berdasarkan user level submitter
|
||||
|
||||
**Curl Example**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Response Example**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"messages": ["Next steps preview successfully retrieved"],
|
||||
"data": {
|
||||
"current_step": 1,
|
||||
"submitter_level_id": 5,
|
||||
"next_steps": [
|
||||
{
|
||||
"id": 2,
|
||||
"stepOrder": 2,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"branchName": "Final_Approval",
|
||||
"conditionType": "always"
|
||||
}
|
||||
],
|
||||
"total_next_steps": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Condition Types
|
||||
|
||||
### 1. `user_level`
|
||||
Steps yang berlaku untuk user level tertentu.
|
||||
|
||||
**Condition Value Format**:
|
||||
```json
|
||||
[4, 5, 6, 7, 8, 9]
|
||||
```
|
||||
|
||||
### 2. `user_level_hierarchy`
|
||||
Steps yang berlaku berdasarkan hierarki user level dengan kondisi yang lebih kompleks.
|
||||
|
||||
**Condition Value Format**:
|
||||
```json
|
||||
{
|
||||
"applies_to_levels": [4, 5, 6, 7, 8, 9],
|
||||
"min_level": 4,
|
||||
"max_level": 9
|
||||
}
|
||||
```
|
||||
|
||||
### 3. `always`
|
||||
Steps yang selalu berlaku untuk semua user level.
|
||||
|
||||
**Condition Value**: `null` atau `"{}"`
|
||||
|
||||
### 4. `custom`
|
||||
Steps dengan kondisi custom (untuk ekstensi masa depan).
|
||||
|
||||
## Workflow Examples
|
||||
|
||||
### Example 1: Simple Two-Branch Workflow
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Simple Two-Branch Approval",
|
||||
"description": "Workflow dengan 2 cabang berdasarkan user level",
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Branch A - Level 2",
|
||||
"requiredUserLevelId": 2,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6]}",
|
||||
"branchName": "Branch_A"
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Branch B - Level 3",
|
||||
"requiredUserLevelId": 3,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [7,8,9]}",
|
||||
"branchName": "Branch_B"
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Complex Multi-Level Branching
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Complex Multi-Level Branching",
|
||||
"description": "Workflow dengan multiple level branching",
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Branch",
|
||||
"requiredUserLevelId": 2,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6,7,8,9]}",
|
||||
"branchName": "Branch_A"
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [10,11,12,13,14,15]}",
|
||||
"branchName": "Branch_B"
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Sub-branch A1",
|
||||
"requiredUserLevelId": 4,
|
||||
"parentStepId": 1,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6]}",
|
||||
"branchName": "SubBranch_A1"
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Sub-branch A2",
|
||||
"requiredUserLevelId": 5,
|
||||
"parentStepId": 1,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [7,8,9]}",
|
||||
"branchName": "SubBranch_A2"
|
||||
},
|
||||
{
|
||||
"stepOrder": 3,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Flow Logic
|
||||
|
||||
### 1. Article Submission Flow
|
||||
|
||||
1. User dengan level tertentu submit artikel
|
||||
2. Sistem menentukan workflow yang akan digunakan
|
||||
3. Sistem mencari step pertama yang applicable berdasarkan user level submitter
|
||||
4. Artikel masuk ke approval flow dengan step yang sesuai
|
||||
|
||||
### 2. Approval Processing Flow
|
||||
|
||||
1. Approver melakukan approval pada step saat ini
|
||||
2. Sistem mencari next steps yang applicable berdasarkan:
|
||||
- User level submitter
|
||||
- Condition type dan value
|
||||
- Branch logic
|
||||
3. Jika ada multiple next steps, sistem akan:
|
||||
- Untuk parallel branches: buat multiple approval flows
|
||||
- Untuk sequential branches: pilih step pertama yang applicable
|
||||
4. Update artikel status sesuai dengan step yang dicapai
|
||||
|
||||
### 3. Branch Determination Logic
|
||||
|
||||
```go
|
||||
func IsStepApplicableForLevel(step *ApprovalWorkflowSteps, submitterLevelId uint) bool {
|
||||
switch step.ConditionType {
|
||||
case "user_level":
|
||||
// Check if submitter level is in allowed levels
|
||||
return contains(allowedLevels, submitterLevelId)
|
||||
|
||||
case "user_level_hierarchy":
|
||||
// Check based on hierarchy rules
|
||||
return checkHierarchyRules(submitterLevelId, step.ConditionValue)
|
||||
|
||||
case "always":
|
||||
return true
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Responses
|
||||
|
||||
1. **Invalid Condition Value**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Invalid condition value format"],
|
||||
"error": "CONDITION_VALUE_INVALID"
|
||||
}
|
||||
```
|
||||
|
||||
2. **No Applicable Steps**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["No applicable next steps found for this user level"],
|
||||
"error": "NO_APPLICABLE_STEPS"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Workflow Not Found**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Workflow not found"],
|
||||
"error": "WORKFLOW_NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
1. **Basic Branching Test**:
|
||||
- Submit artikel dari user level 5
|
||||
- Verify artikel masuk ke Branch A (Level 2)
|
||||
- Approve dan verify masuk ke Final Approval
|
||||
|
||||
2. **Multiple Branch Test**:
|
||||
- Submit artikel dari user level 8
|
||||
- Verify artikel masuk ke Branch A (Level 2)
|
||||
- Submit artikel dari user level 12
|
||||
- Verify artikel masuk ke Branch B (Level 3)
|
||||
|
||||
3. **Condition Validation Test**:
|
||||
- Test dengan condition value yang invalid
|
||||
- Test dengan user level yang tidak ada di condition
|
||||
- Test dengan condition type yang tidak supported
|
||||
|
||||
### Test Data Setup
|
||||
|
||||
```sql
|
||||
-- Insert test user levels
|
||||
INSERT INTO user_levels (name, level_number, is_active) VALUES
|
||||
('Level 1', 1, true),
|
||||
('Level 2', 2, true),
|
||||
('Level 3', 3, true),
|
||||
('Level 4', 4, true),
|
||||
('Level 5', 5, true);
|
||||
|
||||
-- Insert test users
|
||||
INSERT INTO users (username, email, user_level_id) VALUES
|
||||
('user_level_5', 'user5@test.com', 5),
|
||||
('user_level_8', 'user8@test.com', 8),
|
||||
('user_level_12', 'user12@test.com', 12);
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Linear to Multi-Branch
|
||||
|
||||
1. **Backup existing data**:
|
||||
```sql
|
||||
CREATE TABLE approval_workflow_steps_backup AS SELECT * FROM approval_workflow_steps;
|
||||
CREATE TABLE article_approval_flows_backup AS SELECT * FROM article_approval_flows;
|
||||
```
|
||||
|
||||
2. **Add new columns**:
|
||||
```sql
|
||||
-- Run the ALTER TABLE statements mentioned above
|
||||
```
|
||||
|
||||
3. **Migrate existing workflows**:
|
||||
```sql
|
||||
-- Set default values for existing steps
|
||||
UPDATE approval_workflow_steps SET
|
||||
condition_type = 'always',
|
||||
branch_name = 'default_branch',
|
||||
branch_order = step_order
|
||||
WHERE condition_type IS NULL;
|
||||
```
|
||||
|
||||
4. **Test migration**:
|
||||
- Verify existing workflows still work
|
||||
- Test new multi-branch functionality
|
||||
- Rollback if issues found
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Database Indexes**:
|
||||
```sql
|
||||
CREATE INDEX idx_approval_workflow_steps_condition ON approval_workflow_steps(condition_type, branch_name);
|
||||
CREATE INDEX idx_article_approval_flows_branch ON article_approval_flows(current_branch);
|
||||
```
|
||||
|
||||
2. **Caching**:
|
||||
- Cache workflow steps by condition type
|
||||
- Cache user level mappings
|
||||
- Cache branch determination results
|
||||
|
||||
3. **Query Optimization**:
|
||||
- Use proper JOINs for branch queries
|
||||
- Limit result sets with pagination
|
||||
- Use prepared statements for repeated queries
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Access Control**:
|
||||
- Validate user permissions for each approval step
|
||||
- Ensure users can only approve steps they're authorized for
|
||||
- Log all approval actions for audit
|
||||
|
||||
2. **Data Validation**:
|
||||
- Validate condition values before processing
|
||||
- Sanitize JSON input in condition_value
|
||||
- Check for SQL injection in dynamic queries
|
||||
|
||||
3. **Audit Trail**:
|
||||
- Log all branch decisions
|
||||
- Track condition evaluations
|
||||
- Maintain approval history with branch information
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Steps Not Found**:
|
||||
- Check condition_type and condition_value
|
||||
- Verify user level mapping
|
||||
- Check workflow activation status
|
||||
|
||||
2. **Infinite Loops**:
|
||||
- Validate step dependencies
|
||||
- Check for circular references in parent_step_id
|
||||
- Ensure proper step ordering
|
||||
|
||||
3. **Performance Issues**:
|
||||
- Check database indexes
|
||||
- Optimize condition evaluation queries
|
||||
- Consider caching strategies
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check workflow steps
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check next steps for specific flow
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/123/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check user level
|
||||
curl -X GET "http://localhost:8080/api/users/456" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Visual Workflow Designer**: UI untuk membuat workflow secara visual
|
||||
2. **Condition Builder**: UI untuk membuat kondisi tanpa JSON manual
|
||||
3. **Workflow Templates**: Template workflow untuk berbagai use case
|
||||
4. **Advanced Branching**: Support untuk conditional branching yang lebih kompleks
|
||||
5. **Parallel Processing**: True parallel approval processing
|
||||
6. **Workflow Analytics**: Analytics untuk workflow performance dan bottlenecks
|
||||
|
|
@ -0,0 +1,673 @@
|
|||
# Multi-Branch Approval System - Curl Examples
|
||||
|
||||
## Setup dan Konfigurasi
|
||||
|
||||
### 1. Buat Multi-Branch Workflow
|
||||
|
||||
```bash
|
||||
# Example 1: Simple Two-Branch Workflow
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Simple Two-Branch Approval",
|
||||
"description": "Workflow dengan 2 cabang berdasarkan user level",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Branch A - Level 2",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6]}",
|
||||
"branchName": "Branch_A",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Branch B - Level 3",
|
||||
"requiredUserLevelId": 3,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [7,8,9]}",
|
||||
"branchName": "Branch_B",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 2. Complex Multi-Level Branching
|
||||
|
||||
```bash
|
||||
# Example 2: Complex Multi-Level Branching
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Complex Multi-Level Branching",
|
||||
"description": "Workflow dengan multiple level branching",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Branch",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6,7,8,9]}",
|
||||
"branchName": "Branch_A",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [10,11,12,13,14,15]}",
|
||||
"branchName": "Branch_B",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Sub-branch A1",
|
||||
"requiredUserLevelId": 4,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6]}",
|
||||
"branchName": "SubBranch_A1",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Sub-branch A2",
|
||||
"requiredUserLevelId": 5,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [7,8,9]}",
|
||||
"branchName": "SubBranch_A2",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 3,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. Workflow dengan Specific User Levels
|
||||
|
||||
```bash
|
||||
# Example 3: Workflow untuk specific user levels
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Specific User Level Workflow",
|
||||
"description": "Workflow untuk user level tertentu",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Approval",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level",
|
||||
"conditionValue": "[4,5,6]",
|
||||
"branchName": "Level2_Branch",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Approval",
|
||||
"requiredUserLevelId": 3,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level",
|
||||
"conditionValue": "[7,8,9]",
|
||||
"branchName": "Level3_Branch",
|
||||
"branchOrder": 1
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Workflow Management
|
||||
|
||||
### 1. Get All Workflows
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 2. Get Workflow dengan Steps
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 3. Update Workflow
|
||||
|
||||
```bash
|
||||
curl -X PUT "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Updated Multi-Branch Workflow",
|
||||
"description": "Updated description",
|
||||
"isActive": true,
|
||||
"isDefault": false,
|
||||
"requiresApproval": true,
|
||||
"autoPublish": false,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Updated Step",
|
||||
"requiredUserLevelId": 2,
|
||||
"canSkip": false,
|
||||
"isActive": true,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6]}",
|
||||
"branchName": "Updated_Branch",
|
||||
"branchOrder": 1
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Activate/Deactivate Workflow
|
||||
|
||||
```bash
|
||||
# Activate workflow
|
||||
curl -X PUT "http://localhost:8080/api/approval-workflows/1/activate" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Deactivate workflow
|
||||
curl -X PUT "http://localhost:8080/api/approval-workflows/1/deactivate" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Article Approval Flow
|
||||
|
||||
### 1. Submit Article untuk Approval
|
||||
|
||||
```bash
|
||||
# Submit artikel dengan workflow ID tertentu
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
|
||||
# Submit artikel dengan default workflow
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
### 2. Process Multi-Branch Approval
|
||||
|
||||
```bash
|
||||
# Approve dengan multi-branch logic
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Article looks good, approved for next level"
|
||||
}'
|
||||
|
||||
# Approve dengan message yang berbeda
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Content is excellent, proceeding to final approval"
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. Get Next Steps Preview
|
||||
|
||||
```bash
|
||||
# Get preview next steps
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 4. Traditional Approval (untuk backward compatibility)
|
||||
|
||||
```bash
|
||||
# Approve dengan method traditional
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Approved using traditional method"
|
||||
}'
|
||||
```
|
||||
|
||||
### 5. Reject Article
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/reject" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"reason": "Content needs improvement"
|
||||
}'
|
||||
```
|
||||
|
||||
### 6. Request Revision
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/request-revision" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"revisionMessage": "Please improve the introduction section"
|
||||
}'
|
||||
```
|
||||
|
||||
### 7. Resubmit After Revision
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/resubmit" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Monitoring dan Analytics
|
||||
|
||||
### 1. Get Approval History
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/articles/123/approval-history" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 2. Get My Approval Queue
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/my-queue" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 3. Get Pending Approvals
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/pending-approvals" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 4. Get Dashboard Statistics
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/dashboard-stats" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### 5. Get Workload Statistics
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/workload-stats" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## Testing Scenarios
|
||||
|
||||
### Scenario 1: User Level 5 Submit Article
|
||||
|
||||
```bash
|
||||
# 1. Submit artikel dari user level 5
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_LEVEL_5_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
|
||||
# 2. Check next steps preview
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer USER_LEVEL_5_TOKEN"
|
||||
|
||||
# 3. Approve dengan multi-branch logic
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_2_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Approved by Level 2 approver"
|
||||
}'
|
||||
|
||||
# 4. Final approval
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_1_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Final approval completed"
|
||||
}'
|
||||
```
|
||||
|
||||
### Scenario 2: User Level 8 Submit Article
|
||||
|
||||
```bash
|
||||
# 1. Submit artikel dari user level 8
|
||||
curl -X POST "http://localhost:8080/api/articles/124/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_LEVEL_8_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
|
||||
# 2. Check next steps preview (should show different branch)
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/457/next-steps-preview" \
|
||||
-H "Authorization: Bearer USER_LEVEL_8_TOKEN"
|
||||
|
||||
# 3. Approve dengan multi-branch logic
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/457/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_3_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Approved by Level 3 approver"
|
||||
}'
|
||||
|
||||
# 4. Final approval
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/457/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_1_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Final approval completed"
|
||||
}'
|
||||
```
|
||||
|
||||
### Scenario 3: User Level 12 Submit Article
|
||||
|
||||
```bash
|
||||
# 1. Submit artikel dari user level 12
|
||||
curl -X POST "http://localhost:8080/api/articles/125/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_LEVEL_12_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
|
||||
# 2. Check next steps preview (should show Branch B)
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/458/next-steps-preview" \
|
||||
-H "Authorization: Bearer USER_LEVEL_12_TOKEN"
|
||||
|
||||
# 3. Approve dengan multi-branch logic
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/458/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_3_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Approved by Level 3 approver for Branch B"
|
||||
}'
|
||||
|
||||
# 4. Final approval
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/458/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer LEVEL_1_APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Final approval completed"
|
||||
}'
|
||||
```
|
||||
|
||||
## Error Handling Examples
|
||||
|
||||
### 1. Invalid Workflow ID
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 999
|
||||
}'
|
||||
```
|
||||
|
||||
**Expected Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Workflow not found"],
|
||||
"error": "WORKFLOW_NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Invalid Condition Value
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Invalid Workflow",
|
||||
"description": "Workflow dengan invalid condition",
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Invalid Step",
|
||||
"requiredUserLevelId": 2,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "invalid json",
|
||||
"branchName": "Invalid_Branch"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
**Expected Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Invalid condition value format"],
|
||||
"error": "CONDITION_VALUE_INVALID"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. No Applicable Steps
|
||||
|
||||
```bash
|
||||
# Submit artikel dari user level yang tidak ada di condition
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_LEVEL_99_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
```
|
||||
|
||||
**Expected Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["No applicable steps found for this user level"],
|
||||
"error": "NO_APPLICABLE_STEPS"
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### 1. Load Testing dengan Multiple Submissions
|
||||
|
||||
```bash
|
||||
# Script untuk test multiple submissions
|
||||
for i in {1..100}; do
|
||||
curl -X POST "http://localhost:8080/api/articles/$i/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}' &
|
||||
done
|
||||
wait
|
||||
```
|
||||
|
||||
### 2. Concurrent Approval Testing
|
||||
|
||||
```bash
|
||||
# Script untuk test concurrent approvals
|
||||
for i in {1..50}; do
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/$i/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer APPROVER_TOKEN" \
|
||||
-d '{
|
||||
"message": "Concurrent approval test"
|
||||
}' &
|
||||
done
|
||||
wait
|
||||
```
|
||||
|
||||
## Debugging Commands
|
||||
|
||||
### 1. Check Workflow Steps
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" | jq '.'
|
||||
```
|
||||
|
||||
### 2. Check User Level
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/users/123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" | jq '.data.user_levels'
|
||||
```
|
||||
|
||||
### 3. Check Approval Flow Status
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" | jq '.'
|
||||
```
|
||||
|
||||
### 4. Check Next Steps Preview
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" | jq '.'
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Set environment variables untuk testing
|
||||
export API_BASE_URL="http://localhost:8080"
|
||||
export ADMIN_TOKEN="your_admin_token_here"
|
||||
export USER_LEVEL_5_TOKEN="your_user_level_5_token_here"
|
||||
export USER_LEVEL_8_TOKEN="your_user_level_8_token_here"
|
||||
export USER_LEVEL_12_TOKEN="your_user_level_12_token_here"
|
||||
export LEVEL_1_APPROVER_TOKEN="your_level_1_approver_token_here"
|
||||
export LEVEL_2_APPROVER_TOKEN="your_level_2_approver_token_here"
|
||||
export LEVEL_3_APPROVER_TOKEN="your_level_3_approver_token_here"
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
### 1. Create Multiple Workflows
|
||||
|
||||
```bash
|
||||
# Script untuk create multiple workflows
|
||||
for i in {1..5}; do
|
||||
curl -X POST "$API_BASE_URL/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
||||
-d "{
|
||||
\"name\": \"Workflow $i\",
|
||||
\"description\": \"Test workflow $i\",
|
||||
\"isActive\": true,
|
||||
\"steps\": [
|
||||
{
|
||||
\"stepOrder\": 1,
|
||||
\"stepName\": \"Step 1\",
|
||||
\"requiredUserLevelId\": 2,
|
||||
\"conditionType\": \"always\",
|
||||
\"branchName\": \"Branch_$i\"
|
||||
}
|
||||
]
|
||||
}"
|
||||
done
|
||||
```
|
||||
|
||||
### 2. Submit Multiple Articles
|
||||
|
||||
```bash
|
||||
# Script untuk submit multiple articles
|
||||
for i in {1..10}; do
|
||||
curl -X POST "$API_BASE_URL/api/articles/$i/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $USER_LEVEL_5_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
done
|
||||
```
|
||||
|
||||
## Monitoring Commands
|
||||
|
||||
### 1. Check System Health
|
||||
|
||||
```bash
|
||||
curl -X GET "$API_BASE_URL/health" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
### 2. Get System Statistics
|
||||
|
||||
```bash
|
||||
curl -X GET "$API_BASE_URL/api/article-approval-flows/dashboard-stats" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.'
|
||||
```
|
||||
|
||||
### 3. Check Workflow Performance
|
||||
|
||||
```bash
|
||||
curl -X GET "$API_BASE_URL/api/approval-workflows" \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.data | length'
|
||||
```
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
# Multi-Branch Approval System Implementation
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
Sistem Multi-Branch Approval telah berhasil diimplementasikan untuk mendukung workflow approval yang dapat bercabang berdasarkan User Level dari submitter artikel. Implementasi ini memungkinkan:
|
||||
|
||||
- **Conditional Routing**: Approval flow mengikuti jalur berbeda berdasarkan level user yang submit artikel
|
||||
- **Hierarchical Branching**: Support untuk struktur cabang yang kompleks dengan multiple level
|
||||
- **Dynamic Configuration**: Workflow dapat dikonfigurasi secara dinamis tanpa mengubah kode
|
||||
- **Backward Compatibility**: Sistem lama tetap berfungsi tanpa perubahan
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Database Migration
|
||||
|
||||
Jalankan script migration untuk menambahkan support multi-branch:
|
||||
|
||||
```bash
|
||||
# Jalankan migration script
|
||||
psql -d your_database -f docs/migrations/add_multi_branch_support.sql
|
||||
```
|
||||
|
||||
### 2. Create Multi-Branch Workflow
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/approval-workflows/with-steps" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"name": "Multi-Branch Article Approval",
|
||||
"description": "Workflow dengan cabang berdasarkan user level",
|
||||
"isActive": true,
|
||||
"steps": [
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 2 Branch",
|
||||
"requiredUserLevelId": 2,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6,7,8,9]}",
|
||||
"branchName": "Branch_A"
|
||||
},
|
||||
{
|
||||
"stepOrder": 1,
|
||||
"stepName": "Level 3 Branch",
|
||||
"requiredUserLevelId": 3,
|
||||
"conditionType": "user_level_hierarchy",
|
||||
"conditionValue": "{\"applies_to_levels\": [10,11,12,13,14,15]}",
|
||||
"branchName": "Branch_B"
|
||||
},
|
||||
{
|
||||
"stepOrder": 2,
|
||||
"stepName": "Level 1 Final Approval",
|
||||
"requiredUserLevelId": 1,
|
||||
"conditionType": "always",
|
||||
"branchName": "Final_Approval"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. Submit Article untuk Approval
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"workflowId": 1
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Process Multi-Branch Approval
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/article-approval-flows/456/multi-branch-approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Article looks good, approved for next level"
|
||||
}'
|
||||
```
|
||||
|
||||
## 📁 File Changes
|
||||
|
||||
### Database Entities
|
||||
|
||||
1. **`app/database/entity/approval_workflow_steps.entity.go`**
|
||||
- Added multi-branch support fields
|
||||
- Added parent-child relationships
|
||||
- Added condition types and values
|
||||
|
||||
2. **`app/database/entity/article_approval_flows.entity.go`**
|
||||
- Added branch tracking fields
|
||||
- Added parallel flow support
|
||||
- Added parent-child flow relationships
|
||||
|
||||
### Service Layer
|
||||
|
||||
3. **`app/module/approval_workflow_steps/repository/approval_workflow_steps.repository.go`**
|
||||
- Added multi-branch query methods
|
||||
- Added condition-based filtering
|
||||
- Added branch-specific queries
|
||||
|
||||
4. **`app/module/article_approval_flows/service/article_approval_flows.service.go`**
|
||||
- Added multi-branch approval processing
|
||||
- Added condition evaluation logic
|
||||
- Added branch determination methods
|
||||
|
||||
### API Layer
|
||||
|
||||
5. **`app/module/approval_workflows/request/approval_workflows.request.go`**
|
||||
- Added multi-branch fields to request structures
|
||||
- Updated entity conversion methods
|
||||
|
||||
6. **`app/module/article_approval_flows/controller/article_approval_flows.controller.go`**
|
||||
- Added multi-branch approval endpoints
|
||||
- Added next steps preview endpoint
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Condition Types
|
||||
|
||||
1. **`user_level`**: Steps berlaku untuk user level tertentu
|
||||
```json
|
||||
"conditionValue": "[4, 5, 6]"
|
||||
```
|
||||
|
||||
2. **`user_level_hierarchy`**: Steps berlaku berdasarkan hierarki
|
||||
```json
|
||||
"conditionValue": "{\"applies_to_levels\": [4,5,6], \"min_level\": 4, \"max_level\": 9}"
|
||||
```
|
||||
|
||||
3. **`always`**: Steps selalu berlaku
|
||||
```json
|
||||
"conditionValue": "{}"
|
||||
```
|
||||
|
||||
4. **`custom`**: Steps dengan kondisi custom (untuk ekstensi masa depan)
|
||||
|
||||
### Branch Configuration
|
||||
|
||||
- **`branchName`**: Nama cabang untuk identifikasi
|
||||
- **`branchOrder`**: Urutan dalam cabang yang sama
|
||||
- **`parentStepId`**: ID step parent untuk hierarchical branching
|
||||
- **`isParallel`**: Apakah step ini berjalan parallel
|
||||
|
||||
## 📊 Flow Examples
|
||||
|
||||
### Example 1: User Level 5 Submit Article
|
||||
|
||||
```
|
||||
User Level 5 → Branch A (Level 2) → Final Approval (Level 1)
|
||||
```
|
||||
|
||||
### Example 2: User Level 8 Submit Article
|
||||
|
||||
```
|
||||
User Level 8 → Branch A (Level 2) → Final Approval (Level 1)
|
||||
```
|
||||
|
||||
### Example 3: User Level 12 Submit Article
|
||||
|
||||
```
|
||||
User Level 12 → Branch B (Level 3) → Final Approval (Level 1)
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
1. **Basic Branching Test**:
|
||||
```bash
|
||||
# Submit dari user level 5
|
||||
curl -X POST "http://localhost:8080/api/articles/123/submit-approval" \
|
||||
-H "Authorization: Bearer USER_LEVEL_5_TOKEN" \
|
||||
-d '{"workflowId": 1}'
|
||||
|
||||
# Check next steps
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer USER_LEVEL_5_TOKEN"
|
||||
```
|
||||
|
||||
2. **Multiple Branch Test**:
|
||||
```bash
|
||||
# Test dengan user level berbeda
|
||||
curl -X POST "http://localhost:8080/api/articles/124/submit-approval" \
|
||||
-H "Authorization: Bearer USER_LEVEL_12_TOKEN" \
|
||||
-d '{"workflowId": 1}'
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
|
||||
```bash
|
||||
# Load testing dengan multiple submissions
|
||||
for i in {1..100}; do
|
||||
curl -X POST "http://localhost:8080/api/articles/$i/submit-approval" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{"workflowId": 1}' &
|
||||
done
|
||||
wait
|
||||
```
|
||||
|
||||
## 🔍 Monitoring
|
||||
|
||||
### Check Workflow Status
|
||||
|
||||
```bash
|
||||
# Get workflow dengan steps
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Get next steps preview
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/456/next-steps-preview" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
### Dashboard Statistics
|
||||
|
||||
```bash
|
||||
# Get approval statistics
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/dashboard-stats" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## 🚨 Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
1. **No Applicable Steps**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["No applicable next steps found for this user level"],
|
||||
"error": "NO_APPLICABLE_STEPS"
|
||||
}
|
||||
```
|
||||
|
||||
2. **Invalid Condition Value**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Invalid condition value format"],
|
||||
"error": "CONDITION_VALUE_INVALID"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Workflow Not Found**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"messages": ["Workflow not found"],
|
||||
"error": "WORKFLOW_NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 Migration Guide
|
||||
|
||||
### From Linear to Multi-Branch
|
||||
|
||||
1. **Backup existing data**:
|
||||
```sql
|
||||
CREATE TABLE approval_workflow_steps_backup AS SELECT * FROM approval_workflow_steps;
|
||||
CREATE TABLE article_approval_flows_backup AS SELECT * FROM article_approval_flows;
|
||||
```
|
||||
|
||||
2. **Run migration script**:
|
||||
```bash
|
||||
psql -d your_database -f docs/migrations/add_multi_branch_support.sql
|
||||
```
|
||||
|
||||
3. **Test migration**:
|
||||
- Verify existing workflows still work
|
||||
- Test new multi-branch functionality
|
||||
- Rollback if issues found
|
||||
|
||||
### Rollback (if needed)
|
||||
|
||||
```sql
|
||||
-- Uncomment rollback section in migration script
|
||||
-- Run rollback commands to revert changes
|
||||
```
|
||||
|
||||
## 📈 Performance Considerations
|
||||
|
||||
### Database Indexes
|
||||
|
||||
```sql
|
||||
-- Indexes untuk performance
|
||||
CREATE INDEX idx_approval_workflow_steps_condition ON approval_workflow_steps(condition_type, branch_name);
|
||||
CREATE INDEX idx_article_approval_flows_branch ON article_approval_flows(current_branch);
|
||||
```
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
- Cache workflow steps by condition type
|
||||
- Cache user level mappings
|
||||
- Cache branch determination results
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### Access Control
|
||||
|
||||
- Validate user permissions for each approval step
|
||||
- Ensure users can only approve steps they're authorized for
|
||||
- Log all approval actions for audit
|
||||
|
||||
### Data Validation
|
||||
|
||||
- Validate condition values before processing
|
||||
- Sanitize JSON input in condition_value
|
||||
- Check for SQL injection in dynamic queries
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Steps Not Found**:
|
||||
- Check condition_type and condition_value
|
||||
- Verify user level mapping
|
||||
- Check workflow activation status
|
||||
|
||||
2. **Infinite Loops**:
|
||||
- Validate step dependencies
|
||||
- Check for circular references in parent_step_id
|
||||
- Ensure proper step ordering
|
||||
|
||||
3. **Performance Issues**:
|
||||
- Check database indexes
|
||||
- Optimize condition evaluation queries
|
||||
- Consider caching strategies
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check workflow steps
|
||||
curl -X GET "http://localhost:8080/api/approval-workflows/1/with-steps" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check user level
|
||||
curl -X GET "http://localhost:8080/api/users/456" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
# Check approval flow status
|
||||
curl -X GET "http://localhost:8080/api/article-approval-flows/123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
## 🚀 Future Enhancements
|
||||
|
||||
1. **Visual Workflow Designer**: UI untuk membuat workflow secara visual
|
||||
2. **Condition Builder**: UI untuk membuat kondisi tanpa JSON manual
|
||||
3. **Workflow Templates**: Template workflow untuk berbagai use case
|
||||
4. **Advanced Branching**: Support untuk conditional branching yang lebih kompleks
|
||||
5. **Parallel Processing**: True parallel approval processing
|
||||
6. **Workflow Analytics**: Analytics untuk workflow performance dan bottlenecks
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- [Multi-Branch Approval Guide](docs/MULTI_BRANCH_APPROVAL_GUIDE.md)
|
||||
- [Curl Examples](docs/MULTI_BRANCH_CURL_EXAMPLES.md)
|
||||
- [Database Migration](docs/migrations/add_multi_branch_support.sql)
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
Untuk pertanyaan atau masalah dengan implementasi multi-branch approval system, silakan:
|
||||
|
||||
1. Check dokumentasi lengkap di `docs/` folder
|
||||
2. Review curl examples untuk testing
|
||||
3. Check troubleshooting section
|
||||
4. Contact development team untuk support lebih lanjut
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Implementasi selesai dan siap untuk production
|
||||
**Version**: 1.0.0
|
||||
**Last Updated**: $(date)
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
# Troubleshooting Auto Approval Flow
|
||||
|
||||
## 🚨 **Masalah: Artikel Tidak Masuk ke Approval Queue**
|
||||
|
||||
### **Gejala:**
|
||||
- Artikel dibuat dengan `workflow_id = NULL`
|
||||
- Tidak ada record di `article_approval_flows` table
|
||||
- User approver tidak melihat artikel di queue mereka
|
||||
|
||||
### **Penyebab Umum:**
|
||||
|
||||
#### **1. User Level Tidak Memerlukan Approval**
|
||||
```sql
|
||||
-- Cek user level
|
||||
SELECT
|
||||
id,
|
||||
level_name,
|
||||
level_number,
|
||||
is_approval_active
|
||||
FROM user_levels
|
||||
WHERE id = 5;
|
||||
```
|
||||
|
||||
**Solusi:**
|
||||
```sql
|
||||
-- Set user level memerlukan approval
|
||||
UPDATE user_levels
|
||||
SET is_approval_active = true
|
||||
WHERE id = 5;
|
||||
```
|
||||
|
||||
#### **2. Tidak Ada Default Workflow**
|
||||
```sql
|
||||
-- Cek default workflow
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_active,
|
||||
is_default,
|
||||
client_id
|
||||
FROM approval_workflows
|
||||
WHERE is_default = true
|
||||
AND is_active = true;
|
||||
```
|
||||
|
||||
**Solusi:**
|
||||
```sql
|
||||
-- Set workflow sebagai default
|
||||
UPDATE approval_workflows
|
||||
SET is_default = true, is_active = true
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
```
|
||||
|
||||
#### **3. Workflow Tidak Aktif**
|
||||
```sql
|
||||
-- Cek status workflow
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_active,
|
||||
is_default
|
||||
FROM approval_workflows
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
```
|
||||
|
||||
**Solusi:**
|
||||
```sql
|
||||
-- Aktifkan workflow
|
||||
UPDATE approval_workflows
|
||||
SET is_active = true
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
```
|
||||
|
||||
#### **4. Client ID Mismatch**
|
||||
```sql
|
||||
-- Cek client ID workflow vs user
|
||||
SELECT
|
||||
aw.id as workflow_id,
|
||||
aw.name as workflow_name,
|
||||
aw.client_id as workflow_client_id,
|
||||
u.id as user_id,
|
||||
u.client_id as user_client_id
|
||||
FROM approval_workflows aw
|
||||
CROSS JOIN users u
|
||||
WHERE u.id = 5
|
||||
AND aw.is_default = true;
|
||||
```
|
||||
|
||||
**Solusi:**
|
||||
```sql
|
||||
-- Update workflow client ID jika perlu
|
||||
UPDATE approval_workflows
|
||||
SET client_id = '338571d5-3836-47c0-a84f-e88f6fbcbb09'
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
```
|
||||
|
||||
### **Debug Steps:**
|
||||
|
||||
#### **Step 1: Cek Log Aplikasi**
|
||||
```bash
|
||||
# Cek log aplikasi untuk error messages
|
||||
tail -f logs/app.log | grep -i "approval\|workflow\|branch"
|
||||
```
|
||||
|
||||
**Expected Log Messages:**
|
||||
```
|
||||
User level requires approval - setting to pending
|
||||
User level requires approval - proceeding with workflow assignment
|
||||
Found default workflow
|
||||
Article updated with workflow successfully
|
||||
Approval flow created successfully
|
||||
Multi-branch flow initialized successfully
|
||||
```
|
||||
|
||||
#### **Step 2: Cek Database State**
|
||||
```sql
|
||||
-- 1. Cek user level
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.user_level_id,
|
||||
ul.level_name,
|
||||
ul.level_number,
|
||||
ul.is_approval_active
|
||||
FROM users u
|
||||
JOIN user_levels ul ON u.user_level_id = ul.id
|
||||
WHERE u.id = 5;
|
||||
|
||||
-- 2. Cek default workflow
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_active,
|
||||
is_default,
|
||||
client_id
|
||||
FROM approval_workflows
|
||||
WHERE is_default = true
|
||||
AND is_active = true;
|
||||
|
||||
-- 3. Cek artikel terbaru
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
created_by_id,
|
||||
workflow_id,
|
||||
current_approval_step,
|
||||
status_id,
|
||||
bypass_approval,
|
||||
approval_exempt,
|
||||
created_at
|
||||
FROM articles
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 3;
|
||||
|
||||
-- 4. Cek approval flows
|
||||
SELECT
|
||||
aaf.id,
|
||||
aaf.article_id,
|
||||
aaf.workflow_id,
|
||||
aaf.current_step,
|
||||
aaf.current_branch,
|
||||
aaf.status_id,
|
||||
aaf.submitted_by_id,
|
||||
aaf.submitted_at,
|
||||
a.title as article_title
|
||||
FROM article_approval_flows aaf
|
||||
JOIN articles a ON aaf.article_id = a.id
|
||||
ORDER BY aaf.created_at DESC
|
||||
LIMIT 3;
|
||||
```
|
||||
|
||||
#### **Step 3: Test dengan API**
|
||||
```bash
|
||||
# Test create artikel baru
|
||||
curl -X POST "http://localhost:8080/api/articles" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer USER_LEVEL_5_TOKEN" \
|
||||
-d '{
|
||||
"title": "Test Debug Article",
|
||||
"content": "Debug content",
|
||||
"categoryId": 1,
|
||||
"isDraft": false
|
||||
}'
|
||||
|
||||
# Cek approval status
|
||||
curl -X GET "http://localhost:8080/api/articles/{article_id}/approval-status" \
|
||||
-H "Authorization: Bearer USER_TOKEN"
|
||||
```
|
||||
|
||||
### **Quick Fix Script:**
|
||||
|
||||
```sql
|
||||
-- Fix script untuk masalah umum
|
||||
-- 1. Pastikan user level 5 memerlukan approval
|
||||
UPDATE user_levels
|
||||
SET is_approval_active = true
|
||||
WHERE id = 5;
|
||||
|
||||
-- 2. Pastikan workflow adalah default
|
||||
UPDATE approval_workflows
|
||||
SET is_default = true, is_active = true
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
|
||||
-- 3. Pastikan client ID sama
|
||||
UPDATE approval_workflows
|
||||
SET client_id = '338571d5-3836-47c0-a84f-e88f6fbcbb09'
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
|
||||
-- 4. Verifikasi fix
|
||||
SELECT
|
||||
ul.id as level_id,
|
||||
ul.level_name,
|
||||
ul.is_approval_active,
|
||||
aw.id as workflow_id,
|
||||
aw.name as workflow_name,
|
||||
aw.is_active,
|
||||
aw.is_default,
|
||||
aw.client_id
|
||||
FROM user_levels ul
|
||||
CROSS JOIN approval_workflows aw
|
||||
WHERE ul.id = 5
|
||||
AND aw.is_default = true;
|
||||
```
|
||||
|
||||
### **Expected Results After Fix:**
|
||||
|
||||
#### **Database State:**
|
||||
```sql
|
||||
-- Artikel harus memiliki workflow_id
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
workflow_id,
|
||||
current_approval_step,
|
||||
status_id
|
||||
FROM articles
|
||||
WHERE title = 'Test Tni Artikel 1';
|
||||
|
||||
-- Approval flow harus terbuat
|
||||
SELECT
|
||||
aaf.id,
|
||||
aaf.article_id,
|
||||
aaf.workflow_id,
|
||||
aaf.current_step,
|
||||
aaf.current_branch,
|
||||
aaf.status_id
|
||||
FROM article_approval_flows aaf
|
||||
JOIN articles a ON aaf.article_id = a.id
|
||||
WHERE a.title = 'Test Tni Artikel 1';
|
||||
```
|
||||
|
||||
#### **API Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"article_id": 123,
|
||||
"workflow_id": 1,
|
||||
"current_step": 1,
|
||||
"current_branch": "Branch_A",
|
||||
"status": "pending_approval",
|
||||
"next_approver_level": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Monitoring Commands:**
|
||||
|
||||
```bash
|
||||
# Monitor log real-time
|
||||
tail -f logs/app.log | grep -E "(approval|workflow|branch|User level)"
|
||||
|
||||
# Cek database changes
|
||||
watch -n 5 "psql -d your_db -c \"SELECT COUNT(*) FROM article_approval_flows WHERE created_at > NOW() - INTERVAL '1 hour';\""
|
||||
|
||||
# Test API endpoint
|
||||
watch -n 10 "curl -s http://localhost:8080/api/article-approval-flows/my-queue -H 'Authorization: Bearer APPROVER_TOKEN' | jq '.data | length'"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Jika masalah masih terjadi setelah mengikuti troubleshooting ini, cek log aplikasi untuk error messages yang lebih spesifik!** 🔍
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
-- Multi-Branch Approval System Database Migration Script
|
||||
-- Run this script to add multi-branch support to existing database
|
||||
|
||||
-- =====================================================
|
||||
-- 1. ADD NEW COLUMNS TO APPROVAL_WORKFLOW_STEPS TABLE
|
||||
-- =====================================================
|
||||
|
||||
-- Add multi-branch support fields
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS parent_step_id INT REFERENCES approval_workflow_steps(id);
|
||||
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS condition_type VARCHAR(50);
|
||||
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS condition_value TEXT;
|
||||
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS is_parallel BOOLEAN DEFAULT false;
|
||||
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS branch_name VARCHAR(100);
|
||||
|
||||
ALTER TABLE approval_workflow_steps
|
||||
ADD COLUMN IF NOT EXISTS branch_order INT;
|
||||
|
||||
-- =====================================================
|
||||
-- 2. ADD NEW COLUMNS TO ARTICLE_APPROVAL_FLOWS TABLE
|
||||
-- =====================================================
|
||||
|
||||
-- Add multi-branch support fields
|
||||
ALTER TABLE article_approval_flows
|
||||
ADD COLUMN IF NOT EXISTS current_branch VARCHAR(100);
|
||||
|
||||
ALTER TABLE article_approval_flows
|
||||
ADD COLUMN IF NOT EXISTS branch_path TEXT;
|
||||
|
||||
ALTER TABLE article_approval_flows
|
||||
ADD COLUMN IF NOT EXISTS is_parallel_flow BOOLEAN DEFAULT false;
|
||||
|
||||
ALTER TABLE article_approval_flows
|
||||
ADD COLUMN IF NOT EXISTS parent_flow_id INT REFERENCES article_approval_flows(id);
|
||||
|
||||
-- =====================================================
|
||||
-- 3. CREATE INDEXES FOR PERFORMANCE
|
||||
-- =====================================================
|
||||
|
||||
-- Indexes for approval_workflow_steps
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_workflow_steps_condition
|
||||
ON approval_workflow_steps(condition_type, branch_name);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_workflow_steps_parent
|
||||
ON approval_workflow_steps(parent_step_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_approval_workflow_steps_branch
|
||||
ON approval_workflow_steps(branch_name, branch_order);
|
||||
|
||||
-- Indexes for article_approval_flows
|
||||
CREATE INDEX IF NOT EXISTS idx_article_approval_flows_branch
|
||||
ON article_approval_flows(current_branch);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_article_approval_flows_parent
|
||||
ON article_approval_flows(parent_flow_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_article_approval_flows_parallel
|
||||
ON article_approval_flows(is_parallel_flow);
|
||||
|
||||
-- =====================================================
|
||||
-- 4. MIGRATE EXISTING DATA
|
||||
-- =====================================================
|
||||
|
||||
-- Set default values for existing approval_workflow_steps
|
||||
UPDATE approval_workflow_steps
|
||||
SET
|
||||
condition_type = 'always',
|
||||
branch_name = 'default_branch',
|
||||
branch_order = step_order,
|
||||
is_parallel = false
|
||||
WHERE condition_type IS NULL;
|
||||
|
||||
-- Set default values for existing article_approval_flows
|
||||
UPDATE article_approval_flows
|
||||
SET
|
||||
current_branch = 'default_branch',
|
||||
branch_path = '["default_branch"]',
|
||||
is_parallel_flow = false
|
||||
WHERE current_branch IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- 5. CREATE DEFAULT MULTI-BRANCH WORKFLOW
|
||||
-- =====================================================
|
||||
|
||||
-- Insert default multi-branch workflow
|
||||
INSERT INTO approval_workflows (name, description, is_active, is_default, requires_approval, auto_publish, created_at, updated_at)
|
||||
VALUES (
|
||||
'Default Multi-Branch Approval',
|
||||
'Default workflow dengan multi-branch support',
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
||||
-- Get the workflow ID (assuming it's the last inserted)
|
||||
-- Note: In production, you might want to use a specific ID or handle this differently
|
||||
DO $$
|
||||
DECLARE
|
||||
workflow_id_var INT;
|
||||
BEGIN
|
||||
-- Get the ID of the workflow we just created
|
||||
SELECT id INTO workflow_id_var
|
||||
FROM approval_workflows
|
||||
WHERE name = 'Default Multi-Branch Approval'
|
||||
ORDER BY id DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- Insert workflow steps for the multi-branch workflow
|
||||
INSERT INTO approval_workflow_steps (
|
||||
workflow_id, step_order, step_name, required_user_level_id,
|
||||
can_skip, is_active, condition_type, condition_value,
|
||||
branch_name, branch_order, created_at, updated_at
|
||||
) VALUES
|
||||
-- Step 1: Branch A (Level 2) - for user levels 4,5,6
|
||||
(
|
||||
workflow_id_var, 1, 'Level 2 Branch', 2,
|
||||
false, true, 'user_level_hierarchy',
|
||||
'{"applies_to_levels": [4,5,6]}',
|
||||
'Branch_A', 1, NOW(), NOW()
|
||||
),
|
||||
-- Step 1: Branch B (Level 3) - for user levels 7,8,9
|
||||
(
|
||||
workflow_id_var, 1, 'Level 3 Branch', 3,
|
||||
false, true, 'user_level_hierarchy',
|
||||
'{"applies_to_levels": [7,8,9]}',
|
||||
'Branch_B', 1, NOW(), NOW()
|
||||
),
|
||||
-- Step 1: Branch C (Level 3) - for user levels 10,11,12
|
||||
(
|
||||
workflow_id_var, 1, 'Level 3 Branch C', 3,
|
||||
false, true, 'user_level_hierarchy',
|
||||
'{"applies_to_levels": [10,11,12]}',
|
||||
'Branch_C', 1, NOW(), NOW()
|
||||
),
|
||||
-- Step 2: Final Approval (Level 1) - always applies
|
||||
(
|
||||
workflow_id_var, 2, 'Level 1 Final Approval', 1,
|
||||
false, true, 'always',
|
||||
'{}',
|
||||
'Final_Approval', 1, NOW(), NOW()
|
||||
);
|
||||
END $$;
|
||||
|
||||
-- =====================================================
|
||||
-- 6. UPDATE EXISTING ARTICLES TO USE NEW WORKFLOW
|
||||
-- =====================================================
|
||||
|
||||
-- Update existing articles to use the new default workflow
|
||||
UPDATE articles
|
||||
SET workflow_id = (
|
||||
SELECT id FROM approval_workflows
|
||||
WHERE name = 'Default Multi-Branch Approval'
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE workflow_id IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- 7. CREATE SAMPLE DATA FOR TESTING
|
||||
-- =====================================================
|
||||
|
||||
-- Insert sample user levels if they don't exist
|
||||
INSERT INTO user_levels (name, alias_name, level_number, is_active, created_at, updated_at)
|
||||
VALUES
|
||||
('Level 1', 'L1', 1, true, NOW(), NOW()),
|
||||
('Level 2', 'L2', 2, true, NOW(), NOW()),
|
||||
('Level 3', 'L3', 3, true, NOW(), NOW()),
|
||||
('Level 4', 'L4', 4, true, NOW(), NOW()),
|
||||
('Level 5', 'L5', 5, true, NOW(), NOW()),
|
||||
('Level 6', 'L6', 6, true, NOW(), NOW()),
|
||||
('Level 7', 'L7', 7, true, NOW(), NOW()),
|
||||
('Level 8', 'L8', 8, true, NOW(), NOW()),
|
||||
('Level 9', 'L9', 9, true, NOW(), NOW()),
|
||||
('Level 10', 'L10', 10, true, NOW(), NOW()),
|
||||
('Level 11', 'L11', 11, true, NOW(), NOW()),
|
||||
('Level 12', 'L12', 12, true, NOW(), NOW())
|
||||
ON CONFLICT (level_number) DO NOTHING;
|
||||
|
||||
-- =====================================================
|
||||
-- 8. VERIFICATION QUERIES
|
||||
-- =====================================================
|
||||
|
||||
-- Verify the migration was successful
|
||||
SELECT 'Migration completed successfully' as status;
|
||||
|
||||
-- Check workflow steps
|
||||
SELECT
|
||||
aws.id,
|
||||
aws.step_order,
|
||||
aws.step_name,
|
||||
aws.required_user_level_id,
|
||||
aws.condition_type,
|
||||
aws.condition_value,
|
||||
aws.branch_name,
|
||||
aws.branch_order,
|
||||
aw.name as workflow_name
|
||||
FROM approval_workflow_steps aws
|
||||
JOIN approval_workflows aw ON aws.workflow_id = aw.id
|
||||
WHERE aw.name = 'Default Multi-Branch Approval'
|
||||
ORDER BY aws.step_order, aws.branch_order;
|
||||
|
||||
-- Check user levels
|
||||
SELECT id, name, level_number, is_active
|
||||
FROM user_levels
|
||||
ORDER BY level_number;
|
||||
|
||||
-- Check articles with workflow assignments
|
||||
SELECT
|
||||
a.id,
|
||||
a.title,
|
||||
a.workflow_id,
|
||||
aw.name as workflow_name
|
||||
FROM articles a
|
||||
LEFT JOIN approval_workflows aw ON a.workflow_id = aw.id
|
||||
LIMIT 10;
|
||||
|
||||
-- =====================================================
|
||||
-- 9. ROLLBACK SCRIPT (if needed)
|
||||
-- =====================================================
|
||||
|
||||
/*
|
||||
-- Uncomment and run this section if you need to rollback the migration
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS idx_approval_workflow_steps_condition;
|
||||
DROP INDEX IF EXISTS idx_approval_workflow_steps_parent;
|
||||
DROP INDEX IF EXISTS idx_approval_workflow_steps_branch;
|
||||
DROP INDEX IF EXISTS idx_article_approval_flows_branch;
|
||||
DROP INDEX IF EXISTS idx_article_approval_flows_parent;
|
||||
DROP INDEX IF EXISTS idx_article_approval_flows_parallel;
|
||||
|
||||
-- Drop new columns from approval_workflow_steps
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS parent_step_id;
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS condition_type;
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS condition_value;
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS is_parallel;
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS branch_name;
|
||||
ALTER TABLE approval_workflow_steps DROP COLUMN IF EXISTS branch_order;
|
||||
|
||||
-- Drop new columns from article_approval_flows
|
||||
ALTER TABLE article_approval_flows DROP COLUMN IF EXISTS current_branch;
|
||||
ALTER TABLE article_approval_flows DROP COLUMN IF EXISTS branch_path;
|
||||
ALTER TABLE article_approval_flows DROP COLUMN IF EXISTS is_parallel_flow;
|
||||
ALTER TABLE article_approval_flows DROP COLUMN IF EXISTS parent_flow_id;
|
||||
|
||||
-- Remove default multi-branch workflow
|
||||
DELETE FROM approval_workflow_steps
|
||||
WHERE workflow_id IN (
|
||||
SELECT id FROM approval_workflows
|
||||
WHERE name = 'Default Multi-Branch Approval'
|
||||
);
|
||||
|
||||
DELETE FROM approval_workflows
|
||||
WHERE name = 'Default Multi-Branch Approval';
|
||||
|
||||
SELECT 'Rollback completed successfully' as status;
|
||||
*/
|
||||
|
||||
-- =====================================================
|
||||
-- 10. PERFORMANCE OPTIMIZATION
|
||||
-- =====================================================
|
||||
|
||||
-- Update table statistics
|
||||
ANALYZE approval_workflow_steps;
|
||||
ANALYZE article_approval_flows;
|
||||
ANALYZE approval_workflows;
|
||||
ANALYZE user_levels;
|
||||
|
||||
-- =====================================================
|
||||
-- END OF MIGRATION SCRIPT
|
||||
-- =====================================================
|
||||
|
|
@ -3081,6 +3081,127 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/multi-branch-approve": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "API for processing multi-branch approval with conditional routing",
|
||||
"tags": [
|
||||
"ArticleApprovalFlows"
|
||||
],
|
||||
"summary": "Process multi-branch approval",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Insert the Authorization",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ArticleApprovalFlows ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Approval action data",
|
||||
"name": "req",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.ApprovalActionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.BadRequestError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.UnauthorizedError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.InternalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/next-steps-preview": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "API for getting preview of next steps based on submitter's user level",
|
||||
"tags": [
|
||||
"ArticleApprovalFlows"
|
||||
],
|
||||
"summary": "Get next steps preview for multi-branch workflow",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Insert the Authorization",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ArticleApprovalFlows ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.BadRequestError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.UnauthorizedError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.InternalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/reject": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
|
@ -16195,12 +16316,33 @@ const docTemplate = `{
|
|||
"autoApproveAfterHours": {
|
||||
"type": "integer"
|
||||
},
|
||||
"branchName": {
|
||||
"type": "string"
|
||||
},
|
||||
"branchOrder": {
|
||||
"type": "integer"
|
||||
},
|
||||
"canSkip": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"conditionType": {
|
||||
"description": "'user_level', 'user_level_hierarchy', 'always', 'custom'",
|
||||
"type": "string"
|
||||
},
|
||||
"conditionValue": {
|
||||
"description": "JSON string for conditions",
|
||||
"type": "string"
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isParallel": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"parentStepId": {
|
||||
"description": "Multi-branch support fields",
|
||||
"type": "integer"
|
||||
},
|
||||
"requiredUserLevelId": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3070,6 +3070,127 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/multi-branch-approve": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "API for processing multi-branch approval with conditional routing",
|
||||
"tags": [
|
||||
"ArticleApprovalFlows"
|
||||
],
|
||||
"summary": "Process multi-branch approval",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Insert the Authorization",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ArticleApprovalFlows ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Approval action data",
|
||||
"name": "req",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.ApprovalActionRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.BadRequestError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.UnauthorizedError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.InternalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/next-steps-preview": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "API for getting preview of next steps based on submitter's user level",
|
||||
"tags": [
|
||||
"ArticleApprovalFlows"
|
||||
],
|
||||
"summary": "Get next steps preview for multi-branch workflow",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Insert the Authorization",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "ArticleApprovalFlows ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.BadRequestError"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.UnauthorizedError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.InternalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article-approval-flows/{id}/reject": {
|
||||
"put": {
|
||||
"security": [
|
||||
|
|
@ -16184,12 +16305,33 @@
|
|||
"autoApproveAfterHours": {
|
||||
"type": "integer"
|
||||
},
|
||||
"branchName": {
|
||||
"type": "string"
|
||||
},
|
||||
"branchOrder": {
|
||||
"type": "integer"
|
||||
},
|
||||
"canSkip": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"conditionType": {
|
||||
"description": "'user_level', 'user_level_hierarchy', 'always', 'custom'",
|
||||
"type": "string"
|
||||
},
|
||||
"conditionValue": {
|
||||
"description": "JSON string for conditions",
|
||||
"type": "string"
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isParallel": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"parentStepId": {
|
||||
"description": "Multi-branch support fields",
|
||||
"type": "integer"
|
||||
},
|
||||
"requiredUserLevelId": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -113,10 +113,25 @@ definitions:
|
|||
properties:
|
||||
autoApproveAfterHours:
|
||||
type: integer
|
||||
branchName:
|
||||
type: string
|
||||
branchOrder:
|
||||
type: integer
|
||||
canSkip:
|
||||
type: boolean
|
||||
conditionType:
|
||||
description: '''user_level'', ''user_level_hierarchy'', ''always'', ''custom'''
|
||||
type: string
|
||||
conditionValue:
|
||||
description: JSON string for conditions
|
||||
type: string
|
||||
isActive:
|
||||
type: boolean
|
||||
isParallel:
|
||||
type: boolean
|
||||
parentStepId:
|
||||
description: Multi-branch support fields
|
||||
type: integer
|
||||
requiredUserLevelId:
|
||||
type: integer
|
||||
stepName:
|
||||
|
|
@ -3101,6 +3116,85 @@ paths:
|
|||
summary: Approve article
|
||||
tags:
|
||||
- ArticleApprovalFlows
|
||||
/article-approval-flows/{id}/multi-branch-approve:
|
||||
post:
|
||||
description: API for processing multi-branch approval with conditional routing
|
||||
parameters:
|
||||
- description: Insert the Authorization
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: ArticleApprovalFlows ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- description: Approval action data
|
||||
in: body
|
||||
name: req
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.ApprovalActionRequest'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.BadRequestError'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.UnauthorizedError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.InternalServerError'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Process multi-branch approval
|
||||
tags:
|
||||
- ArticleApprovalFlows
|
||||
/article-approval-flows/{id}/next-steps-preview:
|
||||
get:
|
||||
description: API for getting preview of next steps based on submitter's user
|
||||
level
|
||||
parameters:
|
||||
- description: Insert the Authorization
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
- description: ArticleApprovalFlows ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.BadRequestError'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.UnauthorizedError'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.InternalServerError'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get next steps preview for multi-branch workflow
|
||||
tags:
|
||||
- ArticleApprovalFlows
|
||||
/article-approval-flows/{id}/reject:
|
||||
put:
|
||||
description: API for rejecting article
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
-- Script untuk memperbaiki masalah approval flow
|
||||
|
||||
-- 1. Pastikan user level 5 memiliki is_approval_active = true
|
||||
UPDATE user_levels
|
||||
SET is_approval_active = true
|
||||
WHERE id = 5;
|
||||
|
||||
-- 2. Pastikan workflow yang dibuat adalah default workflow
|
||||
UPDATE approval_workflows
|
||||
SET is_default = true, is_active = true
|
||||
WHERE name = 'Multi-Branch Article Approval';
|
||||
|
||||
-- 3. Cek apakah ada workflow default
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_active,
|
||||
is_default,
|
||||
client_id
|
||||
FROM approval_workflows
|
||||
WHERE is_default = true
|
||||
AND is_active = true;
|
||||
|
||||
-- 4. Jika tidak ada default workflow, buat satu
|
||||
-- (Gunakan ID workflow yang sudah dibuat)
|
||||
-- UPDATE approval_workflows
|
||||
-- SET is_default = true
|
||||
-- WHERE id = YOUR_WORKFLOW_ID;
|
||||
|
||||
-- 5. Cek user level 5 setelah update
|
||||
SELECT
|
||||
id,
|
||||
level_name,
|
||||
level_number,
|
||||
is_approval_active,
|
||||
client_id
|
||||
FROM user_levels
|
||||
WHERE id = 5;
|
||||
|
||||
-- 6. Cek user dengan level 5
|
||||
SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.user_level_id,
|
||||
ul.level_name,
|
||||
ul.level_number,
|
||||
ul.is_approval_active
|
||||
FROM users u
|
||||
JOIN user_levels ul ON u.user_level_id = ul.id
|
||||
WHERE u.user_level_id = 5;
|
||||
|
||||
-- 7. Test: Buat artikel baru untuk test
|
||||
-- Gunakan API atau aplikasi untuk membuat artikel baru
|
||||
-- dengan user level 5
|
||||
|
||||
-- 8. Cek hasil setelah test
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
created_by_id,
|
||||
workflow_id,
|
||||
current_approval_step,
|
||||
status_id,
|
||||
bypass_approval,
|
||||
approval_exempt,
|
||||
created_at
|
||||
FROM articles
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;
|
||||
|
||||
-- 9. Cek approval flows yang baru dibuat
|
||||
SELECT
|
||||
aaf.id,
|
||||
aaf.article_id,
|
||||
aaf.workflow_id,
|
||||
aaf.current_step,
|
||||
aaf.current_branch,
|
||||
aaf.status_id,
|
||||
aaf.submitted_by_id,
|
||||
aaf.submitted_at,
|
||||
a.title as article_title
|
||||
FROM article_approval_flows aaf
|
||||
JOIN articles a ON aaf.article_id = a.id
|
||||
ORDER BY aaf.created_at DESC
|
||||
LIMIT 5;
|
||||
Loading…
Reference in New Issue