270 lines
9.1 KiB
Go
270 lines
9.1 KiB
Go
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
|
|
} |