kontenhumas-be/app/module/article_approval_flows/service/article_approval_flows.serv...

818 lines
27 KiB
Plaintext

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(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error) {
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(clientId *uuid.UUID, id uint) (err error) {
return _i.ArticleApprovalFlowsRepository.Delete(clientId, id)
}
// Article submission and approval workflow
func (_i *articleApprovalFlowsService) SubmitArticleForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error) {
// 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(clientId, flow, submittedById)
if err != nil {
return nil, err
}
return flow, nil
}
// processAutoSkipSteps handles automatic step skipping based on user level
func (_i *articleApprovalFlowsService) processAutoSkipSteps(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows, submittedById uint) error {
// Get user level of the submitter
userLevelId, err := _i.getUserLevelId(clientId, 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(clientId *uuid.UUID, userId uint) (uint, error) {
// 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(clientId *uuid.UUID, flowId uint, approvedById uint, message string) (err error) {
// 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(clientId *uuid.UUID, flowId uint, rejectedById uint, reason string) (err error) {
// 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(clientId *uuid.UUID, flowId uint, requestedById uint, revisionMessage string) (err error) {
// 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(clientId *uuid.UUID, flowId uint, resubmittedById uint) (err error) {
// 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(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) {
return _i.ArticleApprovalFlowsRepository.GetPendingApprovals(clientId, userLevelId, page, limit, filters)
}
func (_i *articleApprovalFlowsService) GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) {
return _i.ArticleApprovalFlowsRepository.GetMyApprovalQueue(clientId, userLevelId, page, limit, includePreview, urgentOnly)
}
func (_i *articleApprovalFlowsService) GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error) {
return _i.ArticleApprovalStepLogsRepository.GetApprovalHistory(clientId, articleId, page, limit)
}
// Statistics and analytics
func (_i *articleApprovalFlowsService) GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) {
return _i.ArticleApprovalFlowsRepository.GetPendingCountByLevel(clientId, userLevelId)
}
func (_i *articleApprovalFlowsService) GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error) {
return _i.ArticleApprovalFlowsRepository.GetOverdueCountByLevel(clientId, userLevelId)
}
func (_i *articleApprovalFlowsService) GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, err error) {
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(clientId *uuid.UUID, userLevelId uint) (analytics map[string]interface{}, err error) {
return _i.ArticleApprovalFlowsRepository.GetLevelWorkload(clientId, userLevelId)
}
// Workflow management
func (_i *articleApprovalFlowsService) CanUserApproveStep(clientId *uuid.UUID, flowId uint, userId uint, userLevelId uint) (canApprove bool, reason string, err error) {
// 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(clientId *uuid.UUID, flowId uint) (stepInfo map[string]interface{}, err error) {
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(clientId *uuid.UUID, flowId uint) (nextStep *entity.ApprovalWorkflowSteps, err error) {
// 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
}