Blog personal

Gestor de proyectos en PHPrunner vs. DotProject / Web2Project

Es muy habitual requerir una gestión de proyectos (proyectos y tareas) en muchas actividades de un empresa.

La primera impresión puede ser que se requiera para la gestión de los proyectos informáticos, pero no sólo es para eso, sino que también se requiere para el control de Planes (sistemas, estratégicos, etc.) que normalmente tienen Medidas y que esta Medidas tienen Proyectos de ejecución.

También, otros pretenden que sean herramientas que sustituye a herramientas como MS Project. Esto no es así, estas herramientas sirven para coordinar y controlar muchos proyectos, pero no el detalle de los proyectos.

Cuando he utilizado soluciones de este tipo (DotProject  y Web2Project, producto open source desarrollados en PHP y MySQL) siempre se complementaba los detalles de los proyectos con su seguimiento detallado en MS Project.

También, cuando he trabajado en la ejecución y seguimiento de los planes, una parte importante era el seguimiento de los proyectos, pero otras muy importantes eran:

  • Inventario de las Medidas/Acciones con todos sus atributos de agrupaciones.
  • Informes y cuadros de mando de seguimiento.
  • Resumen de seguimiento para la dirección de la empresa.
  • Etc.

Con esto quiero señalar que si utilizaba, por ejemplo Web2Project, tenía que integrar sus datos con otro aplicativo que manejase el resto de los datos y todos los informes y cuadros de mandos.

Lo que he pretendido es disponer de una solución de gestión de proyectos que pudiera crecer y adecuarse el resto de los datos que se requieren en los Planes y esto es bastante sencillo utilizando PHPRunner, por lo que se requiere es la funcionalidad básica de la gestión de los proyectos, a saber:

  • Las entidades básicas son Compañías, Departamentos, Proyectos y Tareas.
  • Las tareas se planifican según calendario laboral (sin fines de semana) o días naturales.
  • En las tareas debe poderse definir jerarquía entre ellas (agrupación de tareas).
  • Entre las tareas se pueden definir dependencias y poderse recalcular la fecha de inicio sí la tarea de la que depende se retrasa (camino crítico).

Otro aspecto muy importante es la seguridad, de tal forma que:

  • A los proyectos se podrán configurar los acceso según:
    • Público (todos los usuarios podrán acceder)
    • Compañía (sólo podrán acceder los usuarios de la compañía del proyecto).
    • Departamento (sólo podrán acceder los usuarios del departamento del proyecto)
    • Propietario (sólo podrá acceder el propietario del proyecto)
    • Propietario proyecto y propietarios de tareas (Sólo podrán acceder estos proyectos)
  • A las tareas se podrán configurar los acceso según:
    • Público (todos los usuarios podrán acceder)
    • Compañía (sólo podrán acceder los usuarios de la compañía del proyecto).
    • Departamento (sólo podrán acceder los usuarios del departamento del proyecto)
    • Propietario (sólo podrá acceder el propietario del proyecto)
    • Propietario proyecto y propietarios de tareas (Sólo podrán acceder estos proyectos)

Esta gestión se ha realizado definiendo una lógica diferente a la utilizada normalmente en los desarrollos de PHPrunner y complementarios a estos.

Para el apoyo visual de la información se ha incluido la librería de javascript JSGANTT.

El ejemplo se ha desarrollado en inglés y español y se ha partido del modelo de datos de web2Project, para la definición de los atributos de “projects” y “tasks”.

También hay definición de CSS para disponer de un interfaz de aplicación agradable.

Esta figura muestra la página principal del ejercicio.

El desarrollo está desplegado en https://fhumanes.com/project

Los usuarios de acceso son los que se muestran en la imagen con sus password.

MUY IMPORTANTE:

No es una aplicación es una base para desarrollar tú aplicación

Ampliación de la gestión de seguridad

