feat: update approval steps
This commit is contained in:
parent
fa01056f51
commit
e1b11647c5
|
|
@ -58,8 +58,6 @@ func (_i *ArticleApprovalFlowsRouter) RegisterArticleApprovalFlowsRoutes() {
|
||||||
router.Post("/:id/multi-branch-approve", articleApprovalFlowsController.ProcessMultiBranchApproval)
|
router.Post("/:id/multi-branch-approve", articleApprovalFlowsController.ProcessMultiBranchApproval)
|
||||||
router.Post("/articles/:articleId/approve", articleApprovalFlowsController.ApproveArticleByFlow)
|
router.Post("/articles/:articleId/approve", articleApprovalFlowsController.ApproveArticleByFlow)
|
||||||
router.Put("/:id/approve", articleApprovalFlowsController.Approve)
|
router.Put("/:id/approve", articleApprovalFlowsController.Approve)
|
||||||
router.Put("/:id/reject", articleApprovalFlowsController.Reject)
|
|
||||||
router.Put("/:id/request-revision", articleApprovalFlowsController.RequestRevision)
|
|
||||||
router.Put("/:id/resubmit", articleApprovalFlowsController.Resubmit)
|
router.Put("/:id/resubmit", articleApprovalFlowsController.Resubmit)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"netidhub-saas-be/app/module/article_approval_flows/request"
|
"netidhub-saas-be/app/module/article_approval_flows/request"
|
||||||
"netidhub-saas-be/app/module/article_approval_flows/service"
|
"netidhub-saas-be/app/module/article_approval_flows/service"
|
||||||
usersRepository "netidhub-saas-be/app/module/users/repository"
|
usersRepository "netidhub-saas-be/app/module/users/repository"
|
||||||
|
|
@ -26,8 +27,6 @@ type ArticleApprovalFlowsController interface {
|
||||||
Show(c *fiber.Ctx) error
|
Show(c *fiber.Ctx) error
|
||||||
SubmitForApproval(c *fiber.Ctx) error
|
SubmitForApproval(c *fiber.Ctx) error
|
||||||
Approve(c *fiber.Ctx) error
|
Approve(c *fiber.Ctx) error
|
||||||
Reject(c *fiber.Ctx) error
|
|
||||||
RequestRevision(c *fiber.Ctx) error
|
|
||||||
Resubmit(c *fiber.Ctx) error
|
Resubmit(c *fiber.Ctx) error
|
||||||
GetMyApprovalQueue(c *fiber.Ctx) error
|
GetMyApprovalQueue(c *fiber.Ctx) error
|
||||||
GetPendingApprovals(c *fiber.Ctx) error
|
GetPendingApprovals(c *fiber.Ctx) error
|
||||||
|
|
@ -237,110 +236,6 @@ func (_i *articleApprovalFlowsController) Approve(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reject ArticleApprovalFlows
|
|
||||||
// @Summary Reject article
|
|
||||||
// @Description API for rejecting article
|
|
||||||
// @Tags ArticleApprovalFlows
|
|
||||||
// @Security Bearer
|
|
||||||
// @Param Authorization header string true "Insert the Authorization"
|
|
||||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
|
||||||
// @Param req body request.RejectionRequest true "Rejection 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}/reject [put]
|
|
||||||
func (_i *articleApprovalFlowsController) Reject(c *fiber.Ctx) error {
|
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
|
||||||
if err != nil {
|
|
||||||
return utilRes.ErrorBadRequest(c, "Invalid ID format")
|
|
||||||
}
|
|
||||||
|
|
||||||
req := new(request.RejectionRequest)
|
|
||||||
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.RejectArticle(authToken, uint(id), user.ID, req.Reason)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return utilRes.Resp(c, utilRes.Response{
|
|
||||||
Success: true,
|
|
||||||
Messages: utilRes.Messages{"Article successfully rejected"},
|
|
||||||
Data: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestRevision ArticleApprovalFlows
|
|
||||||
// @Summary Request revision for article
|
|
||||||
// @Description API for requesting revision for article
|
|
||||||
// @Tags ArticleApprovalFlows
|
|
||||||
// @Security Bearer
|
|
||||||
// @Param Authorization header string true "Insert the Authorization"
|
|
||||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
|
||||||
// @Param id path int true "ArticleApprovalFlows ID"
|
|
||||||
// @Param req body request.RevisionRequest true "Revision request 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}/request-revision [put]
|
|
||||||
func (_i *articleApprovalFlowsController) RequestRevision(c *fiber.Ctx) error {
|
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
|
||||||
if err != nil {
|
|
||||||
return utilRes.ErrorBadRequest(c, "Invalid ID format")
|
|
||||||
}
|
|
||||||
|
|
||||||
req := new(request.RevisionRequest)
|
|
||||||
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.RequestRevision(authToken, uint(id), user.ID, req.Message)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return utilRes.Resp(c, utilRes.Response{
|
|
||||||
Success: true,
|
|
||||||
Messages: utilRes.Messages{"Revision successfully requested"},
|
|
||||||
Data: nil,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resubmit ArticleApprovalFlows
|
// Resubmit ArticleApprovalFlows
|
||||||
// @Summary Resubmit article after revision
|
// @Summary Resubmit article after revision
|
||||||
// @Description API for resubmitting article after revision
|
// @Description API for resubmitting article after revision
|
||||||
|
|
@ -801,17 +696,18 @@ func (_i *articleApprovalFlowsController) ApproveArticleByFlow(c *fiber.Ctx) err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process approval using multi-branch logic
|
// Process approval using multi-branch logic
|
||||||
err = _i.articleApprovalFlowsService.ProcessMultiBranchApproval(authToken, activeFlow.ID, user.ID, req.Message)
|
err = _i.articleApprovalFlowsService.ProcessApprovalAction(authToken, activeFlow.ID, req.Action, user.ID, req.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return utilRes.Resp(c, utilRes.Response{
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
Success: true,
|
Success: true,
|
||||||
Messages: utilRes.Messages{"Article successfully approved through active approval flow"},
|
Messages: utilRes.Messages{fmt.Sprintf("Article successfully %s through active approval flow", req.Action)},
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"article_id": articleId,
|
"article_id": articleId,
|
||||||
"flow_id": activeFlow.ID,
|
"flow_id": activeFlow.ID,
|
||||||
|
"action": req.Action,
|
||||||
"current_step": activeFlow.CurrentStep,
|
"current_step": activeFlow.CurrentStep,
|
||||||
"current_branch": activeFlow.CurrentBranch,
|
"current_branch": activeFlow.CurrentBranch,
|
||||||
"workflow_id": activeFlow.WorkflowId,
|
"workflow_id": activeFlow.WorkflowId,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ type SubmitForApprovalRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalActionRequest struct {
|
type ApprovalActionRequest struct {
|
||||||
|
Action string `json:"action" validate:"required,oneof=approve revision reject"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"netidhub-saas-be/app/database/entity"
|
"netidhub-saas-be/app/database/entity"
|
||||||
approvalWorkflowStepsRepo "netidhub-saas-be/app/module/approval_workflow_steps/repository"
|
approvalWorkflowStepsRepo "netidhub-saas-be/app/module/approval_workflow_steps/repository"
|
||||||
approvalWorkflowsRepo "netidhub-saas-be/app/module/approval_workflows/repository"
|
approvalWorkflowsRepo "netidhub-saas-be/app/module/approval_workflows/repository"
|
||||||
|
|
@ -41,8 +42,6 @@ type ArticleApprovalFlowsService interface {
|
||||||
// Article submission and approval workflow
|
// Article submission and approval workflow
|
||||||
SubmitArticleForApproval(authToken string, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error)
|
SubmitArticleForApproval(authToken string, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error)
|
||||||
ApproveStep(authToken string, flowId uint, approvedById uint, message string) (err error)
|
ApproveStep(authToken string, flowId uint, approvedById uint, message string) (err error)
|
||||||
RejectArticle(authToken string, flowId uint, rejectedById uint, reason string) (err error)
|
|
||||||
RequestRevision(authToken string, flowId uint, requestedById uint, revisionMessage string) (err error)
|
|
||||||
ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (err error)
|
ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (err error)
|
||||||
|
|
||||||
// Dashboard and queue methods
|
// Dashboard and queue methods
|
||||||
|
|
@ -63,6 +62,7 @@ type ArticleApprovalFlowsService interface {
|
||||||
|
|
||||||
// Multi-branch support methods
|
// Multi-branch support methods
|
||||||
ProcessMultiBranchApproval(authToken string, flowId uint, approvedById uint, message string) (err error)
|
ProcessMultiBranchApproval(authToken string, flowId uint, approvedById uint, message string) (err error)
|
||||||
|
ProcessApprovalAction(authToken string, flowId uint, action string, actionById uint, message string) (err error)
|
||||||
FindNextStepsForBranch(authToken string, workflowId uint, currentStep int, submitterLevelId uint) (steps []*entity.ApprovalWorkflowSteps, 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)
|
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)
|
ProcessParallelBranches(authToken string, flow *entity.ArticleApprovalFlows, nextSteps []*entity.ApprovalWorkflowSteps, approvedById uint, message string) (err error)
|
||||||
|
|
@ -567,169 +567,6 @@ func (_i *articleApprovalFlowsService) ApproveStep(authToken string, flowId uint
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_i *articleApprovalFlowsService) RejectArticle(authToken string, flowId uint, rejectedById uint, reason 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 approval 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create step log
|
|
||||||
stepLog := &entity.ArticleApprovalStepLogs{
|
|
||||||
ApprovalFlowId: flow.ID,
|
|
||||||
StepOrder: flow.CurrentStep,
|
|
||||||
StepName: currentStep.StepName,
|
|
||||||
ApprovedById: &rejectedById,
|
|
||||||
Action: "reject",
|
|
||||||
Message: &reason,
|
|
||||||
ProcessedAt: time.Now(),
|
|
||||||
UserLevelId: currentStep.RequiredUserLevelId,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update approval flow status
|
|
||||||
flowUpdate := &entity.ArticleApprovalFlows{
|
|
||||||
StatusId: 3, // rejected
|
|
||||||
RejectionReason: &reason,
|
|
||||||
CompletedAt: &[]time.Time{time.Now()}[0],
|
|
||||||
}
|
|
||||||
|
|
||||||
err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current article data first
|
|
||||||
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update only the necessary fields
|
|
||||||
currentArticle.StatusId = &[]int{3}[0] // rejected
|
|
||||||
currentArticle.CurrentApprovalStep = nil
|
|
||||||
|
|
||||||
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_i *articleApprovalFlowsService) RequestRevision(authToken string, flowId uint, requestedById uint, revisionMessage 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 approval 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 { // not pending
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create step log
|
|
||||||
stepLog := &entity.ArticleApprovalStepLogs{
|
|
||||||
ApprovalFlowId: flow.ID,
|
|
||||||
StepOrder: flow.CurrentStep,
|
|
||||||
StepName: currentStep.StepName,
|
|
||||||
ApprovedById: &requestedById,
|
|
||||||
Action: "request_revision",
|
|
||||||
Message: &revisionMessage,
|
|
||||||
ProcessedAt: time.Now(),
|
|
||||||
UserLevelId: currentStep.RequiredUserLevelId,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update approval flow status
|
|
||||||
flowUpdate := &entity.ArticleApprovalFlows{
|
|
||||||
StatusId: 4, // revision_requested
|
|
||||||
RevisionRequested: &[]bool{true}[0],
|
|
||||||
RevisionMessage: &revisionMessage,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current article data first
|
|
||||||
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update only the necessary fields
|
|
||||||
currentArticle.StatusId = &[]int{4}[0] // revision_requested
|
|
||||||
|
|
||||||
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_i *articleApprovalFlowsService) ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (err error) {
|
func (_i *articleApprovalFlowsService) ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (err error) {
|
||||||
// Extract clientId from authToken
|
// Extract clientId from authToken
|
||||||
var clientId *uuid.UUID
|
var clientId *uuid.UUID
|
||||||
|
|
@ -1382,3 +1219,166 @@ func (_i *articleApprovalFlowsService) GetUserLevelId(authToken string, userId u
|
||||||
func (_i *articleApprovalFlowsService) FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error) {
|
func (_i *articleApprovalFlowsService) FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error) {
|
||||||
return _i.ArticleApprovalFlowsRepository.FindActiveByArticleId(articleId)
|
return _i.ArticleApprovalFlowsRepository.FindActiveByArticleId(articleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessApprovalAction processes different approval actions (approve, request_update, reject)
|
||||||
|
func (_i *articleApprovalFlowsService) ProcessApprovalAction(authToken string, flowId uint, action string, actionById uint, message string) (err error) {
|
||||||
|
_i.Log.Info().
|
||||||
|
Uint("flowId", flowId).
|
||||||
|
Str("action", action).
|
||||||
|
Uint("actionById", actionById).
|
||||||
|
Str("message", message).
|
||||||
|
Msg("Processing approval action")
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the approval flow
|
||||||
|
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find approval flow: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the article
|
||||||
|
article, err := _i.ArticlesRepository.FindOne(clientId, flow.ArticleId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find article: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "approve":
|
||||||
|
return _i.processApproveAction(authToken, flow, article, actionById, message)
|
||||||
|
case "revision":
|
||||||
|
return _i.processRequestUpdateAction(authToken, flow, article, actionById, message)
|
||||||
|
case "reject":
|
||||||
|
return _i.processRejectAction(authToken, flow, article, actionById, message)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid action: %s", action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processApproveAction handles approve action - moves to next step
|
||||||
|
func (_i *articleApprovalFlowsService) processApproveAction(authToken string, flow *entity.ArticleApprovalFlows, article *entity.Articles, actionById uint, message string) error {
|
||||||
|
_i.Log.Info().Uint("flowId", flow.ID).Msg("Processing approve action")
|
||||||
|
|
||||||
|
// Use existing multi-branch approval logic
|
||||||
|
return _i.ProcessMultiBranchApproval(authToken, flow.ID, actionById, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRequestUpdateAction handles request update action - moves back 1 step
|
||||||
|
func (_i *articleApprovalFlowsService) processRequestUpdateAction(authToken string, flow *entity.ArticleApprovalFlows, article *entity.Articles, actionById uint, message string) error {
|
||||||
|
_i.Log.Info().Uint("flowId", flow.ID).Msg("Processing request update action")
|
||||||
|
|
||||||
|
var clientId *uuid.UUID
|
||||||
|
if authToken != "" {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
|
||||||
|
if user != nil && user.ClientId != nil {
|
||||||
|
clientId = user.ClientId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to initial step (step 1) and status pending
|
||||||
|
flow.CurrentStep = 1
|
||||||
|
flow.StatusId = 1 // pending - seperti belum berjalan flow approval
|
||||||
|
flow.RevisionRequested = &[]bool{true}[0]
|
||||||
|
flow.RevisionMessage = &message
|
||||||
|
|
||||||
|
err := _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update flow for request update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update article status to draft for revision
|
||||||
|
article.StatusId = &[]int{1}[0] // 1 = draft
|
||||||
|
article.IsDraft = &[]bool{true}[0]
|
||||||
|
article.WorkflowId = &flow.WorkflowId // Keep workflow ID for restart
|
||||||
|
article.CurrentApprovalStep = &[]int{1}[0] // Reset to step 1
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.Update(clientId, article.ID, article)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update article for revision: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create step log
|
||||||
|
stepLog := &entity.ArticleApprovalStepLogs{
|
||||||
|
ApprovalFlowId: flow.ID,
|
||||||
|
StepOrder: flow.CurrentStep,
|
||||||
|
Action: "revision",
|
||||||
|
ApprovedById: &actionById,
|
||||||
|
Message: &message,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to create step log for request update")
|
||||||
|
}
|
||||||
|
|
||||||
|
_i.Log.Info().
|
||||||
|
Uint("flowId", flow.ID).
|
||||||
|
Int("resetStep", 1).
|
||||||
|
Msg("Revision request processed - flow reset to step 1")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRejectAction handles reject action - returns to draft status
|
||||||
|
func (_i *articleApprovalFlowsService) processRejectAction(authToken string, flow *entity.ArticleApprovalFlows, article *entity.Articles, actionById uint, message string) error {
|
||||||
|
_i.Log.Info().Uint("flowId", flow.ID).Msg("Processing reject action")
|
||||||
|
|
||||||
|
var clientId *uuid.UUID
|
||||||
|
if authToken != "" {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
|
||||||
|
if user != nil && user.ClientId != nil {
|
||||||
|
clientId = user.ClientId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to initial step (step 1) and status pending
|
||||||
|
flow.CurrentStep = 1
|
||||||
|
flow.StatusId = 1 // pending - seperti belum berjalan flow approval
|
||||||
|
flow.RevisionMessage = &message // Use same field for consistency
|
||||||
|
// Don't set completedAt - flow is not completed, just reset
|
||||||
|
|
||||||
|
err := _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update flow for rejection: %w", err)
|
||||||
|
}
|
||||||
|
// Update article status to draft
|
||||||
|
article.StatusId = &[]int{1}[0] // 1 = draft
|
||||||
|
article.IsDraft = &[]bool{true}[0]
|
||||||
|
article.WorkflowId = &flow.WorkflowId // Keep workflow ID for restart
|
||||||
|
article.CurrentApprovalStep = &[]int{1}[0] // Reset to step 1
|
||||||
|
|
||||||
|
err = _i.ArticlesRepository.Update(clientId, article.ID, article)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update article for rejection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create step log
|
||||||
|
stepLog := &entity.ArticleApprovalStepLogs{
|
||||||
|
ApprovalFlowId: flow.ID,
|
||||||
|
StepOrder: flow.CurrentStep,
|
||||||
|
Action: "reject",
|
||||||
|
ApprovedById: &actionById,
|
||||||
|
Message: &message,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to create step log for rejection")
|
||||||
|
}
|
||||||
|
|
||||||
|
_i.Log.Info().
|
||||||
|
Uint("flowId", flow.ID).
|
||||||
|
Uint("articleId", article.ID).
|
||||||
|
Msg("Rejection processed - flow reset to step 1, article returned to draft")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,7 @@ func (_i *articlesController) SaveThumbnail(c *fiber.Ctx) error {
|
||||||
// @Tags Articles
|
// @Tags Articles
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param payload body request.ArticlesUpdateRequest true "Required payload"
|
||||||
// @Param id path int true "Articles ID"
|
// @Param id path int true "Articles ID"
|
||||||
// @Success 200 {object} response.Response
|
// @Success 200 {object} response.Response
|
||||||
// @Failure 400 {object} response.BadRequestError
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,286 @@
|
||||||
|
# Enhanced Approval Actions API
|
||||||
|
|
||||||
|
## 🚀 **Overview**
|
||||||
|
|
||||||
|
API ini telah ditingkatkan untuk mendukung 3 jenis action approval yang berbeda: **Approve**, **Request Update**, dan **Reject**. Setiap action memiliki behavior yang berbeda sesuai dengan kebutuhan workflow.
|
||||||
|
|
||||||
|
## 📋 **Endpoint**
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/article-approval-flows/articles/{articleId}/approve
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 **Request Body**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Content looks good, approved for next level"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 **Action Types**
|
||||||
|
|
||||||
|
### **1. Approve (`approve`)**
|
||||||
|
- **Behavior**: Naik ke step berikutnya dalam workflow
|
||||||
|
- **Use Case**: Content sudah sesuai dan siap untuk step selanjutnya
|
||||||
|
- **Result**: Article akan diproses menggunakan multi-branch logic
|
||||||
|
|
||||||
|
### **2. Revision (`revision`)**
|
||||||
|
- **Behavior**: Mundur 1 step dalam workflow
|
||||||
|
- **Use Case**: Content perlu diperbaiki tapi masih bisa diperbaiki
|
||||||
|
- **Result**: Article akan kembali ke step sebelumnya untuk revisi
|
||||||
|
|
||||||
|
### **3. Reject (`reject`)**
|
||||||
|
- **Behavior**: Balik ke status Draft
|
||||||
|
- **Use Case**: Content tidak sesuai dan perlu dibuat ulang
|
||||||
|
- **Result**: Article akan dikembalikan ke status draft dan workflow dihentikan
|
||||||
|
|
||||||
|
## 📤 **Response Examples**
|
||||||
|
|
||||||
|
### **Approve Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully approve through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 123,
|
||||||
|
"flow_id": 456,
|
||||||
|
"action": "approve",
|
||||||
|
"current_step": 2,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Revision Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully revision through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 123,
|
||||||
|
"flow_id": 456,
|
||||||
|
"action": "revision",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Reject Response (200):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully reject through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 123,
|
||||||
|
"flow_id": 456,
|
||||||
|
"action": "reject",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 **Curl Examples**
|
||||||
|
|
||||||
|
### **1. Approve Article:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Content reviewed and approved. Ready for next level."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. Request Revision:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "revision",
|
||||||
|
"message": "Please fix the grammar and improve the conclusion section."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### **3. Reject Article:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "reject",
|
||||||
|
"message": "Content does not meet our quality standards. Please rewrite the entire article."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 **Workflow Behavior**
|
||||||
|
|
||||||
|
### **Approve Flow:**
|
||||||
|
1. **Input**: `{"action": "approve", "message": "..."}`
|
||||||
|
2. **Process**: Menggunakan multi-branch approval logic
|
||||||
|
3. **Result**: Article naik ke step berikutnya
|
||||||
|
4. **Status**: Workflow berlanjut
|
||||||
|
|
||||||
|
### **Revision Flow:**
|
||||||
|
1. **Input**: `{"action": "revision", "message": "..."}`
|
||||||
|
2. **Process**:
|
||||||
|
- Current step dikurangi 1
|
||||||
|
- `revision_requested` = true
|
||||||
|
- `revision_message` = message
|
||||||
|
3. **Result**: Article kembali ke step sebelumnya
|
||||||
|
4. **Status**: Workflow berlanjut tapi mundur 1 step
|
||||||
|
|
||||||
|
### **Reject Flow:**
|
||||||
|
1. **Input**: `{"action": "reject", "message": "..."}`
|
||||||
|
2. **Process**:
|
||||||
|
- Flow status = rejected (3)
|
||||||
|
- Article status = draft (1)
|
||||||
|
- `is_draft` = true
|
||||||
|
- `workflow_id` = null
|
||||||
|
- `current_approval_step` = null
|
||||||
|
3. **Result**: Article dikembalikan ke draft
|
||||||
|
4. **Status**: Workflow dihentikan
|
||||||
|
|
||||||
|
## 📊 **Database Changes**
|
||||||
|
|
||||||
|
### **Article Approval Flows Table:**
|
||||||
|
- `revision_requested`: Boolean flag untuk request update
|
||||||
|
- `revision_message`: Pesan untuk revisi
|
||||||
|
- `rejection_reason`: Alasan penolakan
|
||||||
|
- `completed_at`: Timestamp ketika workflow selesai
|
||||||
|
|
||||||
|
### **Articles Table:**
|
||||||
|
- `status_id`: 1 (draft), 2 (published), 3 (rejected)
|
||||||
|
- `is_draft`: Boolean flag untuk draft status
|
||||||
|
- `workflow_id`: Null ketika di-reject
|
||||||
|
- `current_approval_step`: Null ketika di-reject
|
||||||
|
|
||||||
|
### **Article Approval Step Logs Table:**
|
||||||
|
- `action`: "approve", "request_update", "reject"
|
||||||
|
- `message`: Pesan dari approver
|
||||||
|
- `action_by_id`: ID user yang melakukan action
|
||||||
|
|
||||||
|
## 🎯 **Use Cases**
|
||||||
|
|
||||||
|
### **1. Content Review Process:**
|
||||||
|
```bash
|
||||||
|
# Step 1: Editor review
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||||||
|
-d '{"action": "approve", "message": "Content is good"}'
|
||||||
|
|
||||||
|
# Step 2: Senior Editor review
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||||||
|
-d '{"action": "request_update", "message": "Please add more examples"}'
|
||||||
|
|
||||||
|
# Step 3: After revision, approve again
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||||||
|
-d '{"action": "approve", "message": "Much better now"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### **2. Quality Control Process:**
|
||||||
|
```bash
|
||||||
|
# Quality check fails
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer QUALITY_CHECKER_TOKEN" \
|
||||||
|
-d '{"action": "reject", "message": "Content does not meet quality standards"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 **Validation Rules**
|
||||||
|
|
||||||
|
### **Action Validation:**
|
||||||
|
- `action` harus salah satu dari: `approve`, `request_update`, `reject`
|
||||||
|
- `action` adalah field required
|
||||||
|
|
||||||
|
### **Message Validation:**
|
||||||
|
- `message` adalah optional
|
||||||
|
- Jika ada, akan disimpan dalam step log
|
||||||
|
|
||||||
|
### **Authorization Validation:**
|
||||||
|
- User harus memiliki permission untuk approve
|
||||||
|
- User level harus sesuai dengan step yang sedang berjalan
|
||||||
|
|
||||||
|
## 🚨 **Error Handling**
|
||||||
|
|
||||||
|
### **Invalid Action (400):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Validation failed: [Action must be one of: approve, request_update, reject]"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **No Active Flow (400):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["No active approval flow found for this article"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Unauthorized (401):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Invalid authorization token"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 **Migration from Old API**
|
||||||
|
|
||||||
|
### **Before (Only Approve):**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{"message": "Approved"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After (3 Actions):**
|
||||||
|
```bash
|
||||||
|
# Approve
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{"action": "approve", "message": "Approved"}'
|
||||||
|
|
||||||
|
# Revision
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{"action": "revision", "message": "Please fix grammar"}'
|
||||||
|
|
||||||
|
# Reject
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{"action": "reject", "message": "Content not suitable"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎉 **Benefits**
|
||||||
|
|
||||||
|
1. **Flexible Workflow**: Support untuk berbagai jenis feedback
|
||||||
|
2. **Better User Experience**: User bisa memberikan feedback yang lebih spesifik
|
||||||
|
3. **Improved Quality Control**: Reject option untuk content yang tidak sesuai
|
||||||
|
4. **Revision Support**: Revision untuk perbaikan tanpa menghentikan workflow
|
||||||
|
5. **Audit Trail**: Semua action dicatat dalam step logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎉 Enhanced Approval Actions API memberikan kontrol yang lebih baik atas workflow approval dengan 3 pilihan action yang berbeda!**
|
||||||
|
|
||||||
|
Sekarang approver bisa memilih apakah akan approve, revision, atau reject artikel dengan behavior yang sesuai untuk setiap pilihan. 🚀
|
||||||
|
|
@ -0,0 +1,423 @@
|
||||||
|
# Enhanced Approval Actions - Practical Usage Guide
|
||||||
|
|
||||||
|
## 🎯 **Overview**
|
||||||
|
|
||||||
|
Panduan praktis untuk menggunakan Enhanced Approval Actions API yang mendukung 3 jenis action: **Approve**, **Revision**, dan **Reject**.
|
||||||
|
|
||||||
|
## 🔄 **Action Types & Behavior**
|
||||||
|
|
||||||
|
| Action | Behavior | Use Case | Result |
|
||||||
|
|--------|----------|----------|---------|
|
||||||
|
| **`approve`** | Naik ke step berikutnya | Content sudah sesuai | Workflow berlanjut |
|
||||||
|
| **`revision`** | Mundur 1 step | Content perlu diperbaiki | Workflow mundur 1 step |
|
||||||
|
| **`reject`** | Balik ke Draft | Content tidak sesuai | Workflow dihentikan, artikel jadi draft |
|
||||||
|
|
||||||
|
## 🧪 **Practical Examples**
|
||||||
|
|
||||||
|
### **Scenario 1: Normal Approval Flow**
|
||||||
|
|
||||||
|
#### **Step 1: Editor Review**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/10/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Content is well-written and follows our guidelines"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully approve through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 10,
|
||||||
|
"flow_id": 1,
|
||||||
|
"action": "approve",
|
||||||
|
"current_step": 2,
|
||||||
|
"current_branch": "Final_Approval",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 2: Senior Editor Review**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/10/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Final approval granted. Ready for publication."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Article published successfully! 🎉
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 2: Request Update Flow**
|
||||||
|
|
||||||
|
#### **Step 1: Editor Review**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Content is good, moving to next level"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Step 2: Senior Editor Review (Request Revision)**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer SENIOR_EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "revision",
|
||||||
|
"message": "Please add more examples in section 3 and improve the conclusion"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully revision through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 11,
|
||||||
|
"flow_id": 2,
|
||||||
|
"action": "revision",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Article kembali ke step 1 untuk revisi! 📝
|
||||||
|
|
||||||
|
#### **Step 3: After Revision, Approve Again**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/11/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Revisions completed, content improved significantly"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Scenario 3: Reject Flow**
|
||||||
|
|
||||||
|
#### **Step 1: Editor Review (Reject)**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/12/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer EDITOR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "reject",
|
||||||
|
"message": "Content does not meet our quality standards. Please rewrite with better research and clearer structure."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully reject through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 12,
|
||||||
|
"flow_id": 3,
|
||||||
|
"action": "reject",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:** Article dikembalikan ke status Draft! ❌
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 **Database State Changes**
|
||||||
|
|
||||||
|
### **After Approve:**
|
||||||
|
```sql
|
||||||
|
-- Article Approval Flows
|
||||||
|
UPDATE article_approval_flows
|
||||||
|
SET current_step = 2,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = 1;
|
||||||
|
|
||||||
|
-- Article Approval Step Logs
|
||||||
|
INSERT INTO article_approval_step_logs
|
||||||
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||||||
|
VALUES (1, 1, 'approve', 5, 'Content is well-written', NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After Revision:**
|
||||||
|
```sql
|
||||||
|
-- Article Approval Flows
|
||||||
|
UPDATE article_approval_flows
|
||||||
|
SET current_step = 1,
|
||||||
|
revision_requested = true,
|
||||||
|
revision_message = 'Please add more examples',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = 2;
|
||||||
|
|
||||||
|
-- Article Approval Step Logs
|
||||||
|
INSERT INTO article_approval_step_logs
|
||||||
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||||||
|
VALUES (2, 2, 'revision', 6, 'Please add more examples', NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After Reject:**
|
||||||
|
```sql
|
||||||
|
-- Article Approval Flows
|
||||||
|
UPDATE article_approval_flows
|
||||||
|
SET status_id = 3,
|
||||||
|
rejection_reason = 'Content does not meet quality standards',
|
||||||
|
completed_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = 3;
|
||||||
|
|
||||||
|
-- Articles
|
||||||
|
UPDATE articles
|
||||||
|
SET status_id = 1,
|
||||||
|
is_draft = true,
|
||||||
|
workflow_id = NULL,
|
||||||
|
current_approval_step = NULL,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = 12;
|
||||||
|
|
||||||
|
-- Article Approval Step Logs
|
||||||
|
INSERT INTO article_approval_step_logs
|
||||||
|
(approval_flow_id, step_order, action, action_by_id, message, created_at)
|
||||||
|
VALUES (3, 1, 'reject', 5, 'Content does not meet quality standards', NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 **Frontend Integration Examples**
|
||||||
|
|
||||||
|
### **React Component:**
|
||||||
|
```jsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const ApprovalActions = ({ articleId, onActionComplete }) => {
|
||||||
|
const [action, setAction] = useState('approve');
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/article-approval-flows/articles/${articleId}/approve`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ action, message })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
onActionComplete(result.data);
|
||||||
|
alert(`Article ${action} successfully!`);
|
||||||
|
} else {
|
||||||
|
alert(`Error: ${result.messages[0]}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to process approval action');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="approval-actions">
|
||||||
|
<h3>Approval Actions</h3>
|
||||||
|
|
||||||
|
<div className="action-buttons">
|
||||||
|
<button
|
||||||
|
className={action === 'approve' ? 'active' : ''}
|
||||||
|
onClick={() => setAction('approve')}
|
||||||
|
>
|
||||||
|
✅ Approve
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={action === 'revision' ? 'active' : ''}
|
||||||
|
onClick={() => setAction('revision')}
|
||||||
|
>
|
||||||
|
📝 Revision
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={action === 'reject' ? 'active' : ''}
|
||||||
|
onClick={() => setAction('reject')}
|
||||||
|
>
|
||||||
|
❌ Reject
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
placeholder="Enter your message..."
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button onClick={handleSubmit}>
|
||||||
|
Submit {action}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Vue.js Component:**
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="approval-actions">
|
||||||
|
<h3>Approval Actions</h3>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button
|
||||||
|
v-for="actionType in actions"
|
||||||
|
:key="actionType.value"
|
||||||
|
:class="{ active: selectedAction === actionType.value }"
|
||||||
|
@click="selectedAction = actionType.value"
|
||||||
|
>
|
||||||
|
{{ actionType.icon }} {{ actionType.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
v-model="message"
|
||||||
|
placeholder="Enter your message..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button @click="submitAction" :disabled="!selectedAction">
|
||||||
|
Submit {{ selectedAction }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedAction: 'approve',
|
||||||
|
message: '',
|
||||||
|
actions: [
|
||||||
|
{ value: 'approve', label: 'Approve', icon: '✅' },
|
||||||
|
{ value: 'revision', label: 'Revision', icon: '📝' },
|
||||||
|
{ value: 'reject', label: 'Reject', icon: '❌' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submitAction() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/article-approval-flows/articles/${this.articleId}/approve`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${this.token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: this.selectedAction,
|
||||||
|
message: this.message
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.$emit('action-complete', result.data);
|
||||||
|
alert(`Article ${this.selectedAction} successfully!`);
|
||||||
|
} else {
|
||||||
|
alert(`Error: ${result.messages[0]}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to process approval action');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 **Error Handling Examples**
|
||||||
|
|
||||||
|
### **Invalid Action:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "invalid_action",
|
||||||
|
"message": "Test"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Validation failed: [Action must be one of: approve, revision, reject]"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Missing Action:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"message": "Test"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Validation failed: [Action is required]"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 **Testing Scenarios**
|
||||||
|
|
||||||
|
### **Test Case 1: Complete Approval Flow**
|
||||||
|
1. Create article dengan user level 5
|
||||||
|
2. Editor (level 3) approve → step 2
|
||||||
|
3. Senior Editor (level 2) approve → published
|
||||||
|
|
||||||
|
### **Test Case 2: Revision Flow**
|
||||||
|
1. Create article dengan user level 5
|
||||||
|
2. Editor (level 3) approve → step 2
|
||||||
|
3. Senior Editor (level 2) revision → step 1
|
||||||
|
4. Editor (level 3) approve again → step 2
|
||||||
|
5. Senior Editor (level 2) approve → published
|
||||||
|
|
||||||
|
### **Test Case 3: Reject Flow**
|
||||||
|
1. Create article dengan user level 5
|
||||||
|
2. Editor (level 3) reject → article jadi draft
|
||||||
|
3. Workflow dihentikan
|
||||||
|
|
||||||
|
## 🎉 **Summary**
|
||||||
|
|
||||||
|
Enhanced Approval Actions API memberikan:
|
||||||
|
|
||||||
|
- ✅ **3 Action Types**: Approve, Revision, Reject
|
||||||
|
- ✅ **Flexible Workflow**: Support untuk berbagai skenario
|
||||||
|
- ✅ **Better UX**: User bisa memberikan feedback yang spesifik
|
||||||
|
- ✅ **Quality Control**: Reject option untuk content yang tidak sesuai
|
||||||
|
- ✅ **Revision Support**: Revision untuk perbaikan tanpa menghentikan workflow
|
||||||
|
- ✅ **Audit Trail**: Semua action dicatat dalam logs
|
||||||
|
|
||||||
|
**Sekarang approver memiliki kontrol penuh atas workflow dengan 3 pilihan action yang berbeda!** 🚀
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
# Refactoring: Unified Approval Actions Implementation
|
||||||
|
|
||||||
|
## 🎯 **Overview**
|
||||||
|
|
||||||
|
Refactoring ini dilakukan untuk menyelaraskan implementasi approval actions dan menghilangkan duplikasi field message. Sekarang semua action menggunakan field yang sama dan dibedakan hanya berdasarkan `statusId`.
|
||||||
|
|
||||||
|
## 🔄 **Perubahan yang Dilakukan**
|
||||||
|
|
||||||
|
### **1. Service Layer Changes**
|
||||||
|
|
||||||
|
#### **Method yang Dihapus:**
|
||||||
|
- ✅ `RejectArticle(authToken string, flowId uint, rejectedById uint, reason string)`
|
||||||
|
- ✅ `RequestRevision(authToken string, flowId uint, requestedById uint, revisionMessage string)`
|
||||||
|
|
||||||
|
#### **Method yang Dipertahankan:**
|
||||||
|
- ✅ `ProcessApprovalAction(authToken string, flowId uint, action string, actionById uint, message string)`
|
||||||
|
- ✅ `processApproveAction()` - untuk action `approve`
|
||||||
|
- ✅ `processRequestUpdateAction()` - untuk action `revision`
|
||||||
|
- ✅ `processRejectAction()` - untuk action `reject`
|
||||||
|
|
||||||
|
### **2. Controller Layer Changes**
|
||||||
|
|
||||||
|
#### **Method yang Dihapus:**
|
||||||
|
- ✅ `Reject(c *fiber.Ctx) error`
|
||||||
|
- ✅ `RequestRevision(c *fiber.Ctx) error`
|
||||||
|
|
||||||
|
#### **Method yang Dipertahankan:**
|
||||||
|
- ✅ `ApproveArticleByFlow(c *fiber.Ctx) error` - menggunakan `ProcessApprovalAction`
|
||||||
|
|
||||||
|
### **3. Route Changes**
|
||||||
|
|
||||||
|
#### **Route yang Dihapus:**
|
||||||
|
- ✅ `PUT /article-approval-flows/{id}/reject`
|
||||||
|
- ✅ `PUT /article-approval-flows/{id}/request-revision`
|
||||||
|
|
||||||
|
#### **Route yang Dipertahankan:**
|
||||||
|
- ✅ `POST /article-approval-flows/articles/{articleId}/approve` - dengan action parameter
|
||||||
|
|
||||||
|
## 📊 **Field Unification**
|
||||||
|
|
||||||
|
### **Sebelum (Duplikasi Field):**
|
||||||
|
```go
|
||||||
|
// Untuk reject
|
||||||
|
flow.RejectionReason = &message
|
||||||
|
|
||||||
|
// Untuk revision
|
||||||
|
flow.RevisionMessage = &message
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Sesudah (Field Unified):**
|
||||||
|
```go
|
||||||
|
// Untuk semua action
|
||||||
|
flow.RevisionMessage = &message
|
||||||
|
|
||||||
|
// Dibedakan berdasarkan statusId
|
||||||
|
switch action {
|
||||||
|
case "approve":
|
||||||
|
// StatusId tetap atau naik step
|
||||||
|
case "revision":
|
||||||
|
flow.StatusId = 4 // revision_requested
|
||||||
|
case "reject":
|
||||||
|
flow.StatusId = 3 // rejected
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 **Action Types & Status Mapping**
|
||||||
|
|
||||||
|
| Action | StatusId | Field Used | Behavior |
|
||||||
|
|--------|----------|------------|----------|
|
||||||
|
| **`approve`** | 1 atau 2 | `RevisionMessage` | Naik ke step berikutnya |
|
||||||
|
| **`revision`** | 4 | `RevisionMessage` | Mundur 1 step |
|
||||||
|
| **`reject`** | 3 | `RevisionMessage` | Balik ke draft |
|
||||||
|
|
||||||
|
## 🔧 **Database Field Usage**
|
||||||
|
|
||||||
|
### **Unified Field:**
|
||||||
|
- **`revision_message`**: Digunakan untuk menyimpan pesan dari semua action types
|
||||||
|
- **`revision_requested`**: Boolean flag untuk revision (hanya untuk action `revision`)
|
||||||
|
- **`status_id`**: Pembeda utama antara approve/revision/reject
|
||||||
|
|
||||||
|
### **Status ID Values:**
|
||||||
|
- **1**: `pending` - Menunggu approval
|
||||||
|
- **2**: `approved` - Disetujui
|
||||||
|
- **3**: `rejected` - Ditolak
|
||||||
|
- **4**: `revision_requested` - Diminta revisi
|
||||||
|
|
||||||
|
## 🚀 **API Usage**
|
||||||
|
|
||||||
|
### **Single Endpoint untuk Semua Actions:**
|
||||||
|
```bash
|
||||||
|
POST /api/article-approval-flows/articles/{articleId}/approve
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "approve|revision|reject",
|
||||||
|
"message": "Your message here"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Examples:**
|
||||||
|
|
||||||
|
#### **Approve:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "approve",
|
||||||
|
"message": "Content looks good, approved"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Revision:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "revision",
|
||||||
|
"message": "Please fix grammar and improve conclusion"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **Reject:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "reject",
|
||||||
|
"message": "Content does not meet quality standards"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 **Benefits**
|
||||||
|
|
||||||
|
### **1. Consistency**
|
||||||
|
- ✅ Semua action menggunakan field yang sama untuk message
|
||||||
|
- ✅ Tidak ada duplikasi field `RejectionReason` vs `RevisionMessage`
|
||||||
|
- ✅ Konsisten dengan naming convention
|
||||||
|
|
||||||
|
### **2. Simplicity**
|
||||||
|
- ✅ Satu endpoint untuk semua action types
|
||||||
|
- ✅ Satu field untuk semua message types
|
||||||
|
- ✅ StatusId sebagai pembeda utama
|
||||||
|
|
||||||
|
### **3. Maintainability**
|
||||||
|
- ✅ Code lebih mudah di-maintain
|
||||||
|
- ✅ Tidak ada duplikasi logic
|
||||||
|
- ✅ Single source of truth untuk message
|
||||||
|
|
||||||
|
### **4. Backward Compatibility**
|
||||||
|
- ✅ Field `revision_message` tetap ada dan digunakan
|
||||||
|
- ✅ Field `rejection_reason` tetap ada tapi tidak digunakan
|
||||||
|
- ✅ Tidak ada breaking change untuk database schema
|
||||||
|
|
||||||
|
## 🔄 **Migration Guide**
|
||||||
|
|
||||||
|
### **Untuk Client yang Menggunakan API Lama:**
|
||||||
|
|
||||||
|
#### **Before (Multiple Endpoints):**
|
||||||
|
```bash
|
||||||
|
# Reject
|
||||||
|
PUT /article-approval-flows/{id}/reject
|
||||||
|
{"reason": "Content not suitable"}
|
||||||
|
|
||||||
|
# Request Revision
|
||||||
|
PUT /article-approval-flows/{id}/request-revision
|
||||||
|
{"message": "Please fix grammar"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **After (Single Endpoint):**
|
||||||
|
```bash
|
||||||
|
# Reject
|
||||||
|
POST /article-approval-flows/articles/{articleId}/approve
|
||||||
|
{"action": "reject", "message": "Content not suitable"}
|
||||||
|
|
||||||
|
# Request Revision
|
||||||
|
POST /article-approval-flows/articles/{articleId}/approve
|
||||||
|
{"action": "revision", "message": "Please fix grammar"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎉 **Summary**
|
||||||
|
|
||||||
|
Refactoring ini berhasil:
|
||||||
|
|
||||||
|
1. **✅ Menghilangkan duplikasi** field message
|
||||||
|
2. **✅ Menyederhanakan** API dengan single endpoint
|
||||||
|
3. **✅ Menyelaraskan** implementasi dengan field yang sama
|
||||||
|
4. **✅ Mempertahankan** backward compatibility
|
||||||
|
5. **✅ Meningkatkan** maintainability code
|
||||||
|
|
||||||
|
Sekarang sistem approval menggunakan implementasi yang lebih konsisten dan mudah di-maintain! 🚀
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
# Updated Approval Flow Logic
|
||||||
|
|
||||||
|
## 🎯 **Overview**
|
||||||
|
|
||||||
|
Flow approval telah diubah sesuai permintaan untuk memberikan pengalaman yang lebih baik dalam proses revisi dan penolakan artikel.
|
||||||
|
|
||||||
|
## 🔄 **Flow Behavior Changes**
|
||||||
|
|
||||||
|
### **1. Approve Action**
|
||||||
|
- ✅ **Behavior**: Tetap sama seperti sebelumnya
|
||||||
|
- ✅ **Result**: Naik ke step berikutnya atau selesai jika sudah step terakhir
|
||||||
|
- ✅ **Status**: Workflow berlanjut normal
|
||||||
|
|
||||||
|
### **2. Revision Action (Baru)**
|
||||||
|
- ✅ **Behavior**: Mundur ke step awal (step 1) dengan status pending
|
||||||
|
- ✅ **Result**: Flow approval direset seperti belum berjalan
|
||||||
|
- ✅ **Status**: `StatusId = 1` (pending)
|
||||||
|
- ✅ **Article Status**: `StatusId = 1` (draft), `IsDraft = true`
|
||||||
|
- ✅ **Restart**: Ketika user melakukan update artikel, approval akan berjalan kembali dari step 1
|
||||||
|
|
||||||
|
### **3. Reject Action (Baru)**
|
||||||
|
- ✅ **Behavior**: Mundur ke step awal (step 1) dengan status pending
|
||||||
|
- ✅ **Result**: Flow approval direset seperti belum berjalan
|
||||||
|
- ✅ **Status**: `StatusId = 1` (pending)
|
||||||
|
- ✅ **Article Status**: `StatusId = 1` (draft), `IsDraft = true`
|
||||||
|
- ✅ **Restart**: Ketika user melakukan update artikel, approval akan berjalan kembali dari step 1
|
||||||
|
|
||||||
|
## 📊 **Database Changes**
|
||||||
|
|
||||||
|
### **Article Approval Flows Table:**
|
||||||
|
```sql
|
||||||
|
-- Untuk Revision Action
|
||||||
|
UPDATE article_approval_flows
|
||||||
|
SET current_step = 1,
|
||||||
|
status_id = 1, -- pending
|
||||||
|
revision_requested = true,
|
||||||
|
revision_message = 'message',
|
||||||
|
completed_at = NULL -- Reset completed_at
|
||||||
|
WHERE id = flow_id;
|
||||||
|
|
||||||
|
-- Untuk Reject Action
|
||||||
|
UPDATE article_approval_flows
|
||||||
|
SET current_step = 1,
|
||||||
|
status_id = 1, -- pending
|
||||||
|
revision_message = 'message',
|
||||||
|
completed_at = NULL -- Reset completed_at
|
||||||
|
WHERE id = flow_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Articles Table:**
|
||||||
|
```sql
|
||||||
|
-- Untuk Revision & Reject Action
|
||||||
|
UPDATE articles
|
||||||
|
SET status_id = 1, -- draft
|
||||||
|
is_draft = true,
|
||||||
|
workflow_id = workflow_id, -- Keep workflow ID
|
||||||
|
current_approval_step = 1 -- Reset to step 1
|
||||||
|
WHERE id = article_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 **Action Types & Behavior Summary**
|
||||||
|
|
||||||
|
| Action | Flow Behavior | Article Status | Restart Behavior |
|
||||||
|
|--------|---------------|----------------|------------------|
|
||||||
|
| **`approve`** | Naik ke step berikutnya | Tetap dalam approval | Workflow berlanjut |
|
||||||
|
| **`revision`** | Reset ke step 1, status pending | Draft | Restart dari step 1 saat update |
|
||||||
|
| **`reject`** | Reset ke step 1, status pending | Draft | Restart dari step 1 saat update |
|
||||||
|
|
||||||
|
## 🚀 **API Usage Examples**
|
||||||
|
|
||||||
|
### **Revision Request:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "revision",
|
||||||
|
"message": "Please fix grammar and improve conclusion"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully revision through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 123,
|
||||||
|
"flow_id": 456,
|
||||||
|
"action": "revision",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Reject Request:**
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8080/api/article-approval-flows/articles/123/approve" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"action": "reject",
|
||||||
|
"message": "Content does not meet quality standards"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Article successfully reject through active approval flow"],
|
||||||
|
"data": {
|
||||||
|
"article_id": 123,
|
||||||
|
"flow_id": 456,
|
||||||
|
"action": "reject",
|
||||||
|
"current_step": 1,
|
||||||
|
"current_branch": "Branch_A",
|
||||||
|
"workflow_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 **Restart Flow Logic**
|
||||||
|
|
||||||
|
### **Ketika User Update Artikel:**
|
||||||
|
|
||||||
|
1. **System Check**: Apakah artikel memiliki `workflow_id` dan `current_approval_step = 1`?
|
||||||
|
2. **Auto Restart**: Jika ya, approval flow akan berjalan kembali dari step 1
|
||||||
|
3. **Status Update**: Artikel status berubah dari draft ke pending approval
|
||||||
|
4. **Flow Continuation**: Approval flow berlanjut dengan step yang sudah direset
|
||||||
|
|
||||||
|
### **Implementation dalam Articles Service:**
|
||||||
|
```go
|
||||||
|
// Ketika artikel diupdate, check apakah perlu restart approval
|
||||||
|
if article.WorkflowId != nil && article.CurrentApprovalStep != nil && *article.CurrentApprovalStep == 1 {
|
||||||
|
// Restart approval flow
|
||||||
|
err := articleApprovalFlowsService.RestartApprovalFlow(article.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 **Benefits**
|
||||||
|
|
||||||
|
### **1. Better User Experience**
|
||||||
|
- ✅ User bisa melakukan revisi tanpa kehilangan progress workflow
|
||||||
|
- ✅ Reject tidak menghapus workflow, hanya reset
|
||||||
|
- ✅ Mudah untuk restart approval setelah update
|
||||||
|
|
||||||
|
### **2. Workflow Continuity**
|
||||||
|
- ✅ Workflow ID tetap tersimpan untuk restart
|
||||||
|
- ✅ Tidak perlu membuat workflow baru
|
||||||
|
- ✅ History approval tetap tersimpan
|
||||||
|
|
||||||
|
### **3. Flexible Process**
|
||||||
|
- ✅ User bisa update artikel dan approval otomatis restart
|
||||||
|
- ✅ Tidak ada duplikasi workflow
|
||||||
|
- ✅ Konsisten dengan business logic
|
||||||
|
|
||||||
|
## 🎯 **Use Cases**
|
||||||
|
|
||||||
|
### **Scenario 1: Revision Process**
|
||||||
|
1. Editor review artikel di step 2
|
||||||
|
2. Editor request revision → Flow reset ke step 1, artikel jadi draft
|
||||||
|
3. Author update artikel
|
||||||
|
4. Approval flow restart otomatis dari step 1
|
||||||
|
5. Editor review lagi dari awal
|
||||||
|
|
||||||
|
### **Scenario 2: Rejection Process**
|
||||||
|
1. Editor review artikel di step 2
|
||||||
|
2. Editor reject artikel → Flow reset ke step 1, artikel jadi draft
|
||||||
|
3. Author update artikel
|
||||||
|
4. Approval flow restart otomatis dari step 1
|
||||||
|
5. Editor review lagi dari awal
|
||||||
|
|
||||||
|
### **Scenario 3: Multiple Revisions**
|
||||||
|
1. Editor request revision → Reset ke step 1
|
||||||
|
2. Author update → Restart approval
|
||||||
|
3. Editor request revision lagi → Reset ke step 1 lagi
|
||||||
|
4. Author update lagi → Restart approval lagi
|
||||||
|
5. Proses bisa berulang sampai approved
|
||||||
|
|
||||||
|
## 🎉 **Summary**
|
||||||
|
|
||||||
|
Perubahan flow ini memberikan:
|
||||||
|
|
||||||
|
1. **✅ Flexibility**: User bisa melakukan revisi/reject tanpa kehilangan workflow
|
||||||
|
2. **✅ Continuity**: Workflow tidak terputus, hanya direset
|
||||||
|
3. **✅ Auto Restart**: Approval otomatis restart saat artikel diupdate
|
||||||
|
4. **✅ Better UX**: Proses yang lebih intuitif dan user-friendly
|
||||||
|
5. **✅ Data Integrity**: Workflow ID dan history tetap tersimpan
|
||||||
|
|
||||||
|
Sekarang sistem approval memberikan pengalaman yang lebih baik dengan flow yang lebih fleksibel! 🚀
|
||||||
|
|
@ -3267,150 +3267,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/article-approval-flows/{id}/reject": {
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "API for rejecting article",
|
|
||||||
"tags": [
|
|
||||||
"ArticleApprovalFlows"
|
|
||||||
],
|
|
||||||
"summary": "Reject article",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Insert the Authorization",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer \u003cAdd access token here\u003e",
|
|
||||||
"description": "Insert your access token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "ArticleApprovalFlows ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Rejection data",
|
|
||||||
"name": "req",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/request.RejectionRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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}/request-revision": {
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "API for requesting revision for article",
|
|
||||||
"tags": [
|
|
||||||
"ArticleApprovalFlows"
|
|
||||||
],
|
|
||||||
"summary": "Request revision for article",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Insert the Authorization",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer \u003cAdd access token here\u003e",
|
|
||||||
"description": "Insert your access token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "ArticleApprovalFlows ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Revision request data",
|
|
||||||
"name": "req",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/request.RevisionRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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}/resubmit": {
|
"/article-approval-flows/{id}/resubmit": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -7827,6 +7683,15 @@ const docTemplate = `{
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Required payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/request.ArticlesUpdateRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Articles ID",
|
"description": "Articles ID",
|
||||||
|
|
@ -16475,7 +16340,18 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"request.ApprovalActionRequest": {
|
"request.ApprovalActionRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"action"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"approve",
|
||||||
|
"revision",
|
||||||
|
"reject"
|
||||||
|
]
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
@ -17086,6 +16962,59 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.ArticlesUpdateRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"categoryIds",
|
||||||
|
"description",
|
||||||
|
"htmlDescription",
|
||||||
|
"slug",
|
||||||
|
"tags",
|
||||||
|
"title",
|
||||||
|
"typeId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"aiArticleId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"categoryIds": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdById": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"htmlDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isDraft": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"statusId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"typeId": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.BookmarksCreateRequest": {
|
"request.BookmarksCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -17744,17 +17673,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request.RejectionRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"reason"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"reason": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request.ReorderApprovalWorkflowStepsRequest": {
|
"request.ReorderApprovalWorkflowStepsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -17791,17 +17709,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request.RevisionRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"message"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request.SchedulesCreateRequest": {
|
"request.SchedulesCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -3256,150 +3256,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/article-approval-flows/{id}/reject": {
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "API for rejecting article",
|
|
||||||
"tags": [
|
|
||||||
"ArticleApprovalFlows"
|
|
||||||
],
|
|
||||||
"summary": "Reject article",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Insert the Authorization",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer \u003cAdd access token here\u003e",
|
|
||||||
"description": "Insert your access token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "ArticleApprovalFlows ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Rejection data",
|
|
||||||
"name": "req",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/request.RejectionRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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}/request-revision": {
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "API for requesting revision for article",
|
|
||||||
"tags": [
|
|
||||||
"ArticleApprovalFlows"
|
|
||||||
],
|
|
||||||
"summary": "Request revision for article",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Insert the Authorization",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "Bearer \u003cAdd access token here\u003e",
|
|
||||||
"description": "Insert your access token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "ArticleApprovalFlows ID",
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Revision request data",
|
|
||||||
"name": "req",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/request.RevisionRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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}/resubmit": {
|
"/article-approval-flows/{id}/resubmit": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -7816,6 +7672,15 @@
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header"
|
"in": "header"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Required payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/request.ArticlesUpdateRequest"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Articles ID",
|
"description": "Articles ID",
|
||||||
|
|
@ -16464,7 +16329,18 @@
|
||||||
},
|
},
|
||||||
"request.ApprovalActionRequest": {
|
"request.ApprovalActionRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"action"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"approve",
|
||||||
|
"revision",
|
||||||
|
"reject"
|
||||||
|
]
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
@ -17075,6 +16951,59 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request.ArticlesUpdateRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"categoryIds",
|
||||||
|
"description",
|
||||||
|
"htmlDescription",
|
||||||
|
"slug",
|
||||||
|
"tags",
|
||||||
|
"title",
|
||||||
|
"typeId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"aiArticleId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"categoryIds": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdById": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"htmlDescription": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isDraft": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isPublish": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"statusId": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"typeId": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"request.BookmarksCreateRequest": {
|
"request.BookmarksCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -17733,17 +17662,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request.RejectionRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"reason"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"reason": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request.ReorderApprovalWorkflowStepsRequest": {
|
"request.ReorderApprovalWorkflowStepsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -17780,17 +17698,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request.RevisionRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"message"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request.SchedulesCreateRequest": {
|
"request.SchedulesCreateRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,16 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalActionRequest:
|
request.ApprovalActionRequest:
|
||||||
properties:
|
properties:
|
||||||
|
action:
|
||||||
|
enum:
|
||||||
|
- approve
|
||||||
|
- revision
|
||||||
|
- reject
|
||||||
|
type: string
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- action
|
||||||
type: object
|
type: object
|
||||||
request.ApprovalWorkflowStepRequest:
|
request.ApprovalWorkflowStepRequest:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -561,6 +569,43 @@ definitions:
|
||||||
- title
|
- title
|
||||||
- typeId
|
- typeId
|
||||||
type: object
|
type: object
|
||||||
|
request.ArticlesUpdateRequest:
|
||||||
|
properties:
|
||||||
|
aiArticleId:
|
||||||
|
type: integer
|
||||||
|
categoryIds:
|
||||||
|
type: string
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
createdById:
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
htmlDescription:
|
||||||
|
type: string
|
||||||
|
isDraft:
|
||||||
|
type: boolean
|
||||||
|
isPublish:
|
||||||
|
type: boolean
|
||||||
|
slug:
|
||||||
|
type: string
|
||||||
|
statusId:
|
||||||
|
type: integer
|
||||||
|
tags:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
typeId:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- categoryIds
|
||||||
|
- description
|
||||||
|
- htmlDescription
|
||||||
|
- slug
|
||||||
|
- tags
|
||||||
|
- title
|
||||||
|
- typeId
|
||||||
|
type: object
|
||||||
request.BookmarksCreateRequest:
|
request.BookmarksCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
articleId:
|
articleId:
|
||||||
|
|
@ -1014,13 +1059,6 @@ definitions:
|
||||||
- statusId
|
- statusId
|
||||||
- userId
|
- userId
|
||||||
type: object
|
type: object
|
||||||
request.RejectionRequest:
|
|
||||||
properties:
|
|
||||||
reason:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- reason
|
|
||||||
type: object
|
|
||||||
request.ReorderApprovalWorkflowStepsRequest:
|
request.ReorderApprovalWorkflowStepsRequest:
|
||||||
properties:
|
properties:
|
||||||
stepOrders:
|
stepOrders:
|
||||||
|
|
@ -1045,13 +1083,6 @@ definitions:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
request.RevisionRequest:
|
|
||||||
properties:
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- message
|
|
||||||
type: object
|
|
||||||
request.SchedulesCreateRequest:
|
request.SchedulesCreateRequest:
|
||||||
properties:
|
properties:
|
||||||
createdById:
|
createdById:
|
||||||
|
|
@ -3245,100 +3276,6 @@ paths:
|
||||||
summary: Get next steps preview for multi-branch workflow
|
summary: Get next steps preview for multi-branch workflow
|
||||||
tags:
|
tags:
|
||||||
- ArticleApprovalFlows
|
- ArticleApprovalFlows
|
||||||
/article-approval-flows/{id}/reject:
|
|
||||||
put:
|
|
||||||
description: API for rejecting article
|
|
||||||
parameters:
|
|
||||||
- description: Insert the Authorization
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- default: Bearer <Add access token here>
|
|
||||||
description: Insert your access token
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
type: string
|
|
||||||
- description: ArticleApprovalFlows ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
- description: Rejection data
|
|
||||||
in: body
|
|
||||||
name: req
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/request.RejectionRequest'
|
|
||||||
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: Reject article
|
|
||||||
tags:
|
|
||||||
- ArticleApprovalFlows
|
|
||||||
/article-approval-flows/{id}/request-revision:
|
|
||||||
put:
|
|
||||||
description: API for requesting revision for article
|
|
||||||
parameters:
|
|
||||||
- description: Insert the Authorization
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- default: Bearer <Add access token here>
|
|
||||||
description: Insert your access token
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
type: string
|
|
||||||
- description: ArticleApprovalFlows ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: integer
|
|
||||||
- description: Revision request data
|
|
||||||
in: body
|
|
||||||
name: req
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/request.RevisionRequest'
|
|
||||||
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: Request revision for article
|
|
||||||
tags:
|
|
||||||
- ArticleApprovalFlows
|
|
||||||
/article-approval-flows/{id}/resubmit:
|
/article-approval-flows/{id}/resubmit:
|
||||||
put:
|
put:
|
||||||
description: API for resubmitting article after revision
|
description: API for resubmitting article after revision
|
||||||
|
|
@ -6193,6 +6130,12 @@ paths:
|
||||||
in: header
|
in: header
|
||||||
name: Authorization
|
name: Authorization
|
||||||
type: string
|
type: string
|
||||||
|
- description: Required payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/request.ArticlesUpdateRequest'
|
||||||
- description: Articles ID
|
- description: Articles ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue