feat: update client-approval settings

This commit is contained in:
hanif salafi 2025-10-02 09:50:11 +07:00
parent adb0184035
commit 9ba425540e
12 changed files with 1262 additions and 22 deletions

View File

@ -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)

View File

@ -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,
})
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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.

View File

@ -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.

View File

@ -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": {

View File

@ -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": {

View File

@ -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