kontenhumas-be/app/module/approval_workflows/service/approval_workflows.service.go

1155 lines
41 KiB
Go

package service
import (
"encoding/json"
"errors"
"fmt"
"netidhub-saas-be/app/database/entity"
stepRepo "netidhub-saas-be/app/module/approval_workflow_steps/repository"
"netidhub-saas-be/app/module/approval_workflows/repository"
"netidhub-saas-be/app/module/approval_workflows/request"
"netidhub-saas-be/app/module/approval_workflows/response"
articleApprovalFlowsRepo "netidhub-saas-be/app/module/article_approval_flows/repository"
articleCategoriesRepo "netidhub-saas-be/app/module/article_categories/repository"
clientApprovalSettingsRepo "netidhub-saas-be/app/module/client_approval_settings/repository"
userLevelsRepo "netidhub-saas-be/app/module/user_levels/repository"
userLevelsReq "netidhub-saas-be/app/module/user_levels/request"
userRolesRepo "netidhub-saas-be/app/module/user_roles/repository"
usersRepo "netidhub-saas-be/app/module/users/repository"
"netidhub-saas-be/utils/paginator"
utilSvc "netidhub-saas-be/utils/service"
"github.com/google/uuid"
"github.com/rs/zerolog"
)
type approvalWorkflowsService struct {
ApprovalWorkflowsRepository repository.ApprovalWorkflowsRepository
ApprovalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository
ClientApprovalSettingsRepository clientApprovalSettingsRepo.ClientApprovalSettingsRepository
UsersRepository usersRepo.UsersRepository
UserLevelsRepository userLevelsRepo.UserLevelsRepository
UserRolesRepository userRolesRepo.UserRolesRepository
ArticleCategoriesRepository articleCategoriesRepo.ArticleCategoriesRepository
ArticleApprovalFlowsRepository articleApprovalFlowsRepo.ArticleApprovalFlowsRepository
Log zerolog.Logger
}
// ApprovalWorkflowsService define interface of IApprovalWorkflowsService
type ApprovalWorkflowsService interface {
// Basic CRUD
GetAll(authToken string, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, paging paginator.Pagination, err error)
FindOne(authToken string, id uint) (workflow *entity.ApprovalWorkflows, err error)
Create(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error)
Update(authToken string, id uint, workflow *entity.ApprovalWorkflows) (err error)
Delete(authToken string, id uint) (err error)
// Workflow management
GetDefault(authToken string) (workflow *entity.ApprovalWorkflows, err error)
SetDefault(authToken string, id uint) (err error)
ActivateWorkflow(authToken string, id uint) (err error)
DeactivateWorkflow(authToken string, id uint) (err error)
// Workflow with steps
GetWorkflowWithSteps(authToken string, id uint) (workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps, err error)
CreateWorkflowWithSteps(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, err error)
UpdateWorkflowWithSteps(authToken string, id uint, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (err error)
// Validation
ValidateWorkflow(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error)
CanDeleteWorkflow(authToken string, id uint) (canDelete bool, reason string, err error)
// Comprehensive workflow creation with client settings
CreateWorkflowWithClientSettings(authToken string, req request.CreateApprovalWorkflowWithClientSettingsRequest) (workflow *entity.ApprovalWorkflows, clientSettings *entity.ClientApprovalSettings, err error)
// Comprehensive workflow update with client settings
UpdateWorkflowWithClientSettings(authToken string, req request.UpdateApprovalWorkflowWithClientSettingsRequest) (workflow *entity.ApprovalWorkflows, clientSettings *entity.ClientApprovalSettings, err error)
// Comprehensive workflow details
GetComprehensiveWorkflowDetails(authToken string, workflowId uint) (details *response.ComprehensiveWorkflowDetailResponse, err error)
}
func NewApprovalWorkflowsService(
approvalWorkflowsRepository repository.ApprovalWorkflowsRepository,
approvalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository,
clientApprovalSettingsRepository clientApprovalSettingsRepo.ClientApprovalSettingsRepository,
usersRepository usersRepo.UsersRepository,
userLevelsRepository userLevelsRepo.UserLevelsRepository,
userRolesRepository userRolesRepo.UserRolesRepository,
articleCategoriesRepository articleCategoriesRepo.ArticleCategoriesRepository,
articleApprovalFlowsRepository articleApprovalFlowsRepo.ArticleApprovalFlowsRepository,
log zerolog.Logger,
) ApprovalWorkflowsService {
return &approvalWorkflowsService{
ApprovalWorkflowsRepository: approvalWorkflowsRepository,
ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository,
ClientApprovalSettingsRepository: clientApprovalSettingsRepository,
UsersRepository: usersRepository,
UserLevelsRepository: userLevelsRepository,
UserRolesRepository: userRolesRepository,
ArticleCategoriesRepository: articleCategoriesRepository,
ArticleApprovalFlowsRepository: articleApprovalFlowsRepository,
Log: log,
}
}
// Basic CRUD implementations
func (_i *approvalWorkflowsService) GetAll(authToken string, req request.ApprovalWorkflowsQueryRequest) (workflows []*entity.ApprovalWorkflows, 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.ApprovalWorkflowsRepository.GetAll(clientId, req)
}
func (_i *approvalWorkflowsService) FindOne(authToken string, id uint) (workflow *entity.ApprovalWorkflows, 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.ApprovalWorkflowsRepository.FindOne(clientId, id)
}
func (_i *approvalWorkflowsService) Create(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, 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")
}
// Validate workflow and steps
isValid, validationErrors, err := _i.ValidateWorkflow(authToken, workflow, steps)
if err != nil {
return nil, err
}
if !isValid {
return nil, errors.New(fmt.Sprintf("Validation failed: %v", validationErrors))
}
// Create workflow
workflowReturn, err = _i.ApprovalWorkflowsRepository.Create(clientId, workflow)
if err != nil {
return nil, err
}
// Create steps
for i, step := range steps {
step.WorkflowId = workflowReturn.ID
step.StepOrder = i + 1
_, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step)
if err != nil {
// Rollback workflow creation if step creation fails
_i.ApprovalWorkflowsRepository.Delete(clientId, workflowReturn.ID)
return nil, err
}
}
return workflowReturn, nil
}
func (_i *approvalWorkflowsService) Update(authToken string, id uint, workflow *entity.ApprovalWorkflows) (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")
}
// Check if workflow exists
existingWorkflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id)
if err != nil {
return err
}
if existingWorkflow == nil {
return errors.New("workflow not found")
}
return _i.ApprovalWorkflowsRepository.Update(clientId, id, workflow)
}
func (_i *approvalWorkflowsService) 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")
}
// Check if workflow can be deleted
canDelete, reason, err := _i.CanDeleteWorkflow(authToken, id)
if err != nil {
return err
}
if !canDelete {
return errors.New(reason)
}
return _i.ApprovalWorkflowsRepository.Delete(clientId, id)
}
// Workflow management
func (_i *approvalWorkflowsService) GetDefault(authToken string) (workflow *entity.ApprovalWorkflows, 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.ApprovalWorkflowsRepository.FindDefault(clientId)
}
func (_i *approvalWorkflowsService) SetDefault(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")
}
// Check if workflow exists and is active
workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id)
if err != nil {
return err
}
if workflow == nil {
return errors.New("workflow not found")
}
if workflow.IsActive == nil || !*workflow.IsActive {
return errors.New("cannot set inactive workflow as default")
}
return _i.ApprovalWorkflowsRepository.SetDefault(clientId, id)
}
func (_i *approvalWorkflowsService) ActivateWorkflow(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")
}
// Validate workflow before activation
workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, id)
if err != nil {
return err
}
if workflow == nil {
return errors.New("workflow not found")
}
// Get workflow steps and validate
steps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id)
if err != nil {
return err
}
isValid, validationErrors, err := _i.ValidateWorkflow(authToken, workflow, steps)
if err != nil {
return err
}
if !isValid {
return errors.New(fmt.Sprintf("Cannot activate invalid workflow: %v", validationErrors))
}
// Activate workflow
isActive := true
updateData := &entity.ApprovalWorkflows{IsActive: &isActive}
return _i.ApprovalWorkflowsRepository.Update(clientId, id, updateData)
}
func (_i *approvalWorkflowsService) DeactivateWorkflow(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")
}
// Check if this is the default workflow
defaultWorkflow, err := _i.ApprovalWorkflowsRepository.FindDefault(clientId)
if err != nil {
return err
}
if defaultWorkflow != nil && defaultWorkflow.ID == id {
return errors.New("cannot deactivate default workflow")
}
// Check if workflow is being used in active approval flows
canDelete, reason, err := _i.CanDeleteWorkflow(authToken, id)
if err != nil {
return err
}
if !canDelete {
return errors.New(fmt.Sprintf("Cannot deactivate workflow: %s", reason))
}
// Deactivate workflow
isActive := false
updateData := &entity.ApprovalWorkflows{IsActive: &isActive}
return _i.ApprovalWorkflowsRepository.Update(clientId, id, updateData)
}
// Workflow with steps
func (_i *approvalWorkflowsService) GetWorkflowWithSteps(authToken string, id uint) (workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps, err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
if clientId == nil {
return nil, nil, errors.New("clientId not found in auth token")
}
workflow, err = _i.ApprovalWorkflowsRepository.FindOne(clientId, id)
if err != nil {
return nil, nil, err
}
steps, err = _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id)
if err != nil {
return nil, nil, err
}
return workflow, steps, nil
}
func (_i *approvalWorkflowsService) CreateWorkflowWithSteps(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (workflowReturn *entity.ApprovalWorkflows, 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.Create(authToken, workflow, steps)
}
func (_i *approvalWorkflowsService) UpdateWorkflowWithSteps(authToken string, id uint, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (err error) {
// Extract clientId from authToken
var clientId *uuid.UUID
if authToken != "" {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepository, authToken)
if user != nil && user.ClientId != nil {
clientId = user.ClientId
_i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token")
}
}
if clientId == nil {
return errors.New("clientId not found in auth token")
}
// Update workflow
err = _i.Update(authToken, id, workflow)
if err != nil {
return err
}
// Get existing steps
existingSteps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, id)
if err != nil {
return err
}
// Delete existing steps (simplified approach - in production, you might want to update/merge)
for _, existingStep := range existingSteps {
err = _i.ApprovalWorkflowStepsRepository.Delete(clientId, existingStep.ID)
if err != nil {
return err
}
}
// Create new steps
for i, step := range steps {
step.WorkflowId = id
step.StepOrder = i + 1
_, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step)
if err != nil {
return err
}
}
return nil
}
// Validation
func (_i *approvalWorkflowsService) ValidateWorkflow(authToken string, workflow *entity.ApprovalWorkflows, steps []*entity.ApprovalWorkflowSteps) (isValid bool, errors []string, err error) {
errors = make([]string, 0)
// Validate workflow
if workflow.Name == "" {
errors = append(errors, "Workflow name is required")
}
// Validate steps
if len(steps) == 0 {
errors = append(errors, "Workflow must have at least one step")
} else {
// For multi-branch workflow, we need different validation logic
_i.validateMultiBranchSteps(steps, &errors)
}
isValid = len(errors) == 0
return isValid, errors, nil
}
// validateMultiBranchSteps validates steps for multi-branch workflow
func (_i *approvalWorkflowsService) validateMultiBranchSteps(steps []*entity.ApprovalWorkflowSteps, errors *[]string) {
// Group steps by step order to handle parallel branches
stepOrderGroups := make(map[int][]*entity.ApprovalWorkflowSteps)
for i, step := range steps {
// Basic validation for each step
if step.StepName == "" {
*errors = append(*errors, fmt.Sprintf("Step %d name is required", i+1))
}
if step.RequiredUserLevelId == 0 {
*errors = append(*errors, fmt.Sprintf("Step %d must have a required user level", i+1))
}
if step.StepOrder <= 0 {
*errors = append(*errors, fmt.Sprintf("Step %d must have a valid step order (greater than 0)", i+1))
}
// Validate condition type and value
if step.ConditionType != nil {
validConditionTypes := []string{"user_level", "user_level_hierarchy", "always", "custom"}
isValidConditionType := false
for _, validType := range validConditionTypes {
if *step.ConditionType == validType {
isValidConditionType = true
break
}
}
if !isValidConditionType {
*errors = append(*errors, fmt.Sprintf("Step %d has invalid condition type: %s", i+1, *step.ConditionType))
}
// Validate condition value format for specific condition types
if step.ConditionValue != nil && *step.ConditionValue != "" {
if *step.ConditionType == "user_level_hierarchy" || *step.ConditionType == "user_level" {
// Try to parse as JSON to validate format
var conditionData interface{}
if err := json.Unmarshal([]byte(*step.ConditionValue), &conditionData); err != nil {
*errors = append(*errors, fmt.Sprintf("Step %d has invalid condition value format: %s", i+1, *step.ConditionValue))
}
}
}
}
// Group steps by step order
stepOrderGroups[step.StepOrder] = append(stepOrderGroups[step.StepOrder], step)
}
// Validate step order groups
maxStepOrder := 0
for stepOrder, groupSteps := range stepOrderGroups {
if stepOrder > maxStepOrder {
maxStepOrder = stepOrder
}
// Check for duplicate branch names within the same step order
branchNames := make(map[string]bool)
for _, step := range groupSteps {
if step.BranchName != nil && *step.BranchName != "" {
if branchNames[*step.BranchName] {
*errors = append(*errors, fmt.Sprintf("Duplicate branch name '%s' found in step order %d", *step.BranchName, stepOrder))
}
branchNames[*step.BranchName] = true
}
}
// Validate branch order within the same step order
branchOrders := make(map[int]bool)
for _, step := range groupSteps {
if step.BranchOrder != nil {
if branchOrders[*step.BranchOrder] {
*errors = append(*errors, fmt.Sprintf("Duplicate branch order %d found in step order %d", *step.BranchOrder, stepOrder))
}
branchOrders[*step.BranchOrder] = true
}
}
}
// Validate that we have at least one step with step order 1
if len(stepOrderGroups[1]) == 0 {
*errors = append(*errors, "Workflow must have at least one step with step order 1")
}
// Validate that step orders are sequential (no gaps)
for i := 1; i <= maxStepOrder; i++ {
if len(stepOrderGroups[i]) == 0 {
*errors = append(*errors, fmt.Sprintf("Missing step order %d - step orders must be sequential", i))
}
}
}
func (_i *approvalWorkflowsService) CanDeleteWorkflow(authToken string, id uint) (canDelete 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")
}
// Check if workflow is default
defaultWorkflow, err := _i.ApprovalWorkflowsRepository.FindDefault(clientId)
if err != nil {
return false, "", err
}
if defaultWorkflow != nil && defaultWorkflow.ID == id {
return false, "Cannot delete default workflow", nil
}
// Check if workflow is being used in active approval flows
// This would require a method in ArticleApprovalFlowsRepository
// For now, we'll assume it can be deleted
// TODO: Implement check for active approval flows
return true, "", nil
}
// CreateWorkflowWithClientSettings creates a comprehensive approval workflow with client settings in a single transaction
func (_i *approvalWorkflowsService) CreateWorkflowWithClientSettings(authToken string, req request.CreateApprovalWorkflowWithClientSettingsRequest) (workflow *entity.ApprovalWorkflows, clientSettings *entity.ClientApprovalSettings, 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, nil, errors.New("clientId not found in auth token")
}
_i.Log.Info().
Str("workflowName", req.Name).
Interface("clientId", clientId).
Msg("Creating comprehensive approval workflow with client settings")
// Convert request to entities
workflowEntity := req.ToWorkflowEntity()
stepsEntity := req.ToStepsEntity()
clientSettingsEntity := req.ToClientApprovalSettingsEntity()
// Set client ID for all entities
workflowEntity.ClientId = clientId
clientSettingsEntity.ClientId = *clientId
// Validate workflow and steps
isValid, validationErrors, err := _i.ValidateWorkflow(authToken, workflowEntity, stepsEntity)
if err != nil {
return nil, nil, err
}
if !isValid {
return nil, nil, errors.New(fmt.Sprintf("Validation failed: %v", validationErrors))
}
// Check if client approval settings already exist
existingSettings, err := _i.ClientApprovalSettingsRepository.FindByClientId(*clientId)
if err != nil {
return nil, nil, fmt.Errorf("failed to check existing client approval settings: %w", err)
}
if existingSettings != nil {
return nil, nil, errors.New("client approval settings already exist for this client")
}
// Create workflow
workflow, err = _i.ApprovalWorkflowsRepository.Create(clientId, workflowEntity)
if err != nil {
return nil, nil, fmt.Errorf("failed to create workflow: %w", err)
}
_i.Log.Info().
Uint("workflowId", workflow.ID).
Str("workflowName", workflow.Name).
Msg("Workflow created successfully")
// Create workflow steps
for i, step := range stepsEntity {
step.WorkflowId = workflow.ID
step.StepOrder = i + 1
step.ClientId = clientId
_, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step)
if err != nil {
// Rollback workflow creation if step creation fails
_i.Log.Error().Err(err).Msg("Failed to create workflow step, rolling back workflow")
_i.ApprovalWorkflowsRepository.Delete(clientId, workflow.ID)
return nil, nil, fmt.Errorf("failed to create workflow step %d: %w", i+1, err)
}
}
_i.Log.Info().
Uint("workflowId", workflow.ID).
Int("stepsCount", len(stepsEntity)).
Msg("All workflow steps created successfully")
// Set the workflow as default in client settings if specified
if req.IsDefault != nil && *req.IsDefault {
clientSettingsEntity.DefaultWorkflowId = &workflow.ID
}
// Create client approval settings
clientSettings, err = _i.ClientApprovalSettingsRepository.Create(clientId, clientSettingsEntity)
if err != nil {
// Rollback workflow and steps creation if client settings creation fails
_i.Log.Error().Err(err).Msg("Failed to create client approval settings, rolling back workflow and steps")
// Delete workflow steps
for _, step := range stepsEntity {
_i.ApprovalWorkflowStepsRepository.Delete(clientId, step.ID)
}
// Delete workflow
_i.ApprovalWorkflowsRepository.Delete(clientId, workflow.ID)
return nil, nil, fmt.Errorf("failed to create client approval settings: %w", err)
}
_i.Log.Info().
Uint("workflowId", workflow.ID).
Interface("clientId", clientId).
Msg("Client approval settings created successfully")
// If this workflow is set as default, update the default workflow setting
if req.IsDefault != nil && *req.IsDefault {
err = _i.ApprovalWorkflowsRepository.SetDefault(clientId, workflow.ID)
if err != nil {
_i.Log.Warn().Err(err).Msg("Failed to set workflow as default, but workflow and settings were created successfully")
} else {
_i.Log.Info().
Uint("workflowId", workflow.ID).
Msg("Workflow set as default successfully")
}
}
_i.Log.Info().
Uint("workflowId", workflow.ID).
Interface("clientId", clientId).
Msg("Comprehensive approval workflow with client settings created successfully")
return workflow, clientSettings, nil
}
// GetComprehensiveWorkflowDetails retrieves comprehensive workflow details including all related data
func (_i *approvalWorkflowsService) GetComprehensiveWorkflowDetails(authToken string, workflowId uint) (details *response.ComprehensiveWorkflowDetailResponse, 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")
}
_i.Log.Info().
Uint("workflowId", workflowId).
Interface("clientId", clientId).
Msg("Getting comprehensive workflow details")
// Get workflow
workflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, workflowId)
if err != nil {
return nil, fmt.Errorf("failed to get workflow: %w", err)
}
if workflow == nil {
return nil, errors.New("workflow not found")
}
// Get workflow steps
steps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, workflowId)
if err != nil {
return nil, fmt.Errorf("failed to get workflow steps: %w", err)
}
// Get client approval settings
clientSettings, err := _i.ClientApprovalSettingsRepository.FindByClientId(*clientId)
if err != nil {
_i.Log.Warn().Err(err).Msg("Failed to get client approval settings")
// Don't return error, just log warning
}
// Build comprehensive response
details = &response.ComprehensiveWorkflowDetailResponse{
LastUpdated: workflow.UpdatedAt,
}
// Build workflow detail info
workflowDetail := response.WorkflowDetailInfo{
ID: workflow.ID,
Name: workflow.Name,
Description: workflow.Description,
IsDefault: workflow.IsDefault,
IsActive: workflow.IsActive,
RequiresApproval: workflow.RequiresApproval,
AutoPublish: workflow.AutoPublish,
CreatedAt: workflow.CreatedAt,
UpdatedAt: workflow.UpdatedAt,
TotalSteps: len(steps),
MaxStepOrder: 0,
}
if workflow.ClientId != nil {
clientIdStr := workflow.ClientId.String()
workflowDetail.ClientId = &clientIdStr
}
// Calculate workflow statistics
activeSteps := 0
hasBranches := false
for _, step := range steps {
if step.IsActive != nil && *step.IsActive {
activeSteps++
}
if step.ParentStepId != nil {
hasBranches = true
}
if step.StepOrder > workflowDetail.MaxStepOrder {
workflowDetail.MaxStepOrder = step.StepOrder
}
}
workflowDetail.ActiveSteps = activeSteps
workflowDetail.HasBranches = hasBranches
details.Workflow = workflowDetail
// Build step details
stepDetails := make([]response.WorkflowStepDetailInfo, len(steps))
for i, step := range steps {
stepDetail := response.WorkflowStepDetailInfo{
ID: step.ID,
WorkflowId: step.WorkflowId,
StepOrder: step.StepOrder,
StepName: step.StepName,
RequiredUserLevelId: step.RequiredUserLevelId,
CanSkip: step.CanSkip,
AutoApproveAfterHours: step.AutoApproveAfterHours,
IsActive: step.IsActive,
ParentStepId: step.ParentStepId,
ConditionType: step.ConditionType,
ConditionValue: step.ConditionValue,
IsParallel: step.IsParallel,
BranchName: step.BranchName,
BranchOrder: step.BranchOrder,
}
// Get user level name
userLevel, err := _i.UserLevelsRepository.FindOne(clientId, step.RequiredUserLevelId)
if err == nil && userLevel != nil {
stepDetail.RequiredUserLevelName = userLevel.Name
}
// Get parent step name
if step.ParentStepId != nil {
for _, parentStep := range steps {
if parentStep.ID == *step.ParentStepId {
stepDetail.ParentStepName = &parentStep.StepName
break
}
}
}
// Check if step has children
hasChildren := false
for _, childStep := range steps {
if childStep.ParentStepId != nil && *childStep.ParentStepId == step.ID {
hasChildren = true
break
}
}
stepDetail.HasChildren = hasChildren
// Check if first/last step
stepDetail.IsFirstStep = step.StepOrder == 1
stepDetail.IsLastStep = step.StepOrder == workflowDetail.MaxStepOrder
stepDetails[i] = stepDetail
}
details.Steps = stepDetails
// Build client settings detail
if clientSettings != nil {
clientSettingsDetail := response.ClientApprovalSettingsDetail{
ID: clientSettings.ID,
ClientId: clientSettings.ClientId.String(),
RequiresApproval: clientSettings.RequiresApproval,
DefaultWorkflowId: clientSettings.DefaultWorkflowId,
AutoPublishArticles: clientSettings.AutoPublishArticles,
ApprovalExemptUsers: clientSettings.ApprovalExemptUsers,
ApprovalExemptRoles: clientSettings.ApprovalExemptRoles,
ApprovalExemptCategories: clientSettings.ApprovalExemptCategories,
RequireApprovalFor: clientSettings.RequireApprovalFor,
SkipApprovalFor: clientSettings.SkipApprovalFor,
IsActive: clientSettings.IsActive,
CreatedAt: clientSettings.CreatedAt,
UpdatedAt: clientSettings.UpdatedAt,
}
// Set default workflow name
if clientSettings.DefaultWorkflowId != nil {
defaultWorkflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, *clientSettings.DefaultWorkflowId)
if err == nil && defaultWorkflow != nil {
clientSettingsDetail.DefaultWorkflowName = &defaultWorkflow.Name
}
}
// Get exempt users details
exemptUsersDetails := make([]response.UserDetailInfo, 0)
for _, userId := range clientSettings.ApprovalExemptUsers {
user, err := _i.UsersRepository.FindOne(clientId, userId)
if err == nil && user != nil {
userLevel, _ := _i.UserLevelsRepository.FindOne(clientId, user.UserLevelId)
userLevelName := ""
if userLevel != nil {
userLevelName = userLevel.Name
}
exemptUsersDetails = append(exemptUsersDetails, response.UserDetailInfo{
ID: user.ID,
Username: user.Username,
Fullname: user.Fullname,
Email: user.Email,
UserLevelId: user.UserLevelId,
UserLevelName: userLevelName,
IsActive: user.IsActive,
})
}
}
clientSettingsDetail.ExemptUsersDetails = exemptUsersDetails
// Get exempt roles details
exemptRolesDetails := make([]response.UserRoleDetailInfo, 0)
for _, roleId := range clientSettings.ApprovalExemptRoles {
role, err := _i.UserRolesRepository.FindOne(roleId)
if err == nil && role != nil {
exemptRolesDetails = append(exemptRolesDetails, response.UserRoleDetailInfo{
ID: role.ID,
RoleName: role.Name,
IsActive: role.IsActive,
})
}
}
clientSettingsDetail.ExemptRolesDetails = exemptRolesDetails
// Get exempt categories details
exemptCategoriesDetails := make([]response.CategoryDetailInfo, 0)
for _, categoryId := range clientSettings.ApprovalExemptCategories {
category, err := _i.ArticleCategoriesRepository.FindOne(clientId, categoryId)
if err == nil && category != nil {
exemptCategoriesDetails = append(exemptCategoriesDetails, response.CategoryDetailInfo{
ID: category.ID,
CategoryName: category.Title,
IsActive: category.IsActive,
})
}
}
clientSettingsDetail.ExemptCategoriesDetails = exemptCategoriesDetails
details.ClientSettings = clientSettingsDetail
}
// Build related data
relatedData := response.RelatedDataInfo{}
// Get all user levels for this client
userLevels, _, err := _i.UserLevelsRepository.GetAll(clientId, userLevelsReq.UserLevelsQueryRequest{
Pagination: &paginator.Pagination{},
})
if err == nil {
userLevelDetails := make([]response.UserLevelDetailInfo, len(userLevels))
for i, level := range userLevels {
userLevelDetails[i] = response.UserLevelDetailInfo{
ID: level.ID,
Name: level.Name,
AliasName: level.AliasName,
LevelNumber: level.LevelNumber,
IsApprovalActive: level.IsApprovalActive,
IsActive: level.IsActive,
}
}
relatedData.UserLevels = userLevelDetails
}
// Get workflow statistics (simplified - would need more complex queries in real implementation)
statistics := response.WorkflowStatisticsInfo{
TotalArticlesProcessed: 0, // Would need to query article_approval_flows
PendingArticles: 0, // Would need to query active flows
ApprovedArticles: 0, // Would need to query completed flows
RejectedArticles: 0, // Would need to query rejected flows
AverageProcessingTime: 0, // Would need to calculate from flow durations
MostActiveStep: "", // Would need to analyze step activity
LastUsedAt: nil, // Would need to query last flow creation
}
details.Statistics = statistics
details.RelatedData = relatedData
_i.Log.Info().
Uint("workflowId", workflowId).
Int("stepsCount", len(steps)).
Msg("Comprehensive workflow details retrieved successfully")
return details, nil
}
// UpdateWorkflowWithClientSettings updates a comprehensive approval workflow with client settings in a single transaction
func (_i *approvalWorkflowsService) UpdateWorkflowWithClientSettings(authToken string, req request.UpdateApprovalWorkflowWithClientSettingsRequest) (workflow *entity.ApprovalWorkflows, clientSettings *entity.ClientApprovalSettings, 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, nil, errors.New("clientId not found in auth token")
}
_i.Log.Info().
Uint("workflowId", req.WorkflowId).
Interface("clientId", clientId).
Msg("Updating comprehensive approval workflow with client settings")
// Check if workflow exists
existingWorkflow, err := _i.ApprovalWorkflowsRepository.FindOne(clientId, req.WorkflowId)
if err != nil {
return nil, nil, fmt.Errorf("failed to find workflow: %w", err)
}
if existingWorkflow == nil {
return nil, nil, errors.New("workflow not found")
}
// Convert request to entities
workflowEntity := req.ToWorkflowEntity()
stepsEntity := req.ToStepsEntity()
clientSettingsEntity := req.ToClientApprovalSettingsEntity()
// Validate workflow and steps
isValid, validationErrors, err := _i.ValidateWorkflow(authToken, workflowEntity, stepsEntity)
if err != nil {
return nil, nil, err
}
if !isValid {
return nil, nil, fmt.Errorf("validation failed: %v", validationErrors)
}
// Start transaction-like operations with rollback capability
// Update workflow
err = _i.ApprovalWorkflowsRepository.Update(clientId, req.WorkflowId, workflowEntity)
if err != nil {
_i.Log.Error().Err(err).Uint("workflowId", req.WorkflowId).Msg("Failed to update workflow")
return nil, nil, fmt.Errorf("failed to update workflow: %w", err)
}
_i.Log.Info().Uint("workflowId", req.WorkflowId).Msg("Workflow updated successfully")
// Delete existing steps and create new ones
existingSteps, err := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, req.WorkflowId)
if err != nil {
_i.Log.Error().Err(err).Uint("workflowId", req.WorkflowId).Msg("Failed to get existing workflow steps")
return nil, nil, fmt.Errorf("failed to get existing workflow steps: %w", err)
}
for _, step := range existingSteps {
err = _i.ApprovalWorkflowStepsRepository.Delete(clientId, step.ID)
if err != nil {
_i.Log.Error().Err(err).Uint("workflowId", req.WorkflowId).Uint("stepId", step.ID).Msg("Failed to delete existing workflow step")
return nil, nil, fmt.Errorf("failed to delete existing workflow step %d: %w", step.ID, err)
}
}
// Create new workflow steps
for i, step := range stepsEntity {
step.WorkflowId = req.WorkflowId
step.StepOrder = i + 1
step.ClientId = clientId
_, err = _i.ApprovalWorkflowStepsRepository.Create(clientId, step)
if err != nil {
_i.Log.Error().Err(err).Uint("workflowId", req.WorkflowId).Int("stepOrder", i+1).Msg("Failed to create workflow step")
// Rollback: delete the workflow
_i.ApprovalWorkflowsRepository.Delete(clientId, req.WorkflowId)
return nil, nil, fmt.Errorf("failed to create workflow step %d: %w", i+1, err)
}
}
_i.Log.Info().Uint("workflowId", req.WorkflowId).Int("stepsCount", len(stepsEntity)).Msg("Workflow steps updated successfully")
// Set the workflow as default in client settings if specified
if req.IsDefault != nil && *req.IsDefault {
clientSettingsEntity.DefaultWorkflowId = &req.WorkflowId
}
// Update or create client approval settings
existingClientSettings, err := _i.ClientApprovalSettingsRepository.FindByClientId(*clientId)
if err != nil {
_i.Log.Warn().Err(err).Interface("clientId", clientId).Msg("Failed to find existing client approval settings, will create new")
// Create new client settings
clientSettingsEntity.ClientId = *clientId
clientSettings, err = _i.ClientApprovalSettingsRepository.Create(clientId, clientSettingsEntity)
if err != nil {
_i.Log.Error().Err(err).Interface("clientId", clientId).Msg("Failed to create client approval settings")
// Rollback: delete the workflow and steps
rollbackSteps, _ := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, req.WorkflowId)
for _, step := range rollbackSteps {
_i.ApprovalWorkflowStepsRepository.Delete(clientId, step.ID)
}
_i.ApprovalWorkflowsRepository.Delete(clientId, req.WorkflowId)
return nil, nil, fmt.Errorf("failed to create client approval settings: %w", err)
}
} else {
// Update existing client settings
clientSettingsEntity.ID = existingClientSettings.ID
clientSettingsEntity.ClientId = *clientId
clientSettingsEntity.CreatedAt = existingClientSettings.CreatedAt
clientSettings, err = _i.ClientApprovalSettingsRepository.Update(clientId, clientSettingsEntity)
if err != nil {
_i.Log.Error().Err(err).Uint("clientSettingsId", existingClientSettings.ID).Msg("Failed to update client approval settings")
// Rollback: delete the workflow and steps
rollbackSteps, _ := _i.ApprovalWorkflowStepsRepository.GetByWorkflowId(clientId, req.WorkflowId)
for _, step := range rollbackSteps {
_i.ApprovalWorkflowStepsRepository.Delete(clientId, step.ID)
}
_i.ApprovalWorkflowsRepository.Delete(clientId, req.WorkflowId)
return nil, nil, fmt.Errorf("failed to update client approval settings: %w", err)
}
}
_i.Log.Info().Interface("clientId", clientId).Msg("Client approval settings updated successfully")
// If this workflow is set as default, update the default workflow setting
if req.IsDefault != nil && *req.IsDefault {
err = _i.ApprovalWorkflowsRepository.SetDefault(clientId, req.WorkflowId)
if err != nil {
_i.Log.Warn().Err(err).Uint("workflowId", req.WorkflowId).Msg("Failed to set workflow as default")
// Don't fail the entire operation for this
} else {
_i.Log.Info().Uint("workflowId", req.WorkflowId).Msg("Workflow set as default successfully")
}
}
workflow, err = _i.ApprovalWorkflowsRepository.FindOne(clientId, req.WorkflowId)
if err != nil {
return nil, nil, fmt.Errorf("failed to get workflow: %w", err)
}
_i.Log.Info().
Uint("workflowId", workflow.ID).
Interface("clientId", clientId).
Msg("Comprehensive approval workflow with client settings updated successfully")
return workflow, clientSettings, nil
}