package processor import ( "context" "fmt" "eslogad-be/internal/entities" "eslogad-be/internal/repository" "github.com/google/uuid" ) // DepartmentProcessor handles all department-related business logic type DepartmentProcessor interface { Create(ctx context.Context, department *entities.Department) error Get(ctx context.Context, id uuid.UUID) (*entities.Department, error) GetByPath(ctx context.Context, path string) (*entities.Department, error) Update(ctx context.Context, department *entities.Department) error Delete(ctx context.Context, id uuid.UUID) error List(ctx context.Context, search string, limit, offset int) ([]entities.Department, int64, error) // Hierarchy operations GetChildren(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) GetAllDescendants(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) UpdateChildrenPaths(ctx context.Context, oldPath, newPath string) error ValidateHierarchy(ctx context.Context, departmentID, newParentID uuid.UUID) error // Tree building operations GetDepartmentWithDescendants(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) GetDepartmentWithParentAndSiblings(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) GetAllDepartments(ctx context.Context) ([]entities.Department, error) GetDepartmentsByRootPath(ctx context.Context, rootPath string) ([]entities.Department, error) } type DepartmentProcessorImpl struct { departmentRepo *repository.DepartmentRepository txManager *repository.TxManager } // NewDepartmentProcessor creates a new department processor func NewDepartmentProcessor( departmentRepo *repository.DepartmentRepository, txManager *repository.TxManager, ) *DepartmentProcessorImpl { return &DepartmentProcessorImpl{ departmentRepo: departmentRepo, txManager: txManager, } } // Create creates a new department func (p *DepartmentProcessorImpl) Create(ctx context.Context, department *entities.Department) error { // Build path based on parent if department.ParentDepartmentID != nil && *department.ParentDepartmentID != uuid.Nil { parent, err := p.departmentRepo.GetByID(ctx, *department.ParentDepartmentID) if err != nil { return fmt.Errorf("parent department not found: %w", err) } department.Path = parent.Path + "." + department.Code } else { department.Path = department.Code } return p.departmentRepo.Create(ctx, department) } // Get retrieves a department by ID func (p *DepartmentProcessorImpl) Get(ctx context.Context, id uuid.UUID) (*entities.Department, error) { return p.departmentRepo.Get(ctx, id) } // GetByPath retrieves a department by its path func (p *DepartmentProcessorImpl) GetByPath(ctx context.Context, path string) (*entities.Department, error) { return p.departmentRepo.GetByPath(ctx, path) } // Update updates a department with hierarchy validation func (p *DepartmentProcessorImpl) Update(ctx context.Context, department *entities.Department) error { return p.txManager.WithTransaction(ctx, func(txCtx context.Context) error { // Get the current department state current, err := p.departmentRepo.Get(txCtx, department.ID) if err != nil { return err } // Store old values for comparison oldPath := current.Path oldParentID := current.ParentDepartmentID pathChanged := false // Update path if parent or code changed if department.ParentDepartmentID != oldParentID || department.Code != current.Code { // Rebuild path if department.ParentDepartmentID == nil { department.Path = department.Code } else { parent, err := p.departmentRepo.GetByID(txCtx, *department.ParentDepartmentID) if err != nil { return fmt.Errorf("parent department not found: %w", err) } department.Path = parent.Path + "." + department.Code } if department.Path != oldPath { pathChanged = true } } // Update the department if err := p.departmentRepo.Update(txCtx, department); err != nil { return err } // Update children paths if necessary if pathChanged { if err := p.updateChildrenPathsRecursively(txCtx, department.ID, department.Path); err != nil { return fmt.Errorf("failed to update children paths: %w", err) } } return nil }) } // Delete deletes a department func (p *DepartmentProcessorImpl) Delete(ctx context.Context, id uuid.UUID) error { return p.departmentRepo.Delete(ctx, id) } // List lists departments with pagination func (p *DepartmentProcessorImpl) List(ctx context.Context, search string, limit, offset int) ([]entities.Department, int64, error) { return p.departmentRepo.List(ctx, search, limit, offset) } // GetChildren gets direct children of a department func (p *DepartmentProcessorImpl) GetChildren(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) { // First get the parent department to get its path parent, err := p.departmentRepo.Get(ctx, parentID) if err != nil { return nil, err } if parent == nil { return []entities.Department{}, nil } return p.departmentRepo.GetChildren(ctx, parent.Path) } // GetAllDescendants gets all descendants of a department func (p *DepartmentProcessorImpl) GetAllDescendants(ctx context.Context, parentID uuid.UUID) ([]entities.Department, error) { return p.departmentRepo.GetAllDescendants(ctx, parentID) } // UpdateChildrenPaths updates paths for all children func (p *DepartmentProcessorImpl) UpdateChildrenPaths(ctx context.Context, oldPath, newPath string) error { return p.departmentRepo.UpdateChildrenPaths(ctx, oldPath, newPath) } // ValidateHierarchy validates that setting newParentID won't create circular references func (p *DepartmentProcessorImpl) ValidateHierarchy(ctx context.Context, departmentID, newParentID uuid.UUID) error { // Can't be its own parent if departmentID == newParentID { return fmt.Errorf("department cannot be its own parent") } // Check if new parent is a descendant descendants, err := p.GetAllDescendants(ctx, departmentID) if err != nil { return fmt.Errorf("failed to check descendants: %w", err) } for _, desc := range descendants { if desc.ID == newParentID { return fmt.Errorf("cannot set a descendant as parent (circular reference)") } } return nil } // GetDepartmentWithDescendants gets a department and all its descendants func (p *DepartmentProcessorImpl) GetDepartmentWithDescendants(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) { // Get the department dept, err := p.departmentRepo.Get(ctx, departmentID) if err != nil { return nil, err } departments := []entities.Department{*dept} // Get all descendants descendants, err := p.departmentRepo.GetAllDescendants(ctx, departmentID) if err == nil { departments = append(departments, descendants...) } return departments, nil } // GetDepartmentWithParentAndSiblings gets a department with its parent and all siblings func (p *DepartmentProcessorImpl) GetDepartmentWithParentAndSiblings(ctx context.Context, departmentID uuid.UUID) ([]entities.Department, error) { // Get the department dept, err := p.departmentRepo.Get(ctx, departmentID) if err != nil { return nil, err } var departments []entities.Department // If has parent, get parent and all its descendants if dept.ParentDepartmentID != nil { parent, err := p.departmentRepo.Get(ctx, *dept.ParentDepartmentID) if err == nil { departments = append(departments, *parent) // Get all descendants of parent (includes siblings) descendants, err := p.departmentRepo.GetAllDescendants(ctx, parent.ID) if err == nil { departments = append(departments, descendants...) } } else { // Fallback to just department and its descendants return p.GetDepartmentWithDescendants(ctx, departmentID) } } else { // No parent, just get department and descendants return p.GetDepartmentWithDescendants(ctx, departmentID) } return departments, nil } // GetAllDepartments gets all departments in the system func (p *DepartmentProcessorImpl) GetAllDepartments(ctx context.Context) ([]entities.Department, error) { departments, _, err := p.departmentRepo.List(ctx, "", 0, 0) return departments, err } // GetDepartmentsByRootPath gets departments starting from a specific path func (p *DepartmentProcessorImpl) GetDepartmentsByRootPath(ctx context.Context, rootPath string) ([]entities.Department, error) { // Find the root department rootDept, err := p.departmentRepo.GetByPath(ctx, rootPath) if err != nil { return nil, err } return p.GetDepartmentWithDescendants(ctx, rootDept.ID) } // updateChildrenPathsRecursively updates paths for all children recursively func (p *DepartmentProcessorImpl) updateChildrenPathsRecursively(ctx context.Context, parentID uuid.UUID, parentPath string) error { children, err := p.departmentRepo.GetChildren(ctx, parentPath) if err != nil { return err } for _, child := range children { // Update child's path child.Path = parentPath + "." + child.Code if err := p.departmentRepo.Update(ctx, &child); err != nil { return err } // Recursively update grandchildren if err := p.updateChildrenPathsRecursively(ctx, child.ID, child.Path); err != nil { return err } } return nil }