This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "FusionForge".

The branch, master has been updated
       via  21286d8f829554478d46b742b96309e1941159a7 (commit)
      from  ac371f17ed04e2733552a2da780295ab631edf8a (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
https://scm.fusionforge.org/anonscm/gitweb/?p=fusionforge/fusionforge.git;a=commitdiff;h=21286d8f829554478d46b742b96309e1941159a7

commit 21286d8f829554478d46b742b96309e1941159a7
Author: Stéphane-Eymeric Bredthauer <[email protected]>
Date:   Wed Jan 11 01:33:37 2017 +0100

    Tracker: Extra field type Effort

diff --git a/src/common/include/Group.class.php 
b/src/common/include/Group.class.php
index 6b3e3c6..7bac849 100644
--- a/src/common/include/Group.class.php
+++ b/src/common/include/Group.class.php
@@ -8,6 +8,7 @@
  * Copyright 2010-2012, Alain Peyrat - Alcatel-Lucent
  * Copyright 2012-2016, Franck Villaume - TrivialDev
  * Copyright 2013, French Ministry of National Education
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivilDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -3128,6 +3129,34 @@ if there is anything we can do to help you.
                        return true;
                }
        }
+
+       /**
+        *
+        * @param       integer $unit_set_id    the effort unit set id
+        * @return      bool
+        */
+       function setEffortUnitSet($unit_set_id) {
+               db_begin();
+               $res = db_query_params ('UPDATE groups SET unit_set_id=$1 WHERE 
group_id=$2',
+                               array($unit_set_id, $this->getID()));
+               if ($res) {
+                       $this->data_array['unit_set_id']=$unit_set_id;
+                       db_commit();
+                       return true;
+               } else {
+                       db_rollback();
+                       return false;
+               }
+       }
+
+       /**
+        * getEffortUnitSet - Get the effort unit set id.
+        *
+        * @return      integer The id of the effort unit set.
+        */
+       function getEffortUnitSet() {
+               return $this->data_array['unit_set_id'];
+       }
 }
 
 /**
diff --git a/src/common/tracker/ArtifactExtraField.class.php 
b/src/common/tracker/ArtifactExtraField.class.php
index 817c1a7..0532502 100644
--- a/src/common/tracker/ArtifactExtraField.class.php
+++ b/src/common/tracker/ArtifactExtraField.class.php
@@ -5,7 +5,7 @@
  * Copyright 2004, Anthony J. Pugliese
  * Copyright 2009, Roland Mas
  * Copyright 2014, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  *
  * This file is part of FusionForge. FusionForge is free software;
  * you can redistribute it and/or modify it under the terms of the
@@ -49,6 +49,7 @@ define('ARTIFACT_EXTRAFIELDTYPE_MULTIRELEASE',17);
 define('ARTIFACT_EXTRAFIELDTYPE_DATE',18);
 define('ARTIFACT_EXTRAFIELDTYPE_DATETIMERANGE', 19);
 define('ARTIFACT_EXTRAFIELDTYPE_DATERANGE', 20);
+define('ARTIFACT_EXTRAFIELDTYPE_EFFORT',21);
 
 define ("ARTIFACT_EXTRAFIELDTYPEGROUP_SINGLECHOICE", serialize (array 
(ARTIFACT_EXTRAFIELDTYPE_SELECT, ARTIFACT_EXTRAFIELDTYPE_RADIO, 
ARTIFACT_EXTRAFIELDTYPE_STATUS)));
 define ("ARTIFACT_EXTRAFIELDTYPEGROUP_MULTICHOICE", serialize (array 
(ARTIFACT_EXTRAFIELDTYPE_CHECKBOX, ARTIFACT_EXTRAFIELDTYPE_MULTISELECT)));
@@ -754,7 +755,8 @@ class ArtifactExtraField extends FFError {
                        ARTIFACT_EXTRAFIELDTYPE_INTEGER => _('Integer'),
                        ARTIFACT_EXTRAFIELDTYPE_DATETIME => _('Datetime'),
                        ARTIFACT_EXTRAFIELDTYPE_USER => _('User'),
-                       ARTIFACT_EXTRAFIELDTYPE_RELEASE => _('Release')
+                       ARTIFACT_EXTRAFIELDTYPE_RELEASE => _('Release'),
+                       ARTIFACT_EXTRAFIELDTYPE_EFFORT => _('Effort')
                        );
        }
 
diff --git a/src/common/tracker/ArtifactType.class.php 
b/src/common/tracker/ArtifactType.class.php
index da8156f..8c786f3 100644
--- a/src/common/tracker/ArtifactType.class.php
+++ b/src/common/tracker/ArtifactType.class.php
@@ -8,7 +8,7 @@
  * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
  * Copyright 2012, Thorsten “mirabilos” Glaser <[email protected]>
  * Copyright 2014,2016, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  *
  * This file is part of FusionForge. FusionForge is free software;
  * you can redistribute it and/or modify it under the terms of the
@@ -1382,6 +1382,35 @@ class ArtifactType extends FFError {
                }
                return $this->voters;
        }
+
+
+       /**
+        *
+        * @param       integer $unit_set_id    the effort unit set id
+        * @return      bool
+        */
+       function setEffortUnitSet($unit_set_id) {
+               db_begin();
+               $res = db_query_params ('UPDATE artifact_group_list SET 
unit_set_id=$1 WHERE group_artifact_id=$2',
+                               array($unit_set_id, $this->getID()));
+               if ($res) {
+                       $this->data_array['unit_set_id']=$unit_set_id;
+                       db_commit();
+                       return true;
+               } else {
+                       db_rollback();
+                       return false;
+               }
+       }
+
+       /**
+        * getEffortUnitSet - Get the effort unit set id.
+        *
+        * @return      integer The id of the effort unit set.
+        */
+       function getEffortUnitSet() {
+               return $this->data_array['unit_set_id'];
+       }
 }
 
 // Local Variables:
