2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
50 class ProjectTask extends SugarBean {
51 // database table columns
55 //var $assigned_user_id;
56 //var $modified_user_id;
66 var $percent_complete;
71 // related information
72 var $assigned_user_name;
77 var $table_name = 'project_task';
78 var $object_name = 'ProjectTask';
79 var $module_dir = 'ProjectTask';
82 var $new_schema = true;
84 var $relationship_fields = array(
85 'email_id' => 'emails',
88 * @var bool skip updating parent percent complete
90 protected $_skipParentUpdate = false;
92 //////////////////////////////////////////////////////////////////
94 //////////////////////////////////////////////////////////////////
99 function ProjectTask($init=true)
103 // default value for a clean instantiation
104 $this->utilization = 100;
106 global $current_user;
107 if(empty($current_user))
109 $this->assigned_user_id = 1;
110 $admin_user = new User();
111 $admin_user->retrieve($this->assigned_user_id);
112 $this->assigned_user_name = $admin_user->user_name;
116 $this->assigned_user_id = $current_user->id;
117 $this->assigned_user_name = $current_user->user_name;
123 * @param bool $skip updating parent percent complete
125 public function skipParentUpdate($skip = true)
127 $this->_skipParentUpdate = $skip;
129 function save($check_notify = FALSE)
131 //Bug 46012. When saving new Project Tasks instance in a workflow, make sure we set a project_task_id value
132 //associated with the Project if there is no project_task_id specified.
133 if ($this->in_workflow && empty($this->id) && empty($this->project_task_id) && !empty($this->project_id))
135 $this->project_task_id = $this->getNumberOfTasksInProject($this->project_id) + 1;
138 $id = parent::save($check_notify);
139 if($this->_skipParentUpdate == false)
141 $this->updateStatistic();
147 * overriding the base class function to do a join with users table
153 function fill_in_additional_detail_fields()
155 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
156 $this->project_name = $this->_get_project_name($this->project_id);
158 $this->depends_on_name = $this->_get_depends_on_name($this->depends_on_id);
159 if(empty($this->depends_on_name))
161 $this->depends_on_id = '';
163 $this->parent_name = $this->_get_parent_name($this->parent_id);
164 if(empty($this->parent_name))
166 $this->parent_id = '';
174 function fill_in_additional_list_fields()
176 $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
177 //$this->parent_name = $this->_get_parent_name($this->parent_id);
178 $this->project_name = $this->_get_project_name($this->project_id);
184 function get_summary_text()
192 function _get_depends_on_name($depends_on_id)
196 $query = "SELECT name, assigned_user_id FROM {$this->table_name} WHERE id='{$depends_on_id}'";
197 $result = $this->db->query($query,true," Error filling in additional detail fields: ");
198 $row = $this->db->fetchByAssoc($result);
201 $this->depends_on_name_owner = $row['assigned_user_id'];
202 $this->depends_on_name_mod = 'ProjectTask';
203 $return_value = $row['name'];
206 return $return_value;
209 function _get_project_name($project_id)
213 $query = "SELECT name, assigned_user_id FROM project WHERE id='{$project_id}'";
214 $result = $this->db->query($query,true," Error filling in additional detail fields: ");
215 $row = $this->db->fetchByAssoc($result);
218 //$this->parent_name_owner = $row['assigned_user_id'];
219 //$this->parent_name_mod = 'Project';
220 $return_value = $row['name'];
223 return $return_value;
228 function _get_parent_name($parent_id)
232 $query = "SELECT name, assigned_user_id FROM project WHERE id='{$parent_id}'";
233 $result = $this->db->query($query,true," Error filling in additional detail fields: ");
234 $row = $this->db->fetchByAssoc($result);
237 $this->parent_name_owner = $row['assigned_user_id'];
238 $this->parent_name_mod = 'Project';
239 $return_value = $row['name'];
242 return $return_value;
248 function build_generic_where_clause ($the_query_string)
250 $where_clauses = array();
251 $the_query_string = $GLOBALS['db']->quote($the_query_string);
252 array_push($where_clauses, "project_task.name like '$the_query_string%'");
255 foreach($where_clauses as $clause)
257 if($the_where != "") $the_where .= " or ";
258 $the_where .= $clause;
264 function get_list_view_data(){
265 global $action, $currentModule, $focus, $current_module_strings, $app_list_strings, $timedate, $locale;
266 $today = $timedate->handle_offset(date($GLOBALS['timedate']->get_db_date_time_format(), time()), $timedate->dbDayFormat, true);
267 $task_fields =$this->get_list_view_array();
268 //$date_due = $timedate->to_db_date($task_fields['DATE_DUE'],false);
269 if (isset($this->parent_type))
270 $task_fields['PARENT_MODULE'] = $this->parent_type;
273 if ($this->status != "Completed" && $this->status != "Deferred" ) {
274 $task_fields['SET_COMPLETE'] = "<a href='index.php?return_module=$currentModule&return_action=$action&return_id=" . ((!empty($focus->id)) ? $focus->id : "") . "&module=ProjectTask&action=EditView&record={$this->id}&status=Completed'>".SugarThemeRegistry::current()->getImage("close_inline","alt='Close' border='0'")."</a>";
277 if( $date_due < $today){
278 $task_fields['DATE_DUE']= "<font class='overdueTask'>".$task_fields['DATE_DUE']."</font>";
279 }else if( $date_due == $today ){
280 $task_fields['DATE_DUE'] = "<font class='todaysTask'>".$task_fields['DATE_DUE']."</font>";
282 $task_fields['DATE_DUE'] = "<font class='futureTask'>".$task_fields['DATE_DUE']."</font>";
286 if ( !isset($task_fields["FIRST_NAME"]) )
287 $task_fields["FIRST_NAME"] = '';
288 if ( !isset($task_fields["LAST_NAME"]) )
289 $task_fields["LAST_NAME"] = '';
290 $task_fields['CONTACT_NAME']= $locale->getLocaleFormattedName($task_fields["FIRST_NAME"],$task_fields["LAST_NAME"]);
291 $task_fields['TITLE'] = '';
292 if (!empty($task_fields['CONTACT_NAME'])) {
293 $task_fields['TITLE'] .= $current_module_strings['LBL_LIST_CONTACT'].": ".$task_fields['CONTACT_NAME'];
299 function bean_implements($interface){
301 case 'ACL':return true;
305 function listviewACLHelper(){
306 $array_assign = parent::listviewACLHelper();
308 if(!empty($this->parent_name)){
310 if(!empty($this->parent_name_owner)){
311 global $current_user;
312 $is_owner = $current_user->id == $this->parent_name_owner;
315 if(ACLController::checkAccess('Project', 'view', $is_owner)){
316 $array_assign['PARENT'] = 'a';
318 $array_assign['PARENT'] = 'span';
321 if(!empty($this->depends_on_name)){
323 if(!empty($this->depends_on_name_owner)){
324 global $current_user;
325 $is_owner = $current_user->id == $this->depends_on_name_owner;
328 if( ACLController::checkAccess('ProjectTask', 'view', $is_owner)){
329 $array_assign['PARENT_TASK'] = 'a';
331 $array_assign['PARENT_TASK'] = 'span';
334 return $array_assign;
337 function create_export_query(&$order_by, &$where, $relate_link_join='')
339 $custom_join = $this->custom_fields->getJOIN(true, true,$where);
341 $custom_join['join'] .= $relate_link_join;
344 users.user_name as assigned_user_name ";
346 $query .= $custom_join['select'];
349 $query .= " FROM project_task LEFT JOIN project ON project_task.project_id=project.id AND project.deleted=0 ";
352 $query .= $custom_join['join'];
354 $query .= " LEFT JOIN users
355 ON project_task.assigned_user_id=users.id ";
357 $where_auto = " project_task.deleted=0 ";
360 $query .= "where ($where) AND ".$where_auto;
362 $query .= "where ".$where_auto;
364 if(!empty($order_by)){
365 //check to see if order by variable already has table name by looking for dot "."
366 $table_defined_already = strpos($order_by, ".");
368 if($table_defined_already === false){
369 //table not defined yet, define accounts to avoid "ambigous column" SQL error
370 $query .= " ORDER BY $order_by";
372 //table already defined, just add it to end of query
373 $query .= " ORDER BY $order_by";
381 * This method recalculates the percent complete of a parent task
383 public function updateParentProjectTaskPercentage()
386 if (empty($this->parent_task_id))
391 if (!empty($this->project_id))
393 //determine parent task
394 $parentProjectTask = $this->getProjectTaskParent();
397 if ($parentProjectTask)
399 $subProjectTasks = $parentProjectTask->getAllSubProjectTasks();
401 foreach($subProjectTasks as &$task)
403 array_push($tasks, $task->toArray(true));
405 $parentProjectTask->percent_complete = $this->_calculateCompletePercent($tasks);
407 $parentProjectTask->save(isset($GLOBALS['check_notify']) ? $GLOBALS['check_notify'] : '');
413 * Calculate percent complete for parent task based on it's children tasks
414 * @param $subProjectTasks mixed Array of children tasks
415 * @return int percent complete
417 private function _calculateCompletePercent(&$subProjectTasks)
421 //update cumulative calculation - mimics gantt calculation
422 foreach ($subProjectTasks as $key => &$value)
424 if ($value['duration'] == "")
426 $value['duration'] = 0;
429 if ($value['percent_complete'] == "")
431 $value['percent_complete'] = 0;
434 if ($value['duration_unit'] == "Hours")
436 $totalHours += $value['duration'];
437 $cumulativeDone += $value['duration'] * ($value['percent_complete'] / 100);
441 $totalHours += ($value['duration'] * 8);
442 $cumulativeDone += ($value['duration'] * 8) * ($value['percent_complete'] / 100);
446 $cumulativePercentage = 0;
447 if ($totalHours != 0)
449 $cumulativePercentage = round(($cumulativeDone/$totalHours) * 100);
451 return $cumulativePercentage;
455 * Retrieves the parent project task of a project task
456 * returns project task bean
458 function getProjectTaskParent()
461 $projectTaskParent=false;
463 if (!empty($this->parent_task_id) && !empty($this->project_id))
465 $query = "SELECT id FROM project_task WHERE project_id = '{$this->project_id}' AND project_task_id = '{$this->parent_task_id}' AND deleted = 0 ORDER BY date_modified DESC";
466 $project_task_id = $this->db->getOne($query, true, "Error retrieving parent project task");
468 if (!empty($project_task_id))
470 $projectTaskParent = BeanFactory::getBean('ProjectTask', $project_task_id);
474 return $projectTaskParent;
478 * Retrieves all the child project tasks of a project task
479 * returns project task bean array
481 function getAllSubProjectTasks()
483 $projectTasksBeans = array();
485 if (!empty($this->project_task_id) && !empty($this->project_id))
487 //select all tasks from a project
488 $query = "SELECT id, project_task_id, parent_task_id FROM project_task WHERE project_id = '{$this->project_id}' AND deleted = 0 ORDER BY project_task_id";
490 $result = $this->db->query($query, true, "Error retrieving child project tasks");
492 $projectTasks=array();
493 while($row = $this->db->fetchByAssoc($result))
495 $projectTasks[$row['id']]['project_task_id'] = $row['project_task_id'];
496 $projectTasks[$row['id']]['parent_task_id'] = $row['parent_task_id'];
499 $potentialParentTaskIds[$this->project_task_id] = $this->project_task_id;
500 $actualParentTaskIds=array();
501 $subProjectTasks=array();
503 $startProjectTasksCount=0;
504 $endProjectTasksCount=0;
506 //get all child tasks
512 foreach ($projectTasks as $id=>$values)
514 if (in_array($values['parent_task_id'], $potentialParentTaskIds))
516 $potentialParentTaskIds[$values['project_task_id']] = $values['project_task_id'];
517 $actualParentTaskIds[$values['parent_task_id']] = $values['parent_task_id'];
519 $subProjectTasks[$id]=$values;
524 $endProjectTasksCount = count($subProjectTasks);
526 if ($startProjectTasksCount == $endProjectTasksCount)
532 $startProjectTasksCount = $endProjectTasksCount;
536 foreach($subProjectTasks as $id=>$values)
538 //ignore tasks that are parents
539 if(!in_array($values['project_task_id'], $actualParentTaskIds))
541 $projectTaskBean = BeanFactory::getBean('ProjectTask', $id);
542 array_push($projectTasksBeans, $projectTaskBean);
547 return $projectTasksBeans;
552 * getNumberOfTasksInProject
554 * Returns the count of project_tasks for the given project_id
556 * This is a private helper function to get the number of project tasks for a given project_id.
558 * @param $project_id integer value of the project_id associated with this ProjectTask instance
559 * @return total integer value of the count of project tasks, 0 if none found
561 private function getNumberOfTasksInProject($project_id='')
563 if(!empty($project_id))
565 $query = "SELECT count(project_task_id) AS total FROM project_task WHERE project_id = '{$project_id}'";
566 $result = $this->db->query($query, true);
569 $row = $this->db->fetchByAssoc($result);
570 if(!empty($row['total']))
572 return $row['total'];
580 * Update percent complete for project tasks with children tasks based on children's values
582 public function updateStatistic()
585 * @var array Array of tasks for current project
589 * @var array Key-value array of project_task_id => parent_task_id
593 * @var array Array with nodes which have childrens
597 * @var array Array with IDs of list which have been changed
601 $db = DBManagerFactory::getInstance();
602 $this->disable_row_level_security = true;
603 $query = $this->create_new_list_query('', "project_id = {$db->quoted($this->project_id)}");
604 $this->disable_row_level_security = false;
605 $res = $db->query($query);
606 while($row = $db->fetchByAssoc($res))
608 array_push($list, $row);
611 foreach($list as $k => &$v)
613 if(isset($v['project_task_id']) && $v['project_task_id'] != '')
615 $tree[$v['project_task_id']] = $v['parent_task_id'];
616 if(isset($v['parent_task_id']) && $v['parent_task_id'])
618 if(!isset($nodes[$v['parent_task_id']]))
620 $nodes[$v['parent_task_id']] = 1;
626 // fill in $nodes array
627 foreach($nodes as $k => &$v)
633 if(isset($tree[$i]) && $tree[$i]!= '')
646 // calculating of percentages and comparing calculated value with database one
647 foreach($nodes as $k => &$v)
650 $currChildren = array();
656 foreach($list as $id => &$taskRow)
658 if($taskRow['project_task_id'] == $i && $currRow === null)
662 if($taskRow['parent_task_id'] == $i)
664 if(!in_array($taskRow['project_task_id'], array_keys($nodes)))
666 array_push($currChildren, $taskRow);
670 array_push($tmp, $taskRow['project_task_id']);
681 $i = array_shift($tmp);
684 $subres = $this->_calculateCompletePercent($currChildren);
685 if($subres != $list[$currRow]['percent_complete'])
687 $list[$currRow]['percent_complete'] = $subres;
688 array_push($changed, $currRow);
692 // updating data in database for changed tasks
693 foreach($changed as $k => &$v)
695 $task = BeanFactory::getBean('ProjectTask');
696 $task->populateFromRow($list[$v]);
697 $task->skipParentUpdate();
703 function getUtilizationDropdown($focus, $field, $value, $view) {
704 global $app_list_strings;
706 if($view == 'EditView') {
707 global $app_list_strings;
708 $html = '<select name="'.$field.'">';
709 $html .= get_select_options_with_id($app_list_strings['project_task_utilization_options'], $value);
710 $html .= '</select>';
714 return translate('project_task_utilization_options', '', $focus->$field);