Совсем недавно мне в руки попалась задачка со следующим содержанием:
Необходимо создать отчет по сотрудникам за выбранный период времени. При формировании необходимо отразить кол-во занесенных часов с учетом времени подчиненных. У каждого сотрудника могут быть подчинённые, причем у подчиненных могут быть свои подчиненные и т.д.
Пример входных данных:
Таблица сотрудников:
| id (int) | name (string) | email (string) | employer (int) | info (text) |
| 1 | Петр | petr@tm.ru | 0 | |
| 2 | Федор | fedor@tm.ru | 1 | |
| 3 | Николай | nik@tm.ru | 0 | |
| 4 | Яна | yana@tm.ru | 2 | |
| 5 | Антон | anton@tm.ru | 3 | |
| 6 | Екатерина | katya@tm.ru | 1 |
Рабочее время по каждому сотруднику:
| employeeId (int) | time (string) | date (date) |
| 3 | 2:00:00 | 2017-02-10 |
| 1 | 5:00:00 | 2017-02-15 |
| 2 | 1:00:00 | 2017-02-11 |
| 3 | 4:30:00 | 2017-02-12 |
Задача на первый взгляд достаточно простая, единственная сложность в том, что неизвестно максимальный уровень иерархии. Таким образом придётся использовать рекурсивную функцию для обхода всего древа и вычисления суммы.
Первое, что мы сделаем, это представим наше древо сотрудников в виде списка смежности. Для этого считаем всю таблицу сотрудников в массив с индексами соответствующими id-сотрудника. И каждому элементу добавим еще ряд, соответствующий id всех подчинённых. Плюс, нулевым элементом запишем id всех сотрудников имеющих высший уровень иерархии. На выходе получим следующее:
[
[0] => [1, 3],
[1] => [2, 6],
[2] => [4],
[3] => [5],
[4] => [],
[5] => [],
[6] => [],
]
Визуально это выглядит так:
Теперь нам остается только подсчитать сумму часов по каждому работнику, но следует помнить, что сумма часов у работника с id:2 будет равняться сумме часов этого же работника и всех его подчиненных, в данном случае id:2+id:4. А вот уже сумма id:1=id:2+id:4+id:6.
Поскольку мы не знаем максимального уровня иерархии, мы будем использовать рекурсивную функцию для спуска вниз по иерархии и считать часы на всех уровнях итерации начиная с конца. Для этого потребуется две функции:
- sumLevel() — будет считать сумму на текущем уровне иерархии.
- sumAll() — спускается по иерархии и считает общую сумму по каждому сотруднику.
В качестве параметров для этих функций будем использовать только массив с id текущего уровня, а общий массив сотрудников с их часами и другими данными будет идти как глобальный.
Функция sumLevel():
function sumLevel($array = []) {
global $tmp_employers; // глобальный массив сотрудников
$sumAll = 0;
// проход по уровню и вычесление суммы всех сотрудников этого уровня
foreach($array AS $item) {
$sumAll += $tmp_employers[$item]['sum_all'];
}
return $sumAll;
}
Функция sumAll():
function sumAll($array = []) {
global $tmp_employers;
$sumAll = 0;
foreach($array AS $item) {
if(count($tmp_employers[$item]['employers']) > 0) {
sumAll($tmp_employers[$item]['employers']); // подсчет часов на каждом уровне
}
/* сумма всех часов работника равняется сумме его часов + сумме всех часов работников на уровень ниже */
$tmp_employers[$item]['sum_all'] = $tmp_employers[$item]['sum_single'] + sumLevel($tmp_employers[$item]['employers']);
}
return false;
}
На этом основная часть задачи выполнена. Как считать данные с базы и вывести их на экран я не рассматривал, так как хотел разобрать именно алгоритм вычисления суммы в иерархии.
Результат со входными данными получился следующий:
| # | Имя | Кол-во часов с подчиненными | Кол-во часов без подчиненных | |
| 1 | Петр | petr@tm.ru | 6 | 5 |
| 2 | —Федор | fedor@tm.ru | 1 | 1 |
| 3 | —-Яна | yana@tm.ru | 0 | 0 |
| 4 | —Екатерина | katya@tm.ru | 0 | 0 |
| 5 | Николай | nik@tm.ru | 6.5 | 6.5 |
| 6 | —Антон | anton@tm.ru | 0 | 0 |
Полный код можно скачать здесь.