diff --git a/src/common/tracker/EffortUnit.class.php 
b/src/common/tracker/EffortUnit.class.php
new file mode 100644
index 0000000..32382b1
--- /dev/null
+++ b/src/common/tracker/EffortUnit.class.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * FusionForge Effort Units
+ *
+ * Copyright 2016, Stéphane-Eymeric Bredthauer
+ * http://fusionforge.org
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+require_once $gfcommon.'include/FFError.class.php';
+require_once $gfcommon.'tracker/EffortUnitSet.class.php';
+
+class EffortUnit extends FFError {
+
+
+       var $EffortUnitSet;
+       /**
+        * Associative array of data from db.
+        *
+        * @var array   $data_array.
+        */
+       var $data_array;
+
+       var $level;
+       /**
+        * EffortUnit - EffortUnit object constructor
+        *
+        * @param       int|bool        $id     Required - Id of the Effort 
Unit you want to instantiate.
+        * @param       int|bool        $res    Database result from select 
query OR associative array of all columns.
+        */
+       function __construct(&$EffortUnitSet, $id = false, $res = false) {
+               parent::__construct();
+
+               $this->EffortUnitSet = &$EffortUnitSet;
+
+               if (!$id) {
+                       //setting up an empty object
+                       //probably going to call create()
+                       return;
+               }
+               if (!$res) {
+                       if (!$this->fetchData($id)) {
+                               return;
+                       }
+               } else {
+                       //
+                       //      Assoc array was passed in
+                       //
+                       if (is_array($res)) {
+                               $this->data_array =& $res;
+                       } else {
+                               if (db_numrows($res) < 1) {
+                                       //function in class we extended
+                                       $this->setError(_('Effort Unit Not 
Found'));
+                                       $this->data_array=array();
+                                       return;
+                               } else {
+                                       //set up an associative array for use 
by other functions
+                                       $this->data_array = 
db_fetch_array_by_row($res, 0);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * fetchData - May need to refresh database fields if an update 
occurred.
+        *
+        * @param       int     $unit_id The unit_id.
+        * @return      bool    success or not
+        */
+       function fetchData($unit_id) {
+               $res = db_query_params ('SELECT * FROM effort_unit WHERE 
unit_id=$1',
+                               array($unit_id));
+               if (!$res || db_numrows($res) < 1) {
+                       $this->setError(sprintf('fetchData(): %s', db_error()));
+                       return false;
+               }
+               $this->data_array = db_fetch_array($res);
+               return true;
+       }
+
+       /**
+        * create - Create new Effort Unit in the database.
+        *
+        * @param       string  $name                           The name of the 
unit.
+        * @param       integer $conversion_factor      The conversion factor 
to define the current unit.
+        * @param       integer $to_unit                        The unity used 
for the definition of the current unit.
+        * @param       integre $unit_position          The position of the 
unit when listed.
+        * @param       boolean $is_base_unit           True if this unit is a 
base unit.
+        * @param       array   $importData                     For import
+        * @return      bool    success or not
+        */
+       function create($name, $conversion_factor, $to_unit, $unit_position = 
false, $is_base_unit = false, $importData = array()) {
+               db_begin();
+               if(array_key_exists('user', $importData)){
+                       $user = $importData['user'];
+               } else {
+                       $user = ((session_loggedin()) ? user_getid() : 100);
+               }
+               if (array_key_exists('time',$importData)){
+                       $time = $importData['time'];
+               } else {
+                       $time = time();
+               }
+               if ($is_base_unit) {
+                       $to_unit = 1;
+               }
+               $res = db_query_params('INSERT INTO effort_unit(unit_set_id, 
unit_name, conversion_factor, to_unit, unit_position, is_base_unit, 
created_date, created_by, modified_date, modified_by) VALUES($1, $2, $3, $4, 
$5, $6, $7, $8, $9, $10)',
+                               array($this->EffortUnitSet->GetID(), $name, 
$conversion_factor, $to_unit, $unit_position, ($is_base_unit?1:0), $time, 
$user, $time, $user));
+               if (!$res || db_affected_rows($res) < 1) {
+                       $this->setError(_('Error')._(':').' '._('Cannot create 
Effort Unit')._(':').' '.db_error());
+                       db_rollback();
+                       return false;
+               }
+               $id = db_insertid($res, 'effort_unit', 'unit_id');
+               if (!$id) {
+                       $this->setError(_('Error')._(':').' '._('Cannot get 
Effort Unit id')._(':').' '.db_error());
+                       db_rollback();
+                       return false;
+               }
+               if (!$this->fetchData($id)) {
+                       db_rollback();
+                       return false;
+               }
+               if ($is_base_unit) {
+                       $res = db_query_params('UPDATE effort_unit SET to_unit 
= $1 WHERE unit_id = $2',
+                                       array($id, $id));
+                       if (!$res || db_affected_rows($res) < 1) {
+                               $this->setError(_('Error')._(':').' '._('Cannot 
create Effort Unit')._(':').' '.db_error());
+                               db_rollback();
+                               return false;
+                       }
+               }
+               db_commit();
+               return $id;
+       }
+
+       /**
+        * getID - get this Effort Unit ID.
+        *
+        * @return      integer The unit_id.
+        */
+       function getID() {
+               return $this->data_array['unit_id'];
+       }
+
+       /**
+        * getName - get this Effort Unit name.
+        *
+        * @return      string  The name.
+        */
+       function getName() {
+               return $this->data_array['unit_name'];
+       }
+
+       /**
+        * getBase - get this Effort Unit base ID.
+        *
+        * @return      integer unit base ID.
+        */
+       function getToUnit() {
+               return $this->data_array['to_unit'];
+       }
+
+       function getToUnitName() {
+               if (!empty($this->data_array['to_unit'])) {
+                       $toUnit = new EffortUnit($this->EffortUnitSet, 
$this->data_array['to_unit']);
+                       return $toUnit->getName();
+               } else {
+                       return '';
+               }
+       }
+       /**
+        * getMultiplier - get this unit base multiplier.
+        *
+        * @return      float   unit base multiplier.
+        */
+       function getConversionFactor() {
+               return $this->data_array['conversion_factor'];
+       }
+
+       /**
+        * getPosition - get this Effort Unit display position.
+        *
+        * @return      integer unit display position.
+        */
+       function getPosition() {
+               return $this->data_array['unit_position'];
+       }
+
+       function &getEffortUnitSet(){
+               return $this->EffortUnitSet;
+       }
+
+       /**
+        * isDeleted - this Effort Unit is or not deleted.
+        *
+        * @return      boolean is or not deleted.
+        */
+       function isDeleted()  {
+               return ($this->data_array['is_deleted']?true:false);
+       }
+
+       function isBaseUnit() {
+               return ($this->data_array['is_base_unit']?true:false);
+       }
+
+       function delete($importData = array()) {
+               db_begin();
+               if(array_key_exists('user', $importData)){
+                       $user = $importData['user'];
+               } else {
+                       $user = ((session_loggedin()) ? user_getid() : 100);
+               }
+               if (array_key_exists('time',$importData)){
+                       $time = $importData['time'];
+               } else {
+                       $time = time();
+               }
+               $res = db_query_params ('UPDATE effort_unit SET is_deleted=1, 
modified_date=$1, modified_by=$2 WHERE unit_id=$3',
+                               array ($time, $user, $this->getID())) ;
+               if (!$res) {
+                       $this->setError(_('Error deleting Effort Unit')._(': 
').db_error());
+                       db_rollback();
+                       return false;
+               }
+               $data_array['is_deleted'] = 1;
+               db_commit();
+               return true;
+       }
+
+       function update($name, $conversion_factor, $to_unit, $importData = 
array()){
+               db_begin();
+               if(array_key_exists('user', $importData)){
+                       $user = $importData['user'];
+               } else {
+                       $user = ((session_loggedin()) ? user_getid() : 100);
+               }
+               if (array_key_exists('time',$importData)){
+                       $time = $importData['time'];
+               } else {
+                       $time = time();
+               }
+               $res = db_query_params('UPDATE effort_unit SET unit_name=$1, 
conversion_factor=$2, to_unit=$3, modified_date=$4, modified_by=$5 WHERE 
unit_id=$6',
+                               array($name, $conversion_factor, $to_unit, 
$time, $user, $this->getID()));
+               if (!$res || db_affected_rows($res) < 1) {
+                       $this->setError(_('Error')._(':').' '._('Cannot update 
Effort Unit')._(':').' '.db_error());
+                       db_rollback();
+                       return false;
+               }
+
+               if (!$this->fetchData($this->getID())) {
+                       db_rollback();
+                       return false;
+               }
+               db_commit();
+               return true;
+
+       }
+
+       function updatePosition($unit_position) {
+
+               $result=db_query_params ('UPDATE effort_unit SET unit_position= 
$1 WHERE unit_id=$2',
+                               array($unit_position, $this->getID()));
+               if ($result && db_affected_rows($result) > 0) {
+                       return true;
+               }
+               else {
+                       $this->setError(db_error());
+                       return false;
+               }
+       }
+
+       function reorderUnits($new_position) {
+               $unitFactory = new EffortUnitFactory($this->EffortUnitSet);
+               $unitsData = $unitFactory->getUnitsData();
+               $max = count($unitsData);
+               if ($new_position < 1 || $new_position > $max) {
+                       $this->setError(_('Out of range value'));
+                       return false;
+               }
+               $pos = 1;
+               $data = array();
+               for ($i = 0; $i < $max; $i++) {
+                       if ($pos == $new_position) {
+                               $data[$pos] = $this->getID();
+                               $pos++;
+                       }
+                       if ($unitsData[$i]['unit_id'] != $this->getID()) {
+                               $data[$pos] = $unitsData[$i]['unit_id'];
+                               $pos++;
+                       }
+               }
+               if ($pos == $new_position) {
+                       $data[$pos] = $this->getID();
+                       $pos++;
+               }
+               for ($pos = 1; $pos <= count($data); $pos++) {
+                       $unit = new 
EffortUnit($this->EffortUnitSet,$data[$pos]);
+                       if (! $unit->updatePosition($pos))
+                               return false;
+               }
+               return true;
+       }
+
+       function copy($from_unit) {
+               $name = $from_unit->getName();
+               $conversion_factor = $from_unit->getConversionFactor();
+               $position = $from_unit->getPosition();
+               $is_base_unit = $from_unit->isBaseUnit();
+               if (!$is_base_unit) {
+                       $to_unit_name = $from_unit->getToUnitName();
+                       $effortUnitFactory = new 
EffortUnitFactory($this->EffortUnitSet);
+                       $to_unit_id = 
$effortUnitFactory->getUnitByName($to_unit_name)->getID();
+               } else {
+                       $to_unit_id = 1;
+               }
+               if (!$this->create($name, $conversion_factor, $to_unit_id, 
$position, $is_base_unit)) {
+               }
+       }
+}
diff --git a/src/common/tracker/EffortUnitFactory.class.php 
b/src/common/tracker/EffortUnitFactory.class.php
new file mode 100644
index 0000000..e1757c6
--- /dev/null
+++ b/src/common/tracker/EffortUnitFactory.class.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * FusionForge Effort Unit Factory
+ *
+ * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+require_once $gfcommon.'include/FFError.class.php';
+require_once $gfcommon.'tracker/EffortUnitSet.class.php';
+require_once $gfcommon.'tracker/EffortUnit.class.php';
+
+class EffortUnitFactory extends FFError {
+
+       /**
+        * The Effort Unit Set.
+        *
+        * @var  object Effort Unit Set.
+        */
+       var $EffortUnitSet;
+
+       /**
+        * The Effort Units array.
+        *
+        * @var  array  Effort Units.
+        */
+       var $Units;
+
+       var $UnitsData;
+       /**
+        * @param       Object  $EffortUnitSet  The Effort Unit Set object to 
which this EffortUnitFactory is associated.
+        */
+       function __construct(&$EffortUnitSet) {
+               parent::__construct();
+               if (!$EffortUnitSet || !is_object($EffortUnitSet)) {
+                       $this->setError('EffortUnitFactory: No Valid 
EffortUnitSet Object');
+                       return;
+               }
+               if ($EffortUnitSet->isError()) {
+                       $this->setError('EffortUnitFactory: 
'.$EffortUnitSet->getErrorMessage());
+                       return;
+               }
+               $this->EffortUnitSet =& $EffortUnitSet;
+       }
+
+       /**
+        * getUnitsArr - return a array of Units data.
+        *
+        * @return      array   The array of Units data.
+        */
+       function getUnitsData() {
+               if ($this->UnitsData) {
+                       return $this->UnitsData;
+               }
+               $this->UnitsData = array ();
+               $res = db_query_params ('SELECT * FROM effort_unit WHERE 
unit_set_id=$1 AND is_deleted=0 ORDER BY unit_position ASC',
+                               array ($this->EffortUnitSet->getID()));
+               if (!$res) {
+                       $this->setError(db_error());
+                       return false;
+               }
+               while ($arr = db_fetch_array($res)) {
+                       $this->UnitsData[] = $arr;
+               }
+               return $this->UnitsData;
+       }
+
+       /**
+        * getUnits - return an array of Effort Unit objects.
+        *
+        * @return      array   The array of Effort Unit objects.
+        */
+       function getUnits() {
+               if ($this->Units) {
+                       return $this->Units;
+               }
+               $this->Units = array ();
+               $unitsArr = $this->getUnitsData();
+               if (!$unitsArr && $this->isError()) {
+                       $this->setError($this->getErrorMessage());
+                       return false;
+               }
+               foreach ($unitsArr as $unitArr) {
+                       $unit = new EffortUnit($this->EffortUnitSet, true, 
$unitArr);
+                       if (!$unit && $unit->isError()) {
+                               $this->setError($unit->getErrorMessage());
+                               return false;
+                       } else {
+                               $this->Units[] = $unit;
+                       }
+               }
+               return $this->Units;
+       }
+
+       /**
+        * getUnitsArr - return an associative array of Units name & id.
+        *
+        * @return      array   The associative array of Units name & id.
+        */
+       function getUnitsArr() {
+               $result = array ();
+               $unitsArr = $this->getUnitsData();
+               if (!$unitsArr && $this->isError()) {
+                       $this->setError($this->getErrorMessage());
+                       return false;
+               }
+               foreach ($unitsArr as $unitArr) {
+                       $result[$unitArr['unit_id']]=$unitArr['unit_name'];
+               }
+               return $result ;
+       }
+
+       function getBaseUnit() {
+               $units = $this->getUnits();
+               foreach($units as $unit) {
+                       if ($unit->isBaseUnit()) {
+                               return $unit;
+                       }
+               }
+               return false;
+       }
+
+       function getUnitByName($name) {
+               $units = $this->getUnits();
+               foreach($units as $unit) {
+                       if ($unit->getName()==$name) {
+                               return $unit;
+                       }
+               }
+               return false;
+       }
+}
diff --git a/src/common/tracker/EffortUnitSet.class.php 
b/src/common/tracker/EffortUnitSet.class.php
new file mode 100644
index 0000000..47d3b53
--- /dev/null
+++ b/src/common/tracker/EffortUnitSet.class.php
@@ -0,0 +1,339 @@
+<?php
+/**
+ * FusionForge Effort Unit Set
+ *
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+require_once $gfcommon.'include/FFError.class.php';
+require_once $gfcommon.'include/Group.class.php';
+
+define('EFFORTUNITSET_FORGE_LEVEL', 1);
+define('EFFORTUNITSET_PROJECT_LEVEL', 2);
+define('EFFORTUNITSET_TRACKER_LEVEL', 3);
+
+class EffortUnitSet extends FFError {
+       var $Object;
+       /**
+        * The Group object.
+        *
+        * @var  object  $Group.
+        */
+       var $Group;
+
+       /**
+        * Artifact Type object.
+        *
+        * @var object  $ArtifactType.
+        */
+       var $ArtifactType;
+
+       var $data_array;
+
+       var $objectLevel;
+
+       /**
+        * @param       Object  $Object Null, group, tracker object to which 
this EffortUnitSet is associated
+        */
+       function __construct(&$Object=null, $unit_set_id=false, $arr=false) {
+               parent::__construct();
+               $this->Object = $Object;
+               if (!$Object) {
+                       $class ='';
+               } else {
+                       $class = get_class($Object);
+               }
+               switch ($class) {
+                       case 'ArtifactType':
+                       case 'ArtifactTypeHtml':
+                               if (!$Object || !is_object($Object)) {
+                                       $this->setError(_('Invalid Artifact 
Type'));
+                                       return;
+                               }
+                               if ($Object->isError()) {
+                                       $this->setError(_('Effort Unit 
Set')._(':').' '.$Object->getErrorMessage());
+                                       return;
+                               }
+                               $this->ArtifactType = $Object;
+                               $this->Group = $Object->getGroup();
+                               $this->objectLevel = 
EFFORTUNITSET_TRACKER_LEVEL;
+                               break;
+                       case 'Group':
+                               if (!$Object || !is_object($Object)) {
+                                       $this->setError(_('Invalid Project'));
+                                       return;
+                               }
+                               if ($Object->isError()) {
+                                       $this->setError(_('Effort Unit 
Set')._(':').' '.$Object->getErrorMessage());
+                                       return;
+                               }
+                               $this->ArtifactType = null;
+                               $this->Group = $Object;
+                               $this->objectLevel = 
EFFORTUNITSET_PROJECT_LEVEL;
+                               break;
+                       case '':
+                               $this->ArtifactType = null;
+                               $this->Group = null;
+                               $this->objectLevel = EFFORTUNITSET_FORGE_LEVEL;
+                               break;
+               }
+               if ($unit_set_id) {
+                       if (!$arr || !is_array($arr)) {
+                               if (!$this->fetchData($unit_set_id)) {
+                                       return;
+                               }
+                       } else {
+                               $this->data_array =& $arr;
+                       }
+               }
+               return;
+       }
+
+       function create($importData = array()) {
+               if(array_key_exists('user', $importData)){
+                       $user = $importData['user'];
+               } else {
+                       $user = ((session_loggedin()) ? user_getid() : 100);
+               }
+               if (array_key_exists('time',$importData)){
+                       $time = $importData['time'];
+               } else {
+                       $time = time();
+               }
+               // Only Effort Unit Set of the object level can be created as a 
new one
+               switch ($this->objectLevel){
+                       case EFFORTUNITSET_TRACKER_LEVEL:
+                               $res = db_query_params('SELECT 1 FROM 
effort_unit_set WHERE group_id=$1 AND group_artifact_id=$2',
+                                               array($this->Group->getID(), 
$this->ArtifactType->getID()));
+                               if (!$res) {
+                                       $this->setError(_('Error checking if 
Effort Unit Set already exist')._(':').' '.db_error());
+                                       db_rollback();
+                                       return false;
+                               }
+                               if (db_numrows($res)>0) {
+                                       $this->setError(sprintf(_('Effort Unit 
Set already exist for tracker $s'),$this->ArtifactType->getName()));
+                                       db_rollback();
+                                       return false;
+                               }
+                               $query ='INSERT INTO effort_unit_set(level, 
group_id, group_artifact_id, created_date, created_by) VALUES($1, $2, $3, $4, 
$5)';
+                               $params = array(EFFORTUNITSET_TRACKER_LEVEL, 
$this->Group->getID(), $this->ArtifactType->getID(), $time, $user);
+                               break;
+                       case EFFORTUNITSET_PROJECT_LEVEL:
+                               $res = db_query_params('SELECT 1 FROM 
effort_unit_set WHERE group_id=$1',
+                                               array($this->Group->getID()));
+                               if (!$res) {
+                                       $this->setError(_('Error checking if 
Effort Unit Set already exist')._(':').' '.db_error());
+                                       db_rollback();
+                                       return false;
+                               }
+                               if (db_numrows($res)>0) {
+                                       $this->setError(sprintf(_('Effort Unit 
Set already exist for project $s'),$this->Group->getPublicName()));
+                                       db_rollback();
+                                       return false;
+                               }
+                               $query ='INSERT INTO effort_unit_set(level, 
group_id, created_date, created_by) VALUES($1, $2, $3, $4)';
+                               $params = array(EFFORTUNITSET_PROJECT_LEVEL, 
$this->Group->getID(), $time, $user);
+                               break;
+                       case EFFORTUNITSET_FORGE_LEVEL:
+                       default:
+                               $this->setError(_('Effort Unit Set already 
exist for this forge'));
+                               return false;
+                               break;
+               }
+               db_begin();
+               $res = db_query_params($query,$params);
+               $id = db_insertid($res, 'effort_unit_set', 'unit_set_id');
+
+               if (!$res || !$id) {
+                       $this->setError(_('Effort Unit Set')._(':').' 
'.db_error());
+                       db_rollback();
+                       return false;
+               } elseif (!$this->fetchData($id)) {
+                       db_rollback();
+                       return false;
+               } else {
+                       db_commit();
+                       return $id;
+               }
+       }
+
+       function copy($from_unit_set){
+               $unit_set_id = $this->create();
+               $from_unit_factory = new EffortUnitFactory($from_unit_set);
+               $from_baseUnit = $from_unit_factory->getBaseUnit();
+               //$from_baseUnit_id = $from_baseUnit->getID();
+               if (!$this->_recursive_copy($from_baseUnit)) {
+                       return false;
+               }
+               switch ($this->objectLevel) {
+                       case EFFORTUNITSET_PROJECT_LEVEL:
+                               $this->Group->setEffortUnitSet($this->getID());
+                               break;
+                       case EFFORTUNITSET_TRACKER_LEVEL:
+                               
$this->ArtifactType->setEffortUnitSet($this->getID());
+                               break;
+               }
+               return true;
+       }
+
+       function _recursive_copy($from_unit) {
+               $new_unit = new EffortUnit($this);
+               $new_unit_id = $new_unit->copy($from_unit);
+               $from_unit_set = $from_unit->getEffortUnits();
+               $from_unit_factory = new EffortUnitFactory($from_unit_set);
+               $units = $from_unit_factory->getUnits();
+               foreach ($units as $unit) {
+                       if ($unit->getToUnit()==$from_unit->getID() && 
$unit->getID()!=$from_unit->getID()) {
+                               $this->_recursive_copy($unit);
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * fetchData - May need to refresh database fields if an update 
occurred.
+        *
+        * @param       int     $group_id The group_id.
+        * @return      bool    success or not
+        */
+       function fetchData($unit_set_id) {
+               $res = db_query_params ('SELECT * FROM effort_unit_set WHERE 
unit_set_id=$1',
+                               array($unit_set_id));
+               if (!$res || db_numrows($res) < 1) {
+                       $this->setError(sprintf('fetchData(): %s', db_error()));
+                       return false;
+               }
+               $this->data_array = db_fetch_array($res);
+               return true;
+       }
+
+       /**
+        * getID - Simply return the unit_set_id for this object.
+        *
+        * @return int unit_set_id.
+        */
+       function getID() {
+               return $this->data_array['unit_set_id'];
+       }
+
+       function getLevel() {
+               return $this->data_array['level'];
+       }
+
+       function getObjectLevel() {
+               return $this->objectLevel;
+       }
+
+       /**
+        * getGroup - get the Group object this EffortUnitFactory is associated 
with.
+        *
+        * @return      object  The Group object.
+        */
+       function &getGroup() {
+               return $this->Group;
+       }
+
+       function &getArtifactType() {
+               return $this->ArtifactType;
+       }
+
+       function &getObject() {
+               return $this->Object;
+       }
+}
+
+function getAvailableEffortUnitSets(&$Object=null) {
+       if (!$Object) {
+               $class ='';
+       } else {
+               $class = get_class($Object);
+       }
+       switch ($class) {
+               case 'ArtifactType':
+               case 'ArtifactTypeHtml':
+                       if (!$Object || !is_object($Object)) {
+                               $this->setError(_('Invalid Artifact Type'));
+                               return;
+                       }
+                       if ($Object->isError()) {
+                               $this->setError(_('Effort Unit Set')._(':').' 
'.$Object->getErrorMessage());
+                               return;
+                       }
+                       $atid = $Object->getID();
+                       $at_name = $Object->getName();
+                       $group_id = $Object->getGroup()->getID();
+                       $group_name = $Object->getGroup()->getPublicName();
+                       $objectLevel = EFFORTUNITSET_TRACKER_LEVEL;
+                       break;
+               case 'Group':
+                       if (!$Object || !is_object($Object)) {
+                               $this->setError(_('Invalid Project'));
+                               return;
+                       }
+                       if ($Object->isError()) {
+                               $this->setError(_('Effort Unit Set')._(':').' 
'.$Object->getErrorMessage());
+                               return;
+                       }
+                       $group_id = $Object->getID();
+                       $group_name = $Object->getPublicName();
+                       $objectLevel = EFFORTUNITSET_PROJECT_LEVEL;
+                       break;
+               case '':
+                       $objectLevel = EFFORTUNITSET_FORGE_LEVEL;
+                       break;
+       }
+       $effortUnitSets = array();
+       switch($objectLevel) {
+               case EFFORTUNITSET_TRACKER_LEVEL:
+                       $res = db_query_params ('SELECT unit_set_id FROM 
effort_unit_set WHERE group_id=$1 AND group_artifact_id=$2 AND level=$3',
+                                       array($group_id, $atid, 
EFFORTUNITSET_TRACKER_LEVEL));
+                       if (!$res) {
+                               $this->setError(sprintf(_('Error getting 
Tracker "%s" level Effort Unit Set'),$at_name), db_error());
+                               return false;
+                       }
+                       if (db_numrows($res)>0) {
+                               $row = db_fetch_array($res);
+                               $effortUnitSets[$row['unit_set_id']]= 
sprintf(_('Tracker "%s" level Effort Unit Set'),$at_name);
+                       }
+                       // no break
+               case EFFORTUNITSET_PROJECT_LEVEL:
+                       $res = db_query_params ('SELECT unit_set_id FROM 
effort_unit_set WHERE group_id=$1 AND group_artifact_id IS NULL AND level=$2',
+                                       array($group_id, 
EFFORTUNITSET_PROJECT_LEVEL));
+                       if (!$res) {
+                               $this->setError(sprintf(_('Error getting 
Project "%s" level Effort Unit Set'),$group_name), db_error());
+                               return false;
+                       }
+                       if (db_numrows($res)>0) {
+                               $row = db_fetch_array($res);
+                               $effortUnitSets[$row['unit_set_id']]= 
sprintf(_('Project "%s" level Effort Unit Set'),$group_name);
+                       }
+                       // no break
+               case EFFORTUNITSET_FORGE_LEVEL:
+                       $res = db_query_params ('SELECT unit_set_id FROM 
effort_unit_set WHERE group_id IS NULL AND group_artifact_id IS NULL AND 
level=$1',
+                                       array(EFFORTUNITSET_FORGE_LEVEL));
+                       if (!$res) {
+                               $this->setError(_('Error getting Forge level 
Effort Unit Set'), db_error());
+                               return false;
+                       }
+                       if (db_numrows($res)>0) {
+                               $row = db_fetch_array($res);
+                               $effortUnitSets[$row['unit_set_id']]= _('Forge 
level Effort Unit Set');
+                       }
+       }
+       return $effortUnitSets;
+}
diff --git a/src/common/tracker/actions/admin-ind.php 
b/src/common/tracker/actions/admin-ind.php
index 00549e6..7a094a0 100644
--- a/src/common/tracker/actions/admin-ind.php
+++ b/src/common/tracker/actions/admin-ind.php
@@ -7,7 +7,7 @@
  * Copyright 2010, FusionForge Team
  * Copyright 2011, Franck Villaume - Capgemini
  * Copyright 2012-2016, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  *
  * This file is part of FusionForge. FusionForge is free software;
  * you can redistribute it and/or modify it under the terms of the
@@ -113,6 +113,7 @@ if (!isset($at_arr) || !$at_arr || count($at_arr) < 1) {
        if (!empty($roadmaps)) {
                echo html_e('p', array('id' => 'roadmapadminlink'), 
util_make_link('/tracker/admin/?group_id='.$group_id.'&admin_roadmap=1', 
_('Manage your roadmaps.')));
        }
+       echo html_e('p', array('id' => 'efforunitsadminlink'), 
util_make_link('/tracker/admin/?group_id='.$group_id.'&effort_units=1', 
_('Manage your Effort Units.')));
 }
 
 //
diff --git a/src/common/tracker/actions/admin-tracker.php 
b/src/common/tracker/actions/admin-tracker.php
index 3d53487..ba7a28b 100644
--- a/src/common/tracker/actions/admin-tracker.php
+++ b/src/common/tracker/actions/admin-tracker.php
@@ -4,6 +4,7 @@
  *
  * Copyright 2010, FusionForge Team
  * Copyright 2012, Franck Villaume - TrivialDev
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -44,6 +45,12 @@ echo _('Set up preferences like expiration times, email 
addresses.');
 echo '</p>';
 
 echo '<p>';
+echo 
util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$ath->getID().'&effort_units=1',
 '<strong>'._('Manage Effort Units').'</strong>');
+echo '<br />';
+echo _('Manage Effort Units for Effort custom extra field.');
+echo '</p>';
+
+echo '<p>';
 echo 
util_make_link('/tracker/admin/?group_id='.$group_id.'&atid='.$ath->getID().'&add_extrafield=1',
 '<strong>'._('Manage Custom Fields').'</strong>');
 echo '<br />';
 echo _('Add new boxes like Phases, Quality Metrics, Components, etc.  Once 
added they can be used with other selection boxes (for example, Categories or 
Groups) to describe and browse bugs or other artifact types.');
diff --git a/src/common/tracker/actions/effort_units.php 
b/src/common/tracker/actions/effort_units.php
new file mode 100644
index 0000000..641edb0
--- /dev/null
+++ b/src/common/tracker/actions/effort_units.php
@@ -0,0 +1,541 @@
+<?php
+/**
+ * Project Admin page to edit units
+*
+* Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
+* http://fusionforge.org/
+*
+* This file is part of FusionForge. FusionForge is free software;
+* you can redistribute it and/or modify it under the terms of the
+* GNU General Public License as published by the Free Software
+* Foundation; either version 2 of the Licence, or (at your option)
+* any later version.
+*
+* FusionForge is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with FusionForge; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+require_once $gfcommon.'include/pre.php';
+require_once $gfcommon.'tracker/EffortUnit.class.php';
+require_once $gfcommon.'tracker/EffortUnitSet.class.php';
+require_once $gfcommon.'tracker/EffortUnitFactory.class.php';
+require_once $gfcommon.'tracker/include/ArtifactTypeHtml.class.php';
+require_once $gfcommon.'tracker/include/ArtifactTypeFactoryHtml.class.php';
+
+
+global $HTML, $error_msg, $feedback;
+
+$group_id = getIntFromRequest('group_id',false);
+$atid= getIntFromRequest('atid',false);
+
+$object = null;
+$group = false;
+$ath = false;
+$unitSetId = false;
+
+if (!$atid && !$group_id) {
+       // Forge level
+       session_require_global_perm('forge_admin');
+       $title = _('Effort Units for the Forge');
+       $headerArr = array('title'=>$title);
+       site_admin_header($headerArr);
+       // hardcode
+       $unitSetId = 1;
+       $object = null;
+
+} elseif (!$atid && $group_id) {
+       // Projet level
+       $group = group_get_object($group_id);
+       if (!$group || !is_object($group)) {
+               exit_error(_('Error creating group'), 'admin');
+       } elseif ($group->isError()) {
+               exit_error($group->getErrorMessage(), 'admin');
+       }
+       $perm = $group->getPermission();
+       if (!$perm || !is_object($perm) || !$perm->isArtifactAdmin()) {
+               exit_permission_denied();
+       }
+
+
+       $title = _('Effort Units for project').' '.$group->getPublicName();
+       $headerArr = array('title'=>$title, 'group'=>$group->getID());
+
+       project_admin_header($headerArr);
+       $object = $group;
+       $unitSetId = $group->getEffortUnitSet();
+
+} elseif ($atid && $group_id) {
+       // Tracker level
+       session_require_perm('tracker_admin', $group_id);
+       $group = group_get_object($group_id);
+       if (!$group || !is_object($group)) {
+               exit_error(_('Error creating group'), 'tracker');
+       } elseif ($group->isError()) {
+               exit_error($group->getErrorMessage(), 'tracker');
+       }
+       $ath = new ArtifactTypeHtml($group,$atid);
+       if (!$ath || !is_object($ath)) {
+               exit_error(_('ArtifactType could not be created'),'tracker');
+       }
+       if ($ath->isError()) {
+               exit_error($ath->getErrorMessage(),'tracker');
+       }
+
+       $title = _('Effort Units for tracker').' '.$ath->getName();
+       $headerArr = array('title'=>$title,     'modal'=>1);
+       $ath->adminHeader($headerArr);
+       $unitSetId = $ath->getEffortUnitSet();
+       $object = $ath;
+
+}
+$effortUnitSet = new EffortUnitSet($object, $unitSetId);
+
+switch (getStringFromRequest('function','')) {
+       case 'add':
+               add_unit($effortUnitSet);
+               break;
+       case 'postadd':
+               postadd_unit($effortUnitSet);
+               show_units($effortUnitSet);
+               break;
+       case 'postdelete':
+               postdelete_unit($effortUnitSet);
+               show_units($effortUnitSet);
+               break;
+       case 'delete':
+               delete_unit($effortUnitSet);
+               break;
+       case 'edit':
+               edit_unit($effortUnitSet);
+               break;
+       case 'postedit':
+               postedit_unit($effortUnitSet);
+               show_units($effortUnitSet);
+               break;
+       case 'updownorder':
+               order_unit($effortUnitSet);
+               show_units($effortUnitSet);
+               break;
+       case 'copy_set':
+               copy_set($effortUnitSet);
+               break;
+       case 'postcopy_set':
+               postcopy_set($effortUnitSet);
+               show_units($effortUnitSet);
+               break;
+       default:
+               show_units($effortUnitSet);
+}
+
+if (!$atid && !$group_id) {
+       // FusionForge level
+       site_admin_footer();
+} elseif (!$atid && $group_id) {
+       // Projet level
+       project_admin_footer();
+} elseif ($atid && $group_id) {
+       $ath->footer();
+       // Tracker level
+}
+
+function show_units(&$effortUnitSet){
+       global $HTML;
+
+       $currentURL = getStringFromServer('PHP_SELF');
+       $urlParameters = urlParameters($effortUnitSet);
+       $inputParameters = inputParameters($effortUnitSet);
+
+       switch ($effortUnitSet->getObjectLevel()) {
+               case EFFORTUNITSET_FORGE_LEVEL:
+                       $AvailableEffortUnitSets = getAvailableEffortUnitSets();
+                       $isEditable = true;
+                       break;
+               case EFFORTUNITSET_PROJECT_LEVEL:
+                       echo html_e('h2', array(), _('Effort Unit Set'));
+                       echo sprintf(_('The Project "%s" is 
using'),$effortUnitSet->getGroup()->getPublicName()).' ';
+                       $AvailableEffortUnitSets = 
getAvailableEffortUnitSets($effortUnitSet->getGroup());
+                       switch ($effortUnitSet->getLevel()) {
+                               case EFFORTUNITSET_FORGE_LEVEL:
+                                       $currentSetName = _('Forge level Effort 
Unit Set');
+                                       echo $currentSetName.' 
'.util_make_link('/admin/effortunitsedit.php','('._('Admin').')',true);
+                                       $isEditable = false;
+                                       break;
+                               case EFFORTUNITSET_PROJECT_LEVEL:
+                                       echo sprintf(_('Project "%s" level 
Effort Unit Set'),$effortUnitSet->getGroup()->getPublicName());
+                                               $isEditable = true;
+                                       break;
+                       }
+                       break;
+               case EFFORTUNITSET_TRACKER_LEVEL:
+                       echo html_e('h2', array(), _('Effort Unit Set'));
+                       echo sprintf(_('The Tracker "%s" is 
using'),$effortUnitSet->getArtifactType()->getName()).' ';
+                       $AvailableEffortUnitSets = 
getAvailableEffortUnitSets($effortUnitSet->getArtifactType());
+                       switch ($effortUnitSet->getLevel()) {
+                               case EFFORTUNITSET_FORGE_LEVEL:
+                                       $currentSetName = _('Forge level Effort 
Unit Set');
+                                       echo $currentSetName.' 
'.util_make_link('/admin/effortunitsedit.php','('._('Admin').')',true);
+                                       $isEditable = false;
+                                       break;
+                               case EFFORTUNITSET_PROJECT_LEVEL:
+                                       $currentSetName = sprintf(_('Project 
"%s" level Effort Unit Set'),$effortUnitSet->getGroup()->getPublicName());
+                                       echo $currentSetName.' 
'.util_make_link('/project/admin/effortunits.php?group_id='.$effortUnitSet->getGroup()->getID(),'('._('Admin').')',true);
+                                       $isEditable = false;
+                                       break;
+                               case EFFORTUNITSET_TRACKER_LEVEL:
+                                       echo sprintf(_('Tracker "%s" level 
Effort Unit Set'),$effortUnitSet->getArtifactType()->getName());
+                                       $isEditable = true;
+                                       break;
+                       }
+                       break;
+       }
+
+       // TODO: chose an other set
+
+       //copy current set to the current level
+       if (!$isEditable) {
+               echo $HTML->openForm(array('action' => $currentURL, 'method' => 
'get'));
+               echo $inputParameters;
+               echo html_e('input', array('type'=>'hidden', 
'name'=>'function', 'value'=>'copy_set'));
+               echo html_ao('p');
+               echo sprintf(_('Copy the %s'), $currentSetName)._(':');
+               echo html_ac(html_ap() - 1);
+               echo html_ao('p');
+               echo html_e('input',array('type'=>'submit', 
'value'=>_('Copy')));
+               echo html_ac(html_ap() - 1);
+               echo $HTML->closeForm();
+       }
+
+       echo html_e('h2', array(), _('Effort Units list'));
+       $unitFactory = new EffortUnitFactory($effortUnitSet);
+       if (!$unitFactory || !is_object($unitFactory)) {
+               echo $HTML->error_msg(_('Error creating EffortUnitFactory'));
+               return;
+       } elseif ($unitFactory->isError()) {
+               echo $HTML->error_msg($unitFactory->getErrorMessage());
+               return;
+       }
+       $units = $unitFactory->getUnits();
+       if ($unitFactory->isError()) {
+               echo $HTML->error_msg($unitFactory->getErrorMessage());
+               return false;
+       }
+
+       if (is_array($units) && count($units)>0) {
+               if ($isEditable) {
+                       $titleArray = array(_('Up/Down positions'), _('Unit 
name'), _('Definition'));
+               } else {
+                       $titleArray = array(_('Unit name'), _('Definition'));
+               }
+               $linksArray = array();
+               $class = ' ';
+               $id = 'unit_id';
+               $thClassArray = array();
+               $thTitleArray = array();
+               $thOtherAttrsArray = array();
+               echo $HTML->listTableTop($titleArray, $linksArray, $class, $id, 
$thClassArray, $thTitleArray, $thOtherAttrsArray);
+               $i=0;
+               foreach($units as $unit) {
+                       $cells = array();
+                       if ($isEditable) {
+                               $pos =  $unit->getPosition();
+                               if ($pos==1) {
+                                       $content = 
html_image('ic/btn_up.png','19','18',array('alt'=>'Up'));
+                               } else {
+                                       $content = 
util_make_link($currentURL.'?'.($urlParameters ? 
$urlParameters.'&':'').'unit_id='.$unit->getID().'&new_pos='.($pos - 
1).'&function=updownorder', 
html_image('ic/btn_up.png','19','18',array('alt'=>'Up', 'title'=>_('Move Up 
this custom field element'))));
+                               }
+                               if ($pos == count($units)) {
+                                       $content .= 
html_image('ic/btn_down.png','19','18',array('alt'=>'Down'));
+                               } else {
+                                       $content .= 
util_make_link($currentURL.'?'.($urlParameters ? 
$urlParameters.'&':'').'unit_id='.$unit->getID().'&new_pos='.($pos + 
1).'&function=updownorder', 
html_image('ic/btn_down.png','19','18',array('alt'=>'Down', 'title'=>_('Move 
Down this custom field element'))));
+                               }
+                               $cells[] = array($content, 
'class'=>'align-center');
+                       }
+                       $content = $unit->getName();
+                       if ($isEditable) {
+                               $content .= 
util_make_link($currentURL.'?'.($urlParameters ? 
$urlParameters.'&':'').'unit_id='.$unit->getID().'&function=edit', 
$HTML->getEditFilePic(_('Edit')));
+                               if (!$unit->isBaseUnit()) {
+                                       $content .= 
util_make_link($currentURL.'?'.($urlParameters ? 
$urlParameters.'&':'').'unit_id='.$unit->getID().'&function=delete', 
$HTML->getDeletePic(_('Delete')));
+                               }
+                       }
+                       $cells[][] = $content;
+                       $cells[][] = ($unit->isBaseUnit()?_('Base 
Unit'):$unit->getConversionFactor().' x '.$unit->getToUnitName());
+                       echo $HTML->multiTableRow(array('class' => 
$HTML->boxGetAltRowStyle($i, true)), $cells);
+                       $i += 1;
+               }
+               echo $HTML->listTableBottom();
+       } else {
+               echo html_e('p',array(),_('No unit'));
+       }
+       if ($isEditable) {
+               echo html_e('br');
+               echo $HTML->openForm(array('action' => $currentURL, 'method' => 
'get'));
+               if (is_array($units) && count($units)==0) {
+                       echo html_e('input', array('type'=>'hidden', 
'name'=>'is_base', 'value'=>1));
+               }
+               echo $inputParameters;
+               echo html_e('input', array('type'=>'hidden', 
'name'=>'function', 'value'=>'add'));
+               echo html_e('input',array('type'=>'submit', 'value'=>_('Add new 
unit')));
+               echo $HTML->closeForm();
+       }
+}
+
+function add_unit(&$effortUnitSet){
+       global $HTML;
+       $currentURL = getStringFromServer('PHP_SELF');
+       $urlParameters = urlParameters($effortUnitSet);
+       $inputParameters = inputParameters($effortUnitSet);
+       echo html_e('h2', array(), _('Add Effort Unit'));
+       echo $HTML->openForm(array_merge(array('action' => $currentURL, 
'method' => 'post')));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'form_key', 
'value'=>form_generate_key()));
+       echo $inputParameters;
+       echo html_e('input', array('type'=>'hidden', 'name'=>'function', 
'value'=>'postadd'));
+       echo html_ao('p');
+       echo html_e('label', array('for'=>'name'),_('Name'));
+       echo html_e('input', array('type'=>'text', 'name'=>'name', 
'id'=>'name'));
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p');
+       echo html_e('label', array('for'=>'factor'),_('Definition'));
+       echo html_e('input', array('type'=>'number', 'name'=>'factor', 
'id'=>'factor', 'min'=>0));
+       $unitFactory = new EffortUnitFactory($effortUnitSet);
+       $unitsArr = $unitFactory->getUnitsArr();
+       echo html_build_select_box_from_array($unitsArr, 'to_unit');
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p');
+       echo html_e('input', array('type' => 'submit', 'value' => _('Add')));
+       echo html_e('input', array('type' => 'button', 'value' => _('Cancel'), 
'onclick' => 'window.location=\''.util_make_uri($currentURL.($urlParameters ? 
'?'.$urlParameters:'')).'\''));
+       echo html_ac(html_ap() - 1);
+       echo $HTML->closeForm();
+}
+
+function postadd_unit(&$effortUnitSet){
+       global $HTML;
+       $unit = new EffortUnit($effortUnitSet);
+       if (!$unit || !is_object($unit)) {
+               echo $HTML->error_msg(_('Effort Unit could not be created'));
+               return false;
+       }
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       $name = getStringFromRequest('name', '');
+       $factor = getIntFromRequest('factor', 1);
+       $to_unit = getIntFromRequest('to_unit');
+       $position = 0;
+       $unit->create($name, $factor, $to_unit, $position);
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       echo $HTML->feedback(sprintf(_('%s successfully created.'), $name));
+       return true;
+}
+
+function edit_unit (&$effortUnitSet) {
+       global $HTML;
+       $currentURL = getStringFromServer('PHP_SELF');
+       $urlParameters = urlParameters($effortUnitSet);
+       $inputParameters = inputParameters($effortUnitSet);
+       $unitId = getIntFromRequest('unit_id',0);
+       $unit = new EffortUnit($effortUnitSet,$unitId);
+       if (!$unit || !is_object($unit)) {
+               echo $HTML->error_msg(_('Effort Unit could not be created'));
+               return false;
+       }
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       echo html_e('h2', array(), _('Edit Effort Unit'));
+       echo $HTML->openForm(array('action' => $currentURL, 'method' => 
'post'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'function', 
'value'=>'postedit'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'unit_id', 
'value'=>$unitId));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'form_key', 
'value'=>form_generate_key()));
+       echo $inputParameters;
+       echo html_ao('p');
+       echo html_e('label', array('for'=>'name'),_('Name'));
+       echo html_e('input', array('type'=>'text', 'name'=>'name', 
'id'=>'name', 'value'=> $unit->getName()));
+       echo html_ac(html_ap() - 1);
+       if ($unit->isBaseUnit()) {
+               echo html_e('input', array('type'=>'hidden', 'name'=>'factor', 
'value'=>1 ));
+               echo html_e('input', array('type'=>'hidden', 'name'=>'to_unit', 
'value'=>$unit->getToUnit() ));
+       } else {
+               echo html_ao('p');
+               echo html_e('label', array('for'=>'factor'),_('Definition'));
+               echo html_e('input', array('type'=>'number', 'name'=>'factor', 
'id'=>'factor', 'min'=>0, 'value'=> $unit->getConversionFactor()));
+               $unitFactory = new EffortUnitFactory($effortUnitSet);
+               $unitsArr = $unitFactory->getUnitsArr();
+               unset($unitsArr[$unit->getID()]);
+               echo html_build_select_box_from_array($unitsArr, 'to_unit', 
$unit->getToUnit());
+               echo html_ac(html_ap() - 1);
+       }
+       echo html_ao('p');
+       echo html_e('input', array('type' => 'submit', 'value' => _('Update')));
+       echo html_e('input', array('type' => 'button', 'value' => _('Cancel'), 
'onclick' => 'window.location=\''.util_make_uri($currentURL.($urlParameters ? 
'?'.$urlParameters:'')).'\''));
+       echo html_ac(html_ap() - 1);
+       echo $HTML->closeForm();
+}
+
+function postedit_unit(&$effortUnitSet){
+       global $HTML;
+       $unitId = getIntFromRequest('unit_id',0);
+       $unit = new EffortUnit($effortUnitSet,$unitId);
+       if (!$unit || !is_object($unit)) {
+               echo $HTML->error_msg(_('Effort Unit could not be created'));
+               return false;
+       }
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       $name = getStringFromRequest('name', '');
+       $factor = getIntFromRequest('factor', 1);
+       $to_unit = getIntFromRequest('to_unit');
+       $unit->update($name, $factor, $to_unit);
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       echo $HTML->feedback(sprintf(_('%s successfully updated.'), $name));
+       return true;
+}
+
+function delete_unit($effortUnitSet){
+       global $HTML;
+       $currentURL = getStringFromServer('PHP_SELF');
+       $urlParameters = urlParameters($effortUnitSet);
+       $inputParameters = inputParameters($effortUnitSet);
+       $unitId = getIntFromRequest('unit_id',0);
+       echo html_e('h2', array(), _('Effort Unit delete confirmation'));
+       echo $HTML->openForm(array('action' => $currentURL, 'method' => 
'post'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'function', 
'value'=>'postdelete'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'unit_id', 
'value'=>$unitId));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'form_key', 
'value'=>form_generate_key()));
+       echo $inputParameters;
+       echo html_ao('p',array('class'=>'important'));
+       echo _('Are you sure you want to delete this Effort Unit?');
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p', array('class'=>'align-center'));
+       echo html_e('input', array('type'=>'checkbox', 
'name'=>'confirm_delete', 'id'=>'confirm_delete', 'value'=>1));
+       echo html_e('label', array('for'=>'confirm_delete'),_('I am Sure'));
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p');
+       echo html_e('input', array('type' => 'submit', 'value' => _('Delete')));
+       echo html_e('input', array('type' => 'button', 'value' => _('Cancel'), 
'onclick' => 'window.location=\''.util_make_uri($currentURL.($urlParameters ? 
'?'.$urlParameters:'')).'\''));
+       echo html_ac(html_ap() - 1);
+       echo $HTML->closeForm();
+}
+
+function postdelete_unit(&$effortUnitSet) {
+       global $HTML;
+       $unitId = getIntFromRequest('unit_id','0');
+       $unit = new EffortUnit($effortUnitSet,$unitId);
+       if (!$unit || !is_object($unit)) {
+               echo $HTML->error_msg(_('Effort Unit could not be created'));
+               return false;
+       }
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       $sure = getIntFromRequest('confirm_delete',0);
+       if (!$sure) {
+               echo $HTML->error_msg(_('You haven\' confirm the delete of the 
Unit'));
+               return false;
+       }
+       $unit->delete();
+       if ($unit->isError()) {
+               echo $HTML->error_msg($unit->getErrorMessage());
+               return false;
+       }
+       echo $HTML->feedback(sprintf(_('%s successfully deleted.'), $name));
+       return true;
+}
+
+function order_unit(&$effortUnitSet) {
+       global $HTML;
+       $unitId = getIntFromRequest('unit_id','0');
+       $unit = new EffortUnit($effortUnitSet,$unitId);
+       $new_pos = getIntFromRequest('new_pos','0');
+       if ($new_pos) {
+               $unit->reorderUnits($new_pos);
+       }
+       return true;
+}
+
+function copy_set(&$effortUnitSet){
+       global $HTML;
+       $currentURL = getStringFromServer('PHP_SELF');
+       $urlParameters = urlParameters($effortUnitSet);
+       $inputParameters = inputParameters($effortUnitSet);
+       echo html_e('h2', array(), _('Effort Unit Set copy confirmation'));
+       echo $HTML->openForm(array('action' => $currentURL, 'method' => 
'post'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'function', 
'value'=>'postcopy_set'));
+       echo html_e('input', array('type'=>'hidden', 'name'=>'form_key', 
'value'=>form_generate_key()));
+       echo $inputParameters;
+       echo html_ao('p',array('class'=>'important'));
+       echo _('Are you sure you want to copy this Effort Units?');
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p', array('class'=>'align-center'));
+       echo html_e('input', array('type'=>'checkbox', 'name'=>'confirm_copy', 
'id'=>'confirm_copy', 'value'=>1));
+       echo html_e('label', array('for'=>'confirm_copy'),_('I am Sure'));
+       echo html_ac(html_ap() - 1);
+       echo html_ao('p');
+       echo html_e('input', array('type' => 'submit', 'value' => _('Copy')));
+       echo html_e('input', array('type' => 'button', 'value' => _('Cancel'), 
'onclick' => 'window.location=\''.util_make_uri($currentURL.($urlParameters ? 
'?'.$urlParameters:'')).'\''));
+       echo html_ac(html_ap() - 1);
+       echo $HTML->closeForm();
+}
+
+function postcopy_set(&$effortUnitSet) {
+       global $HTML;
+       $sure = getIntFromRequest('confirm_copy',0);
+       if (!$sure) {
+               echo $HTML->error_msg( _('You haven\' confirm the copy of the 
Effort Unit Set'));
+               return false;
+       }
+       $object = $effortUnitSet->getObject();
+       $newEffortUnitSet = new EffortUnitSet($object);
+       if (!$newEffortUnitSet->copy($effortUnitSet)) {
+               echo $HTML->error_msg('Error coping Effort Unit Set');
+               return false;
+       }
+       if ($newEffortUnitSet->isError()) {
+               echo $HTML->error_msg($newEffortUnitSet->getErrorMessage());
+               return false;
+       }
+       $effortUnitSet = $newEffortUnitSet;
+       echo $HTML->feedback(sprintf(_('Effort Unit Set successfully 
copied.')));
+       return true;
+}
+
+function inputParameters($effortUnitSet){
+       $return = '';
+       switch ($effortUnitSet->getObjectLevel()) {
+               case EFFORTUNITSET_TRACKER_LEVEL:
+                       $return .= html_e('input', array('type'=>'hidden', 
'name'=>'effort_units', 'value'=>1));
+                       $return .= html_e('input', array('type'=>'hidden', 
'name'=>'atid', 'value'=>$effortUnitSet->getArtifactType()->getID()));
+               case EFFORTUNITSET_PROJECT_LEVEL:
+                       $return .= html_e('input', array('type'=>'hidden', 
'name'=>'group_id', 'value'=>$effortUnitSet->getGroup()->getID()));
+       }
+       return $return;
+}
+
+function urlParameters($effortUnitSet){
+       $return = '';
+       switch ($effortUnitSet->getObjectLevel()) {
+               case EFFORTUNITSET_TRACKER_LEVEL:
+                       $return = 
'group_id='.$effortUnitSet->getGroup()->getID().'&atid='.$effortUnitSet->getArtifactType()->getID().'&effort_units=1';
+                       break;
+               case EFFORTUNITSET_PROJECT_LEVEL:
+                       $return = 
'group_id='.$effortUnitSet->getGroup()->getID();
+                       break;
+       }
+       return $return;
+}
\ No newline at end of file
diff --git a/src/common/tracker/include/ArtifactTypeHtml.class.php 
b/src/common/tracker/include/ArtifactTypeHtml.class.php
index b4c0325..5dd89ca 100644
--- a/src/common/tracker/include/ArtifactTypeHtml.class.php
+++ b/src/common/tracker/include/ArtifactTypeHtml.class.php
@@ -7,7 +7,7 @@
  * Copyright 2011, Franck Villaume - Capgemini
  * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
  * Copyright 2014,2016, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -30,6 +30,7 @@ require_once $gfcommon.'tracker/ArtifactType.class.php';
 require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
 require_once $gfcommon.'tracker/ArtifactExtraFieldElement.class.php';
 require_once $gfcommon.'tracker/ArtifactWorkflow.class.php';
+require_once $gfcommon.'tracker/EffortUnitFactory.class.php';
 require_once $gfcommon.'include/utils_crossref.php';
 require_once $gfcommon.'include/UserManager.class.php';
 require_once $gfcommon.'widget/WidgetLayoutManager.class.php';
@@ -135,6 +136,10 @@ class ArtifactTypeHtml extends ArtifactType {
                $title_arr[]=_('Update Settings');
                $attr_arr[] = array('title'=>_('Set up preferences like 
expiration times, email addresses.'));
 
+               
$links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&effort_units=1';
+               $title_arr[]=_('Manage Effort Units');
+               $attr_arr[] = array('title'=>_('Manage Effort Units for Effort 
custom extra field.'));
+
                
$links_arr[]='/tracker/admin/?group_id='.$group_id.'&atid='.$this->getID().'&add_extrafield=1';
                $title_arr[]=_('Manage Custom Fields');
                $attr_arr[] = array('title'=>_('Add new boxes like Phases, 
Quality Metrics, Components, etc.  Once added they can be used with other 
selection boxes (for example, Categories or Groups) to describe and browse bugs 
or other artifact types.'));
@@ -407,6 +412,8 @@ class ArtifactTypeHtml extends ArtifactType {
                                        $post_name =  ' <i>'._('(YYYY-MM-DD 
YYYY-MM-DD Format)').'</i>';
                                }
                                $str = 
$this->renderDateRange($efarr[$i]['extra_field_id'],$selected[$efarr[$i]['extra_field_id']],
 $attrs);
+                       } elseif ($efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
+                               $str = 
$this->renderEffort($efarr[$i]['extra_field_id'],$selected[$efarr[$i]['extra_field_id']],$efarr[$i]['attribute1'],$efarr[$i]['attribute2'],
 $attrs);
                        }
                        $template = 
str_replace('{$PostName:'.$efarr[$i]['field_name'].'}',$post_name,$template);
                        $template = 
str_replace('{$'.$efarr[$i]['field_name'].'}',$str,$template);
@@ -583,7 +590,8 @@ class ArtifactTypeHtml extends ArtifactType {
                        } elseif ($efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_TEXT ||
                                $efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_INTEGER ||
                                $efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_RELATION ||
-                               $efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_DATETIME) {
+                               $efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_DATETIME ||
+                                       $efarr[$i]['field_type'] == 
ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
 
                                //text fields might be really wide, so need a 
row to themselves.
                                if (($col_count == 1) && 
($efarr[$i]['attribute1'] > 30)) {
@@ -1141,6 +1149,44 @@ class ArtifactTypeHtml extends ArtifactType {
                return html_e('input', array_merge(array('type'=>'text', 
'name'=>'extra_fields['.$extra_field_id.']', 'pattern'=>$datepattern.' 
'.$datepattern, 'maxlength'=>21, 'size'=>21, 'value'=>$dateRange),$attrs));
        }
 
+       /**
+        * renderEffort - this function builds a Effort extra field.
+        *
+        * @param       int     $extra_field_id The ID of this field.
+        * @param       string  $contents       The data for this field.
+        * @param       string  $size
+        * @param       string  $maxlength
+        * @param       array   $attrs          Array of other attributes
+        * @return      string  text area and data.
+        */
+       function renderEffort ($extra_field_id, $contents, $size, $maxlength, 
$attrs = array()) {
+               $effortUnitSet = New EffortUnitSet($this, 
$this->getEffortUnitSet());
+               $effortUnitFactory = New EffortUnitFactory($effortUnitSet);
+               $units = $effortUnitFactory->getUnits();
+               if (!$contents) {
+                       reset($units);
+                       $contents = '0U'.key($units);
+               }
+               $pos = strpos($contents, 'U');
+               if ($pos) {
+                       $value = substr($contents, 0, $pos);
+                       $unitId = substr($contents, $pos+strlen($contents));
+               } else {
+                       $value = 0;
+                       $unitId = null;
+               }
+               if (isset($attrs['class'])) {
+                       $attrs['class'] .= ' effort';
+               } else {
+                       $attrs['class'] = 'effort';
+               }
+               $attrs['data-effortid'] = $extra_field_id;
+               $return = html_e('input', array('type'=>'hidden', 
'name'=>'extra_fields['.$extra_field_id.']', 'value'=>$contents));
+               $return .= html_e('input', array_merge(array('type'=>'number', 
'name'=>'value['.$extra_field_id.']', 'value'=>$value, 'size'=>$size, 
'maxlength'=>$maxlength, 'min'=>0), $attrs));
+               $return .= html_build_select_box_from_array($units, 
'unit['.$extra_field_id.']', $unitId, false, $attrs);
+               return $return;
+       }
+
        function technicianBox($name = 'assigned_to[]', $checked = 'xzxz', 
$show_100 = true, $text_100 = 'none', $extra_id = '-1', $extra_name = '', 
$multiple = false, $attrs = array()) {
                if ($text_100=='none'){
                        $text_100=_('Nobody');
@@ -1253,6 +1299,11 @@ class ArtifactTypeHtml extends ArtifactType {
                }
                return false;
        };
+       $(".effort").on('change', function(){
+               var effortid = $(this).data("effortid");
+               console.log(effortid);
+               
$("input[name='extra_fields["+effortid+"]']").val($("input[name='value["+effortid+"]']").val()+'U'+$("select[name='unit["+effortid+"]']").val())
+       });
        $("input[type='radio'].readonly, 
input[type='checkbox'].readonly").on('click', function(){
                return false;
        }).on('keydown', function(event){
@@ -1272,10 +1323,7 @@ class ArtifactTypeHtml extends ArtifactType {
                                fields = answer['fields'];
                                $.each(fields, function (index, field) {
                                        fieldObj = 
$("[name^='extra_fields["+field.id+"]']");
-                               console.log(fieldObj);
-                               console.log(fieldObj.is("input[type='radio']"));
                                        if 
(fieldObj.is("input[type='checkbox']")){
-                               console.log("checkbox");
                                                fieldObj.each(function() {
                                                        var in_array = -1;
                                                        for (var key in 
field.value) {
@@ -1291,9 +1339,7 @@ class ArtifactTypeHtml extends ArtifactType {
                                                        }
                                                });
                                        } else if 
(fieldObj.is("input[type='radio']")){
-                               console.log("radio");
                                                fieldObj.each(function() {
-                                                       console.log($(this));
                                                        var in_array = -1;
                                                        for (var key in 
field.value) {
                                                                if 
(field.value[key] == $(this).val()) {
@@ -1301,7 +1347,6 @@ class ArtifactTypeHtml extends ArtifactType {
                                                                        break;
                                                                }
                                                        }
-                                                       console.log(in_array);
                                                        if (in_array > -1) {
                                                                
$(this).prop("checked",true);
                                                        } else {
diff --git a/src/common/tracker/views/form-addextrafield.php 
b/src/common/tracker/views/form-addextrafield.php
index 70b20ba..c029fe1 100644
--- a/src/common/tracker/views/form-addextrafield.php
+++ b/src/common/tracker/views/form-addextrafield.php
@@ -4,7 +4,7 @@
  *
  * Copyright 2010 (c) FusionForge Team
  * Copyright 2014-2016, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -220,7 +220,8 @@ $jsvariable ="
        var typeFormula = ".ARTIFACT_EXTRAFIELDTYPE_FORMULA.";
        var typeDateTime = ".ARTIFACT_EXTRAFIELDTYPE_DATETIME.";
        var typeUser = ".ARTIFACT_EXTRAFIELDTYPE_USER.";
-       var typeRelease = ".ARTIFACT_EXTRAFIELDTYPE_RELEASE.";";
+       var typeRelease = ".ARTIFACT_EXTRAFIELDTYPE_RELEASE.";
+       var typeEffort = ".ARTIFACT_EXTRAFIELDTYPE_EFFORT.";";
 
 $javascript = <<<'EOS'
        $("p[class^='for-']").hide()
@@ -272,6 +273,12 @@ $javascript = <<<'EOS'
                $("p.for-release").show();
                $("p[class^='for-']:not(.for-release)").hide();
        });
+       $("input[value="+typeEffort+"]").on('change', function(){
+               $("label[for='attribute1']").text(size);
+               $("label[for='attribute2']").text(maxLength);
+               $("p.for-effort").show();
+               $("p[class^='for-']:not(.for-effort)").hide();
+       });
 
 EOS;
 echo html_e('script', array( 'type'=>'text/javascript'), 
'//<![CDATA['."\n".'$(function(){'.$jsvariable."\n".$javascript.'});'."\n".'//]]>');
@@ -286,7 +293,7 @@ $texts = array_values($eftypes);
 echo html_build_radio_buttons_from_arrays($vals, $texts, 'field_type', '', 
false, '', false ,'', false, array('required'=>'required') );
 echo html_ac(html_ap() - 1);
 
-echo html_ao('p', array('class'=>'for-text for-textarea for-integer 
for-relation'));
+echo html_ao('p', array('class'=>'for-text for-textarea for-integer 
for-relation for-effort'));
 echo html_e('label', array('for'=>'attribute1'), _('Size')._(':'));
 echo html_e('input', array('type'=>'text', 'name'=>'attribute1', 
'value'=>'20', 'size'=>'2', 'maxlength'=>'2')).html_e('br');
 echo html_e('label', array('for'=>'attribute2'), _('Maxlength')._(':'));
diff --git a/src/common/tracker/views/form-updateextrafield.php 
b/src/common/tracker/views/form-updateextrafield.php
index 3d89ce2..adb9e51 100644
--- a/src/common/tracker/views/form-updateextrafield.php
+++ b/src/common/tracker/views/form-updateextrafield.php
@@ -4,7 +4,7 @@
  *
  * Copyright 2010 (c) FusionForge Team
  * Copyright 2014-2015, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -91,7 +91,7 @@ if (!$ac || !is_object($ac)) {
                echo html_e('input', array('type'=>'text', 'id'=>'attribute2', 
'name'=>'attribute2', 'value'=>$ac->getAttribute2(), 'size'=>'2', 
'maxlength'=>'2'));
                echo html_ac(html_ap() - 1);
 
-       } elseif ($efType == ARTIFACT_EXTRAFIELDTYPE_TEXT || $efType == 
ARTIFACT_EXTRAFIELDTYPE_INTEGER || $efType == ARTIFACT_EXTRAFIELDTYPE_RELATION) 
{
+       } elseif ($efType == ARTIFACT_EXTRAFIELDTYPE_TEXT || $efType == 
ARTIFACT_EXTRAFIELDTYPE_INTEGER || $efType == ARTIFACT_EXTRAFIELDTYPE_RELATION 
|| $efType == ARTIFACT_EXTRAFIELDTYPE_EFFORT) {
                echo html_ao('p');
                echo html_e('label', array('for'=>'attribute1'), _('Size'));
                echo html_e('input', array('type'=>'text', 'id'=>'attribute1', 
'name'=>'attribute1', 'value'=>$ac->getAttribute1(), 'size'=>'2', 
'maxlength'=>'2'));
diff --git a/src/db/20161129-tracker-field-type-effort.sql 
b/src/db/20161129-tracker-field-type-effort.sql
new file mode 100644
index 0000000..0d93f30
--- /dev/null
+++ b/src/db/20161129-tracker-field-type-effort.sql
@@ -0,0 +1,75 @@
+CREATE TABLE effort_unit_set
+(
+       unit_set_id serial NOT NULL,
+       level integer NOT NULL DEFAULT 1,
+       group_id integer DEFAULT NULL,
+       group_artifact_id integer DEFAULT NULL,
+       created_date integer NOT NULL,
+       created_by integer NOT NULL,
+       CONSTRAINT effort_unit_set_pk PRIMARY KEY (unit_set_id),
+       CONSTRAINT effort_unit_set_group_id_fk FOREIGN KEY (group_id)
+               REFERENCES groups (group_id) MATCH SIMPLE
+               ON UPDATE NO ACTION ON DELETE CASCADE,
+       CONSTRAINT effort_unit_set_group_artifact_id_fk FOREIGN KEY 
(group_artifact_id)
+               REFERENCES artifact_group_list (group_artifact_id) MATCH SIMPLE
+               ON UPDATE NO ACTION ON DELETE CASCADE,
+       CONSTRAINT effort_unit_set_created_by_fk FOREIGN KEY (created_by)
+               REFERENCES users (user_id) MATCH FULL
+               ON UPDATE NO ACTION ON DELETE NO ACTION
+);
+
+CREATE TABLE effort_unit
+(
+       unit_id serial NOT NULL,
+       unit_set_id integer NOT NULL,
+       unit_name text NOT NULL,
+       conversion_factor integer NOT NULL,
+       to_unit integer,
+       unit_position integer NOT NULL,
+       is_base_unit integer NOT NULL DEFAULT 0,
+       is_deleted integer NOT NULL DEFAULT 0,
+       created_date integer NOT NULL,
+       created_by integer NOT NULL,
+       modified_date integer NOT NULL,
+       modified_by integer NOT NULL,
+       CONSTRAINT effort_unit_pk PRIMARY KEY (unit_id),
+       CONSTRAINT effort_unit_to_unit_fk FOREIGN KEY (to_unit)
+               REFERENCES effort_unit (unit_id) MATCH SIMPLE
+               ON UPDATE NO ACTION ON DELETE CASCADE,
+       CONSTRAINT effort_unit_created_by_fk FOREIGN KEY (created_by)
+               REFERENCES users (user_id) MATCH FULL
+               ON UPDATE NO ACTION ON DELETE NO ACTION,
+       CONSTRAINT effort_unit_modified_by_fk FOREIGN KEY (modified_by)
+               REFERENCES users (user_id) MATCH FULL
+               ON UPDATE NO ACTION ON DELETE NO ACTION
+);
+
+INSERT INTO effort_unit_set(level, created_date, created_by)
+VALUES(1, CAST(EXTRACT(EPOCH FROM TRANSACTION_TIMESTAMP()) AS INTEGER), 100);
+
+INSERT INTO effort_unit(unit_set_id, unit_name, conversion_factor, to_unit, 
unit_position, is_base_unit, created_date, created_by, modified_date, 
modified_by)
+VALUES(CURRVAL('effort_unit_set_unit_set_id_seq'), 'Hours', 1, 
CURRVAL('effort_unit_unit_id_seq'), 1, 1, CAST(EXTRACT(EPOCH FROM 
TRANSACTION_TIMESTAMP()) AS INTEGER), 100, CAST(EXTRACT(EPOCH FROM 
TRANSACTION_TIMESTAMP()) AS INTEGER), 100);
+
+ALTER TABLE groups
+       ADD COLUMN unit_set_id integer NOT NULL DEFAULT 1,
+       ADD CONSTRAINT groups_unit_set_id_fk FOREIGN KEY (unit_set_id)
+               REFERENCES effort_unit_set (unit_set_id) MATCH SIMPLE
+               ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+ALTER TABLE artifact_group_list
+       ADD COLUMN unit_set_id integer NOT NULL DEFAULT 1,
+       ADD CONSTRAINT artifact_group_list_unit_set_id_fk FOREIGN KEY 
(unit_set_id)
+               REFERENCES effort_unit_set (unit_set_id) MATCH SIMPLE
+               ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+DROP VIEW artifact_group_list_vw;
+
+CREATE OR REPLACE VIEW artifact_group_list_vw AS 
+       SELECT agl.group_artifact_id, agl.group_id, agl.name, agl.description, 
+                       agl.email_all_updates, agl.email_address, 
agl.due_period, 
+                       agl.submit_instructions, agl.browse_instructions, 
agl.browse_list, 
+                       agl.datatype, agl.status_timeout, 
agl.custom_status_field, 
+                       agl.custom_renderer, agl.auto_assign_field, 
agl.unit_set_id,
+                       aca.count, aca.open_count
+               FROM artifact_group_list agl
+               LEFT JOIN artifact_counts_agg aca USING (group_artifact_id);
\ No newline at end of file
diff --git a/src/www/admin/effortunitsedit.php 
b/src/www/admin/effortunitsedit.php
new file mode 100644
index 0000000..b864145
--- /dev/null
+++ b/src/www/admin/effortunitsedit.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Manage Effort Units for the Forge (Site Admin page) 
+ *
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
+ * http://fusionforge.org/
+ *
+ * This file is part of FusionForge. FusionForge is free software;
+ * you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the Licence, or (at your option)
+ * any later version.
+ *
+ * FusionForge is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FusionForge; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+require_once '../env.inc.php';
+require_once $gfcommon.'include/pre.php';
+require_once $gfwww.'admin/admin_utils.php';
+include $gfcommon.'tracker/actions/effort_units.php';
\ No newline at end of file
diff --git a/src/www/admin/index.php b/src/www/admin/index.php
index 7f8dd05..62a84f6 100644
--- a/src/www/admin/index.php
+++ b/src/www/admin/index.php
@@ -12,6 +12,7 @@
  * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
  * Copyright 2011, Franck Villaume - Capgemini
  * Copyright 2013-2014, Franck Villaume - TrivialDev
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -208,6 +209,7 @@ echo html_ac(html_ap() - 1);
 <ul>
        <li><?php echo util_make_link('/admin/massmail.php', sprintf(_('Mail 
Engine for %s Subscribers'), forge_get_config ('forge_name'))); ?></li>
        <li><?php echo util_make_link('/admin/unsubscribe.php', 
forge_get_config('forge_name').' '._('Site Mailings Maintenance')); ?></li>
+       <li><?php echo util_make_link('/admin/effortunitsedit.php', _('Manage 
Effort Units')); ?></li>
        <li><?php echo util_make_link('/admin/edit_frs_filetype.php', _('Add, 
Delete, or Edit File Types')); ?></li>
        <li><?php echo util_make_link('/admin/edit_frs_processor.php', _('Add, 
Delete, or Edit Processors')); ?></li>
        <li><?php echo util_make_link('/admin/edit_theme.php', _('Add, Delete, 
or Edit Themes')); ?></li>
diff --git a/src/www/project/admin/effortunits.php 
b/src/www/project/admin/effortunits.php
new file mode 100644
index 0000000..a1e7cc5
--- /dev/null
+++ b/src/www/project/admin/effortunits.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Manage Effort units for Project (Project Admin page)
+*
+* Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev
+* http://fusionforge.org/
+*
+* This file is part of FusionForge. FusionForge is free software;
+* you can redistribute it and/or modify it under the terms of the
+* GNU General Public License as published by the Free Software
+* Foundation; either version 2 of the Licence, or (at your option)
+* any later version.
+*
+* FusionForge is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along
+* with FusionForge; if not, write to the Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+require_once '../../env.inc.php';
+require_once $gfcommon.'include/pre.php';
+require_once $gfwww.'project/admin/project_admin_utils.php';
+include $gfcommon.'tracker/actions/effort_units.php';
\ No newline at end of file
diff --git a/src/www/project/admin/project_admin_utils.php 
b/src/www/project/admin/project_admin_utils.php
index 2c19b73..024f08f 100644
--- a/src/www/project/admin/project_admin_utils.php
+++ b/src/www/project/admin/project_admin_utils.php
@@ -6,7 +6,8 @@
  * Copyright 2002-2004 (c) GForge Team
  * Copyright 2011, Franck Villaume - Capgemini
  * Copyright (C) 2011 Alain Peyrat - Alcatel-Lucent
- * Coyright 2014, Franck Villaume - TrivialDev
+ * Copyright 2014, Franck Villaume - TrivialDev
+ * Copyright 2017, Stéphane-Eymeric Bredthauer - TrivialDev 
  * http://fusionforge.org/
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -62,6 +63,10 @@ function project_admin_header($params) {
        $attr_r[] = array('title' => _('Show the significant change of your 
project.'));
        $links[] = '/project/admin/history.php?group_id='.$group_id;
 
+       $labels[] = _('Effort Units');
+       $attr_r[] = array('title' => _('Manage Effort Units used in your 
project.'));
+       $links[] = '/project/admin/effortunits.php?group_id='.$group_id;
+
        if(forge_get_config('use_people')) {
                $labels[] = _('Post Jobs');
                $attr_r[] = array('title' => _('Hiring new people. Describe the 
job'));
diff --git a/src/www/tracker/admin/index.php b/src/www/tracker/admin/index.php
index cd1779d..690a17d 100644
--- a/src/www/tracker/admin/index.php
+++ b/src/www/tracker/admin/index.php
@@ -6,7 +6,7 @@
  * Copyright 2010, FusionForge Team
  * Copyright (C) 2012 Alain Peyrat - Alcatel-Lucent
  * Copyright 2013-2014,2016, Franck Villaume - TrivialDev
- * Copyright 2016, Stéphane-Eymeric Bredthauer - TrivialDev
+ * Copyright 2016-2017, Stéphane-Eymeric Bredthauer - TrivialDev
  * http://fusionforge.org
  *
  * This file is part of FusionForge. FusionForge is free software;
@@ -40,6 +40,8 @@ require_once $gfcommon.'tracker/ArtifactExtraField.class.php';
 require_once $gfcommon.'tracker/ArtifactExtraFieldElement.class.php';
 require_once $gfcommon.'tracker/Roadmap.class.php';
 require_once $gfcommon.'tracker/RoadmapFactory.class.php';
+require_once $gfcommon.'tracker/EffortUnit.class.php';
+require_once $gfcommon.'tracker/EffortUnitFactory.class.php';
 
 $group_id = getIntFromRequest('group_id');
 $atid = getIntFromRequest('atid');
@@ -119,7 +121,9 @@ if ($group_id && $atid) {
                $actions = array('add_extrafield', 'customize_list', 
'workflow', 'workflow_roles', 'workflow_required_fields', 'add_opt',
                        'updownorder_opt', 'post_changes_order', 
'post_changes_alphaorder', 'post_changes_default', 'copy_opt', 'add_canned',
                        'clone_tracker', 'edittemplate',
-                       'update_canned', 'delete_canned', 'update_box', 
'update_opt', 'delete', 'delete_opt', 'deleteextrafield','update_type');
+                       'update_canned', 'delete_canned', 'update_box', 
'update_opt', 'delete', 'delete_opt', 'deleteextrafield','update_type',
+                       'effort_units'
+               );
                $action = '';
                foreach ($actions as $a) {
                        if (getStringFromRequest($a)) {
@@ -234,6 +238,10 @@ if ($group_id && $atid) {
 
                include $gfcommon.'tracker/views/form-updatetracker.php';
 
+       } elseif ($action == 'effort_units') {
+
+               include $gfcommon.'tracker/actions/effort_units.php';
+
        } else {
 
                include $gfcommon.'tracker/actions/admin-tracker.php';
@@ -252,6 +260,9 @@ if ($group_id && $atid) {
        } elseif (getStringFromRequest('admin_roadmap')) {
                include $gfcommon.'tracker/views/form-adminroadmap.php';
 
+       } elseif (getStringFromRequest('effort_units')) {
+               include $gfcommon.'tracker/actions/effort_units.php';
+
        } else {
                include $gfcommon.'tracker/actions/admin-ind.php';
        }

-----------------------------------------------------------------------

Summary of changes:
 src/common/include/Group.class.php                 |  29 ++
 src/common/tracker/ArtifactExtraField.class.php    |   6 +-
 src/common/tracker/ArtifactType.class.php          |  31 +-
 src/common/tracker/EffortUnit.class.php            | 334 +++++++++++++
 src/common/tracker/EffortUnitFactory.class.php     | 146 ++++++
 src/common/tracker/EffortUnitSet.class.php         | 339 +++++++++++++
 src/common/tracker/actions/admin-ind.php           |   3 +-
 src/common/tracker/actions/admin-tracker.php       |   7 +
 src/common/tracker/actions/effort_units.php        | 541 +++++++++++++++++++++
 .../tracker/include/ArtifactTypeHtml.class.php     |  61 ++-
 src/common/tracker/views/form-addextrafield.php    |  13 +-
 src/common/tracker/views/form-updateextrafield.php |   4 +-
 src/db/20161129-tracker-field-type-effort.sql      |  75 +++
 .../effortunitsedit.php}                           |  11 +-
 src/www/admin/index.php                            |   2 +
 src/www/project/admin/effortunits.php              |  27 +
 src/www/project/admin/project_admin_utils.php      |   7 +-
 src/www/tracker/admin/index.php                    |  15 +-
 18 files changed, 1625 insertions(+), 26 deletions(-)
 create mode 100644 src/common/tracker/EffortUnit.class.php
 create mode 100644 src/common/tracker/EffortUnitFactory.class.php
 create mode 100644 src/common/tracker/EffortUnitSet.class.php
 create mode 100644 src/common/tracker/actions/effort_units.php
 create mode 100644 src/db/20161129-tracker-field-type-effort.sql
 copy src/www/{account/check_forwarded_session.php => 
admin/effortunitsedit.php} (77%)
 create mode 100644 src/www/project/admin/effortunits.php


hooks/post-receive
-- 
FusionForge

_______________________________________________
Fusionforge-commits mailing list
[email protected]
http://lists.fusionforge.org/cgi-bin/mailman/listinfo/fusionforge-commits

Reply via email to