package service import ( "errors" "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" utilSvc "netidhub-saas-be/utils/service" "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 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) Update(id uint, flow *entity.ArticleApprovalFlows) (err error) Delete(authToken string, id uint) (err error) // Article submission and approval workflow 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) 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) // Dashboard and queue methods 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) // Statistics and analytics 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) // Workflow management 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) } 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 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") } return _i.ArticleApprovalFlowsRepository.GetAll(clientId, req) } 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") } return _i.ArticleApprovalFlowsRepository.FindOne(clientId, id) } 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") } return _i.ArticleApprovalFlowsRepository.Create(clientId, flow) } func (_i *articleApprovalFlowsService) Update(id uint, flow *entity.ArticleApprovalFlows) (err error) { return _i.ArticleApprovalFlowsRepository.Update(id, flow) } 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") } return _i.ArticleApprovalFlowsRepository.Delete(clientId, id) } // Article submission and approval workflow 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") } // 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 err = _i.processAutoSkipSteps(authToken, flow, submittedById) if err != nil { return nil, err } return flow, nil } // processAutoSkipSteps handles automatic step skipping based on user level 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") } // Get user level of the submitter userLevelId, err := _i.getUserLevelId(authToken, submittedById) 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 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") } // 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] } } } } 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") } // 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 } 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) { // 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 != 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 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") } return _i.ArticleApprovalFlowsRepository.GetPendingApprovals(clientId, userLevelId, page, limit, filters) } 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") } return _i.ArticleApprovalFlowsRepository.GetMyApprovalQueue(clientId, userLevelId, page, limit, includePreview, urgentOnly) } 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") } return _i.ArticleApprovalStepLogsRepository.GetApprovalHistory(clientId, articleId, page, limit) } // Statistics and analytics 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") } return _i.ArticleApprovalFlowsRepository.GetPendingCountByLevel(clientId, userLevelId) } 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") } return _i.ArticleApprovalFlowsRepository.GetOverdueCountByLevel(clientId, userLevelId) } 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") } 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 } 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") } return _i.ArticleApprovalFlowsRepository.GetLevelWorkload(clientId, userLevelId) } // Workflow management 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") } // 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 } 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") } 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 } 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") } // 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 }