dukcapil/internal/processor/department_processor.go
2025-09-20 16:31:22 +07:00

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
}