Update department etc
This commit is contained in:
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"eslogad-be/internal/logger"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +43,7 @@ type LetterProcessor interface {
|
||||
GetEnhancedDispositionsByLetter(ctx context.Context, letterID uuid.UUID) (*contract.ListEnhancedDispositionsResponse, error)
|
||||
|
||||
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||
UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error)
|
||||
UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, string, error)
|
||||
|
||||
GetDepartmentDispositionStatus(ctx context.Context, req *contract.GetDepartmentDispositionStatusRequest) (*contract.ListDepartmentDispositionStatusResponse, error)
|
||||
UpdateDispositionStatus(ctx context.Context, req *contract.UpdateDispositionStatusRequest) (*contract.DepartmentDispositionStatusResponse, error)
|
||||
@@ -60,6 +59,7 @@ type LetterServiceImpl struct {
|
||||
activityLogger ActivityLogger
|
||||
letterDispositionProcessor LetterDispositionProcessor
|
||||
notificationProcessor processor.NotificationProcessor
|
||||
activityProcessor ActivityLogger
|
||||
}
|
||||
|
||||
type NumberGenerator interface {
|
||||
@@ -68,6 +68,7 @@ type NumberGenerator interface {
|
||||
|
||||
type RecipientProcessor interface {
|
||||
CreateDefaultRecipients(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingRecipient, error)
|
||||
CreateRecipients(ctx context.Context, letterID uuid.UUID, departmentIDs []uuid.UUID) ([]entities.LetterIncomingRecipient, error)
|
||||
CreateSingleRecipient(ctx context.Context, recipient *entities.LetterIncomingRecipient) error
|
||||
}
|
||||
|
||||
@@ -89,6 +90,7 @@ func NewLetterService(
|
||||
activityLogger ActivityLogger,
|
||||
letterDispositionProcessor LetterDispositionProcessor,
|
||||
notificationProcessor processor.NotificationProcessor,
|
||||
activityProc ActivityLogger,
|
||||
) *LetterServiceImpl {
|
||||
return &LetterServiceImpl{
|
||||
processor: processor,
|
||||
@@ -98,6 +100,7 @@ func NewLetterService(
|
||||
activityLogger: activityLogger,
|
||||
letterDispositionProcessor: letterDispositionProcessor,
|
||||
notificationProcessor: notificationProcessor,
|
||||
activityProcessor: activityProc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +205,7 @@ func (s *LetterServiceImpl) logLetterCreation(ctx context.Context, letterID uuid
|
||||
userID := appcontext.FromGinContext(ctx).UserID
|
||||
err := s.activityLogger.LogLetterCreated(ctx, letterID, userID, letterNumber)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).Error("error when insert into log", err)
|
||||
// Log error but don't fail the operation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,8 +232,7 @@ func (s *LetterServiceImpl) addCreatorAsRecipient(ctx context.Context, letterID
|
||||
|
||||
// Save the recipient
|
||||
if err := s.recipientProcessor.CreateSingleRecipient(ctx, &recipient); err != nil {
|
||||
// Log error but don't fail the whole operation
|
||||
logger.FromContext(ctx).Error("failed to add creator as recipient", err)
|
||||
// Failed to add creator as recipient
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -248,7 +250,34 @@ func (s *LetterServiceImpl) sendLetterNotifications(ctx context.Context, letter
|
||||
fmt.Sprintf("%s: %s", letter.SenderInstitution.Name, letter.Subject))
|
||||
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).Error("failed to send notification", err)
|
||||
// Failed to send notification, continue anyway
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LetterServiceImpl) sendDispositionNotifications(ctx context.Context, letterID uuid.UUID, recipients []entities.LetterIncomingRecipient) {
|
||||
// Get letter details for notification
|
||||
appContext := appcontext.FromGinContext(ctx)
|
||||
letter, err := s.processor.GetIncomingLetterByID(ctx, letterID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, recipient := range recipients {
|
||||
if recipient.RecipientUserID != nil && recipient.Status != entities.RecipientStatusCompleted {
|
||||
subject := "Surat Masuk"
|
||||
message := fmt.Sprintf("Disposisi surat dari %s: %s", appContext.UserName, letter.Subject)
|
||||
|
||||
err := s.notificationProcessor.SendIncomingLetterNotification(
|
||||
ctx,
|
||||
letterID,
|
||||
*recipient.RecipientUserID,
|
||||
subject,
|
||||
message)
|
||||
|
||||
if err != nil {
|
||||
// Failed to send notification, continue anyway
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,7 +385,7 @@ func (s *LetterServiceImpl) ListIncomingLetters(ctx context.Context, req *contra
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
if err := <-errChan; err != nil {
|
||||
logger.FromContext(ctx).Error("batch load error", err)
|
||||
// Batch load error, continue anyway
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,16 +456,40 @@ func (s *LetterServiceImpl) CreateDispositions(ctx context.Context, req *contrac
|
||||
}
|
||||
|
||||
var result *contract.ListDispositionsResponse
|
||||
var recipients []entities.LetterIncomingRecipient
|
||||
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
var err error
|
||||
result, err = s.processor.CreateDispositions(txCtx, req)
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req.ToDepartmentIDs) > 0 && s.recipientProcessor != nil {
|
||||
recipients, err = s.recipientProcessor.CreateRecipients(txCtx, req.LetterID, req.ToDepartmentIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.activityLogger != nil && result != nil && len(result.Dispositions) > 0 {
|
||||
if err := s.activityLogger.LogLetterDispositionStatusUpdate(txCtx, req.LetterID, userID, "disposition_created"); err != nil {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send notifications to newly created recipients asynchronously
|
||||
if s.notificationProcessor != nil && len(recipients) > 0 {
|
||||
go s.sendDispositionNotifications(context.Background(), req.LetterID, recipients)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -445,11 +498,64 @@ func (s *LetterServiceImpl) GetEnhancedDispositionsByLetter(ctx context.Context,
|
||||
}
|
||||
|
||||
func (s *LetterServiceImpl) CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||
return s.processor.CreateDiscussion(ctx, letterID, req)
|
||||
userID := appcontext.FromGinContext(ctx).UserID
|
||||
|
||||
var result *contract.LetterDiscussionResponse
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
var err error
|
||||
result, err = s.processor.CreateDiscussion(txCtx, letterID, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Log activity for discussion creation
|
||||
if s.activityLogger != nil && result != nil {
|
||||
// Create a simple activity log
|
||||
if err := s.activityLogger.LogLetterDispositionStatusUpdate(txCtx, letterID, userID, "discussion_created"); err != nil {
|
||||
// Don't fail the transaction for logging errors
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *LetterServiceImpl) UpdateDiscussion(ctx context.Context, letterID uuid.UUID, discussionID uuid.UUID, req *contract.UpdateLetterDiscussionRequest) (*contract.LetterDiscussionResponse, error) {
|
||||
return s.processor.UpdateDiscussion(ctx, letterID, discussionID, req)
|
||||
userID := appcontext.FromGinContext(ctx).UserID
|
||||
|
||||
var result *contract.LetterDiscussionResponse
|
||||
|
||||
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
var err error
|
||||
var oldMessage string
|
||||
result, oldMessage, err = s.processor.UpdateDiscussion(txCtx, letterID, discussionID, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Log activity for discussion update (could use oldMessage for more detailed logging)
|
||||
if s.activityLogger != nil && result != nil {
|
||||
// Create a simple activity log - oldMessage could be included in a more detailed log
|
||||
_ = oldMessage // Mark as intentionally unused for now
|
||||
if err := s.activityLogger.LogLetterDispositionStatusUpdate(txCtx, letterID, userID, "discussion_updated"); err != nil {
|
||||
// Don't fail the transaction for logging errors
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *LetterServiceImpl) GetDepartmentDispositionStatus(ctx context.Context, req *contract.GetDepartmentDispositionStatusRequest) (*contract.ListDepartmentDispositionStatusResponse, error) {
|
||||
|
||||
@@ -2,7 +2,10 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"eslogad-be/config"
|
||||
"eslogad-be/internal/contract"
|
||||
"eslogad-be/internal/entities"
|
||||
"eslogad-be/internal/repository"
|
||||
@@ -17,10 +20,11 @@ type MasterServiceImpl struct {
|
||||
institutionRepo *repository.InstitutionRepository
|
||||
dispRepo *repository.DispositionActionRepository
|
||||
departmentRepo *repository.DepartmentRepository
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewMasterService(label *repository.LabelRepository, priority *repository.PriorityRepository, institution *repository.InstitutionRepository, disp *repository.DispositionActionRepository, department *repository.DepartmentRepository) *MasterServiceImpl {
|
||||
return &MasterServiceImpl{labelRepo: label, priorityRepo: priority, institutionRepo: institution, dispRepo: disp, departmentRepo: department}
|
||||
func NewMasterService(label *repository.LabelRepository, priority *repository.PriorityRepository, institution *repository.InstitutionRepository, disp *repository.DispositionActionRepository, department *repository.DepartmentRepository, cfg *config.Config) *MasterServiceImpl {
|
||||
return &MasterServiceImpl{labelRepo: label, priorityRepo: priority, institutionRepo: institution, dispRepo: disp, departmentRepo: department, config: cfg}
|
||||
}
|
||||
|
||||
// Labels
|
||||
@@ -215,6 +219,385 @@ func (s *MasterServiceImpl) ListDispositionActions(ctx context.Context) (*contra
|
||||
}
|
||||
|
||||
// Departments
|
||||
func (s *MasterServiceImpl) CreateDepartment(ctx context.Context, req *contract.CreateDepartmentRequest) (*contract.GetDepartmentResponse, error) {
|
||||
// Build the path based on parent
|
||||
var path string
|
||||
if req.ParentID != nil {
|
||||
// Get parent department to build the path
|
||||
parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Build path as parent.path + code
|
||||
path = parent.Path + "." + req.Code
|
||||
} else {
|
||||
// Root level department, just use the code as path
|
||||
path = req.Code
|
||||
}
|
||||
|
||||
entity := &entities.Department{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
Path: path,
|
||||
}
|
||||
if err := s.departmentRepo.Create(ctx, entity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get parent name if parent exists
|
||||
var parentName *string
|
||||
if req.ParentID != nil {
|
||||
if parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID); err == nil {
|
||||
parentName = &parent.Name
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.GetDepartmentResponse{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
Code: entity.Code,
|
||||
Path: entity.Path,
|
||||
ParentID: req.ParentID,
|
||||
ParentName: parentName,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) GetDepartment(ctx context.Context, id uuid.UUID) (*contract.GetDepartmentResponse, error) {
|
||||
entity, err := s.departmentRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Derive parent_id and parent_name from path
|
||||
var parentID *uuid.UUID
|
||||
var parentName *string
|
||||
parts := strings.Split(entity.Path, ".")
|
||||
if len(parts) > 1 {
|
||||
// Has parent, try to find it
|
||||
parentPath := strings.Join(parts[:len(parts)-1], ".")
|
||||
if parent, err := s.departmentRepo.GetByPath(ctx, parentPath); err == nil {
|
||||
parentID = &parent.ID
|
||||
parentName = &parent.Name
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.GetDepartmentResponse{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
Code: entity.Code,
|
||||
Path: entity.Path,
|
||||
ParentID: parentID,
|
||||
ParentName: parentName,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) UpdateDepartment(ctx context.Context, id uuid.UUID, req *contract.UpdateDepartmentRequest) (*contract.GetDepartmentResponse, error) {
|
||||
entity, err := s.departmentRepo.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store the old path before changes
|
||||
oldPath := entity.Path
|
||||
|
||||
if req.Name != nil {
|
||||
entity.Name = *req.Name
|
||||
}
|
||||
if req.Code != nil {
|
||||
entity.Code = *req.Code
|
||||
}
|
||||
|
||||
// Rebuild path if parent is being changed or code is being changed
|
||||
if req.ParentID != nil || req.Code != nil {
|
||||
// Determine the code to use (new code if provided, otherwise existing)
|
||||
code := entity.Code
|
||||
if req.Code != nil {
|
||||
code = *req.Code
|
||||
}
|
||||
|
||||
// Build the new path based on parent
|
||||
var path string
|
||||
if req.ParentID != nil {
|
||||
if *req.ParentID == uuid.Nil {
|
||||
// Moving to root level
|
||||
path = code
|
||||
} else {
|
||||
// Get parent department to build the path
|
||||
parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Build path as parent.path + code
|
||||
path = parent.Path + "." + code
|
||||
}
|
||||
} else if req.Code != nil {
|
||||
// Code changed but parent not specified, rebuild path with current parent
|
||||
// Extract parent path from current path
|
||||
parts := strings.Split(entity.Path, ".")
|
||||
if len(parts) > 1 {
|
||||
// Has parent, rebuild with new code
|
||||
parentPath := strings.Join(parts[:len(parts)-1], ".")
|
||||
path = parentPath + "." + code
|
||||
} else {
|
||||
// Root level, just use new code
|
||||
path = code
|
||||
}
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
entity.Path = path
|
||||
}
|
||||
}
|
||||
|
||||
// Update the department
|
||||
if err := s.departmentRepo.Update(ctx, entity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the path changed, update all children paths
|
||||
if oldPath != entity.Path {
|
||||
if err := s.departmentRepo.UpdateChildrenPaths(ctx, oldPath, entity.Path); err != nil {
|
||||
// Log the error but don't fail the operation
|
||||
// You might want to handle this differently based on your requirements
|
||||
// For now, we'll continue since the parent update succeeded
|
||||
}
|
||||
}
|
||||
|
||||
// Derive parent_id and parent_name from path for response
|
||||
var parentID *uuid.UUID
|
||||
var parentName *string
|
||||
if req.ParentID != nil {
|
||||
parentID = req.ParentID
|
||||
// Get parent name
|
||||
if parent, err := s.departmentRepo.GetByID(ctx, *req.ParentID); err == nil {
|
||||
parentName = &parent.Name
|
||||
}
|
||||
} else {
|
||||
// Derive from path if not provided in request
|
||||
parts := strings.Split(entity.Path, ".")
|
||||
if len(parts) > 1 {
|
||||
parentPath := strings.Join(parts[:len(parts)-1], ".")
|
||||
if parent, err := s.departmentRepo.GetByPath(ctx, parentPath); err == nil {
|
||||
parentID = &parent.ID
|
||||
parentName = &parent.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.GetDepartmentResponse{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
Code: entity.Code,
|
||||
Path: entity.Path,
|
||||
ParentID: parentID,
|
||||
ParentName: parentName,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) DeleteDepartment(ctx context.Context, id uuid.UUID) error {
|
||||
return s.departmentRepo.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) GetOrganizationalChartByID(ctx context.Context, departmentID uuid.UUID) (*contract.OrganizationalChartResponse, error) {
|
||||
// First get the department to find its path
|
||||
department, err := s.departmentRepo.Get(ctx, departmentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now get the organizational chart starting from this department's path
|
||||
return s.GetOrganizationalChart(ctx, department.Path)
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) GetOrganizationalChart(ctx context.Context, rootPath string) (*contract.OrganizationalChartResponse, error) {
|
||||
var departments []entities.Department
|
||||
var err error
|
||||
|
||||
// Get config values
|
||||
parentPath := s.config.Department.ParentPath
|
||||
excludedPaths := s.config.Department.ExcludedPaths
|
||||
|
||||
if rootPath == "" {
|
||||
// Get all departments with parent filter
|
||||
departments, err = s.departmentRepo.GetAllWithParentFilter(ctx, parentPath, excludedPaths)
|
||||
} else {
|
||||
// Get departments under specific path
|
||||
departments, err = s.departmentRepo.GetByPathPrefix(ctx, rootPath)
|
||||
// Filter out excluded paths manually for specific path queries
|
||||
filteredDepts := make([]entities.Department, 0)
|
||||
for _, dept := range departments {
|
||||
excluded := false
|
||||
for _, excludedPath := range excludedPaths {
|
||||
if strings.Contains(dept.Path, excludedPath) {
|
||||
excluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !excluded {
|
||||
filteredDepts = append(filteredDepts, dept)
|
||||
}
|
||||
}
|
||||
departments = filteredDepts
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the tree structure
|
||||
nodeMap := make(map[string]*contract.DepartmentNode)
|
||||
roots := make([]*contract.DepartmentNode, 0)
|
||||
|
||||
// Calculate base level offset based on parent path
|
||||
baseLevelOffset := 0
|
||||
if parentPath != "" {
|
||||
baseLevelOffset = len(strings.Split(parentPath, ".")) - 1
|
||||
}
|
||||
|
||||
// First pass: create all nodes including missing parents
|
||||
for _, dept := range departments {
|
||||
pathParts := strings.Split(dept.Path, ".")
|
||||
|
||||
// Create any missing parent nodes
|
||||
for i := 1; i <= len(pathParts); i++ {
|
||||
currentPath := strings.Join(pathParts[:i], ".")
|
||||
if _, exists := nodeMap[currentPath]; !exists {
|
||||
// Calculate level for this path
|
||||
adjustedLevel := i - baseLevelOffset
|
||||
if adjustedLevel < 1 {
|
||||
adjustedLevel = 1
|
||||
}
|
||||
|
||||
// Create node (placeholder for missing parents, real data for existing)
|
||||
var node *contract.DepartmentNode
|
||||
if currentPath == dept.Path {
|
||||
// This is the actual department
|
||||
node = &contract.DepartmentNode{
|
||||
ID: dept.ID,
|
||||
Name: dept.Name,
|
||||
Code: dept.Code,
|
||||
Path: dept.Path,
|
||||
Level: adjustedLevel,
|
||||
Children: make([]*contract.DepartmentNode, 0),
|
||||
}
|
||||
} else {
|
||||
// This is a missing parent - create placeholder
|
||||
// Extract the last segment as the name
|
||||
lastSegment := pathParts[i-1]
|
||||
node = &contract.DepartmentNode{
|
||||
ID: uuid.Nil, // Use nil UUID for placeholder
|
||||
Name: strings.ToUpper(strings.ReplaceAll(lastSegment, "_", " ")),
|
||||
Code: lastSegment,
|
||||
Path: currentPath,
|
||||
Level: adjustedLevel,
|
||||
Children: make([]*contract.DepartmentNode, 0),
|
||||
}
|
||||
}
|
||||
nodeMap[currentPath] = node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: build the tree relationships
|
||||
// Only process nodes that actually exist in the database (not placeholders)
|
||||
processedPaths := make(map[string]bool)
|
||||
for _, dept := range departments {
|
||||
if processedPaths[dept.Path] {
|
||||
continue
|
||||
}
|
||||
processedPaths[dept.Path] = true
|
||||
|
||||
node := nodeMap[dept.Path]
|
||||
pathParts := strings.Split(dept.Path, ".")
|
||||
|
||||
// Check if this should be a root node
|
||||
isRoot := false
|
||||
if rootPath != "" && dept.Path == rootPath {
|
||||
// Explicitly requested root
|
||||
isRoot = true
|
||||
} else if rootPath == "" && parentPath != "" && dept.Path == parentPath {
|
||||
// The configured parent path is the root when showing all
|
||||
isRoot = true
|
||||
} else if len(pathParts) == 1 {
|
||||
// Single segment path
|
||||
isRoot = true
|
||||
} else {
|
||||
// Find parent path
|
||||
parentPathStr := strings.Join(pathParts[:len(pathParts)-1], ".")
|
||||
if parent, exists := nodeMap[parentPathStr]; exists {
|
||||
// Check if this child is already added
|
||||
alreadyAdded := false
|
||||
for _, child := range parent.Children {
|
||||
if child.Path == node.Path {
|
||||
alreadyAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyAdded {
|
||||
parent.Children = append(parent.Children, node)
|
||||
}
|
||||
} else {
|
||||
// Parent doesn't exist - this is an orphaned node
|
||||
// Only include it as a root if it's a direct child of the parent path
|
||||
if parentPath != "" {
|
||||
// Check if this is a direct child of the configured parent
|
||||
expectedParent := parentPath
|
||||
actualParent := strings.Join(pathParts[:len(pathParts)-1], ".")
|
||||
if actualParent != expectedParent {
|
||||
// This is an orphaned node - skip it
|
||||
continue
|
||||
}
|
||||
}
|
||||
isRoot = true
|
||||
}
|
||||
}
|
||||
|
||||
if isRoot {
|
||||
// Check for duplicates in roots
|
||||
alreadyInRoots := false
|
||||
for _, r := range roots {
|
||||
if r.Path == node.Path {
|
||||
alreadyInRoots = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyInRoots {
|
||||
roots = append(roots, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort children at each level
|
||||
var sortChildren func([]*contract.DepartmentNode)
|
||||
sortChildren = func(nodes []*contract.DepartmentNode) {
|
||||
for _, node := range nodes {
|
||||
if len(node.Children) > 0 {
|
||||
// Sort children by name
|
||||
sort.Slice(node.Children, func(i, j int) bool {
|
||||
return node.Children[i].Name < node.Children[j].Name
|
||||
})
|
||||
sortChildren(node.Children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort root nodes
|
||||
sort.Slice(roots, func(i, j int) bool {
|
||||
return roots[i].Name < roots[j].Name
|
||||
})
|
||||
sortChildren(roots)
|
||||
|
||||
return &contract.OrganizationalChartResponse{
|
||||
Chart: roots,
|
||||
TotalNodes: len(departments),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MasterServiceImpl) ListDepartments(ctx context.Context, req *contract.ListDepartmentsRequest) (*contract.ListDepartmentsResponse, error) {
|
||||
// Set default values if not provided
|
||||
page := req.Page
|
||||
@@ -232,7 +615,11 @@ func (s *MasterServiceImpl) ListDepartments(ctx context.Context, req *contract.L
|
||||
|
||||
offset := (page - 1) * limit
|
||||
|
||||
list, total, err := s.departmentRepo.List(ctx, req.Search, limit, offset)
|
||||
// Use filtered list with parent path from config
|
||||
parentPath := s.config.Department.ParentPath
|
||||
excludedPaths := s.config.Department.ExcludedPaths
|
||||
|
||||
list, total, err := s.departmentRepo.ListWithParentFilter(ctx, req.Search, limit, offset, parentPath, excludedPaths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type UserProcessor interface {
|
||||
UpdateUserProfile(ctx context.Context, userID uuid.UUID, req *contract.UpdateUserProfileRequest) (*contract.UserProfileResponse, error)
|
||||
|
||||
// New optimized listing
|
||||
ListUsersWithFilters(ctx context.Context, req *contract.ListUsersRequest) ([]contract.UserResponse, int, error)
|
||||
ListUsersWithFilters(ctx context.Context, search *string, roleCode *string, isActive *bool, limit, offset int) ([]contract.UserResponse, int, error)
|
||||
|
||||
// Get active users for mention purposes
|
||||
GetActiveUsersForMention(ctx context.Context, search *string, limit int) ([]contract.UserResponse, error)
|
||||
|
||||
@@ -47,16 +47,24 @@ func (s *UserServiceImpl) GetUserByEmail(ctx context.Context, email string) (*co
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) ListUsers(ctx context.Context, req *contract.ListUsersRequest) (*contract.ListUsersResponse, error) {
|
||||
// Handle pagination parameters in service layer
|
||||
page := req.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
limit := req.Limit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100 // Max limit to prevent performance issues
|
||||
}
|
||||
|
||||
offset := (page - 1) * limit
|
||||
|
||||
userResponses, totalCount, err := s.userProcessor.ListUsersWithFilters(ctx, req)
|
||||
// Pass calculated offset and limit to processor
|
||||
userResponses, totalCount, err := s.userProcessor.ListUsersWithFilters(ctx, req.Search, req.RoleCode, req.IsActive, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,5 +107,13 @@ func (s *UserServiceImpl) ListTitles(ctx context.Context) (*contract.ListTitlesR
|
||||
|
||||
// GetActiveUsersForMention retrieves active users for mention purposes
|
||||
func (s *UserServiceImpl) GetActiveUsersForMention(ctx context.Context, search *string, limit int) ([]contract.UserResponse, error) {
|
||||
// Handle limit in service layer
|
||||
if limit <= 0 {
|
||||
limit = 50 // Default limit for mention suggestions
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100 // Max limit to prevent performance issues
|
||||
}
|
||||
|
||||
return s.userProcessor.GetActiveUsersForMention(ctx, search, limit)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user