606 lines
20 KiB
Go
606 lines
20 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/rs/zerolog"
|
||
|
|
"time"
|
||
|
|
"web-medols-be/app/database/entity"
|
||
|
|
"web-medols-be/app/module/article_approval_flows/repository"
|
||
|
|
"web-medols-be/app/module/article_approval_flows/request"
|
||
|
|
approvalWorkflowsRepo "web-medols-be/app/module/approval_workflows/repository"
|
||
|
|
approvalWorkflowStepsRepo "web-medols-be/app/module/approval_workflow_steps/repository"
|
||
|
|
approvalStepLogsRepo "web-medols-be/app/module/article_approval_step_logs/repository"
|
||
|
|
articlesRepo "web-medols-be/app/module/articles/repository"
|
||
|
|
"web-medols-be/utils/paginator"
|
||
|
|
)
|
||
|
|
|
||
|
|
type articleApprovalFlowsService struct {
|
||
|
|
ArticleApprovalFlowsRepository repository.ArticleApprovalFlowsRepository
|
||
|
|
ApprovalWorkflowsRepository approvalWorkflowsRepo.ApprovalWorkflowsRepository
|
||
|
|
ApprovalWorkflowStepsRepository approvalWorkflowStepsRepo.ApprovalWorkflowStepsRepository
|
||
|
|
ArticleApprovalStepLogsRepository approvalStepLogsRepo.ArticleApprovalStepLogsRepository
|
||
|
|
ArticlesRepository articlesRepo.ArticlesRepository
|
||
|
|
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,
|
||
|
|
log zerolog.Logger,
|
||
|
|
) ArticleApprovalFlowsService {
|
||
|
|
return &articleApprovalFlowsService{
|
||
|
|
ArticleApprovalFlowsRepository: articleApprovalFlowsRepository,
|
||
|
|
ApprovalWorkflowsRepository: approvalWorkflowsRepository,
|
||
|
|
ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository,
|
||
|
|
ArticleApprovalStepLogsRepository: articleApprovalStepLogsRepository,
|
||
|
|
ArticlesRepository: articlesRepository,
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article status and workflow info
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
WorkflowId: &workflow.ID,
|
||
|
|
CurrentApprovalStep: &flow.CurrentStep,
|
||
|
|
StatusId: &[]int{1}[0], // pending approval
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, articleId, articleUpdate)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create initial step log
|
||
|
|
stepLog := &entity.ArticleApprovalStepLogs{
|
||
|
|
ApprovalFlowId: flow.ID,
|
||
|
|
StepOrder: 1,
|
||
|
|
StepName: firstStep.StepName,
|
||
|
|
Action: "submitted",
|
||
|
|
Message: &[]string{"Article submitted for approval"}[0],
|
||
|
|
ProcessedAt: time.Now(),
|
||
|
|
UserLevelId: firstStep.RequiredUserLevelId,
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = _i.ArticleApprovalStepLogsRepository.Create(clientId, stepLog)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return flow, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
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 {
|
||
|
|
// No next step - approval complete
|
||
|
|
flowUpdate := &entity.ArticleApprovalFlows{
|
||
|
|
StatusId: 2, // approved
|
||
|
|
CompletedAt: &[]time.Time{time.Now()}[0],
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticleApprovalFlowsRepository.Update(flowId, flowUpdate)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article status
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
StatusId: &[]int{2}[0], // approved
|
||
|
|
CurrentApprovalStep: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article current step
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
CurrentApprovalStep: &nextStep.StepOrder,
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article status
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
StatusId: &[]int{3}[0], // rejected
|
||
|
|
CurrentApprovalStep: nil,
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article status
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
StatusId: &[]int{4}[0], // revision_requested
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update article status
|
||
|
|
articleUpdate := &entity.Articles{
|
||
|
|
StatusId: &[]int{1}[0], // pending approval
|
||
|
|
CurrentApprovalStep: &[]int{1}[0],
|
||
|
|
}
|
||
|
|
|
||
|
|
err = _i.ArticlesRepository.Update(clientId, flow.ArticleId, articleUpdate)
|
||
|
|
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
|
||
|
|
}
|