2025-09-28 01:53:09 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
2025-10-01 06:18:48 +00:00
|
|
|
"encoding/json"
|
2025-09-28 01:53:09 +00:00
|
|
|
"errors"
|
2025-10-01 21:17:11 +00:00
|
|
|
"fmt"
|
2025-09-30 13:34:56 +00:00
|
|
|
"netidhub-saas-be/app/database/entity"
|
|
|
|
|
approvalWorkflowStepsRepo "netidhub-saas-be/app/module/approval_workflow_steps/repository"
|
|
|
|
|
approvalWorkflowsRepo "netidhub-saas-be/app/module/approval_workflows/repository"
|
|
|
|
|
"netidhub-saas-be/app/module/article_approval_flows/repository"
|
|
|
|
|
"netidhub-saas-be/app/module/article_approval_flows/request"
|
|
|
|
|
approvalStepLogsRepo "netidhub-saas-be/app/module/article_approval_step_logs/repository"
|
|
|
|
|
articlesRepo "netidhub-saas-be/app/module/articles/repository"
|
|
|
|
|
usersRepo "netidhub-saas-be/app/module/users/repository"
|
|
|
|
|
"netidhub-saas-be/utils/paginator"
|
2025-10-01 03:10:18 +00:00
|
|
|
utilSvc "netidhub-saas-be/utils/service"
|
2025-09-28 01:53:09 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type articleApprovalFlowsService struct {
|
|
|
|
|
ArticleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository
|
|
|
|
|
ApprovalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository
|
|
|
|
|
ApprovalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository
|
|
|
|
|
ArticleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository
|
|
|
|
|
ArticlesRepository articlesRepo.ArticlesRepository
|
|
|
|
|
UsersRepository usersRepo.UsersRepository
|
|
|
|
|
Log zerolog.Logger
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ArticleApprovalFlowsService define interface of IArticleApprovalFlowsService
|
|
|
|
|
type ArticleApprovalFlowsService interface {
|
|
|
|
|
// Basic CRUD
|
2025-10-01 03:10:18 +00:00
|
|
|
GetAll(authToken string, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
|
|
|
|
|
FindOne(authToken string, id uint) (flow *entity.ArticleApprovalFlows, err error)
|
|
|
|
|
Create(authToken string, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
Update(id uint, flow *entity.ArticleApprovalFlows) (err error)
|
2025-10-01 03:10:18 +00:00
|
|
|
Delete(authToken string, id uint) (err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
|
|
|
|
|
// Article submission and approval workflow
|
2025-10-01 03:10:18 +00:00
|
|
|
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)
|
|
|
|
|
ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
|
|
|
|
|
// Dashboard and queue methods
|
2025-10-01 03:10:18 +00:00
|
|
|
GetPendingApprovals(authToken string, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
|
|
|
|
|
GetMyApprovalQueue(authToken string, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
|
|
|
|
|
GetApprovalHistory(authToken string, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
|
|
|
|
|
// Statistics and analytics
|
2025-10-01 03:10:18 +00:00
|
|
|
GetPendingCountByLevel(authToken string, userLevelId uint) (count int64, err error)
|
|
|
|
|
GetOverdueCountByLevel(authToken string, userLevelId uint) (count int64, err error)
|
|
|
|
|
GetApprovalStatistics(authToken string, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, err error)
|
|
|
|
|
GetWorkloadAnalytics(authToken string, userLevelId uint) (analytics map[string]interface{}, err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
|
|
|
|
|
// Workflow management
|
2025-10-01 03:10:18 +00:00
|
|
|
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)
|
2025-10-01 06:18:48 +00:00
|
|
|
|
|
|
|
|
// Multi-branch support methods
|
|
|
|
|
ProcessMultiBranchApproval(authToken string, flowId uint, approvedById uint, message string) (err error)
|
2025-10-01 21:17:11 +00:00
|
|
|
ProcessApprovalAction(authToken string, flowId uint, action string, actionById uint, message string) (err error)
|
2025-10-01 06:18:48 +00:00
|
|
|
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)
|
2025-10-01 11:07:42 +00:00
|
|
|
FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error)
|
2025-09-28 01:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewArticleApprovalFlowsService(
|
|
|
|
|
articleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository,
|
|
|
|
|
approvalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository,
|
|
|
|
|
approvalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository,
|
|
|
|
|
articleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository,
|
|
|
|
|
articlesRepository articlesRepo.ArticlesRepository,
|
|
|
|
|
usersRepository usersRepo.UsersRepository,
|
|
|
|
|
log zerolog.Logger,
|
|
|
|
|
) ArticleApprovalFlowsService {
|
|
|
|
|
return &articleApprovalFlowsService{
|
|
|
|
|
ArticleApprovalFlowsRepository: articleApprovalFlowsRepository,
|
|
|
|
|
ApprovalWorkflowsRepository: approvalWorkflowsRepository,
|
|
|
|
|
ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository,
|
|
|
|
|
ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository,
|
|
|
|
|
ArticlesRepository: articlesRepository,
|
|
|
|
|
UsersRepository: usersRepository,
|
|
|
|
|
Log: log,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Basic CRUD implementations
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetAll(authToken string, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, 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 nil, paginator.Pagination{}, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetAll(clientId, req)
|
|
|
|
|
}
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) FindOne(authToken string, id uint) (flow *entity.ArticleApprovalFlows, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.FindOne(clientId, id)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) Create(authToken string, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.Create(clientId, flow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (_i *articleApprovalFlowsService) Update(id uint, flow *entity.ArticleApprovalFlows) (err error) {
|
|
|
|
|
return _i.ArticleApprovalFlowsRepository.Update(id, flow)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) Delete(authToken string, id uint) (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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.Delete(clientId, id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Article submission and approval workflow
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) SubmitArticleForApproval(authToken string, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// Check if article already has an active approval flow
|
|
|
|
|
existingFlow, err := _i.ArticleApprovalFlowsRepository.FindActiveByArticleId(articleId)
|
|
|
|
|
if err == nil && existingFlow != nil {
|
|
|
|
|
return nil, errors.New("article already has an active approval flow")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get workflow (use default if not specified)
|
|
|
|
|
var workflow *entity.ApprovalWorkflows
|
|
|
|
|
if workflowId != nil {
|
|
|
|
|
workflow, err = _i.ApprovalWorkflowsRepository.FindOne(clientId, *workflowId)
|
|
|
|
|
} else {
|
|
|
|
|
workflow, err = _i.ApprovalWorkflowsRepository.FindDefault(clientId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if workflow == nil {
|
|
|
|
|
return nil, errors.New("no workflow found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if workflow.IsActive != nil && !*workflow.IsActive {
|
|
|
|
|
return nil, errors.New("workflow is not active")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get first step of workflow
|
|
|
|
|
firstStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, workflow.ID, 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if firstStep == nil {
|
|
|
|
|
return nil, errors.New("workflow has no steps")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create approval flow
|
|
|
|
|
flow = &entity.ArticleApprovalFlows{
|
|
|
|
|
ArticleId: articleId,
|
|
|
|
|
WorkflowId: workflow.ID,
|
|
|
|
|
CurrentStep: 1,
|
|
|
|
|
StatusId: 1, // pending
|
|
|
|
|
SubmittedById: submittedById,
|
|
|
|
|
SubmittedAt: time.Now(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flow, err = _i.ArticleApprovalFlowsRepository.Create(clientId, flow)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current article data first
|
|
|
|
|
currentArticle, err := _i.ArticlesRepository.FindOne(clientId, articleId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update only the necessary fields
|
|
|
|
|
currentArticle.WorkflowId = &workflow.ID
|
|
|
|
|
currentArticle.CurrentApprovalStep = &flow.CurrentStep
|
|
|
|
|
currentArticle.StatusId = &[]int{1}[0] // pending approval
|
|
|
|
|
|
|
|
|
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, articleId, currentArticle)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process auto-skip logic based on user level
|
2025-10-01 03:10:18 +00:00
|
|
|
err = _i.processAutoSkipSteps(authToken, flow, submittedById)
|
2025-09-28 01:53:09 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return flow, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// processAutoSkipSteps handles automatic step skipping based on user level
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) processAutoSkipSteps(authToken string, flow *entity.ArticleApprovalFlows, submittedById uint) 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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// Get user level of the submitter
|
2025-10-01 03:10:18 +00:00
|
|
|
userLevelId, err := _i.getUserLevelId(authToken, submittedById)
|
2025-09-28 01:53:09 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get all workflow steps
|
|
|
|
|
steps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, flow.WorkflowId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort steps by step order
|
|
|
|
|
sortStepsByOrder(steps)
|
|
|
|
|
|
|
|
|
|
// Process each step to determine if it should be auto-skipped
|
|
|
|
|
for _, step := range steps {
|
|
|
|
|
shouldSkip := _i.shouldSkipStep(userLevelId, step.RequiredUserLevelId)
|
|
|
|
|
|
|
|
|
|
if shouldSkip {
|
|
|
|
|
// Create skip log
|
|
|
|
|
stepLog := &entity.ArticleApprovalStepLogs{
|
|
|
|
|
ApprovalFlowId: flow.ID,
|
|
|
|
|
StepOrder: step.StepOrder,
|
|
|
|
|
StepName: step.StepName,
|
|
|
|
|
ApprovedById: &submittedById,
|
|
|
|
|
Action: "auto_skip",
|
|
|
|
|
Message: &[]string{"Step auto-skipped due to user level"}[0],
|
|
|
|
|
ProcessedAt: time.Now(),
|
|
|
|
|
UserLevelId: step.RequiredUserLevelId,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update flow to next step (handle step order starting from 0)
|
|
|
|
|
nextStepOrder := step.StepOrder + 1
|
|
|
|
|
flow.CurrentStep = nextStepOrder
|
|
|
|
|
} else {
|
|
|
|
|
// Stop at first step that cannot be skipped
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update flow with final current step
|
|
|
|
|
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
|
|
|
|
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.CurrentApprovalStep = &flow.CurrentStep
|
|
|
|
|
|
|
|
|
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if all steps were skipped (workflow complete)
|
|
|
|
|
// Find the highest step order
|
|
|
|
|
maxStepOrder := 0
|
|
|
|
|
for _, step := range steps {
|
|
|
|
|
if step.StepOrder > maxStepOrder {
|
|
|
|
|
maxStepOrder = step.StepOrder
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flow.CurrentStep > maxStepOrder {
|
|
|
|
|
// All steps completed, mark as approved
|
|
|
|
|
flow.StatusId = 2 // approved
|
|
|
|
|
flow.CurrentStep = 0 // Set to 0 to indicate completion
|
|
|
|
|
flow.CompletedAt = &[]time.Time{time.Now()}[0]
|
|
|
|
|
|
|
|
|
|
err = _i.ArticleApprovalFlowsRepository.Update(flow.ID, flow)
|
|
|
|
|
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{2}[0] // approved
|
|
|
|
|
currentArticle.CurrentApprovalStep = &[]int{0}[0] // Set to 0 to indicate completion
|
|
|
|
|
|
|
|
|
|
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
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) getUserLevelId(authToken string, userId uint) (uint, 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 0, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// Get user from database to retrieve user level
|
|
|
|
|
user, err := _i.UsersRepository.FindOne(clientId, userId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
_i.Log.Error().Err(err).Uint("userId", userId).Msg("Failed to find user")
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if user.UserLevel == nil {
|
|
|
|
|
_i.Log.Error().Uint("userId", userId).Msg("User has no user level")
|
|
|
|
|
return 0, errors.New("user has no user level")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_i.Log.Info().
|
|
|
|
|
Uint("userId", userId).
|
|
|
|
|
Uint("userLevelId", user.UserLevel.ID).
|
|
|
|
|
Str("userLevelName", user.UserLevel.Name).
|
|
|
|
|
Msg("Retrieved user level from database")
|
|
|
|
|
|
|
|
|
|
return user.UserLevel.ID, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// shouldSkipStep determines if a step should be auto-skipped based on user level
|
|
|
|
|
func (_i *articleApprovalFlowsService) shouldSkipStep(userLevelId, requiredLevelId uint) bool {
|
|
|
|
|
// Get user level details to compare level numbers
|
|
|
|
|
// User level with lower level_number (higher authority) can skip steps requiring higher level_number
|
|
|
|
|
// For now, we'll use a simple comparison based on IDs
|
|
|
|
|
// In production, this should compare level_number fields
|
|
|
|
|
|
|
|
|
|
// Simple logic: if user level ID is less than required level ID, they can skip
|
|
|
|
|
// This assumes level 1 (ID=1) has higher authority than level 2 (ID=2), etc.
|
|
|
|
|
return userLevelId < requiredLevelId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sortStepsByOrder sorts workflow steps by their step order
|
|
|
|
|
func sortStepsByOrder(steps []*entity.ApprovalWorkflowSteps) {
|
|
|
|
|
// Simple bubble sort for step order
|
|
|
|
|
n := len(steps)
|
|
|
|
|
for i := 0; i < n-1; i++ {
|
|
|
|
|
for j := 0; j < n-i-1; j++ {
|
|
|
|
|
if steps[j].StepOrder > steps[j+1].StepOrder {
|
|
|
|
|
steps[j], steps[j+1] = steps[j+1], steps[j]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) ApproveStep(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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if there's a next step
|
|
|
|
|
nextStep, err := _i.ApprovalWorkflowStepsRepository.GetNextStep(clientId, flow.WorkflowId, flow.CurrentStep)
|
|
|
|
|
if err != nil && err.Error() != "record not found" {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if nextStep == nil || nextStep.ID == 0 {
|
|
|
|
|
// No next step - approval complete
|
|
|
|
|
flowUpdate := &entity.ArticleApprovalFlows{
|
|
|
|
|
StatusId: 2, // approved
|
|
|
|
|
CurrentStep: 0, // Set to 0 to indicate completion
|
|
|
|
|
CompletedAt: &[]time.Time{time.Now()}[0],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Debug logging
|
|
|
|
|
_i.Log.Info().
|
|
|
|
|
Interface("flowUpdate :: ", flowUpdate).
|
|
|
|
|
Msg("Retrieved next step from database")
|
|
|
|
|
|
|
|
|
|
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{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
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Move to next step
|
|
|
|
|
flowUpdate := &entity.ArticleApprovalFlows{
|
|
|
|
|
CurrentStep: nextStep.StepOrder,
|
|
|
|
|
StatusId: 1, // pending
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.CurrentApprovalStep = &nextStep.StepOrder
|
|
|
|
|
|
|
|
|
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) ResubmitAfterRevision(authToken string, flowId uint, resubmittedById uint) (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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// 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 != 4 { // not revision_requested
|
|
|
|
|
return errors.New("approval flow is not in revision requested state")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset approval flow to pending
|
|
|
|
|
flowUpdate := &entity.ArticleApprovalFlows{
|
|
|
|
|
StatusId: 1, // pending
|
|
|
|
|
RevisionRequested: &[]bool{false}[0],
|
|
|
|
|
RevisionMessage: nil,
|
|
|
|
|
CurrentStep: 1, // restart from first step
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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{1}[0] // pending approval
|
|
|
|
|
currentArticle.CurrentApprovalStep = &[]int{1}[0]
|
|
|
|
|
|
|
|
|
|
err = _i.ArticlesRepository.UpdateSkipNull(clientId, flow.ArticleId, currentArticle)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create resubmission log
|
|
|
|
|
stepLog := &entity.ArticleApprovalStepLogs{
|
|
|
|
|
ApprovalFlowId: flow.ID,
|
|
|
|
|
StepOrder: 1,
|
|
|
|
|
StepName: "Resubmission",
|
|
|
|
|
ApprovedById: &resubmittedById,
|
|
|
|
|
Action: "resubmit",
|
|
|
|
|
Message: &[]string{"Article resubmitted after revision"}[0],
|
|
|
|
|
ProcessedAt: time.Now(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dashboard and queue methods
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetPendingApprovals(authToken string, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, 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 nil, paginator.Pagination{}, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetPendingApprovals(clientId, userLevelId, page, limit, filters)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetMyApprovalQueue(authToken string, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, 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 nil, paginator.Pagination{}, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetMyApprovalQueue(clientId, userLevelId, page, limit, includePreview, urgentOnly)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetApprovalHistory(authToken string, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, 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 nil, paginator.Pagination{}, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalStepLogsRepository.GetApprovalHistory(clientId, articleId, page, limit)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Statistics and analytics
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetPendingCountByLevel(authToken string, userLevelId uint) (count int64, 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 0, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetPendingCountByLevel(clientId, userLevelId)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetOverdueCountByLevel(authToken string, userLevelId uint) (count int64, 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 0, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetOverdueCountByLevel(clientId, userLevelId)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetApprovalStatistics(authToken string, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
stats = make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
// Get approved count
|
|
|
|
|
approvedCount, err := _i.ArticleApprovalFlowsRepository.GetApprovedCountByPeriod(clientId, userLevelId, startDate, endDate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get rejected count
|
|
|
|
|
rejectedCount, err := _i.ArticleApprovalFlowsRepository.GetRejectedCountByPeriod(clientId, userLevelId, startDate, endDate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get revision request count
|
|
|
|
|
revisionCount, err := _i.ArticleApprovalFlowsRepository.GetRevisionRequestCountByPeriod(clientId, userLevelId, startDate, endDate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stats["approved_count"] = approvedCount
|
|
|
|
|
stats["rejected_count"] = rejectedCount
|
|
|
|
|
stats["revision_requested_count"] = revisionCount
|
|
|
|
|
stats["total_processed"] = approvedCount + rejectedCount + revisionCount
|
|
|
|
|
stats["period_start"] = startDate
|
|
|
|
|
stats["period_end"] = endDate
|
|
|
|
|
|
|
|
|
|
return stats, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetWorkloadAnalytics(authToken string, userLevelId uint) (analytics map[string]interface{}, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
return _i.ArticleApprovalFlowsRepository.GetLevelWorkload(clientId, userLevelId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Workflow management
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) CanUserApproveStep(authToken string, flowId uint, userId uint, userLevelId uint) (canApprove bool, 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 false, "clientId not found in auth token", errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// Get approval flow
|
|
|
|
|
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flow == nil {
|
|
|
|
|
return false, "approval flow not found", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flow.StatusId != 1 && flow.StatusId != 4 { // not pending or revision_requested
|
|
|
|
|
return false, "approval flow is not in pending state", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current step
|
|
|
|
|
currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if currentStep == nil {
|
|
|
|
|
return false, "current step not found", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user level matches required level
|
|
|
|
|
if currentStep.RequiredUserLevelId != userLevelId {
|
|
|
|
|
return false, "user level does not match required level for this step", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user submitted the article (cannot approve own submission)
|
|
|
|
|
if flow.SubmittedById == userId {
|
|
|
|
|
return false, "cannot approve own submission", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true, "", nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetCurrentStepInfo(authToken string, flowId uint) (stepInfo map[string]interface{}, 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 nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
stepInfo = make(map[string]interface{})
|
|
|
|
|
|
|
|
|
|
// Get approval flow
|
|
|
|
|
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flow == nil {
|
|
|
|
|
return nil, errors.New("approval flow not found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get current step
|
|
|
|
|
currentStep, err := _i.ApprovalWorkflowStepsRepository.FindByWorkflowAndStep(clientId, flow.WorkflowId, flow.CurrentStep)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stepInfo["current_step"] = flow.CurrentStep
|
|
|
|
|
stepInfo["step_name"] = currentStep.StepName
|
|
|
|
|
stepInfo["required_user_level_id"] = currentStep.RequiredUserLevelId
|
|
|
|
|
stepInfo["can_skip"] = currentStep.CanSkip
|
|
|
|
|
stepInfo["auto_approve_after_hours"] = currentStep.AutoApproveAfterHours
|
|
|
|
|
stepInfo["status"] = flow.StatusId
|
|
|
|
|
|
|
|
|
|
return stepInfo, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 03:10:18 +00:00
|
|
|
func (_i *articleApprovalFlowsService) GetNextStepPreview(authToken string, flowId uint) (nextStep *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
|
|
|
|
|
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if clientId == nil {
|
|
|
|
|
return nil, errors.New("clientId not found in auth token")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 01:53:09 +00:00
|
|
|
// Get approval flow
|
|
|
|
|
flow, err := _i.ArticleApprovalFlowsRepository.FindOne(clientId, flowId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if flow == nil {
|
|
|
|
|
return nil, errors.New("approval flow not found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get next step
|
|
|
|
|
nextStep, err = _i.ApprovalWorkflowStepsRepository.GetNextStep(clientId, flow.WorkflowId, flow.CurrentStep)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nextStep, nil
|
|
|
|
|
}
|
2025-10-01 06:18:48 +00:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
2025-10-01 11:07:42 +00:00
|
|
|
|
|
|
|
|
func (_i *articleApprovalFlowsService) FindActiveByArticleId(articleId uint) (flow *entity.ArticleApprovalFlows, err error) {
|
|
|
|
|
return _i.ArticleApprovalFlowsRepository.FindActiveByArticleId(articleId)
|
|
|
|
|
}
|
2025-10-01 21:17:11 +00:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|