From 9ba425540e26bb2e620980ecc1fdc681c6965f08 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Thu, 2 Oct 2025 09:50:11 +0700 Subject: [PATCH] feat: update client-approval settings --- .../approval_workflows.module.go | 6 +- .../approval_workflows.controller.go | 42 +++ .../request/approval_workflows.request.go | 80 +++++ .../service/approval_workflows.service.go | 150 +++++++- app/module/users/mapper/users.mapper.go | 1 + app/module/users/response/users.response.go | 12 + app/module/users/service/users.service.go | 93 ++++- docs/COMPREHENSIVE_APPROVAL_WORKFLOW_API.md | 322 ++++++++++++++++++ ...SERS_INFO_APPROVAL_WORKFLOW_ENHANCEMENT.md | 187 ++++++++++ docs/swagger/docs.go | 147 ++++++++ docs/swagger/swagger.json | 147 ++++++++ docs/swagger/swagger.yaml | 97 ++++++ 12 files changed, 1262 insertions(+), 22 deletions(-) create mode 100644 docs/COMPREHENSIVE_APPROVAL_WORKFLOW_API.md create mode 100644 docs/USERS_INFO_APPROVAL_WORKFLOW_ENHANCEMENT.md diff --git a/app/module/approval_workflows/approval_workflows.module.go b/app/module/approval_workflows/approval_workflows.module.go index 2c3d5a7..825de48 100644 --- a/app/module/approval_workflows/approval_workflows.module.go +++ b/app/module/approval_workflows/approval_workflows.module.go @@ -1,11 +1,12 @@ package approval_workflows import ( - "github.com/gofiber/fiber/v2" - "go.uber.org/fx" "netidhub-saas-be/app/module/approval_workflows/controller" "netidhub-saas-be/app/module/approval_workflows/repository" "netidhub-saas-be/app/module/approval_workflows/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" ) // ApprovalWorkflowsRouter struct of ApprovalWorkflowsRouter @@ -50,6 +51,7 @@ func (_i *ApprovalWorkflowsRouter) RegisterApprovalWorkflowsRoutes() { router.Get("/:id/with-steps", approvalWorkflowsController.GetWithSteps) router.Post("/", approvalWorkflowsController.Save) router.Post("/with-steps", approvalWorkflowsController.SaveWithSteps) + router.Post("/with-client-settings", approvalWorkflowsController.SaveWithClientSettings) router.Put("/:id", approvalWorkflowsController.Update) router.Put("/:id/with-steps", approvalWorkflowsController.UpdateWithSteps) router.Put("/:id/set-default", approvalWorkflowsController.SetDefault) diff --git a/app/module/approval_workflows/controller/approval_workflows.controller.go b/app/module/approval_workflows/controller/approval_workflows.controller.go index 9ce40d6..9f5d465 100644 --- a/app/module/approval_workflows/controller/approval_workflows.controller.go +++ b/app/module/approval_workflows/controller/approval_workflows.controller.go @@ -32,6 +32,7 @@ type ApprovalWorkflowsController interface { GetWithSteps(c *fiber.Ctx) error SaveWithSteps(c *fiber.Ctx) error UpdateWithSteps(c *fiber.Ctx) error + SaveWithClientSettings(c *fiber.Ctx) error } func NewApprovalWorkflowsController(approvalWorkflowsService service.ApprovalWorkflowsService, log zerolog.Logger) ApprovalWorkflowsController { @@ -479,3 +480,44 @@ func (_i *approvalWorkflowsController) UpdateWithSteps(c *fiber.Ctx) error { Messages: utilRes.Messages{"ApprovalWorkflows with steps successfully updated"}, }) } + +// SaveWithClientSettings ApprovalWorkflows +// @Summary Create comprehensive ApprovalWorkflows with client settings +// @Description API for creating ApprovalWorkflows with workflow steps and client approval settings in a single call +// @Tags ApprovalWorkflows +// @Security Bearer +// @Param Authorization header string true "Insert the Authorization" +// @Param req body request.CreateApprovalWorkflowWithClientSettingsRequest true "Comprehensive approval workflow data" +// @Success 201 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /approval-workflows/with-client-settings [post] +func (_i *approvalWorkflowsController) SaveWithClientSettings(c *fiber.Ctx) error { + req := new(request.CreateApprovalWorkflowWithClientSettingsRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get authToken from context + authToken := c.Get("Authorization") + + // Create comprehensive workflow with client settings + workflowData, clientSettingsData, err := _i.approvalWorkflowsService.CreateWorkflowWithClientSettings(authToken, *req) + if err != nil { + return err + } + + // Combine workflow, steps, and client settings data + responseData := map[string]interface{}{ + "workflow": workflowData, + "clientSettings": clientSettingsData, + "message": "Comprehensive approval workflow with client settings created successfully", + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Comprehensive approval workflow with client settings successfully created"}, + Data: responseData, + }) +} diff --git a/app/module/approval_workflows/request/approval_workflows.request.go b/app/module/approval_workflows/request/approval_workflows.request.go index 9b57fac..c159181 100644 --- a/app/module/approval_workflows/request/approval_workflows.request.go +++ b/app/module/approval_workflows/request/approval_workflows.request.go @@ -186,6 +186,86 @@ type ApprovalWorkflowsQueryRequestContext struct { IsDefault string `json:"isDefault"` } +// CreateApprovalWorkflowWithClientSettingsRequest - Comprehensive request for creating approval workflow with client settings +type CreateApprovalWorkflowWithClientSettingsRequest struct { + // Workflow details + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + IsActive *bool `json:"isActive"` + IsDefault *bool `json:"isDefault"` + RequiresApproval *bool `json:"requiresApproval"` + AutoPublish *bool `json:"autoPublish"` + Steps []ApprovalWorkflowStepRequest `json:"steps" validate:"required,min=1"` + + // Client approval settings + ClientApprovalSettings ClientApprovalSettingsRequest `json:"clientApprovalSettings" validate:"required"` +} + +type ClientApprovalSettingsRequest struct { + RequiresApproval *bool `json:"requiresApproval"` + AutoPublishArticles *bool `json:"autoPublishArticles"` + ApprovalExemptUsers []uint `json:"approvalExemptUsers"` + ApprovalExemptRoles []uint `json:"approvalExemptRoles"` + ApprovalExemptCategories []uint `json:"approvalExemptCategories"` + RequireApprovalFor []string `json:"requireApprovalFor"` + SkipApprovalFor []string `json:"skipApprovalFor"` + IsActive *bool `json:"isActive"` +} + +func (req CreateApprovalWorkflowWithClientSettingsRequest) ToWorkflowEntity() *entity.ApprovalWorkflows { + return &entity.ApprovalWorkflows{ + Name: req.Name, + Description: &req.Description, + IsActive: req.IsActive, + IsDefault: req.IsDefault, + RequiresApproval: req.RequiresApproval, + AutoPublish: req.AutoPublish, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +func (req CreateApprovalWorkflowWithClientSettingsRequest) ToStepsEntity() []*entity.ApprovalWorkflowSteps { + steps := make([]*entity.ApprovalWorkflowSteps, len(req.Steps)) + for i, stepReq := range req.Steps { + steps[i] = &entity.ApprovalWorkflowSteps{ + StepOrder: stepReq.StepOrder, + StepName: stepReq.StepName, + RequiredUserLevelId: stepReq.RequiredUserLevelId, + CanSkip: stepReq.CanSkip, + AutoApproveAfterHours: stepReq.AutoApproveAfterHours, + IsActive: stepReq.IsActive, + + // Multi-branch support fields + ParentStepId: stepReq.ParentStepId, + ConditionType: stepReq.ConditionType, + ConditionValue: stepReq.ConditionValue, + IsParallel: stepReq.IsParallel, + BranchName: stepReq.BranchName, + BranchOrder: stepReq.BranchOrder, + + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + } + return steps +} + +func (req CreateApprovalWorkflowWithClientSettingsRequest) ToClientApprovalSettingsEntity() *entity.ClientApprovalSettings { + return &entity.ClientApprovalSettings{ + RequiresApproval: req.ClientApprovalSettings.RequiresApproval, + AutoPublishArticles: req.ClientApprovalSettings.AutoPublishArticles, + ApprovalExemptUsers: req.ClientApprovalSettings.ApprovalExemptUsers, + ApprovalExemptRoles: req.ClientApprovalSettings.ApprovalExemptRoles, + ApprovalExemptCategories: req.ClientApprovalSettings.ApprovalExemptCategories, + RequireApprovalFor: req.ClientApprovalSettings.RequireApprovalFor, + SkipApprovalFor: req.ClientApprovalSettings.SkipApprovalFor, + IsActive: req.ClientApprovalSettings.IsActive, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + func (req ApprovalWorkflowsQueryRequestContext) ToParamRequest() ApprovalWorkflowsQueryRequest { var name *string var description *string diff --git a/app/module/approval_workflows/service/approval_workflows.service.go b/app/module/approval_workflows/service/approval_workflows.service.go index b0bb181..fc239a6 100644 --- a/app/module/approval_workflows/service/approval_workflows.service.go +++ b/app/module/approval_workflows/service/approval_workflows.service.go @@ -8,6 +8,7 @@ import ( 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" + clientApprovalSettingsRepo "netidhub-saas-be/app/module/client_approval_settings/repository" usersRepo "netidhub-saas-be/app/module/users/repository" "netidhub-saas-be/utils/paginator" utilSvc "netidhub-saas-be/utils/service" @@ -17,10 +18,11 @@ import ( ) type approvalWorkflowsService struct { - ApprovalWorkflowsRepository repository.ApprovalWorkflowsRepository - ApprovalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository - UsersRepository usersRepo.UsersRepository - Log zerolog.Logger + ApprovalWorkflowsRepository repository.ApprovalWorkflowsRepository + ApprovalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository + ClientApprovalSettingsRepository clientApprovalSettingsRepo.ClientApprovalSettingsRepository + UsersRepository usersRepo.UsersRepository + Log zerolog.Logger } // ApprovalWorkflowsService define interface of IApprovalWorkflowsService @@ -46,19 +48,24 @@ type ApprovalWorkflowsService interface { // 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) } func NewApprovalWorkflowsService( approvalWorkflowsRepository repository.ApprovalWorkflowsRepository, approvalWorkflowStepsRepository stepRepo.ApprovalWorkflowStepsRepository, + clientApprovalSettingsRepository clientApprovalSettingsRepo.ClientApprovalSettingsRepository, usersRepository usersRepo.UsersRepository, log zerolog.Logger, ) ApprovalWorkflowsService { return &approvalWorkflowsService{ - ApprovalWorkflowsRepository: approvalWorkflowsRepository, - ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, - UsersRepository: usersRepository, - Log: log, + ApprovalWorkflowsRepository: approvalWorkflowsRepository, + ApprovalWorkflowStepsRepository: approvalWorkflowStepsRepository, + ClientApprovalSettingsRepository: clientApprovalSettingsRepository, + UsersRepository: usersRepository, + Log: log, } } @@ -579,3 +586,130 @@ func (_i *approvalWorkflowsService) CanDeleteWorkflow(authToken string, id uint) 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 +} diff --git a/app/module/users/mapper/users.mapper.go b/app/module/users/mapper/users.mapper.go index d1d354a..246cf92 100644 --- a/app/module/users/mapper/users.mapper.go +++ b/app/module/users/mapper/users.mapper.go @@ -39,6 +39,7 @@ func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsReposit IsActive: usersReq.IsActive, CreatedAt: usersReq.CreatedAt, UpdatedAt: usersReq.UpdatedAt, + // ApprovalWorkflowInfo will be set by the service layer } } return usersRes diff --git a/app/module/users/response/users.response.go b/app/module/users/response/users.response.go index 2b4c309..c5484a2 100644 --- a/app/module/users/response/users.response.go +++ b/app/module/users/response/users.response.go @@ -25,6 +25,18 @@ type UsersResponse struct { IsActive *bool `json:"isActive"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` + + // Approval Workflow Info + ApprovalWorkflowInfo *ApprovalWorkflowInfo `json:"approvalWorkflowInfo,omitempty"` +} + +type ApprovalWorkflowInfo struct { + HasWorkflowSetup bool `json:"hasWorkflowSetup"` + DefaultWorkflowId *uint `json:"defaultWorkflowId,omitempty"` + DefaultWorkflowName *string `json:"defaultWorkflowName,omitempty"` + RequiresApproval *bool `json:"requiresApproval,omitempty"` + AutoPublishArticles *bool `json:"autoPublishArticles,omitempty"` + IsApprovalActive *bool `json:"isApprovalActive,omitempty"` } type ParetoLoginResponse struct { diff --git a/app/module/users/service/users.service.go b/app/module/users/service/users.service.go index a682eac..c765562 100644 --- a/app/module/users/service/users.service.go +++ b/app/module/users/service/users.service.go @@ -6,6 +6,8 @@ import ( "fmt" "netidhub-saas-be/app/database/entity" "netidhub-saas-be/app/database/entity/users" + approvalWorkflowsRepo "netidhub-saas-be/app/module/approval_workflows/repository" + clientApprovalSettingsRepo "netidhub-saas-be/app/module/client_approval_settings/repository" userLevelsRepository "netidhub-saas-be/app/module/user_levels/repository" "netidhub-saas-be/app/module/users/mapper" "netidhub-saas-be/app/module/users/repository" @@ -25,11 +27,13 @@ import ( // UsersService type usersService struct { - Repo repository.UsersRepository - UserLevelsRepo userLevelsRepository.UserLevelsRepository - Log zerolog.Logger - Keycloak *config.KeycloakConfig - Smtp *config.SmtpConfig + Repo repository.UsersRepository + UserLevelsRepo userLevelsRepository.UserLevelsRepository + ApprovalWorkflowsRepo approvalWorkflowsRepo.ApprovalWorkflowsRepository + ClientApprovalSettingsRepo clientApprovalSettingsRepo.ClientApprovalSettingsRepository + Log zerolog.Logger + Keycloak *config.KeycloakConfig + Smtp *config.SmtpConfig } // UsersService define interface of IUsersService @@ -55,14 +59,24 @@ type UsersService interface { } // NewUsersService init UsersService -func NewUsersService(repo repository.UsersRepository, userLevelsRepo userLevelsRepository.UserLevelsRepository, log zerolog.Logger, keycloak *config.KeycloakConfig, smtp *config.SmtpConfig) UsersService { +func NewUsersService( + repo repository.UsersRepository, + userLevelsRepo userLevelsRepository.UserLevelsRepository, + approvalWorkflowsRepo approvalWorkflowsRepo.ApprovalWorkflowsRepository, + clientApprovalSettingsRepo clientApprovalSettingsRepo.ClientApprovalSettingsRepository, + log zerolog.Logger, + keycloak *config.KeycloakConfig, + smtp *config.SmtpConfig, +) UsersService { return &usersService{ - Repo: repo, - UserLevelsRepo: userLevelsRepo, - Log: log, - Keycloak: keycloak, - Smtp: smtp, + Repo: repo, + UserLevelsRepo: userLevelsRepo, + ApprovalWorkflowsRepo: approvalWorkflowsRepo, + ClientApprovalSettingsRepo: clientApprovalSettingsRepo, + Log: log, + Keycloak: keycloak, + Smtp: smtp, } } @@ -140,8 +154,63 @@ func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersRes } userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + if userInfo == nil { + return nil, fmt.Errorf("user not found") + } - return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo, clientId), nil + // Get approval workflow info + approvalWorkflowInfo, err := _i.getApprovalWorkflowInfo(clientId) + if err != nil { + _i.Log.Warn().Err(err).Msg("Failed to get approval workflow info") + // Don't return error, just log warning and continue without workflow info + } + + // Map user response with approval workflow info + usersRes := mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo, clientId) + if usersRes != nil && approvalWorkflowInfo != nil { + usersRes.ApprovalWorkflowInfo = approvalWorkflowInfo + } + + return usersRes, nil +} + +// getApprovalWorkflowInfo retrieves approval workflow information for the client +func (_i *usersService) getApprovalWorkflowInfo(clientId *uuid.UUID) (*response.ApprovalWorkflowInfo, error) { + if clientId == nil { + return &response.ApprovalWorkflowInfo{ + HasWorkflowSetup: false, + }, nil + } + + // Check if client has approval settings + clientSettings, err := _i.ClientApprovalSettingsRepo.FindByClientId(*clientId) + if err != nil { + return nil, fmt.Errorf("failed to get client approval settings: %w", err) + } + + workflowInfo := &response.ApprovalWorkflowInfo{ + HasWorkflowSetup: clientSettings != nil, + } + + if clientSettings != nil { + workflowInfo.RequiresApproval = clientSettings.RequiresApproval + workflowInfo.AutoPublishArticles = clientSettings.AutoPublishArticles + workflowInfo.IsApprovalActive = clientSettings.IsActive + + // Get default workflow info if exists + if clientSettings.DefaultWorkflowId != nil { + workflowInfo.DefaultWorkflowId = clientSettings.DefaultWorkflowId + + defaultWorkflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *clientSettings.DefaultWorkflowId) + if err != nil { + _i.Log.Warn().Err(err).Msg("Failed to get default workflow details") + } else if defaultWorkflow != nil { + workflowInfo.DefaultWorkflowName = &defaultWorkflow.Name + } + } + } + + return workflowInfo, nil } func (_i *usersService) Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, err error) { diff --git a/docs/COMPREHENSIVE_APPROVAL_WORKFLOW_API.md b/docs/COMPREHENSIVE_APPROVAL_WORKFLOW_API.md new file mode 100644 index 0000000..91ab2ed --- /dev/null +++ b/docs/COMPREHENSIVE_APPROVAL_WORKFLOW_API.md @@ -0,0 +1,322 @@ +# Comprehensive Approval Workflow API + +## Overview +This document describes the new comprehensive API endpoint that creates approval workflows along with all their dependencies (client approval settings) in a single API call. + +## Endpoint Details + +### POST `/approval-workflows/with-client-settings` + +**Description**: Creates a comprehensive approval workflow with workflow steps and client approval settings in a single transaction. + +**Authentication**: Bearer token required + +**Request Body**: +```json +{ + "name": "Standard Article Approval", + "description": "Standard approval workflow for articles", + "isActive": true, + "isDefault": true, + "requiresApproval": true, + "autoPublish": false, + "steps": [ + { + "stepOrder": 1, + "stepName": "Editor Review", + "requiredUserLevelId": 2, + "canSkip": false, + "autoApproveAfterHours": null, + "isActive": true, + "parentStepId": null, + "conditionType": "always", + "conditionValue": null, + "isParallel": false, + "branchName": null, + "branchOrder": null + }, + { + "stepOrder": 2, + "stepName": "Manager Approval", + "requiredUserLevelId": 3, + "canSkip": false, + "autoApproveAfterHours": 24, + "isActive": true, + "parentStepId": null, + "conditionType": "always", + "conditionValue": null, + "isParallel": false, + "branchName": null, + "branchOrder": null + } + ], + "clientApprovalSettings": { + "requiresApproval": true, + "autoPublishArticles": false, + "approvalExemptUsers": [], + "approvalExemptRoles": [], + "approvalExemptCategories": [], + "requireApprovalFor": ["article", "news"], + "skipApprovalFor": ["announcement"], + "isActive": true + } +} +``` + +**Response**: +```json +{ + "success": true, + "messages": ["Comprehensive approval workflow with client settings successfully created"], + "data": { + "workflow": { + "id": 1, + "name": "Standard Article Approval", + "description": "Standard approval workflow for articles", + "isActive": true, + "isDefault": true, + "requiresApproval": true, + "autoPublish": false, + "clientId": "uuid-here", + "createdAt": "2024-01-01T00:00:00Z", + "updatedAt": "2024-01-01T00:00:00Z" + }, + "clientSettings": { + "id": 1, + "clientId": "uuid-here", + "requiresApproval": true, + "defaultWorkflowId": 1, + "autoPublishArticles": false, + "approvalExemptUsers": [], + "approvalExemptRoles": [], + "approvalExemptCategories": [], + "requireApprovalFor": ["article", "news"], + "skipApprovalFor": ["announcement"], + "isActive": true, + "createdAt": "2024-01-01T00:00:00Z", + "updatedAt": "2024-01-01T00:00:00Z" + }, + "message": "Comprehensive approval workflow with client settings created successfully" + } +} +``` + +## Features + +### 1. Atomic Transaction +- Creates workflow, workflow steps, and client approval settings in a single transaction +- Automatic rollback if any step fails +- Ensures data consistency + +### 2. Comprehensive Validation +- Validates workflow structure and steps +- Checks for existing client approval settings +- Validates multi-branch workflow logic +- Ensures proper step ordering + +### 3. Multi-Branch Support +- Supports complex approval workflows with parallel branches +- Conditional step execution +- Parent-child step relationships +- Branch naming and ordering + +### 4. Client Settings Integration +- Automatically links workflow to client approval settings +- Sets default workflow if specified +- Configures approval exemptions +- Sets content type approval rules + +### 5. Error Handling +- Comprehensive error messages +- Automatic cleanup on failure +- Detailed logging for debugging +- Graceful degradation + +## Request Structure Details + +### Workflow Fields +- `name`: Workflow name (required) +- `description`: Workflow description (required) +- `isActive`: Whether workflow is active +- `isDefault`: Whether to set as default workflow +- `requiresApproval`: Whether workflow requires approval +- `autoPublish`: Whether to auto-publish after approval + +### Step Fields +- `stepOrder`: Order of the step in workflow +- `stepName`: Name of the approval step +- `requiredUserLevelId`: User level required for this step +- `canSkip`: Whether this step can be skipped +- `autoApproveAfterHours`: Auto-approve after specified hours +- `isActive`: Whether step is active + +### Multi-Branch Fields +- `parentStepId`: Parent step for branching +- `conditionType`: Condition type (user_level, user_level_hierarchy, always, custom) +- `conditionValue`: JSON condition value +- `isParallel`: Whether step runs in parallel +- `branchName`: Name of the branch +- `branchOrder`: Order within branch + +### Client Settings Fields +- `requiresApproval`: Global approval requirement +- `autoPublishArticles`: Auto-publish after creation +- `approvalExemptUsers`: User IDs exempt from approval +- `approvalExemptRoles`: Role IDs exempt from approval +- `approvalExemptCategories`: Category IDs exempt from approval +- `requireApprovalFor`: Content types requiring approval +- `skipApprovalFor`: Content types skipping approval +- `isActive`: Whether settings are active + +## Usage Examples + +### Basic Workflow +```bash +curl -X POST "https://api.example.com/approval-workflows/with-client-settings" \ + -H "Authorization: Bearer your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Simple Approval", + "description": "Two-step approval process", + "isActive": true, + "isDefault": true, + "steps": [ + { + "stepOrder": 1, + "stepName": "Editor Review", + "requiredUserLevelId": 2, + "canSkip": false, + "isActive": true + }, + { + "stepOrder": 2, + "stepName": "Manager Approval", + "requiredUserLevelId": 3, + "canSkip": false, + "isActive": true + } + ], + "clientApprovalSettings": { + "requiresApproval": true, + "autoPublishArticles": false, + "isActive": true + } + }' +``` + +### Complex Multi-Branch Workflow +```bash +curl -X POST "https://api.example.com/approval-workflows/with-client-settings" \ + -H "Authorization: Bearer your-token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Complex Multi-Branch Approval", + "description": "Complex workflow with parallel branches", + "isActive": true, + "isDefault": false, + "steps": [ + { + "stepOrder": 1, + "stepName": "Initial Review", + "requiredUserLevelId": 2, + "canSkip": false, + "isActive": true + }, + { + "stepOrder": 2, + "stepName": "Technical Review", + "requiredUserLevelId": 4, + "canSkip": false, + "isActive": true, + "parentStepId": 1, + "conditionType": "user_level", + "conditionValue": "{\"minLevel\": 4}", + "isParallel": true, + "branchName": "technical", + "branchOrder": 1 + }, + { + "stepOrder": 2, + "stepName": "Content Review", + "requiredUserLevelId": 3, + "canSkip": false, + "isActive": true, + "parentStepId": 1, + "conditionType": "user_level", + "conditionValue": "{\"minLevel\": 3}", + "isParallel": true, + "branchName": "content", + "branchOrder": 2 + }, + { + "stepOrder": 3, + "stepName": "Final Approval", + "requiredUserLevelId": 5, + "canSkip": false, + "isActive": true + } + ], + "clientApprovalSettings": { + "requiresApproval": true, + "autoPublishArticles": false, + "approvalExemptUsers": [1], + "approvalExemptRoles": [1], + "approvalExemptCategories": [1], + "requireApprovalFor": ["article", "news", "blog"], + "skipApprovalFor": ["announcement", "update"], + "isActive": true + } + }' +``` + +## Error Responses + +### Validation Error +```json +{ + "success": false, + "messages": ["Validation failed: [\"Workflow name is required\", \"Workflow must have at least one step\"]"], + "data": null +} +``` + +### Client Settings Already Exist +```json +{ + "success": false, + "messages": ["client approval settings already exist for this client"], + "data": null +} +``` + +### Authentication Error +```json +{ + "success": false, + "messages": ["clientId not found in auth token"], + "data": null +} +``` + +## Implementation Details + +### Files Modified/Created +1. `app/module/approval_workflows/request/approval_workflows.request.go` - Added comprehensive request structs +2. `app/module/approval_workflows/service/approval_workflows.service.go` - Added service method with transaction logic +3. `app/module/approval_workflows/controller/approval_workflows.controller.go` - Added controller method +4. `app/module/approval_workflows/approval_workflows.module.go` - Added route registration + +### Dependencies +- Client Approval Settings Repository +- Approval Workflow Steps Repository +- Users Repository +- Validation utilities +- Response utilities + +### Transaction Safety +- Automatic rollback on any failure +- Comprehensive error handling +- Detailed logging for debugging +- Data consistency guarantees + +This comprehensive API endpoint provides a single, atomic way to create complete approval workflows with all necessary dependencies, ensuring data consistency and providing a better developer experience. diff --git a/docs/USERS_INFO_APPROVAL_WORKFLOW_ENHANCEMENT.md b/docs/USERS_INFO_APPROVAL_WORKFLOW_ENHANCEMENT.md new file mode 100644 index 0000000..7919900 --- /dev/null +++ b/docs/USERS_INFO_APPROVAL_WORKFLOW_ENHANCEMENT.md @@ -0,0 +1,187 @@ +# Users Info API Enhancement - Approval Workflow Information + +## Overview +This document describes the enhancement made to the `users/info` API to include approval workflow information, allowing users to know whether their client has set up approval workflows or not. + +## Changes Made + +### 1. Response Structure Enhancement + +**File**: `app/module/users/response/users.response.go` + +Added new fields to `UsersResponse` struct: +```go +type UsersResponse struct { + // ... existing fields ... + + // Approval Workflow Info + ApprovalWorkflowInfo *ApprovalWorkflowInfo `json:"approvalWorkflowInfo,omitempty"` +} + +type ApprovalWorkflowInfo struct { + HasWorkflowSetup bool `json:"hasWorkflowSetup"` + DefaultWorkflowId *uint `json:"defaultWorkflowId,omitempty"` + DefaultWorkflowName *string `json:"defaultWorkflowName,omitempty"` + RequiresApproval *bool `json:"requiresApproval,omitempty"` + AutoPublishArticles *bool `json:"autoPublishArticles,omitempty"` + IsApprovalActive *bool `json:"isApprovalActive,omitempty"` +} +``` + +### 2. Service Layer Enhancement + +**File**: `app/module/users/service/users.service.go` + +#### Added Dependencies +- `ApprovalWorkflowsRepository` - to access approval workflow data +- `ClientApprovalSettingsRepository` - to access client approval settings + +#### Enhanced ShowUserInfo Method +The `ShowUserInfo` method now: +1. Retrieves user information as before +2. Calls `getApprovalWorkflowInfo()` to get workflow information +3. Combines both pieces of information in the response + +#### New Method: getApprovalWorkflowInfo +```go +func (_i *usersService) getApprovalWorkflowInfo(clientId *uuid.UUID) (*response.ApprovalWorkflowInfo, error) +``` + +This method: +- Checks if client has approval settings configured +- Retrieves default workflow information if available +- Returns comprehensive workflow status information + +### 3. Mapper Enhancement + +**File**: `app/module/users/mapper/users.mapper.go` + +Updated `UsersResponseMapper` to include a comment indicating that `ApprovalWorkflowInfo` will be set by the service layer. + +## API Response Examples + +### Client with Workflow Setup +```json +{ + "success": true, + "messages": ["Users successfully retrieve"], + "data": { + "id": 1, + "username": "john.doe", + "email": "john.doe@example.com", + "fullname": "John Doe", + "userLevelId": 2, + "userLevelGroup": "Editor", + "isActive": true, + "approvalWorkflowInfo": { + "hasWorkflowSetup": true, + "defaultWorkflowId": 1, + "defaultWorkflowName": "Standard Article Approval", + "requiresApproval": true, + "autoPublishArticles": false, + "isApprovalActive": true + } + } +} +``` + +### Client without Workflow Setup +```json +{ + "success": true, + "messages": ["Users successfully retrieve"], + "data": { + "id": 1, + "username": "john.doe", + "email": "john.doe@example.com", + "fullname": "John Doe", + "userLevelId": 2, + "userLevelGroup": "Editor", + "isActive": true, + "approvalWorkflowInfo": { + "hasWorkflowSetup": false + } + } +} +``` + +### Client with Workflow but No Default Workflow +```json +{ + "success": true, + "messages": ["Users successfully retrieve"], + "data": { + "id": 1, + "username": "john.doe", + "email": "john.doe@example.com", + "fullname": "John Doe", + "userLevelId": 2, + "userLevelGroup": "Editor", + "isActive": true, + "approvalWorkflowInfo": { + "hasWorkflowSetup": true, + "requiresApproval": true, + "autoPublishArticles": false, + "isApprovalActive": true + } + } +} +``` + +## Field Descriptions + +### ApprovalWorkflowInfo Fields + +| Field | Type | Description | +|-------|------|-------------| +| `hasWorkflowSetup` | `bool` | Indicates whether the client has any approval workflow configuration | +| `defaultWorkflowId` | `*uint` | ID of the default workflow (if set) | +| `defaultWorkflowName` | `*string` | Name of the default workflow (if set) | +| `requiresApproval` | `*bool` | Whether the client requires approval for articles | +| `autoPublishArticles` | `*bool` | Whether articles are auto-published after creation | +| `isApprovalActive` | `*bool` | Whether the approval system is active for this client | + +## Error Handling + +The implementation includes robust error handling: + +1. **Graceful Degradation**: If workflow information cannot be retrieved, the API still returns user information without workflow data +2. **Logging**: All errors are logged for debugging purposes +3. **Non-blocking**: Workflow information retrieval failures don't prevent user information from being returned + +## Usage Scenarios + +### 1. Frontend Dashboard +The frontend can now check `approvalWorkflowInfo.hasWorkflowSetup` to: +- Show/hide approval workflow setup prompts +- Display workflow status indicators +- Guide users through workflow configuration + +### 2. Article Creation Flow +Before creating articles, the frontend can check: +- `requiresApproval` - to determine if approval is needed +- `autoPublishArticles` - to know if articles will be auto-published +- `isApprovalActive` - to verify the approval system is active + +### 3. Workflow Management +The frontend can display: +- Current default workflow name +- Workflow configuration status +- Approval system status + +## Benefits + +1. **Single API Call**: Users can get both user info and workflow status in one request +2. **Real-time Status**: Always shows current workflow configuration status +3. **User Experience**: Users immediately know if they need to set up workflows +4. **Frontend Efficiency**: Reduces the need for multiple API calls +5. **Backward Compatibility**: Existing API consumers continue to work without changes + +## Implementation Notes + +- The `approvalWorkflowInfo` field is optional (`omitempty`) to maintain backward compatibility +- Error handling ensures the API remains stable even if workflow services are unavailable +- The implementation follows the existing code patterns and architecture +- All changes are non-breaking and additive + +This enhancement provides users with immediate visibility into their approval workflow setup status, improving the overall user experience and reducing confusion about workflow configuration. diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 06c87a7..49bcbd1 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -1755,6 +1755,64 @@ const docTemplate = `{ } } }, + "/approval-workflows/with-client-settings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ApprovalWorkflows with workflow steps and client approval settings in a single call", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Create comprehensive ApprovalWorkflows with client settings", + "parameters": [ + { + "type": "string", + "description": "Insert the Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Comprehensive approval workflow data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateApprovalWorkflowWithClientSettingsRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/approval-workflows/with-steps": { "post": { "security": [ @@ -17122,6 +17180,50 @@ const docTemplate = `{ } } }, + "request.ClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "request.ClientWithUserCreateRequest": { "type": "object", "required": [ @@ -17286,6 +17388,51 @@ const docTemplate = `{ } } }, + "request.CreateApprovalWorkflowWithClientSettingsRequest": { + "type": "object", + "required": [ + "clientApprovalSettings", + "description", + "name", + "steps" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "clientApprovalSettings": { + "description": "Client approval settings", + "allOf": [ + { + "$ref": "#/definitions/request.ClientApprovalSettingsRequest" + } + ] + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "description": "Workflow details", + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, "request.CreateClientApprovalSettingsRequest": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index c73084c..5c140c4 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -1744,6 +1744,64 @@ } } }, + "/approval-workflows/with-client-settings": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ApprovalWorkflows with workflow steps and client approval settings in a single call", + "tags": [ + "ApprovalWorkflows" + ], + "summary": "Create comprehensive ApprovalWorkflows with client settings", + "parameters": [ + { + "type": "string", + "description": "Insert the Authorization", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "description": "Comprehensive approval workflow data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateApprovalWorkflowWithClientSettingsRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/approval-workflows/with-steps": { "post": { "security": [ @@ -17111,6 +17169,50 @@ } } }, + "request.ClientApprovalSettingsRequest": { + "type": "object", + "properties": { + "approvalExemptCategories": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptRoles": { + "type": "array", + "items": { + "type": "integer" + } + }, + "approvalExemptUsers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "autoPublishArticles": { + "type": "boolean" + }, + "isActive": { + "type": "boolean" + }, + "requireApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + }, + "requiresApproval": { + "type": "boolean" + }, + "skipApprovalFor": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "request.ClientWithUserCreateRequest": { "type": "object", "required": [ @@ -17275,6 +17377,51 @@ } } }, + "request.CreateApprovalWorkflowWithClientSettingsRequest": { + "type": "object", + "required": [ + "clientApprovalSettings", + "description", + "name", + "steps" + ], + "properties": { + "autoPublish": { + "type": "boolean" + }, + "clientApprovalSettings": { + "description": "Client approval settings", + "allOf": [ + { + "$ref": "#/definitions/request.ClientApprovalSettingsRequest" + } + ] + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "description": "Workflow details", + "type": "string" + }, + "requiresApproval": { + "type": "boolean" + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/request.ApprovalWorkflowStepRequest" + } + } + } + }, "request.CreateClientApprovalSettingsRequest": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 3a839c6..2dc99a8 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -693,6 +693,35 @@ definitions: - id - prov_id type: object + request.ClientApprovalSettingsRequest: + properties: + approvalExemptCategories: + items: + type: integer + type: array + approvalExemptRoles: + items: + type: integer + type: array + approvalExemptUsers: + items: + type: integer + type: array + autoPublishArticles: + type: boolean + isActive: + type: boolean + requireApprovalFor: + items: + type: string + type: array + requiresApproval: + type: boolean + skipApprovalFor: + items: + type: string + type: array + type: object request.ClientWithUserCreateRequest: properties: adminUser: @@ -805,6 +834,36 @@ definitions: - stepOrder - workflowId type: object + request.CreateApprovalWorkflowWithClientSettingsRequest: + properties: + autoPublish: + type: boolean + clientApprovalSettings: + allOf: + - $ref: '#/definitions/request.ClientApprovalSettingsRequest' + description: Client approval settings + description: + type: string + isActive: + type: boolean + isDefault: + type: boolean + name: + description: Workflow details + type: string + requiresApproval: + type: boolean + steps: + items: + $ref: '#/definitions/request.ApprovalWorkflowStepRequest' + minItems: 1 + type: array + required: + - clientApprovalSettings + - description + - name + - steps + type: object request.CreateClientApprovalSettingsRequest: properties: approvalExemptCategories: @@ -3003,6 +3062,44 @@ paths: summary: Get default ApprovalWorkflows tags: - ApprovalWorkflows + /approval-workflows/with-client-settings: + post: + description: API for creating ApprovalWorkflows with workflow steps and client + approval settings in a single call + parameters: + - description: Insert the Authorization + in: header + name: Authorization + required: true + type: string + - description: Comprehensive approval workflow data + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.CreateApprovalWorkflowWithClientSettingsRequest' + responses: + "201": + description: Created + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create comprehensive ApprovalWorkflows with client settings + tags: + - ApprovalWorkflows /approval-workflows/with-steps: post: description: API for creating ApprovalWorkflows with steps