]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/ProjectTask/ProjectTask.php
Release 6.5.1
[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-2012 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->custom_fields->getJOIN(true, true,$where);
340                 if($custom_join)
341                                 $custom_join['join'] .= $relate_link_join;
342                 $query = "SELECT
343                                 project_task.*,
344                 users.user_name as assigned_user_name ";
345         if($custom_join){
346                         $query .=  $custom_join['select'];
347                 }
348
349         $query .= " FROM project_task LEFT JOIN project ON project_task.project_id=project.id AND project.deleted=0 ";
350
351                 if($custom_join){
352                         $query .=  $custom_join['join'];
353                 }
354         $query .= " LEFT JOIN users
355                         ON project_task.assigned_user_id=users.id ";
356
357         $where_auto = " project_task.deleted=0 ";
358
359         if($where != "")
360                 $query .= "where ($where) AND ".$where_auto;
361         else
362             $query .= "where ".$where_auto;
363
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, ".");
367
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";
371                 }else{
372                         //table already defined, just add it to end of query
373                     $query .= " ORDER BY $order_by";
374                 }
375         }
376         return $query;
377     }
378
379
380     /**
381     * This method recalculates the percent complete of a parent task
382     */
383     public function updateParentProjectTaskPercentage()
384         {
385
386                 if (empty($this->parent_task_id))
387                 {
388                         return;
389                 }
390
391                 if (!empty($this->project_id))
392                 {
393             //determine parent task
394             $parentProjectTask = $this->getProjectTaskParent();
395
396             //get task children
397             if ($parentProjectTask)
398             {
399                 $subProjectTasks = $parentProjectTask->getAllSubProjectTasks();
400                 $tasks = array();
401                 foreach($subProjectTasks as &$task)
402                 {
403                     array_push($tasks, $task->toArray(true));
404                 }
405                 $parentProjectTask->percent_complete = $this->_calculateCompletePercent($tasks);
406                 unset($tasks);
407                 $parentProjectTask->save(isset($GLOBALS['check_notify']) ? $GLOBALS['check_notify'] : '');
408             }
409                 }
410         }
411
412     /**
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
416      */
417     private function _calculateCompletePercent(&$subProjectTasks)
418     {
419         $totalHours = 0;
420         $cumulativeDone = 0;
421         //update cumulative calculation - mimics gantt calculation
422         foreach ($subProjectTasks as $key => &$value)
423         {
424             if ($value['duration'] == "")
425             {
426                 $value['duration'] = 0;
427             }
428
429             if ($value['percent_complete'] == "")
430             {
431                 $value['percent_complete'] = 0;
432             }
433
434             if ($value['duration_unit'] == "Hours")
435             {
436                 $totalHours += $value['duration'];
437                 $cumulativeDone += $value['duration'] * ($value['percent_complete'] / 100);
438             }
439             else
440             {
441                 $totalHours += ($value['duration'] * 8);
442                 $cumulativeDone += ($value['duration'] * 8) * ($value['percent_complete'] / 100);
443             }
444         }
445
446         $cumulativePercentage = 0;
447         if ($totalHours != 0)
448         {
449             $cumulativePercentage = round(($cumulativeDone/$totalHours) * 100);
450         }
451         return $cumulativePercentage;
452     }
453
454     /**
455     * Retrieves the parent project task of a project task
456     * returns project task bean
457     */
458     function getProjectTaskParent()
459     {
460
461         $projectTaskParent=false;
462
463         if (!empty($this->parent_task_id) && !empty($this->project_id))
464         {
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");
467
468             if (!empty($project_task_id))
469             {
470                 $projectTaskParent = BeanFactory::getBean('ProjectTask', $project_task_id);
471             }
472         }
473
474         return $projectTaskParent;
475     }
476
477     /**
478     * Retrieves all the child project tasks of a project task
479     * returns project task bean array
480     */
481     function getAllSubProjectTasks()
482     {
483                 $projectTasksBeans = array();
484
485         if (!empty($this->project_task_id) && !empty($this->project_id))
486                 {
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";
489
490             $result = $this->db->query($query, true, "Error retrieving child project tasks");
491
492             $projectTasks=array();
493             while($row = $this->db->fetchByAssoc($result))
494             {
495                 $projectTasks[$row['id']]['project_task_id'] = $row['project_task_id'];
496                 $projectTasks[$row['id']]['parent_task_id'] = $row['parent_task_id'];
497             }
498
499             $potentialParentTaskIds[$this->project_task_id] = $this->project_task_id;
500             $actualParentTaskIds=array();
501             $subProjectTasks=array();
502
503             $startProjectTasksCount=0;
504             $endProjectTasksCount=0;
505
506             //get all child tasks
507             $run = true;
508             while ($run)
509             {
510                 $count=0;
511
512                 foreach ($projectTasks as $id=>$values)
513                 {
514                     if (in_array($values['parent_task_id'], $potentialParentTaskIds))
515                     {
516                         $potentialParentTaskIds[$values['project_task_id']] = $values['project_task_id'];
517                         $actualParentTaskIds[$values['parent_task_id']] = $values['parent_task_id'];
518
519                         $subProjectTasks[$id]=$values;
520                         $count=$count+1;
521                     }
522                 }
523
524                 $endProjectTasksCount = count($subProjectTasks);
525
526                 if ($startProjectTasksCount == $endProjectTasksCount)
527                 {
528                     $run = false;
529                 }
530                 else
531                 {
532                     $startProjectTasksCount = $endProjectTasksCount;
533                 }
534             }
535
536             foreach($subProjectTasks as $id=>$values)
537             {
538                 //ignore tasks that are parents
539                 if(!in_array($values['project_task_id'], $actualParentTaskIds))
540                 {
541                     $projectTaskBean = BeanFactory::getBean('ProjectTask', $id);
542                     array_push($projectTasksBeans, $projectTaskBean);
543                 }
544             }
545                 }
546
547                 return $projectTasksBeans;
548         }
549         
550         
551         /**
552          * getNumberOfTasksInProject
553          * 
554          * Returns the count of project_tasks for the given project_id
555          * 
556          * This is a private helper function to get the number of project tasks for a given project_id.
557          * 
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
560          */
561     private function getNumberOfTasksInProject($project_id='')
562     {
563         if(!empty($project_id))
564         {
565                 $query = "SELECT count(project_task_id) AS total FROM project_task WHERE project_id = '{$project_id}'";
566                 $result = $this->db->query($query, true);
567                 if($result)
568                 {
569                         $row = $this->db->fetchByAssoc($result);
570                         if(!empty($row['total']))
571                         {
572                            return $row['total'];
573                         }
574                 }
575         }
576         return 0;
577     }   
578
579     /**
580      * Update percent complete for project tasks with children tasks based on children's values
581      */
582     public function updateStatistic()
583     {
584         /**
585          * @var array Array of tasks for current project
586          */
587         $list = array();
588         /**
589          * @var array Key-value array of project_task_id => parent_task_id
590          */
591         $tree = array();
592         /**
593          * @var array Array with nodes which have childrens
594          */
595         $nodes = array();
596         /**
597          * @var array Array with IDs of list which have been changed
598          */
599         $changed = array();
600
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))
607         {
608             array_push($list, $row);
609         }
610         // fill in $tree
611         foreach($list as $k => &$v)
612         {
613             if(isset($v['project_task_id']) && $v['project_task_id'] != '')
614             {
615                 $tree[$v['project_task_id']] = $v['parent_task_id'];
616                 if(isset($v['parent_task_id']) && $v['parent_task_id'])
617                 {
618                     if(!isset($nodes[$v['parent_task_id']]))
619                     {
620                         $nodes[$v['parent_task_id']] = 1;
621                     }
622                 }
623             }
624         }
625         unset($v);
626         // fill in $nodes array
627         foreach($nodes as $k => &$v)
628         {
629             $run = true;
630             $i = $k;
631             while($run)
632             {
633                 if(isset($tree[$i]) &&  $tree[$i]!= '')
634                 {
635                     $i = $tree[$i];
636                     $v++;
637                 }
638                 else
639                 {
640                     $run = false;
641                 }
642             }
643         }
644         arsort($nodes);
645         unset($v);
646         // calculating of percentages and comparing calculated value with database one
647         foreach($nodes as $k => &$v)
648         {
649             $currRow = null;
650             $currChildren = array();
651             $run = true;
652             $tmp = array();
653             $i = $k;
654             while($run)
655             {
656                 foreach($list as $id => &$taskRow)
657                 {
658                     if($taskRow['project_task_id'] == $i && $currRow === null)
659                     {
660                         $currRow = $id;
661                     }
662                     if($taskRow['parent_task_id'] == $i)
663                     {
664                         if(!in_array($taskRow['project_task_id'], array_keys($nodes)))
665                         {
666                             array_push($currChildren, $taskRow);
667                         }
668                         else
669                         {
670                             array_push($tmp, $taskRow['project_task_id']);
671                         }
672                     }
673                 }
674                 unset($taskRow);
675                 if(count($tmp) == 0)
676                 {
677                     $run = false;
678                 }
679                 else
680                 {
681                     $i = array_shift($tmp);
682                 }
683             }
684             $subres = $this->_calculateCompletePercent($currChildren);
685             if($subres != $list[$currRow]['percent_complete'])
686             {
687                 $list[$currRow]['percent_complete'] = $subres;
688                 array_push($changed, $currRow);
689             }
690         }
691         unset($v);
692         // updating data in database for changed tasks
693         foreach($changed as $k => &$v)
694         {
695             $task = BeanFactory::getBean('ProjectTask');
696             $task->populateFromRow($list[$v]);
697             $task->skipParentUpdate();
698             $task->save(false);
699         }
700     }
701 }
702
703 function getUtilizationDropdown($focus, $field, $value, $view) {
704         global $app_list_strings;
705
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>';
711         return $html;
712     }
713
714     return translate('project_task_utilization_options', '', $focus->$field);
715 }
716 ?>