He cambiado la gestión de seguridad, haciendo qué:

  • Todos los datos de Proyectos y Tareas sean consultables para todos los usuarios que tengan acceso al sistema.
  • Sólo podrán modificar Proyectos (1) o Tareas (3), los usuarios que se ajusten a la configuración de acceso que tengan estas informaciones.
  • Las Tareas y ficheros (2) heredaran de Proyectos (1) los accesos cuando los estemos tratando.
  • De igual forma, los Log (4) y Ficheros (4) heredaran los accesos de Tareas (3) cuando los estemos tratando.
  • Para agilizar el interfaz, de ha cambiado la lógica de PHPRunner para que las Altas/Inserciones de Log y Ficheros se hagan de forma directa en «POPUP».

Códificación de elementos más importantes

Este conjunto de elementos son las características más importante del desarrrollo, por ello voy a exponerlas en el artículo.

access_project.php
<?php
global $conn;

$user_id=$_SESSION['user_id']; 
$company_id=$_SESSION['company_id'];
$department_id = $_SESSION['dept_id'];

if ($_SESSION['id_group'] == -1) { // Group Admin 
$sql_1 = "
Select  GROUP_CONCAT(P.project_id) project
from (
    SELECT project_id FROM projects
     ) P";
} else { // Groups Not Admin
$sql_1 = "
Select  GROUP_CONCAT(P.project_id) project
from (
    SELECT project_id FROM projects
    WHERE private = 0
  UNION
    SELECT project_id FROM projects
    WHERE private = 1 AND companies_company_id = $company_id
  UNION
    SELECT project_id FROM projects
    WHERE private = 2 AND departments_dept_id = $department_id
  UNION
    SELECT project_id FROM projects
    WHERE (private = 3 or private = 4) AND creator = $user_id
  UNION
    SELECT project_id FROM projects
    WHERE private = 4 AND project_id In (
      SELECT distinct projects_project_id FROM tasks
      WHERE creator = $user_id ) ) P";
}
$resql_1 = db_query($sql_1,$conn);
$row_1 = db_fetch_array($resql_1);

$_SESSION['project_access']= $row_1['project']; 
if ($_SESSION['project_access'] == '') {
		$_SESSION['project_access']="0"; // 0,  In case you do not have any authorized
}

?>

access_task.php

<?php
global $conn;

$user_id=$_SESSION['user_id']; 
$company_id=$_SESSION['company_id'];
$department_id = $_SESSION['dept_id'];
$project_id =$_SESSION['tasks_masterkey1'];

if ($_SESSION['id_group'] == -1) { // Group Admin 
$sql_1 = "
Select  GROUP_CONCAT(T.task_id) tasks
from (
    SELECT task_id FROM tasks where projects_project_id = $project_id
     ) T";
} else { // Groups Not Admin
$sql_1 = "
select  GROUP_CONCAT(T.task_id) tasks
from (
    SELECT task_id FROM tasks
    WHERE projects_project_id = $project_id
    AND access = 0
  UNION
    SELECT task_id FROM tasks join projects on (projects_project_id = project_id)
    WHERE projects_project_id = $project_id
    AND access = 1
    AND companies_company_id = $company_id
  UNION
    SELECT task_id FROM tasks join projects on (projects_project_id = project_id)
    WHERE projects_project_id = $project_id
    AND access = 2
    AND departments_dept_id = $department_id
  UNION
    SELECT task_id FROM tasks join projects on (projects_project_id = project_id)
    WHERE projects_project_id = $project_id
    AND ( access = 3 or access = 4)
    AND projects.creator = $user_id
  UNION
    SELECT task_id FROM tasks
    WHERE projects_project_id = $project_id
    AND access = 4
    AND creator = $user_id ) T 
";
}
$resql_1 = db_query($sql_1,$conn);
$row_1 = db_fetch_array($resql_1);

$_SESSION['task_access']= $row_1['tasks'];

?>

calc_task_dymanic_and_project.php

<?php
global $conn;

// Key of Project in List
$key = $_SESSION['project_id'];

// Calc task Dynamic or Group ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

$sql_1 = "SELECT
 t.`task_id`, t.`dynamic`, t.`order`
