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

789 lines
26 KiB
Go

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"
"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(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error)
Create(clientId *uuid.UUID, flow *entity.ArticleApprovalFlows) (flowReturn *entity.ArticleApprovalFlows, err error)
Update(id uint, flow *entity.ArticleApprovalFlows) (err error)
Delete(clientId *uuid.UUID, id uint) (err error)
// Article submission and approval workflow
SubmitArticleForApproval(clientId *uuid.UUID, articleId uint, submittedById uint, workflowId *uint) (flow *entity.ArticleApprovalFlows, err error)
ApproveStep(clientId *uuid.UUID, flowId uint, approvedById uint, message string) (err error)
RejectArticle(clientId *uuid.UUID, flowId uint, rejectedById uint, reason string) (err error)
RequestRevision(clientId *uuid.UUID, flowId uint, requestedById uint, revisionMessage string) (err error)
ResubmitAfterRevision(clientId *uuid.UUID, flowId uint, resubmittedById uint) (err error)
// Dashboard and queue methods
GetPendingApprovals(clientId *uuid.UUID, userLevelId uint, page, limit int, filters map[string]interface{}) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
GetMyApprovalQueue(clientId *uuid.UUID, userLevelId uint, page, limit int, includePreview bool, urgentOnly bool) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error)
GetApprovalHistory(clientId *uuid.UUID, articleId uint, page, limit int) (logs []*entity.ArticleApprovalStepLogs, paging paginator.Pagination, err error)
// Statistics and analytics
GetPendingCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error)
GetOverdueCountByLevel(clientId *uuid.UUID, userLevelId uint) (count int64, err error)
GetApprovalStatistics(clientId *uuid.UUID, userLevelId uint, startDate, endDate time.Time) (stats map[string]interface{}, err error)
GetWorkloadAnalytics(clientId *uuid.UUID, userLevelId uint) (analytics map[string]interface{}, err error)
// Workflow management
CanUserApproveStep(clientId *uuid.UUID, flowId uint, userId uint, userLevelId uint) (canApprove bool, reason string, err error)
GetCurrentStepInfo(clientId *uuid.UUID, flowId uint) (stepInfo map[string]interface{}, err error)
GetNextStepPreview(clientId *uuid.UUID, 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(clientId *uuid.UUID, req request.ArticleApprovalFlowsQueryRequest) (flows []*entity.ArticleApprovalFlows, paging paginator.Pagination, err error) {
return _i.ArticleApprovalFlowsRepository.GetAll(clientId, req)
}
func (_i *articleApprovalFlowsService) FindOne(clientId *uuid.UUID, id uint) (flow *entity.ArticleApprovalFlows, err error) {
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
}