]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/ProjectTask/ProjectTask.php
Release 6.5.10
[Github/sugarcrm.git] / modules / ProjectTask / ProjectTask.php
1 <?php
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-2013 SugarCRM Inc.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38
39
40
41
42
43
44
45
46
47
48
49
50 class ProjectTask extends SugarBean {
51         // database table columns
52         var $id;
53         var $date_entered;
54         var $date_modified;
55         //var $assigned_user_id;
56         //var $modified_user_id;
57         //var $created_by;
58         var $name;
59     var $description;
60     var $project_id;
61     var $project_task_id;
62     var $date_start;
63     var $date_finish;
64     var $duration;
65     var $duration_unit;
66     var $percent_complete;
67     var $parent_task_id;
68     var $predecessors;
69     var $priority;
70
71         // related information
72         var $assigned_user_name;
73         var $parent_name;
74         var $depends_on_name;
75         var $email_id;
76
77         var $table_name = 'project_task';
78         var $object_name = 'ProjectTask';
79         var $module_dir = 'ProjectTask';
80
81         var $field_name_map;
82         var $new_schema = true;
83
84         var $relationship_fields = array(
85                 'email_id' => 'emails',
86         );
87     /**
88      * @var bool skip updating parent percent complete
89      */
90     protected $_skipParentUpdate = false;
91         
92         //////////////////////////////////////////////////////////////////
93         // METHODS
94         //////////////////////////////////////////////////////////////////
95
96         /*
97          *
98          */
99         function ProjectTask($init=true)
100         {
101                 parent::SugarBean();
102                 if ($init) {
103                         // default value for a clean instantiation
104                         $this->utilization = 100;
105
106                         global $current_user;
107                         if(empty($current_user))
108                         {
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;
113                         }
114                         else
115                         {
116                                 $this->assigned_user_id = $current_user->id;
117                                 $this->assigned_user_name = $current_user->user_name;
118                         }
119
120                 }
121         }
122     /**
123      * @param bool $skip updating parent percent complete
124      */
125     public function skipParentUpdate($skip = true)
126     {
127         $this->_skipParentUpdate = $skip;
128     }
129         function save($check_notify = FALSE)
130         {
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))
134         {
135             $this->project_task_id = $this->getNumberOfTasksInProject($this->project_id) + 1;
136         }
137         
138         $id = parent::save($check_notify);
139         if($this->_skipParentUpdate == false)
140         {
141             $this->updateStatistic();
142         }
143         return $id;
144         }
145
146         /**
147          * overriding the base class function to do a join with users table
148          */
149
150         /*
151          *
152          */
153    function fill_in_additional_detail_fields()
154    {
155       $this->assigned_user_name = get_assigned_user_name($this->assigned_user_id);
156       $this->project_name = $this->_get_project_name($this->project_id);
157                 /*
158         $this->depends_on_name = $this->_get_depends_on_name($this->depends_on_id);
159                 if(empty($this->depends_on_name))
160                 {
161                         $this->depends_on_id = '';
162                 }
163                 $this->parent_name = $this->_get_parent_name($this->parent_id);
164                 if(empty($this->parent_name))
165                 {
166                         $this->parent_id = '';
167                 }
168         */
169    }
170
171         /*
172          *
173          */
174    function fill_in_additional_list_fields()
175    {
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);
179    }
180
181         /*
182          *
183          */
184         function get_summary_text()
185         {
186                 return $this->name;
187         }
188
189         /*
190          *
191          */
192         function _get_depends_on_name($depends_on_id)
193         {
194                 $return_value = '';
195
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);
199                 if($row != null)
200                 {
201                         $this->depends_on_name_owner = $row['assigned_user_id'];
202                         $this->depends_on_name_mod = 'ProjectTask';
203                         $return_value = $row['name'];
204                 }
205
206                 return $return_value;
207         }
208
209     function _get_project_name($project_id)
210     {
211         $return_value = '';
212
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);
216         if($row != null)
217         {
218             //$this->parent_name_owner = $row['assigned_user_id'];
219             //$this->parent_name_mod = 'Project';
220             $return_value = $row['name'];
221         }
222
223         return $return_value;
224     }
225         /*
226          *
227          */
228         function _get_parent_name($parent_id)
229         {
230                 $return_value = '';
231
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);
235                 if($row != null)
236                 {
237                         $this->parent_name_owner = $row['assigned_user_id'];
238                         $this->parent_name_mod = 'Project';
239                         $return_value = $row['name'];
240                 }
241
242                 return $return_value;
243         }
244
245         /*
246          *
247          */
248         function build_generic_where_clause ($the_query_string)
249         {
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%'");
253
254                 $the_where = "";
255                 foreach($where_clauses as $clause)
256                 {
257                         if($the_where != "") $the_where .= " or ";
258                         $the_where .= $clause;
259                 }
260
261                 return $the_where;
262         }
263
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;
271
272                 /*
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>";
275                 }
276
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>";
281                 }else{
282                         $task_fields['DATE_DUE'] = "<font class='futureTask'>".$task_fields['DATE_DUE']."</font>";
283                 }
284         */
285
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'];
294                 }
295
296                 return $task_fields;
297         }
298
299         function bean_implements($interface){
300                 switch($interface){
301                         case 'ACL':return true;
302                 }
303                 return false;
304         }
305         function listviewACLHelper(){
306                 $array_assign = parent::listviewACLHelper();
307                 $is_owner = false;
308                 if(!empty($this->parent_name)){
309
310                         if(!empty($this->parent_name_owner)){
311                                 global $current_user;
312                                 $is_owner = $current_user->id == $this->parent_name_owner;
313                         }
314                 }
315                         if(ACLController::checkAccess('Project', 'view', $is_owner)){
316                                 $array_assign['PARENT'] = 'a';
317                         }else{
318                                 $array_assign['PARENT'] = 'span';
319                         }
320                 $is_owner = false;
321                 if(!empty($this->depends_on_name)){
322
323                         if(!empty($this->depends_on_name_owner)){
324                                 global $current_user;
325                                 $is_owner = $current_user->id == $this->depends_on_name_owner;
326                         }
327                 }
328                         if( ACLController::checkAccess('ProjectTask', 'view', $is_owner)){
329                                 $array_assign['PARENT_TASK'] = 'a';
330                         }else{
331                                 $array_assign['PARENT_TASK'] = 'span';
332                         }
333
334                 return $array_assign;
335         }
336
337     function create_export_query(&$order_by, &$where, $relate_link_join='')
338     {
339         $custom_join = $this->getCustomJoin(true, true, $where);
340         $custom_join['join'] .= $relate_link_join;
341                 $query = "SELECT
342                                 project_task.*,
343                 users.user_name as assigned_user_name ";
344         $query .=  $custom_join['select'];
345
346         $query .= " FROM project_task LEFT JOIN project ON project_task.project_id=project.id AND project.deleted=0 ";
347
348         $query .=  $custom_join['join'];
349         $query .= " LEFT JOIN users
350                         ON project_task.assigned_user_id=users.id ";
351
352         $where_auto = " project_task.deleted=0 ";
353
354         if($where != "")
355                 $query .= "where ($where) AND ".$where_auto;
356         else
357             $query .= "where ".$where_auto;
358
359         if(!empty($order_by)){
360                 //check to see if order by variable already has table name by looking for dot "."
361                 $table_defined_already = strpos($order_by, ".");
362
363                 if($table_defined_already === false){
364                         //table not defined yet, define accounts to avoid "ambigous column" SQL error
365                         $query .= " ORDER BY $order_by";
366                 }else{
367                         //table already defined, just add it to end of query
368                     $query .= " ORDER BY $order_by";
369                 }
370         }
371         return $query;
372     }
373
374
375     /**
376     * This method recalculates the percent complete of a parent task
377     */
378     public function updateParentProjectTaskPercentage()
379         {
380
381                 if (empty($this->parent_task_id))
382                 {
383                         return;
384                 }
385
386                 if (!empty($this->project_id))
387                 {
388             //determine parent task
389             $parentProjectTask = $this->getProjectTaskParent();
390
391             //get task children
392             if ($parentProjectTask)
393             {
394                 $subProjectTasks = $parentProjectTask->getAllSubProjectTasks();
395                 $tasks = array();
396                 foreach($subProjectTasks as &$task)
397                 {
398                     array_push($tasks, $task->toArray(true));
399                 }
400                 $parentProjectTask->percent_complete = $this->_calculateCompletePercent($tasks);
401                 unset($tasks);
402                 $parentProjectTask->save(isset($GLOBALS['check_notify']) ? $GLOBALS['check_notify'] : '');
403             }
404                 }
405         }
406
407     /**
408      * Calculate percent complete for parent task based on it's children tasks
409      * @param $subProjectTasks mixed Array of children tasks
410      * @return int percent complete
411      */
412     private function _calculateCompletePercent(&$subProjectTasks)
413     {
414         $totalHours = 0;
415         $cumulativeDone = 0;
416         //update cumulative calculation - mimics gantt calculation
417         foreach ($subProjectTasks as $key => &$value)
418         {
419             if ($value['duration'] == "")
420             {
421                 $value['duration'] = 0;
422             }
423
424             if ($value['percent_complete'] == "")
425             {
426                 $value['percent_complete'] = 0;
427             }
428
429             if ($value['duration_unit'] == "Hours")
430             {
431                 $totalHours += $value['duration'];
432                 $cumulativeDone += $value['duration'] * ($value['percent_complete'] / 100);
433             }
434             else
435             {
436                 $totalHours += ($value['duration'] * 8);
437                 $cumulativeDone += ($value['duration'] * 8) * ($value['percent_complete'] / 100);
438             }
439         }
440
441         $cumulativePercentage = 0;
442         if ($totalHours != 0)
443         {
444             $cumulativePercentage = round(($cumulativeDone/$totalHours) * 100);
445         }
446         return $cumulativePercentage;
447     }
448
449     /**
450     * Retrieves the parent project task of a project task
451     * returns project task bean
452     */
453     function getProjectTaskParent()
454     {
455
456         $projectTaskParent=false;
457
458         if (!empty($this->parent_task_id) && !empty($this->project_id))
459         {
460             $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";
461             $project_task_id = $this->db->getOne($query, true, "Error retrieving parent project task");
462
463             if (!empty($project_task_id))
464             {
465                 $projectTaskParent = BeanFactory::getBean('ProjectTask', $project_task_id);
466             }
467         }
468
469         return $projectTaskParent;
470     }
471
472     /**
473     * Retrieves all the child project tasks of a project task
474     * returns project task bean array
475     */
476     function getAllSubProjectTasks()
477     {
478                 $projectTasksBeans = array();
479
480         if (!empty($this->project_task_id) && !empty($this->project_id))
481                 {
482             //select all tasks from a project
483             $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";
484
485             $result = $this->db->query($query, true, "Error retrieving child project tasks");
486
487             $projectTasks=array();
488             while($row = $this->db->fetchByAssoc($result))
489             {
490                 $projectTasks[$row['id']]['project_task_id'] = $row['project_task_id'];
491                 $projectTasks[$row['id']]['parent_task_id'] = $row['parent_task_id'];
492             }
493
494             $potentialParentTaskIds[$this->project_task_id] = $this->project_task_id;
495             $actualParentTaskIds=array();
496             $subProjectTasks=array();
497
498             $startProjectTasksCount=0;
499             $endProjectTasksCount=0;
500
501             //get all child tasks
502             $run = true;
503             while ($run)
504             {
505                 $count=0;
506
507                 foreach ($projectTasks as $id=>$values)
508                 {
509                     if (in_array($values['parent_task_id'], $potentialParentTaskIds))
510                     {
511                         $potentialParentTaskIds[$values['project_task_id']] = $values['project_task_id'];
512                         $actualParentTaskIds[$values['parent_task_id']] = $values['parent_task_id'];
513
514                         $subProjectTasks[$id]=$values;
515                         $count=$count+1;
516                     }
517                 }
518
519                 $endProjectTasksCount = count($subProjectTasks);
520
521                 if ($startProjectTasksCount == $endProjectTasksCount)
522                 {
523                     $run = false;
524                 }
525                 else
526                 {
527                     $startProjectTasksCount = $endProjectTasksCount;
528                 }
529             }
530
531             foreach($subProjectTasks as $id=>$values)
532             {
533                 //ignore tasks that are parents
534                 if(!in_array($values['project_task_id'], $actualParentTaskIds))
535                 {
536                     $projectTaskBean = BeanFactory::getBean('ProjectTask', $id);
537                     array_push($projectTasksBeans, $projectTaskBean);
538                 }
539             }
540                 }
541
542                 return $projectTasksBeans;
543         }
544         
545         
546         /**
547          * getNumberOfTasksInProject
548          * 
549          * Returns the count of project_tasks for the given project_id
550          * 
551          * This is a private helper function to get the number of project tasks for a given project_id.
552          * 
553          * @param $project_id integer value of the project_id associated with this ProjectTask instance
554          * @return total integer value of the count of project tasks, 0 if none found
555          */
556     private function getNumberOfTasksInProject($project_id='')
557     {
558         if(!empty($project_id))
559         {
560                 $query = "SELECT count(project_task_id) AS total FROM project_task WHERE project_id = '{$project_id}'";
561                 $result = $this->db->query($query, true);
562                 if($result)
563                 {
564                         $row = $this->db->fetchByAssoc($result);
565                         if(!empty($row['total']))
566                         {
567                            return $row['total'];
568                         }
569                 }
570         }
571         return 0;
572     }   
573
574     /**
575      * Update percent complete for project tasks with children tasks based on children's values
576      */
577     public function updateStatistic()
578     {
579         /**
580          * @var array Array of tasks for current project
581          */
582         $list = array();
583         /**
584          * @var array Key-value array of project_task_id => parent_task_id
585          */
586         $tree = array();
587         /**
588          * @var array Array with nodes which have childrens
589          */
590         $nodes = array();
591         /**
592          * @var array Array with IDs of list which have been changed
593          */
594         $changed = array();
595
596         $db = DBManagerFactory::getInstance();
597         $this->disable_row_level_security = true;
598         $query = $this->create_new_list_query('', "project_id = {$db->quoted($this->project_id)}");
599         $this->disable_row_level_security = false;
600         $res = $db->query($query);
601         while($row = $db->fetchByAssoc($res))
602         {
603             array_push($list, $row);
604         }
605         // fill in $tree
606         foreach($list as $k => &$v)
607         {
608             if(isset($v['project_task_id']) && $v['project_task_id'] != '')
609             {
610                 $tree[$v['project_task_id']] = $v['parent_task_id'];
611                 if(isset($v['parent_task_id']) && $v['parent_task_id'])
612                 {
613                     if(!isset($nodes[$v['parent_task_id']]))
614                     {
615                         $nodes[$v['parent_task_id']] = 1;
616                     }
617                 }
618             }
619         }
620         unset($v);
621         // fill in $nodes array
622         foreach($nodes as $k => &$v)
623         {
624             $run = true;
625             $i = $k;
626             while($run)
627             {
628                 if(isset($tree[$i]) &&  $tree[$i]!= '')
629                 {
630                     $i = $tree[$i];
631                     $v++;
632                 }
633                 else
634                 {
635                     $run = false;
636                 }
637             }
638         }
639         arsort($nodes);
640         unset($v);
641         // calculating of percentages and comparing calculated value with database one
642         foreach($nodes as $k => &$v)
643         {
644             $currRow = null;
645             $currChildren = array();
646             $run = true;
647             $tmp = array();
648             $i = $k;
649             while($run)
650             {
651                 foreach($list as $id => &$taskRow)
652                 {
653                     if($taskRow['project_task_id'] == $i && $currRow === null)
654                     {
655                         $currRow = $id;
656                     }
657                     if($taskRow['parent_task_id'] == $i)
658                     {
659                         if(!in_array($taskRow['project_task_id'], array_keys($nodes)))
660                         {
661                             array_push($currChildren, $taskRow);
662                         }
663                         else
664                         {
665                             array_push($tmp, $taskRow['project_task_id']);
666                         }
667                     }
668                 }
669                 unset($taskRow);
670                 if(count($tmp) == 0)
671                 {
672                     $run = false;
673                 }
674                 else
675                 {
676                     $i = array_shift($tmp);
677                 }
678             }
679             $subres = $this->_calculateCompletePercent($currChildren);
680             if($subres != $list[$currRow]['percent_complete'])
681             {
682                 $list[$currRow]['percent_complete'] = $subres;
683                 array_push($changed, $currRow);
684             }
685         }
686         unset($v);
687         // updating data in database for changed tasks
688         foreach($changed as $k => &$v)
689         {
690             $task = BeanFactory::getBean('ProjectTask');
691             $task->populateFromRow($list[$v]);
692             $task->skipParentUpdate();
693             $task->save(false);
694         }
695     }
696 }
697
698 function getUtilizationDropdown($focus, $field, $value, $view) {
699         global $app_list_strings;
700
701         if($view == 'EditView') {
702                 global $app_list_strings;
703         $html = '<select name="'.$field.'">';
704         $html .= get_select_options_with_id($app_list_strings['project_task_utilization_options'], $value);
705         $html .= '</select>';
706         return $html;
707     }
708
709     return translate('project_task_utilization_options', '', $focus->$field);
710 }
711 ?>