FROM tasks t
WHERE
t.`dynamic` = 1 and
t.`projects_project_id` = $key order by t.`order` desc";


if ($resql_1 = db_query($sql_1,$conn)) {
    /*  calc tasks Dymanic*/
$hours_a_day = $_SESSION['Config'][array_search('hours_a_day', array_column($_SESSION['Config'], 'name'))][value];
    while ($row_1 = db_fetch_array($resql_1)) {
        $parent = $row_1['task_id'];
				$sql_2 = "
SELECT
t.`parent`,
min(t.`start_date`) start_date,
max(t.`end_date`) end_date,
sum(if (duration_type = 0,$hours_a_day,1)*duration) duration,
sum((if (duration_type = 0,$hours_a_day,1)*duration)*percent_complete/100) completed
FROM tasks t
WHERE
t.`parent` = $parent
order by 1 
";
				if ($resql_2 = db_query($sql_2,$conn)) {
				$row_2 = db_fetch_array($resql_2);
				$task_parent = $row_2['parent'];
				$task_start_date = $row_2['start_date'];
				$task_end_date = $row_2['end_date'];
				$task_duration = $row_2['duration'];
				$task_completed = $row_2['completed'];

				$free_weekend = $_SESSION['Config'][array_search('free_weekend', array_column($_SESSION['Config'], 'name'))][value];
				require_once "ICM/function_dates.php";   // Function of dates
				if ( $free_weekend == 'yes' ) {
						$function_datediff = 'TOTAL_WEEKDAYS';
				} else {
						$function_datediff = 'DATEDIFF';
				}

				$sql_3="
update tasks
set
start_date = '$task_start_date',
end_date = '$task_end_date',
duration_type = 0,
duration= $function_datediff('$task_end_date','$task_start_date'),
percent_complete=ROUND($task_completed*100/$task_duration,0)
where task_id = $task_parent
";
				db_query($sql_3,$conn);
				}
    }

}
// Calc Project  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
$sql_1 = "
SELECT
min(t.`start_date`) start_date,
max(ifnull(`actual_end_date`,`end_date`)) end_date,
sum(hours_worked) hours_worked,
sum(if (duration_type = 0,$hours_a_day,1)*duration) duration,
sum((if (duration_type = 0,$hours_a_day,1)*duration)*percent_complete/100) completed
FROM tasks t
WHERE
t.`dynamic` = 0 and
t.`projects_project_id` = $key
-- order by t.`order`
";
if ($resql_1 = db_query($sql_1,$conn)) {
		$row_1 = db_fetch_array($resql_1);
		$task_start_date = $row_1['start_date'];
		$task_end_date = $row_1['end_date'];
		$task_duration = $row_1['duration'];
		$task_completed = $row_1['completed'];
		$task_hours_worked = $row_1['hours_worked'];

$sql_2="
update projects
set
actual_end_date = '$task_end_date',
worked_hours = $task_hours_worked, 
scheduled_hours= $task_duration,
percent_complete=ROUND($task_completed*100/$task_duration,0)
where `project_id` = $key
";
db_query($sql_2,$conn);

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

}
?>

date_settings.php

<?php
global $conn;
// Ajust datetime to time of file Config
$TimeStart = $_SESSION['Config'][array_search('start_time_day', array_column($_SESSION['Config'], 'name'))][value];
$values['start_date'] = substr($values['start_date'],0,11).$TimeStart;

// Valor of Config
$hours_a_day = $_SESSION['Config'][array_search('hours_a_day', array_column($_SESSION['Config'], 'name'))][value];
$free_weekend = $_SESSION['Config'][array_search('free_weekend', array_column($_SESSION['Config'], 'name'))][value];

include "ICM/function_dates.php";   // Function of dates

if ( $values['duration_type'] == 1) {
		$increment_day = ceil($values['duration']/$hours_a_day);
		} else { 
		$increment_day = $values['duration'];
		}
