What is the best way to implement user hierarchy (multi level)

Hi,
I have this scenario that one user is actually a department manager and he is standing in the head of a pyramid.
The pyramid depth level is unknown, can be one, or can be 4.

I want to create a user, define his manager.
End of the day, in the reports I want to see an aggregation of a user and the performance of his employees/users.

What are the best practices in this case? is done in a demo project?

Thanks .

Hi,
I cannot remember an example for it, but I would create a new entity called Department with a field User manager and List<User> employees which has a @ManyToMany cardinality.
After that you’ll be able to find all departments where the current user is a manager and all employees of this department.
Will that work for your case?

Hi
No, I need the full hierarchy… and find the path.

User A
 -> User B
   -> User C

I need to know that User is under A and B.
Thanks

  1. Each user can have only one manager.
  2. You need an API that will return:
    a) All managers for the user (For C A and B will be returned)
    b) All subordinates of the user (for A all subordinates of A + all subordinates of B + all subordinates of C will be returned)

Are the statements above correct?

Hi.

  1. yes, but remember I can have “parent” and a grand parent
  2. yes. maybe a way to query it efficiently.
    Thanks

Here is a sample project: org-structure-sample.zip (90.9 KB)

A new entity Employee with a one-to-one association to User is created. The Employee has a reference to a parent employee and subordinates.

@Table(name = "SAMPLE_EMPLOYEE")
@Entity(name = "sample$Employee")
@NamePattern("%s|user")
public class Employee extends StandardEntity {

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "USER_ID")
    protected User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MANAGER_ID")
    protected Employee manager;

    @OneToMany(mappedBy = "manager")
    protected List<Employee> subordinates;

    public List<Employee> getSubordinates() {
        return subordinates;
    }

    public void setSubordinates(List<Employee> subordinates) {
        this.subordinates = subordinates;
    }

    public Employee getManager() {
        return manager;
    }

    public void setManager(Employee manager) {
        this.manager = manager;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

An entity listener that automatically creates a related employee when a new user is created:

/**
 * Listener automatically creates an employee when the user is created
 */
@Component("sample_MyUserEntityListener")
public class MyUserEntityListener implements BeforeInsertEntityListener<User> {

    @Inject
    protected Metadata metadata;

    @Override
    public void onBeforeInsert(User user, EntityManager entityManager) {
        Employee employee = metadata.create(Employee.class);
        employee.setUser(user);
        entityManager.persist(employee);
    }
}

A middleware service that returns all subordinates and managers for the selected employee.

@Service(OrgStructureService.NAME)
public class OrgStructureServiceBean implements OrgStructureService {

    @Inject
    protected DataManager dataManager;

    @Inject
    protected EntityStates entityStates;

    @Override
    public List<Employee> loadAllManagers(Employee employee) {
        List<Employee> managers = new ArrayList<>();
        if (!entityStates.isLoadedWithView(employee, "employee-with-manager")) {
            employee = dataManager.reload(employee, "employee-with-manager");
        }
        while (employee.getManager() != null) {
            managers.add(employee.getManager());
            employee = dataManager.reload(employee.getManager(), "employee-with-manager");
        }
        return managers;
    }

    @Override
    public List<Employee> loadAllSubordinates(Employee manager) {
        return loadAllSubordinates(Collections.singletonList(manager));
    }

    protected List<Employee> loadAllSubordinates(List<Employee> managers) {
        List<Employee> subordinates = dataManager.load(Employee.class)
                .query("select s from sample$Employee e join e.subordinates s where e.id in :managers")
                .parameter("managers", managers)
                .view("employee-with-subordinates")
                .list();

        if (!subordinates.isEmpty()) {
            subordinates.addAll(loadAllSubordinates(subordinates));
        }

        return subordinates;
    }
}

Each employee returned by the service has a user property, so you can easily get a User entity from it.
You can test the service using the Org Structure Test Screen that is available from the menu.

3 Likes