Option Explicit
' ---------------------------------------------------
' Remember this Module requires Tools>References>
' Microsoft Scripting Runtime
' to be set in the VBA Window or it will error with:
' "User Defined Type Not Defined"
' ---------------------------------------------------
Public Sub RunHierarchy()
' ===================================================
' Create the many dictionaries used - note this is
' early binding so heed the warning above regarding
' *** Microsoft Scripting Runtime ***
' ===================================================
Dim dicSupervisors As Scripting.Dictionary
Set dicSupervisors = New Scripting.Dictionary
Dim dicStaff As Scripting.Dictionary
Set dicStaff = New Scripting.Dictionary
Dim dicStaffReverse As Scripting.Dictionary
Set dicStaffReverse = New Scripting.Dictionary
Dim dicPersonsSupervisor As Scripting.Dictionary
Set dicPersonsSupervisor = New Scripting.Dictionary
Dim dicPersonsChainOfCommand As Scripting.Dictionary
Set dicPersonsChainOfCommand = New Scripting.Dictionary
Dim dicPersonsChainOfCommandSorted As Scripting.Dictionary
Set dicPersonsChainOfCommandSorted = New Scripting.Dictionary
Dim lngLoop As Long, lngLoop2 As Long
Dim rngUsed As Range
Dim strID As String, strName As String, strSuperID As String
' ===================================================
' Fill the dicSupervisors dictionary
' {Key} {Item}
' 1001 Person B
' ===================================================
' NB: this requires the Supervisors' IDs and Names
' to be in Columns A and B respectively of the Source
' Worksheet
' ===================================================
Set rngUsed = shSource.UsedRange
For lngLoop = 1 To rngUsed.Rows.Count
strID = rngUsed.Cells(lngLoop, 1).Text
strName = rngUsed.Cells(lngLoop, 2).Text
If IsNumeric(strID) And (Not (dicSupervisors.exists(strID))) Then
dicSupervisors.Add key:=strID, Item:=strName
End If
Next lngLoop
' ===================================================
' Fill the Staff and StaffReverse dictionaries
' Creates two dictonaries, one (dicStaff) with the structure:
' {Key} {Item}
' 1001 Person B
'...and the other (dicStaffReverse):
' {Item} {Key}
' Person B 1001
' ===================================================
' NB: this requires the Staff IDs and Names
' to be in Columns C and D respectively of the Source
' Worksheet
' ===================================================
For lngLoop = 1 To rngUsed.Rows.Count
strID = rngUsed.Cells(lngLoop, 3).Text
strName = rngUsed.Cells(lngLoop, 4).Text
If IsNumeric(strID) And (Not (dicStaff.exists(strID))) Then
dicStaff.Add key:=strID, Item:=strName
dicStaffReverse.Add key:=strName, Item:=strID
End If
Next lngLoop
' ===================================================
' Fill the PersonsSupervisor dictionary
' This will have a structure like:
' {Key} (Staffer's ID) {Item} (Manager's ID)
' 1001 1000
' ===================================================
For lngLoop = 1 To rngUsed.Rows.Count
strID = rngUsed.Cells(lngLoop, 3).Text
strSuperID = rngUsed.Cells(lngLoop, 1).Text
If IsNumeric(strID) And (Not (dicPersonsSupervisor.exists(strID))) Then
dicPersonsSupervisor.Add key:=strID, Item:=strSuperID
ElseIf strSuperID = "1000" Then ' NB: Hard coded 1000 as the top of the hierarchy
dicPersonsSupervisor.Add key:=strID, Item:="BOARD" ' The CEO?! (Doesn't really matter - note used)
End If
Next lngLoop
' ===================================================
' Build the "Helper" PersonsSupervisors Worksheet.
' This will have a structure like:
' 1000
' 1003 1000
' 1005 1001 1000
' ... which represents the *bottom-up* structure
' with the Staffer reports Manger, reports to Manager,
' to the top of the hierarchy for each Staffer.
' The output to this can be seen
' in the PersonsSupervisors Worksheet (which could
' be hidden if desired, of course)
' ===================================================
Call WritePersonsSupervisors(ThisWorkbook.Worksheets(shPersonsSupervisors.Name), dicPersonsSupervisor)
' ===================================================
' Build the PersonsChainOfCommand dictionary.
' This will have a structure like:
' {Key} {Item} (level)
' 1000.Person A 1
' 1000-1001-1004.Person E 3
' 1000-1001.Person B 2
' ... which represents the *top-down* structure
' with the CEO manages Manger, manages Manager,
' to finally Staffer at the bottom of the top of the
' hierarchy for each Staffer.
' The number at the end (Item) is the level the Staff
' is in the organisation, which is used later when
' "indenting" the output....
' ===================================================
Set rngUsed = shPersonsSupervisors.UsedRange
For lngLoop = 1 To rngUsed.Rows.Count
strName = rngUsed.Cells(lngLoop, 1).Text & "." & dicStaff(rngUsed.Cells(lngLoop, 1).Text)
strID = " "
For lngLoop2 = 2 To rngUsed.Columns.Count
strID = rngUsed.Cells(lngLoop, lngLoop2).Text & " " & strID & " "
Next lngLoop2
strID = Replace(Trim(strID), " ", "-")
If Len(strID) = 0 Then
dicPersonsChainOfCommand.Add key:=strName, Item:=CStr(UBound(Split(strID, "-")) + 2)
Else
dicPersonsChainOfCommand.Add key:=strID & "-" & strName, Item:=CStr(UBound(Split(strID, "-")) + 2)
End If
Next lngLoop
' ===================================================
' Sort the PersonsChainOfCommand dictionary
' ===================================================
' This will have a structure like:
' {Key} {Item} (level)
' 1000.Person A 1
' 1000-1001.Person B 2
' 1000-1001-1004.Person E 3
' ===================================================
Set dicPersonsChainOfCommandSorted = SortDictionaryByKey(dicPersonsChainOfCommand, xlAscending)
' ===================================================
' Write the full and selected hierarchies
' ===================================================
Call WriteHierarchy(ThisWorkbook.Worksheets(shFullOrganisationHierarchy.Name), dicPersonsChainOfCommandSorted, dicStaffReverse)
' - Partial (selected) hierarchy
Dim strFromID As String
strFromID = InputBox("Enter a valid staff ID:", "Organisation Hierarchy Bulider")
If dicSupervisors.exists(strFromID) Then
Call WriteHierarchyFrom(ThisWorkbook.Worksheets(shChosenOrganisationHierarchy.Name), dicPersonsChainOfCommandSorted, dicStaffReverse, strFromID)
Else
shChosenOrganisationHierarchy.Cells.Clear
MsgBox ("Sorry, no such manager " & strFromID)
End If
End Sub
' +++ Using dicPersonsSupervisor dictionary
Private Sub WritePersonsSupervisors(shReport As Worksheet, dict As Scripting.Dictionary)
Dim varKey As Variant
Dim lngRow As Long, lngColumn As Long, lngRowsN As Long
shReport.Cells.Clear
lngRow = 1
For Each varKey In dict.Keys
shReport.Cells(lngRow, 1) = varKey
shReport.Cells(lngRow, 2) = dict(varKey)
lngRow = lngRow + 1
Next
lngRowsN = shReport.UsedRange.Rows.Count
For lngColumn = 2 To 10
For lngRow = 1 To lngRowsN
If shReport.Cells(lngRow, lngColumn).Value = "" Then
' do nothing
ElseIf shReport.Cells(lngRow, lngColumn) = "1000" Then
'do nothing
Else
shReport.Cells(lngRow, lngColumn + 1) = dict(CStr(shReport.Cells(lngRow, lngColumn)))
End If
Next lngRow
Next lngColumn
End Sub
' +++ Using dicPersonsChainOfCommandSorted, dicStaffReverse
Private Sub WriteHierarchy(shReport As Worksheet, dict As Scripting.Dictionary, dict2 As Scripting.Dictionary)
shReport.Cells.Clear
Dim varKey As Variant, lngRow As Long
lngRow = 1
For Each varKey In dict.Keys
shReport.Cells(lngRow, CInt(dict(varKey)) + 1) = Split(varKey, ".")(1)
shReport.Cells(lngRow, CInt(dict(varKey))) = dict2(Split(varKey, ".")(1))
lngRow = lngRow + 1
Next
End Sub
' +++ Using 1) dicPersonsChainOfCommandSorted and 2) dicStaffReverse dictionaries
Private Sub WriteHierarchyFrom(shReport As Worksheet, dict As Scripting.Dictionary, dict2 As Scripting.Dictionary, strID As String)
shReport.Cells.Clear
Dim varKey As Variant, lngRow As Long
lngRow = 1
For Each varKey In dict.Keys
If UBound(Split(varKey, strID)) > 0 Then
shReport.Cells(lngRow, CInt(dict(varKey)) + 1) = Split(varKey, ".")(1)
shReport.Cells(lngRow, CInt(dict(varKey))) = dict2(Split(varKey, ".")(1))
End If
lngRow = lngRow + 1
Next
shReport.UsedRange.Cut Destination:=shReport.Range("A1")
End Sub
' Source: From https://excelmacromastery.com/
Public Function SortDictionaryByKey(dict As Object _
, Optional sortorder As XlSortOrder = xlAscending) As Object
Dim arrList As Object
Set arrList = CreateObject("System.Collections.ArrayList")
' Put keys in an ArrayList
Dim key As Variant, coll As New Collection
For Each key In dict
arrList.Add key
Next key
' Sort the keys
arrList.Sort
' For descending order, reverse
If sortorder = xlDescending Then
arrList.Reverse
End If
' Create new dictionary
Dim dictNew As Object
Set dictNew = CreateObject("Scripting.Dictionary")
' Read through the sorted keys and add to new dictionary
For Each key In arrList
dictNew.Add key, dict(key)
Next key
' Clean up
Set arrList = Nothing
Set dict = Nothing
' Return the new dictionary
Set SortDictionaryByKey = dictNew
End Function