$increment_day = $increment_day -1;
if ( $free_weekend == 'yes' ) {
		$values['end_date'] = getFutureBusinessDayWeek($increment_day, $values['start_date']);
		} else {
		$values['end_date'] = getFutureBusinessDay($increment_day, $values['start_date']);
		}

$TimeEnd = $_SESSION['Config'][array_search('end_time_day', array_column($_SESSION['Config'], 'name'))][value];
$values['end_date'] = $values['end_date'].' '.$TimeEnd;

?>

function_dates.php

<?php

function getFutureBusinessDayWeek($num_business_days, $today_ymd = null, $holiday_dates_ymd = []) {
    $num_business_days = min($num_business_days, 1000);
    $business_day_count = 0;
    $current_timestamp = empty($today_ymd) ? time() : strtotime($today_ymd);
    while ($business_day_count < $num_business_days) {
        $next1WD = strtotime('+1 weekday', $current_timestamp);
        $next1WDDate = date('Y-m-d', $next1WD);        
        if (!in_array($next1WDDate, $holiday_dates_ymd)) {
            $business_day_count++;
        }
        $current_timestamp = $next1WD;
    }
    return date('Y-m-d', $current_timestamp);
}
function getFutureBusinessDay($num_business_days, $today_ymd = null, $holiday_dates_ymd = []) {
    $num_business_days = min($num_business_days, 1000);
    $business_day_count = 0;
    $current_timestamp = empty($today_ymd) ? time() : strtotime($today_ymd);
    while ($business_day_count < $num_business_days) {
        $next1WD = strtotime('+1 day', $current_timestamp);
        $next1WDDate = date('Y-m-d', $next1WD);        
        if (!in_array($next1WDDate, $holiday_dates_ymd)) {
            $business_day_count++;
        }
        $current_timestamp = $next1WD;
    }
    return date('Y-m-d', $current_timestamp);
}

?>

gantt_project.php

<?php

global $conn;

$keyMaster= $_SESSION['project_id'];

// unset($_SESSION['project_id']); // Not delete

$trunk1="<link rel=\"stylesheet\" type=\"text/css\" href=\"jsgantt/jsgantt.css\" />
<script language=\"javascript\" src=\"jsgantt/jsgantt.js\"></script>
<div style=\"position:relative\" class=\"gantt\" id=\"GanttChartDIV\"></div>
<script>";

