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) (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) (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(). Interface("clientId", clientId). Msg("Getting comprehensive workflow details") // Get workflow - first try to get default workflow, if not found get any active workflow workflow, err := _i.ApprovalWorkflowsRepository.FindDefault(clientId) if err != nil || workflow == nil { _i.Log.Info().Msg("No default workflow found, getting first active workflow") // If no default workflow, get the first active workflow for this client queryReq := request.ApprovalWorkflowsQueryRequest{ Pagination: &paginator.Pagination{ Limit: 1, }, } workflows, _, err := _i.ApprovalWorkflowsRepository.GetAll(clientId, queryReq) if err != nil { return nil, fmt.Errorf("failed to get workflows: %w", err) } if len(workflows) == 0 { return nil, errors.New("no active workflows found for this client") } workflow = workflows[0] } workflowId := workflow.ID // 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 }