if ($_SESSION['language'] == "Spanish") {
$trunk1 .= "
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'week');
if (g.getDivId() != null) {
  g.setCaptionType('Complete');  // Set to Show Caption (None,Caption,Resource,Duration,Complete)
  g.setQuarterColWidth(36);
	g.setShowDur(0); // Show/Hide Duration (0/1)
	g.setShowComp(0); // Show/Hide % Compl. (0/1)
	g.setDateInputFormat('yyyy-mm-dd');  // Set format of input dates ('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy-mm-dd')
	g.setDateTaskTableDisplayFormat('dd/mm/yyyy'); // Set format to display dates ('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy-mm-dd')
  g.setDateTaskDisplayFormat('day dd month yyyy'); // Shown in tool tip box
  g.setDayMajorDateDisplayFormat('mon yyyy - Week ww') // Set format to display dates in the \"Major\" header of the \"Day\" view
  g.setWeekMinorDateDisplayFormat('dd mon') // Set format to display dates in the \"Minor\" header of the \"Week\" view
  g.setShowTaskInfoLink(1); // Show link in tool tip (0/1)
  g.setShowEndWeekDate(0); // Show/Hide the date for the last day of the week in header for daily view (1/0)
  g.setUseSingleCell(10000); // Set the threshold at which we will only use one cell per table row (0 disables).  Helps with rendering performance for large charts.
  g.setFormatArr('Day', 'Week', 'Month', 'Quarter'); // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers

// Language
  g.addLang('es',{
'january': 'Enero',
'february': 'Febrero',
'march': 'Marzo',
'april': 'Abril',
'maylong': 'Mayo',
'june': 'Junio',
'july': 'Julio',
'august': 'Agosto',
'september': 'Septiembre',
'october':'Octubre',
'november':'Noviembre',
'december':'Diciembre',
'jan':'Ene',
'feb':'Feb',
'mar':'Mar',
'apr':'Abr',
'may':'May',
'jun':'Jun',
'jul':'Jul',
'aug':'Ago',
'sep':'Sep',
'oct':'Oct',
'nov':'Nov',
'dec':'Dic',

'sunday':'Domingo',
'monday':'Lunes',
'tuesday':'Martes',
'wednesday':'Miércoles',
'thursday':'Jueves',
'friday':'Viernes',
'saturday':'Sábado',
'sun':'	Dom',
'mon':'	Lun',
'tue':'	Mar',
'wed':'	Mie',
'thu':'	Jue',
'fri':'	Vie',
'sat':'	Sab',
'resource':'Recurso',
'duration':'Duración',
'comp':'% Compl.',
'completion':'Terminado',
'startdate':'Inicio',
'enddate':'Fin',
'moreinfo':'+información',
'notes':'Notas',

'format':'Formato',
'hour':'Hora',
'day':'Dia',
'week':'Semana',
'month':'Mes',
'quarter':'Trimestre',
'hours':'Horas',
'days':'Días',
'weeks':'Semanas',
'months':'Meses',
'quarters':'Trimestres',
'hr':'Hr',
'dy':'D',
'wk':'Sem',
'mth':'Mes',
'qtr':'Trim',
'hrs':'Hrs',
'dys':'Dias',
'wks':'Sems',
'mths':'Meses',
'qtrs':'Trims'});

  g.setLang('es');
  // Parameters(pID, pName,pStart,pEnd,pStyle,pLink (unused), pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption, pNotes, pGantt)
";
} else {
$trunk1 .= "
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'week');
if (g.getDivId() != null) {
  g.setCaptionType('Complete');  // Set to Show Caption (None,Caption,Resource,Duration,Complete)
  g.setQuarterColWidth(36);
	g.setShowDur(0); // Show/Hide Duration (0/1)
	g.setShowComp(0); // Show/Hide % Compl. (0/1)
	g.setDateInputFormat('yyyy-mm-dd');  // Set format of input dates ('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy-mm-dd')
	g.setDateTaskTableDisplayFormat('mm.dd.yyyy'); // Set format to display dates ('mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy-mm-dd')

  // g.setDateTaskDisplayFormat('day dd month yyyy'); // Shown in tool tip box
  // g.setDayMajorDateDisplayFormat('yyyy mon- Week ww') // Set format to display dates in the \"Major\" header of the \"Day\" view
  // g.setWeekMinorDateDisplayFormat('mon dd') // Set format to display dates in the \"Minor\" header of the \"Week\" view
  g.setShowTaskInfoLink(1); // Show link in tool tip (0/1)
  g.setShowEndWeekDate(0); // Show/Hide the date for the last day of the week in header for daily view (1/0)
  g.setUseSingleCell(10000); // Set the threshold at which we will only use one cell per table row (0 disables).  Helps with rendering performance for large charts.
  g.setFormatArr('Day', 'Week', 'Month', 'Quarter'); // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers
  // Parameters(pID, pName,pStart,pEnd,pStyle,pLink (unused), pMile, pRes, pComp, pGroup, pParent, pOpen, pDepend, pCaption, pNotes, pGantt)
";
		}
;

$trunk2='';
// Read Task the Project
$sql="SELECT
t.`task_id`,
t.`name`,
DATE_FORMAT(t.`start_date`,'%Y-%m-%d') `start_date`,
DATE_FORMAT(t.`end_date`,'%Y-%m-%d') `end_date`,
t.`dynamic`,
t.`related_url`,
t.`milestone`,
t.`percent_complete`,
ifnull(t.`parent`,0) parent,
ifnull(t.`dependent`,0) dependent,
t.`description`,
ifnull(u.login,'') creator
FROM tasks t
inner join users u on (t.creator=u.user_id)
WHERE t.`projects_project_id` = $keyMaster
ORDER BY t.`order`";
$resql = db_query($sql,$conn);

 while ($row = db_fetch_array($resql)) {
		$task_id = $row['task_id'];
		$task_name = addslashes($row['name']);
		$task_start_date = $row['start_date'];
		$task_end_date = $row['end_date'];
		$task_color = 'gtaskblue';
		if ($row['dynamic'] == 1){
				$task_color = 'ggroupblack';
				}
		$task_link = $row['related_url'];
		$task_mile = $row['milestone'];
		$task_resource = $row['creator'];
		$task_completion= $row['percent_complete'];
		$task_group = $row['dynamic'];
		$task_parent = $row['parent'];
		$task_open = '0';
		$task_depend = $row['dependent'];
		$task_caption = '';
		$task_description = $row['description'];
		$task_note = addslashes(
						str_replace(hex2bin('0a'),'',
						str_replace(hex2bin('0d'),'',
						str_replace(PHP_EOL,'<br />',$task_description)
						)
						)
						);

		$trunk2.="g.AddTaskItem(new JSGantt.TaskItem(
		";
		$trunk2.="$task_id,'$task_name','$task_start_date','$task_end_date','$task_color','$task_link',$task_mile,'$task_resource',$task_completion,$task_group,$task_parent,$task_open,'$task_depend','$task_caption','$task_note',g));
		";
    }
$trunk3="
    g.Draw();	
    g.DrawDependencies();


  }
  else
  {
    alert(\"not defined\");
  }

</script>";
$value=$trunk1.$trunk2.$trunk3;
echo $value;

?>

log_task.php

<?php
global $conn;

// Key of Project ans Task in List
$key_task = $values['tasks_task_id'];

$sql_0="
select projects_project_id from tasks
where task_id = $key_task
";
$resql_0 = db_query($sql_0,$conn);
$row_0 = db_fetch_array($resql_0);
$key_project = $row_0['projects_project_id'];
$_SESSION['project_id']= $key_project;

// datum of task LOG
$task_hours = $values['hours'];
$task_percent_complete = $values['percent_complete'];
$task_end_date = $values['task_end_date'];

// Calc hours worked
$sql_0="
select sum(hours) hours from task_log
where tasks_task_id = $key_task
";
$resql_0 = db_query($sql_0,$conn);
$row_0 = db_fetch_array($resql_0);
$task_hours = $row_0['hours'];

// Calc Task  and Project  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

if ($task_percent_complete == 100) {
		$task_end = ", actual_end_date = '$task_end_date'";
		} else {
		$task_end = "";
		}


$sql_1="
update tasks
set
percent_complete=$task_percent_complete,
hours_worked= $task_hours
$task_end
where `task_id` = $key_task
";
db_query($sql_1,$conn);

// Update Tasks dependence if close task
$update_dates_by_dependent = $_SESSION['Config'][array_search('update_dates_by_dependent', array_column($_SESSION['Config'], 'name'))][value];

if ($update_dates_by_dependent == 'yes' && $task_percent_complete == 100) {
		
		$sql_1="
SELECT `task_id`, `start_date`, `duration`, `duration_type`, `end_date` FROM tasks
where dependent = $key_task
";
		$resql_1 = db_query($sql_1,$conn);
		while ($values = db_fetch_array($resql_1)) {
		$values['start_date'] = $task_end_date.' '; // New date of end Task
		
		require_once ('ICM/date_settings.php'); // Calc dates of Task
		
		$t_start_date = $values['start_date'];
		$t_end_date = $values['end_date'];
		$t_task_id =  $values['task_id'];

		$sql_2="
		update tasks
		set
		start_date = '$t_start_date',
		end_date = '$t_end_date'
		where task_id = $t_task_id
		";
		db_query($sql_2,$conn);
}

}

require_once ("ICM/calc_task_dymanic_and_project.php");

?>
order_task.php

<?php
global $conn;

// Key of Project in List
$key = $_SESSION['project_id'];


// Control if actualize field  of tasks of project

$Updated=0;
if ( $Updated == 0 ) {

// Create tables auxiliares of order
$sql = "SELECT task_id,if (parent IS NULL,'',parent) parent , name, projects_project_id, start_date, end_date
FROM tasks Where projects_project_id = $key order by start_date";

$results = array(); // All Tasks of Project

if ($resql = db_query($sql,$conn)) {
    /* obtener array asociativo */
    while ($row = db_fetch_array($resql)) {
        $results[] = $row;
    }
}

// Create ARRAY with  Hierarchy of tasks
function convertToHierarchy($results, $idField='task_id', $parentIdField='parent', $childrenField='children') {
	$hierarchy = array(); // -- Stores the final data
	$itemReferences = array(); // -- temporary array, storing references to all items in a single-dimention

	foreach ( $results as $item ) {
		$id       = $item[$idField];
		$parentId = $item[$parentIdField];
		if (isset($itemReferences[$parentId])) { // parent exists
			$itemReferences[$parentId][$childrenField][$id] = $item; // assign item to parent
			$itemReferences[$id] =& $itemReferences[$parentId][$childrenField][$id]; // reference parent's item in single-dimentional array
		} elseif (!$parentId || !isset($hierarchy[$parentId])) { // -- parent Id empty or does not exist. Add it to the root
			$hierarchy[$id] = $item;
			$itemReferences[$id] =& $hierarchy[$id];
		}
	}
	unset($results, $item, $id, $parentId);
	// -- Run through the root one more time. If any child got added before it's parent, fix it.
	foreach ( $hierarchy as $id => &$item ) {
		$parentId = $item[$parentIdField];

		if ( isset($itemReferences[$parentId] ) ) { // -- parent DOES exist
			$itemReferences[$parentId][$childrenField][$id] = $item; // -- assign it to the parent's list of children
			unset($hierarchy[$id]); // -- remove it from the root of the hierarchy
		}
	}
	unset($itemReferences, $id, $item, $parentId);
	return $hierarchy;
}
// Transforma el ARRAY de la jerarquía en un ARRAY plano y añade información de Orden, Nivel y si es Registro Maestro ( tiene registros dependientes)
function makeOneLevelArray($in, &$out, $level, &$num_record) {
    foreach ($in as $v) {
        $num_record++;
        $out[$num_record] = $v;
        $out[$num_record]['order'] = $num_record;
        $out[$num_record]['level'] = $level;
        if (!empty($v['children'])) {
            $out[$num_record]['Maestro'] = 1;
            unset($out[$num_record]['children']); //  delete children of array of out
            makeOneLevelArray($v['children'], $out, $level+1, $num_record );
        } else {
           $out[$num_record]['Maestro'] = 0; 
        }       
    }
}

$Hierarchy = convertToHierarchy($results);

$taskArray = array();
$level=1;
$num_record = 0;
makeOneLevelArray($Hierarchy, $taskArray, $level, $num_record);

$sql = "update tasks set `level`= ? , `order` = ?, `dynamic` = ? where task_id = ?";
$stmt = $conn->prepare($sql);

foreach ($taskArray as $v) {
	$task_id = $v['task_id'];
	$task_orden = $v['order'];
	$task_level = $v['level'];
	$task_dynamic = $v['Maestro'];
  $stmt->bind_param('sssd',$task_level, $task_orden, $task_dynamic, $task_id);
  $stmt->execute();
}
 require_once ("ICM/calc_task_dymanic_and_project.php");
}
?>

set_user_group.php

<?php
global $conn;

$user_id=$_SESSION['user_id']; 
$login = $_SESSION['login'];

//
$sql_1 = "
SELECT GroupID FROM project_ugmembers
WHERE UserName = '$login'
order by 1 asc";
$resql_1 = db_query($sql_1,$conn);
$_SESSION['id_group']=0;
if ($row_1 = db_fetch_array($resql_1)) {
		$_SESSION['id_group']= $row_1['GroupID'];
}

?>