1<?php 2/* Copyright (C) 2008-2014 Laurent Destailleur <eldy@users.sourceforge.net> 3 * Copyright (C) 2010-2012 Regis Houssin <regis.houssin@inodbox.com> 4 * Copyright (C) 2014 Marcos García <marcosgdf@gmail.com> 5 * Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr> 6 * Copyright (C) 2020 Juanjo Menent <jmenent@2byte.es> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 */ 21 22/** 23 * \file htdocs/projet/class/task.class.php 24 * \ingroup project 25 * \brief This file is a CRUD class file for Task (Create/Read/Update/Delete) 26 */ 27 28require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; 29require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php'; 30 31 32/** 33 * Class to manage tasks 34 */ 35class Task extends CommonObject 36{ 37 /** 38 * @var string ID to identify managed object 39 */ 40 public $element = 'project_task'; 41 42 /** 43 * @var string Name of table without prefix where object is stored 44 */ 45 public $table_element = 'projet_task'; 46 47 /** 48 * @var string Field with ID of parent key if this field has a parent 49 */ 50 public $fk_element = 'fk_task'; 51 52 /** 53 * @var string String with name of icon for myobject. 54 */ 55 public $picto = 'projecttask'; 56 57 /** 58 * @var array List of child tables. To test if we can delete object. 59 */ 60 protected $childtables = array('projet_task_time'); 61 62 /** 63 * @var int ID parent task 64 */ 65 public $fk_task_parent = 0; 66 67 /** 68 * @var string Label of task 69 */ 70 public $label; 71 72 /** 73 * @var string description 74 */ 75 public $description; 76 77 public $duration_effective; // total of time spent on this task 78 public $planned_workload; 79 public $date_c; 80 public $date_start; 81 public $date_end; 82 public $progress; 83 84 /** 85 * @var int ID 86 */ 87 public $fk_statut; 88 89 public $priority; 90 91 /** 92 * @var int ID 93 */ 94 public $fk_user_creat; 95 96 /** 97 * @var int ID 98 */ 99 public $fk_user_valid; 100 101 public $rang; 102 103 public $timespent_min_date; 104 public $timespent_max_date; 105 public $timespent_total_duration; 106 public $timespent_total_amount; 107 public $timespent_nblinesnull; 108 public $timespent_nblines; 109 // For detail of lines of timespent record, there is the property ->lines in common 110 111 // Var used to call method addTimeSpent(). Bad practice. 112 public $timespent_id; 113 public $timespent_duration; 114 public $timespent_old_duration; 115 public $timespent_date; 116 public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds) 117 public $timespent_withhour; // 1 = we entered also start hours for timesheet line 118 public $timespent_fk_user; 119 public $timespent_thm; 120 public $timespent_note; 121 122 public $comments = array(); 123 124 public $oldcopy; 125 126 127 /** 128 * Constructor 129 * 130 * @param DoliDB $db Database handler 131 */ 132 public function __construct($db) 133 { 134 $this->db = $db; 135 } 136 137 138 /** 139 * Create into database 140 * 141 * @param User $user User that create 142 * @param int $notrigger 0=launch triggers after, 1=disable triggers 143 * @return int <0 if KO, Id of created object if OK 144 */ 145 public function create($user, $notrigger = 0) 146 { 147 global $conf, $langs; 148 149 //For the date 150 $now = dol_now(); 151 152 $error = 0; 153 154 // Clean parameters 155 $this->label = trim($this->label); 156 $this->description = trim($this->description); 157 158 // Check parameters 159 // Put here code to add control on parameters values 160 161 // Insert request 162 $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet_task ("; 163 $sql .= "entity"; 164 $sql .= ", fk_projet"; 165 $sql .= ", ref"; 166 $sql .= ", fk_task_parent"; 167 $sql .= ", label"; 168 $sql .= ", description"; 169 $sql .= ", datec"; 170 $sql .= ", fk_user_creat"; 171 $sql .= ", dateo"; 172 $sql .= ", datee"; 173 $sql .= ", planned_workload"; 174 $sql .= ", progress"; 175 $sql .= ") VALUES ("; 176 $sql .= ((int) $conf->entity); 177 $sql .= ", ".((int) $this->fk_project); 178 $sql .= ", ".(!empty($this->ref) ? "'".$this->db->escape($this->ref)."'" : 'null'); 179 $sql .= ", ".((int) $this->fk_task_parent); 180 $sql .= ", '".$this->db->escape($this->label)."'"; 181 $sql .= ", '".$this->db->escape($this->description)."'"; 182 $sql .= ", '".$this->db->idate($now)."'"; 183 $sql .= ", ".((int) $user->id); 184 $sql .= ", ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : 'null'); 185 $sql .= ", ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : 'null'); 186 $sql .= ", ".(($this->planned_workload != '' && $this->planned_workload >= 0) ? $this->planned_workload : 'null'); 187 $sql .= ", ".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null'); 188 $sql .= ")"; 189 190 $this->db->begin(); 191 192 dol_syslog(get_class($this)."::create", LOG_DEBUG); 193 $resql = $this->db->query($sql); 194 if (!$resql) { 195 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 196 } 197 198 if (!$error) { 199 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet_task"); 200 201 if (!$notrigger) { 202 // Call trigger 203 $result = $this->call_trigger('TASK_CREATE', $user); 204 if ($result < 0) { 205 $error++; 206 } 207 // End call triggers 208 } 209 } 210 211 // Update extrafield 212 if (!$error) { 213 if (!$error) { 214 $result = $this->insertExtraFields(); 215 if ($result < 0) { 216 $error++; 217 } 218 } 219 } 220 221 // Commit or rollback 222 if ($error) { 223 foreach ($this->errors as $errmsg) { 224 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR); 225 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 226 } 227 $this->db->rollback(); 228 return -1 * $error; 229 } else { 230 $this->db->commit(); 231 return $this->id; 232 } 233 } 234 235 236 /** 237 * Load object in memory from database 238 * 239 * @param int $id Id object 240 * @param int $ref ref object 241 * @param int $loadparentdata Also load parent data 242 * @return int <0 if KO, 0 if not found, >0 if OK 243 */ 244 public function fetch($id, $ref = '', $loadparentdata = 0) 245 { 246 global $langs, $conf; 247 248 $sql = "SELECT"; 249 $sql .= " t.rowid,"; 250 $sql .= " t.ref,"; 251 $sql .= " t.fk_projet as fk_project,"; 252 $sql .= " t.fk_task_parent,"; 253 $sql .= " t.label,"; 254 $sql .= " t.description,"; 255 $sql .= " t.duration_effective,"; 256 $sql .= " t.planned_workload,"; 257 $sql .= " t.datec,"; 258 $sql .= " t.dateo,"; 259 $sql .= " t.datee,"; 260 $sql .= " t.fk_user_creat,"; 261 $sql .= " t.fk_user_valid,"; 262 $sql .= " t.fk_statut,"; 263 $sql .= " t.progress,"; 264 $sql .= " t.priority,"; 265 $sql .= " t.note_private,"; 266 $sql .= " t.note_public,"; 267 $sql .= " t.rang"; 268 if (!empty($loadparentdata)) { 269 $sql .= ", t2.ref as task_parent_ref"; 270 $sql .= ", t2.rang as task_parent_position"; 271 } 272 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task as t"; 273 if (!empty($loadparentdata)) { 274 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t2 ON t.fk_task_parent = t2.rowid"; 275 } 276 $sql .= " WHERE "; 277 if (!empty($ref)) { 278 $sql .= "entity IN (".getEntity('project').")"; 279 $sql .= " AND t.ref = '".$this->db->escape($ref)."'"; 280 } else { 281 $sql .= "t.rowid = ".((int) $id); 282 } 283 284 dol_syslog(get_class($this)."::fetch", LOG_DEBUG); 285 $resql = $this->db->query($sql); 286 if ($resql) { 287 $num_rows = $this->db->num_rows($resql); 288 289 if ($num_rows) { 290 $obj = $this->db->fetch_object($resql); 291 292 $this->id = $obj->rowid; 293 $this->ref = $obj->ref; 294 $this->fk_project = $obj->fk_project; 295 $this->fk_task_parent = $obj->fk_task_parent; 296 $this->label = $obj->label; 297 $this->description = $obj->description; 298 $this->duration_effective = $obj->duration_effective; 299 $this->planned_workload = $obj->planned_workload; 300 $this->date_c = $this->db->jdate($obj->datec); 301 $this->date_start = $this->db->jdate($obj->dateo); 302 $this->date_end = $this->db->jdate($obj->datee); 303 $this->fk_user_creat = $obj->fk_user_creat; 304 $this->fk_user_valid = $obj->fk_user_valid; 305 $this->fk_statut = $obj->fk_statut; 306 $this->progress = $obj->progress; 307 $this->priority = $obj->priority; 308 $this->note_private = $obj->note_private; 309 $this->note_public = $obj->note_public; 310 $this->rang = $obj->rang; 311 312 if (!empty($loadparentdata)) { 313 $this->task_parent_ref = $obj->task_parent_ref; 314 $this->task_parent_position = $obj->task_parent_position; 315 } 316 317 // Retrieve all extrafield 318 $this->fetch_optionals(); 319 } 320 321 $this->db->free($resql); 322 323 if ($num_rows) { 324 return 1; 325 } else { 326 return 0; 327 } 328 } else { 329 $this->error = "Error ".$this->db->lasterror(); 330 return -1; 331 } 332 } 333 334 335 /** 336 * Update database 337 * 338 * @param User $user User that modify 339 * @param int $notrigger 0=launch triggers after, 1=disable triggers 340 * @return int <=0 if KO, >0 if OK 341 */ 342 public function update($user = null, $notrigger = 0) 343 { 344 global $conf, $langs; 345 $error = 0; 346 347 // Clean parameters 348 if (isset($this->fk_project)) { 349 $this->fk_project = trim($this->fk_project); 350 } 351 if (isset($this->ref)) { 352 $this->ref = trim($this->ref); 353 } 354 if (isset($this->fk_task_parent)) { 355 $this->fk_task_parent = (int) $this->fk_task_parent; 356 } 357 if (isset($this->label)) { 358 $this->label = trim($this->label); 359 } 360 if (isset($this->description)) { 361 $this->description = trim($this->description); 362 } 363 if (isset($this->duration_effective)) { 364 $this->duration_effective = trim($this->duration_effective); 365 } 366 if (isset($this->planned_workload)) { 367 $this->planned_workload = trim($this->planned_workload); 368 } 369 370 // Check parameters 371 // Put here code to add control on parameters values 372 373 // Update request 374 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task SET"; 375 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").","; 376 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "'".$this->db->escape($this->id)."'").","; 377 $sql .= " fk_task_parent=".(isset($this->fk_task_parent) ? $this->fk_task_parent : "null").","; 378 $sql .= " label=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").","; 379 $sql .= " description=".(isset($this->description) ? "'".$this->db->escape($this->description)."'" : "null").","; 380 $sql .= " duration_effective=".(isset($this->duration_effective) ? $this->duration_effective : "null").","; 381 $sql .= " planned_workload=".((isset($this->planned_workload) && $this->planned_workload != '') ? $this->planned_workload : "null").","; 382 $sql .= " dateo=".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null').","; 383 $sql .= " datee=".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null').","; 384 $sql .= " progress=".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null').","; 385 $sql .= " rang=".((!empty($this->rang)) ? $this->rang : "0"); 386 $sql .= " WHERE rowid=".((int) $this->id); 387 388 $this->db->begin(); 389 390 dol_syslog(get_class($this)."::update", LOG_DEBUG); 391 $resql = $this->db->query($sql); 392 if (!$resql) { 393 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 394 } 395 396 // Update extrafield 397 if (!$error) { 398 $result = $this->insertExtraFields(); 399 if ($result < 0) { 400 $error++; 401 } 402 } 403 404 if (!$error && !empty($conf->global->PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE)) { 405 // Close the parent project if it is open (validated) and its tasks are 100% completed 406 $project = new Project($this->db); 407 if ($project->fetch($this->fk_project) > 0) { 408 if ($project->statut == Project::STATUS_VALIDATED) { 409 $project->getLinesArray(null); // this method does not return <= 0 if fails 410 $projectCompleted = array_reduce( 411 $project->lines, 412 function ($allTasksCompleted, $task) { 413 return $allTasksCompleted && $task->progress >= 100; 414 }, 415 1 416 ); 417 if ($projectCompleted) { 418 if ($project->setClose($user) <= 0) { 419 $error++; 420 } 421 } 422 } 423 } else { 424 $error++; 425 } 426 if ($error) { 427 $this->errors[] = $project->error; 428 } 429 } 430 431 if (!$error) { 432 if (!$notrigger) { 433 // Call trigger 434 $result = $this->call_trigger('TASK_MODIFY', $user); 435 if ($result < 0) { 436 $error++; 437 } 438 // End call triggers 439 } 440 } 441 442 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) { 443 // We remove directory 444 if ($conf->projet->dir_output) { 445 $project = new Project($this->db); 446 $project->fetch($this->fk_project); 447 448 $olddir = $conf->projet->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->oldcopy->ref); 449 $newdir = $conf->projet->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->ref); 450 if (file_exists($olddir)) { 451 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 452 $res = dol_move($olddir, $newdir); 453 if (!$res) { 454 $langs->load("errors"); 455 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir); 456 $error++; 457 } 458 } 459 } 460 } 461 462 // Commit or rollback 463 if ($error) { 464 foreach ($this->errors as $errmsg) { 465 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR); 466 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 467 } 468 $this->db->rollback(); 469 return -1 * $error; 470 } else { 471 $this->db->commit(); 472 return 1; 473 } 474 } 475 476 477 /** 478 * Delete task from database 479 * 480 * @param User $user User that delete 481 * @param int $notrigger 0=launch triggers after, 1=disable triggers 482 * @return int <0 if KO, >0 if OK 483 */ 484 public function delete($user, $notrigger = 0) 485 { 486 487 global $conf, $langs; 488 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 489 490 $error = 0; 491 492 $this->db->begin(); 493 494 if ($this->hasChildren() > 0) { 495 dol_syslog(get_class($this)."::delete Can't delete record as it has some sub tasks", LOG_WARNING); 496 $this->error = 'ErrorRecordHasSubTasks'; 497 $this->db->rollback(); 498 return 0; 499 } 500 501 $objectisused = $this->isObjectUsed($this->id); 502 if (!empty($objectisused)) { 503 dol_syslog(get_class($this)."::delete Can't delete record as it has some child", LOG_WARNING); 504 $this->error = 'ErrorRecordHasChildren'; 505 $this->db->rollback(); 506 return 0; 507 } 508 509 if (!$error) { 510 // Delete linked contacts 511 $res = $this->delete_linked_contact(); 512 if ($res < 0) { 513 $this->error = 'ErrorFailToDeleteLinkedContact'; 514 //$error++; 515 $this->db->rollback(); 516 return 0; 517 } 518 } 519 520 if (!$error) { 521 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_time"; 522 $sql .= " WHERE fk_task=".$this->id; 523 524 $resql = $this->db->query($sql); 525 if (!$resql) { 526 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 527 } 528 } 529 530 if (!$error) { 531 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_extrafields"; 532 $sql .= " WHERE fk_object=".$this->id; 533 534 $resql = $this->db->query($sql); 535 if (!$resql) { 536 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 537 } 538 } 539 540 if (!$error) { 541 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task"; 542 $sql .= " WHERE rowid=".((int) $this->id); 543 544 $resql = $this->db->query($sql); 545 if (!$resql) { 546 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 547 } 548 } 549 550 if (!$error) { 551 if (!$notrigger) { 552 // Call trigger 553 $result = $this->call_trigger('TASK_DELETE', $user); 554 if ($result < 0) { 555 $error++; 556 } 557 // End call triggers 558 } 559 } 560 561 // Commit or rollback 562 if ($error) { 563 foreach ($this->errors as $errmsg) { 564 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); 565 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 566 } 567 $this->db->rollback(); 568 return -1 * $error; 569 } else { 570 //Delete associated link file 571 if ($conf->projet->dir_output) { 572 $projectstatic = new Project($this->db); 573 $projectstatic->fetch($this->fk_project); 574 575 $dir = $conf->projet->dir_output."/".dol_sanitizeFileName($projectstatic->ref).'/'.dol_sanitizeFileName($this->id); 576 dol_syslog(get_class($this)."::delete dir=".$dir, LOG_DEBUG); 577 if (file_exists($dir)) { 578 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 579 $res = @dol_delete_dir_recursive($dir); 580 if (!$res) { 581 $this->error = 'ErrorFailToDeleteDir'; 582 $this->db->rollback(); 583 return 0; 584 } 585 } 586 } 587 588 $this->db->commit(); 589 590 return 1; 591 } 592 } 593 594 /** 595 * Return nb of children 596 * 597 * @return int <0 if KO, 0 if no children, >0 if OK 598 */ 599 public function hasChildren() 600 { 601 $error = 0; 602 $ret = 0; 603 604 $sql = "SELECT COUNT(*) as nb"; 605 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task"; 606 $sql .= " WHERE fk_task_parent=".$this->id; 607 608 dol_syslog(get_class($this)."::hasChildren", LOG_DEBUG); 609 $resql = $this->db->query($sql); 610 if (!$resql) { 611 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 612 } else { 613 $obj = $this->db->fetch_object($resql); 614 if ($obj) { 615 $ret = $obj->nb; 616 } 617 $this->db->free($resql); 618 } 619 620 if (!$error) { 621 return $ret; 622 } else { 623 return -1; 624 } 625 } 626 627 /** 628 * Return nb of time spent 629 * 630 * @return int <0 if KO, 0 if no children, >0 if OK 631 */ 632 public function hasTimeSpent() 633 { 634 $error = 0; 635 $ret = 0; 636 637 $sql = "SELECT COUNT(*) as nb"; 638 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time"; 639 $sql .= " WHERE fk_task=".$this->id; 640 641 dol_syslog(get_class($this)."::hasTimeSpent", LOG_DEBUG); 642 $resql = $this->db->query($sql); 643 if (!$resql) { 644 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 645 } else { 646 $obj = $this->db->fetch_object($resql); 647 if ($obj) { 648 $ret = $obj->nb; 649 } 650 $this->db->free($resql); 651 } 652 653 if (!$error) { 654 return $ret; 655 } else { 656 return -1; 657 } 658 } 659 660 661 /** 662 * Return clicable name (with picto eventually) 663 * 664 * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto 665 * @param string $option 'withproject' or '' 666 * @param string $mode Mode 'task', 'time', 'contact', 'note', document' define page to link to. 667 * @param int $addlabel 0=Default, 1=Add label into string, >1=Add first chars into string 668 * @param string $sep Separator between ref and label if option addlabel is set 669 * @param int $notooltip 1=Disable tooltip 670 * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking 671 * @return string Chaine avec URL 672 */ 673 public function getNomUrl($withpicto = 0, $option = '', $mode = 'task', $addlabel = 0, $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1) 674 { 675 global $conf, $langs, $user; 676 677 if (!empty($conf->dol_no_mouse_hover)) { 678 $notooltip = 1; // Force disable tooltips 679 } 680 681 $result = ''; 682 $label = img_picto('', $this->picto).' <u>'.$langs->trans("Task").'</u>'; 683 if (!empty($this->ref)) { 684 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref; 685 } 686 if (!empty($this->label)) { 687 $label .= '<br><b>'.$langs->trans('LabelTask').':</b> '.$this->label; 688 } 689 if ($this->date_start || $this->date_end) { 690 $label .= "<br>".get_date_range($this->date_start, $this->date_end, '', $langs, 0); 691 } 692 693 $url = DOL_URL_ROOT.'/projet/tasks/'.$mode.'.php?id='.$this->id.($option == 'withproject' ? '&withproject=1' : ''); 694 // Add param to save lastsearch_values or not 695 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); 696 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { 697 $add_save_lastsearch_values = 1; 698 } 699 if ($add_save_lastsearch_values) { 700 $url .= '&save_lastsearch_values=1'; 701 } 702 703 $linkclose = ''; 704 if (empty($notooltip)) { 705 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { 706 $label = $langs->trans("ShowTask"); 707 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; 708 } 709 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; 710 $linkclose .= ' class="classfortooltip nowraponall"'; 711 } else { 712 $linkclose .= ' class="nowraponall"'; 713 } 714 715 $linkstart = '<a href="'.$url.'"'; 716 $linkstart .= $linkclose.'>'; 717 $linkend = '</a>'; 718 719 $picto = 'projecttask'; 720 721 $result .= $linkstart; 722 if ($withpicto) { 723 $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1); 724 } 725 if ($withpicto != 2) { 726 $result .= $this->ref; 727 } 728 $result .= $linkend; 729 if ($withpicto != 2) { 730 $result .= (($addlabel && $this->label) ? $sep.dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : ''); 731 } 732 733 return $result; 734 } 735 736 /** 737 * Initialise an instance with random values. 738 * Used to build previews or test instances. 739 * id must be 0 if object instance is a specimen. 740 * 741 * @return void 742 */ 743 public function initAsSpecimen() 744 { 745 $this->id = 0; 746 747 $this->fk_project = ''; 748 $this->ref = 'TK01'; 749 $this->fk_task_parent = null; 750 $this->label = 'Specimen task TK01'; 751 $this->duration_effective = ''; 752 $this->fk_user_creat = null; 753 $this->progress = '25'; 754 $this->fk_statut = null; 755 $this->note = 'This is a specimen task not'; 756 } 757 758 /** 759 * Return list of tasks for all projects or for one particular project 760 * Sort order is on project, then on position of task, and last on start date of first level task 761 * 762 * @param User $usert Object user to limit tasks affected to a particular user 763 * @param User $userp Object user to limit projects of a particular user and public projects 764 * @param int $projectid Project id 765 * @param int $socid Third party id 766 * @param int $mode 0=Return list of tasks and their projects, 1=Return projects and tasks if exists 767 * @param string $filteronproj Filter on project ref or label 768 * @param string $filteronprojstatus Filter on project status ('-1'=no filter, '0,1'=Draft+Validated only) 769 * @param string $morewherefilter Add more filter into where SQL request (must start with ' AND ...') 770 * @param string $filteronprojuser Filter on user that is a contact of project 771 * @param string $filterontaskuser Filter on user assigned to task 772 * @param array $extrafields Show additional column from project or task 773 * @param int $includebilltime Calculate also the time to bill and billed 774 * @param array $search_array_options Array of search 775 * @return array Array of tasks 776 */ 777 public function getTasksArray($usert = null, $userp = null, $projectid = 0, $socid = 0, $mode = 0, $filteronproj = '', $filteronprojstatus = '-1', $morewherefilter = '', $filteronprojuser = 0, $filterontaskuser = 0, $extrafields = array(), $includebilltime = 0, $search_array_options = array()) 778 { 779 global $conf, $hookmanager; 780 781 $tasks = array(); 782 783 //print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'<br>'; 784 785 // List of tasks (does not care about permissions. Filtering will be done later) 786 $sql = "SELECT "; 787 if ($filteronprojuser > 0 || $filterontaskuser > 0) { 788 $sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task 789 } 790 $sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,"; 791 $sql .= " t.rowid as taskid, t.ref as taskref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut as status,"; 792 $sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang,"; 793 $sql .= " t.description, "; 794 $sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,"; 795 $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount"; 796 if (!empty($extrafields->attributes['projet']['label'])) { 797 foreach ($extrafields->attributes['projet']['label'] as $key => $val) { 798 $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key.' as options_'.$key : ''); 799 } 800 } 801 if (!empty($extrafields->attributes['projet_task']['label'])) { 802 foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { 803 $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key.' as options_'.$key : ''); 804 } 805 } 806 if ($includebilltime) { 807 $sql .= ", SUM(tt.task_duration * ".$this->db->ifsql("invoice_id IS NULL", "1", "0").") as tobill, SUM(tt.task_duration * ".$this->db->ifsql("invoice_id IS NULL", "0", "1").") as billed"; 808 } 809 810 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; 811 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid"; 812 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_extrafields as efp ON (p.rowid = efp.fk_object)"; 813 814 if ($mode == 0) { 815 if ($filteronprojuser > 0) { 816 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; 817 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc"; 818 } 819 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; 820 if ($includebilltime) { 821 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; 822 } 823 if ($filterontaskuser > 0) { 824 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2"; 825 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2"; 826 } 827 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)"; 828 $sql .= " WHERE p.entity IN (".getEntity('project').")"; 829 $sql .= " AND t.fk_projet = p.rowid"; 830 } elseif ($mode == 1) { 831 if ($filteronprojuser > 0) { 832 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; 833 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc"; 834 } 835 if ($filterontaskuser > 0) { 836 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; 837 if ($includebilltime) { 838 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; 839 } 840 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2"; 841 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2"; 842 } else { 843 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t on t.fk_projet = p.rowid"; 844 if ($includebilltime) { 845 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid"; 846 } 847 } 848 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)"; 849 $sql .= " WHERE p.entity IN (".getEntity('project').")"; 850 } else { 851 return 'BadValueForParameterMode'; 852 } 853 854 if ($filteronprojuser > 0) { 855 $sql .= " AND p.rowid = ec.element_id"; 856 $sql .= " AND ctc.rowid = ec.fk_c_type_contact"; 857 $sql .= " AND ctc.element = 'project'"; 858 $sql .= " AND ec.fk_socpeople = ".((int) $filteronprojuser); 859 $sql .= " AND ec.statut = 4"; 860 $sql .= " AND ctc.source = 'internal'"; 861 } 862 if ($filterontaskuser > 0) { 863 $sql .= " AND t.fk_projet = p.rowid"; 864 $sql .= " AND p.rowid = ec2.element_id"; 865 $sql .= " AND ctc2.rowid = ec2.fk_c_type_contact"; 866 $sql .= " AND ctc2.element = 'project_task'"; 867 $sql .= " AND ec2.fk_socpeople = ".((int) $filterontaskuser); 868 $sql .= " AND ec2.statut = 4"; 869 $sql .= " AND ctc2.source = 'internal'"; 870 } 871 if ($socid) { 872 $sql .= " AND p.fk_soc = ".((int) $socid); 873 } 874 if ($projectid) { 875 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectid).")"; 876 } 877 if ($filteronproj) { 878 $sql .= natural_search(array("p.ref", "p.title"), $filteronproj); 879 } 880 if ($filteronprojstatus && $filteronprojstatus != '-1') { 881 $sql .= " AND p.fk_statut IN (".$this->db->sanitize($filteronprojstatus).")"; 882 } 883 if ($morewherefilter) { 884 $sql .= $morewherefilter; 885 } 886 // Add where from extra fields 887 $extrafieldsobjectkey = 'projet_task'; 888 $extrafieldsobjectprefix = 'efpt.'; 889 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; 890 // Add where from hooks 891 $parameters = array(); 892 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook 893 $sql .= $hookmanager->resPrint; 894 if ($includebilltime) { 895 $sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,"; 896 $sql .= " t.datec, t.dateo, t.datee, t.tms,"; 897 $sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,"; 898 $sql .= " t.dateo, t.datee, t.planned_workload, t.rang,"; 899 $sql .= " t.description, "; 900 $sql .= " s.rowid, s.nom, s.email,"; 901 $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount"; 902 if (!empty($extrafields->attributes['projet']['label'])) { 903 foreach ($extrafields->attributes['projet']['label'] as $key => $val) { 904 $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key : ''); 905 } 906 } 907 if (!empty($extrafields->attributes['projet_task']['label'])) { 908 foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { 909 $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key : ''); 910 } 911 } 912 } 913 914 915 $sql .= " ORDER BY p.ref, t.rang, t.dateo"; 916 917 //print $sql;exit; 918 dol_syslog(get_class($this)."::getTasksArray", LOG_DEBUG); 919 $resql = $this->db->query($sql); 920 if ($resql) { 921 $num = $this->db->num_rows($resql); 922 $i = 0; 923 // Loop on each record found, so each couple (project id, task id) 924 while ($i < $num) { 925 $error = 0; 926 927 $obj = $this->db->fetch_object($resql); 928 929 if ((!$obj->public) && (is_object($userp))) { // If not public project and we ask a filter on project owned by a user 930 if (!$this->getUserRolesForProjectsOrTasks($userp, 0, $obj->projectid, 0)) { 931 $error++; 932 } 933 } 934 if (is_object($usert)) { // If we ask a filter on a user affected to a task 935 if (!$this->getUserRolesForProjectsOrTasks(0, $usert, $obj->projectid, $obj->taskid)) { 936 $error++; 937 } 938 } 939 940 if (!$error) { 941 $tasks[$i] = new Task($this->db); 942 $tasks[$i]->id = $obj->taskid; 943 $tasks[$i]->ref = $obj->taskref; 944 $tasks[$i]->fk_project = $obj->projectid; 945 $tasks[$i]->projectref = $obj->ref; 946 $tasks[$i]->projectlabel = $obj->plabel; 947 $tasks[$i]->projectstatus = $obj->projectstatus; 948 949 $tasks[$i]->fk_opp_status = $obj->fk_opp_status; 950 $tasks[$i]->opp_amount = $obj->opp_amount; 951 $tasks[$i]->opp_percent = $obj->opp_percent; 952 $tasks[$i]->budget_amount = $obj->budget_amount; 953 $tasks[$i]->usage_bill_time = $obj->usage_bill_time; 954 955 $tasks[$i]->label = $obj->label; 956 $tasks[$i]->description = $obj->description; 957 $tasks[$i]->fk_parent = $obj->fk_task_parent; // deprecated 958 $tasks[$i]->fk_task_parent = $obj->fk_task_parent; 959 $tasks[$i]->duration = $obj->duration_effective; 960 $tasks[$i]->planned_workload = $obj->planned_workload; 961 962 if ($includebilltime) { 963 $tasks[$i]->tobill = $obj->tobill; 964 $tasks[$i]->billed = $obj->billed; 965 } 966 967 $tasks[$i]->progress = $obj->progress; 968 $tasks[$i]->fk_statut = $obj->status; 969 $tasks[$i]->public = $obj->public; 970 $tasks[$i]->date_start = $this->db->jdate($obj->date_start); 971 $tasks[$i]->date_end = $this->db->jdate($obj->date_end); 972 $tasks[$i]->rang = $obj->rang; 973 974 $tasks[$i]->socid = $obj->thirdparty_id; // For backward compatibility 975 $tasks[$i]->thirdparty_id = $obj->thirdparty_id; 976 $tasks[$i]->thirdparty_name = $obj->thirdparty_name; 977 $tasks[$i]->thirdparty_email = $obj->thirdparty_email; 978 979 if (!empty($extrafields->attributes['projet']['label'])) { 980 foreach ($extrafields->attributes['projet']['label'] as $key => $val) { 981 if ($extrafields->attributes['projet']['type'][$key] != 'separate') { 982 $tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key}; 983 } 984 } 985 } 986 987 if (!empty($extrafields->attributes['projet_task']['label'])) { 988 foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) { 989 if ($extrafields->attributes['projet_task']['type'][$key] != 'separate') { 990 $tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key}; 991 } 992 } 993 } 994 } 995 996 $i++; 997 } 998 $this->db->free($resql); 999 } else { 1000 dol_print_error($this->db); 1001 } 1002 1003 return $tasks; 1004 } 1005 1006 /** 1007 * Return list of roles for a user for each projects or each tasks (or a particular project or a particular task). 1008 * 1009 * @param User $userp Return roles on project for this internal user. If set, usert and taskid must not be defined. 1010 * @param User $usert Return roles on task for this internal user. If set userp must NOT be defined. -1 means no filter. 1011 * @param int $projectid Project id list separated with , to filter on project 1012 * @param int $taskid Task id to filter on a task 1013 * @param integer $filteronprojstatus Filter on project status if userp is set. Not used if userp not defined. 1014 * @return array Array (projectid => 'list of roles for project' or taskid => 'list of roles for task') 1015 */ 1016 public function getUserRolesForProjectsOrTasks($userp, $usert, $projectid = '', $taskid = 0, $filteronprojstatus = -1) 1017 { 1018 $arrayroles = array(); 1019 1020 dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks userp=".is_object($userp)." usert=".is_object($usert)." projectid=".$projectid." taskid=".$taskid); 1021 1022 // We want role of user for a projet or role of user for a task. Both are not possible. 1023 if (empty($userp) && empty($usert)) { 1024 $this->error = "CallWithWrongParameters"; 1025 return -1; 1026 } 1027 if (!empty($userp) && !empty($usert)) { 1028 $this->error = "CallWithWrongParameters"; 1029 return -1; 1030 } 1031 1032 /* Liste des taches et role sur les projets ou taches */ 1033 $sql = "SELECT pt.rowid as pid, ec.element_id, ctc.code, ctc.source"; 1034 if ($userp) { 1035 $sql .= " FROM ".MAIN_DB_PREFIX."projet as pt"; 1036 } 1037 if ($usert && $filteronprojstatus > -1) { 1038 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p, ".MAIN_DB_PREFIX."projet_task as pt"; 1039 } 1040 if ($usert && $filteronprojstatus <= -1) { 1041 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task as pt"; 1042 } 1043 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec"; 1044 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc"; 1045 $sql .= " WHERE pt.rowid = ec.element_id"; 1046 if ($userp && $filteronprojstatus > -1) { 1047 $sql .= " AND pt.fk_statut = ".((int) $filteronprojstatus); 1048 } 1049 if ($usert && $filteronprojstatus > -1) { 1050 $sql .= " AND pt.fk_projet = p.rowid AND p.fk_statut = ".((int) $filteronprojstatus); 1051 } 1052 if ($userp) { 1053 $sql .= " AND ctc.element = 'project'"; 1054 } 1055 if ($usert) { 1056 $sql .= " AND ctc.element = 'project_task'"; 1057 } 1058 $sql .= " AND ctc.rowid = ec.fk_c_type_contact"; 1059 if ($userp) { 1060 $sql .= " AND ec.fk_socpeople = ".((int) $userp->id); 1061 } 1062 if ($usert) { 1063 $sql .= " AND ec.fk_socpeople = ".((int) $usert->id); 1064 } 1065 $sql .= " AND ec.statut = 4"; 1066 $sql .= " AND ctc.source = 'internal'"; 1067 if ($projectid) { 1068 if ($userp) { 1069 $sql .= " AND pt.rowid IN (".$this->db->sanitize($projectid).")"; 1070 } 1071 if ($usert) { 1072 $sql .= " AND pt.fk_projet IN (".$this->db->sanitize($projectid).")"; 1073 } 1074 } 1075 if ($taskid) { 1076 if ($userp) { 1077 $sql .= " ERROR SHOULD NOT HAPPENS"; 1078 } 1079 if ($usert) { 1080 $sql .= " AND pt.rowid = ".((int) $taskid); 1081 } 1082 } 1083 //print $sql; 1084 1085 dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks execute request", LOG_DEBUG); 1086 $resql = $this->db->query($sql); 1087 if ($resql) { 1088 $num = $this->db->num_rows($resql); 1089 $i = 0; 1090 while ($i < $num) { 1091 $obj = $this->db->fetch_object($resql); 1092 if (empty($arrayroles[$obj->pid])) { 1093 $arrayroles[$obj->pid] = $obj->code; 1094 } else { 1095 $arrayroles[$obj->pid] .= ','.$obj->code; 1096 } 1097 $i++; 1098 } 1099 $this->db->free($resql); 1100 } else { 1101 dol_print_error($this->db); 1102 } 1103 1104 return $arrayroles; 1105 } 1106 1107 1108 /** 1109 * Return list of id of contacts of task 1110 * 1111 * @param string $source Source 1112 * @return array Array of id of contacts 1113 */ 1114 public function getListContactId($source = 'internal') 1115 { 1116 $contactAlreadySelected = array(); 1117 $tab = $this->liste_contact(-1, $source); 1118 //var_dump($tab); 1119 $num = count($tab); 1120 $i = 0; 1121 while ($i < $num) { 1122 if ($source == 'thirdparty') { 1123 $contactAlreadySelected[$i] = $tab[$i]['socid']; 1124 } else { 1125 $contactAlreadySelected[$i] = $tab[$i]['id']; 1126 } 1127 $i++; 1128 } 1129 return $contactAlreadySelected; 1130 } 1131 1132 1133 /** 1134 * Add time spent 1135 * 1136 * @param User $user User object 1137 * @param int $notrigger 0=launch triggers after, 1=disable triggers 1138 * @return int <=0 if KO, >0 if OK 1139 */ 1140 public function addTimeSpent($user, $notrigger = 0) 1141 { 1142 global $conf, $langs; 1143 1144 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG); 1145 1146 $ret = 0; 1147 1148 // Check parameters 1149 if (!is_object($user)) { 1150 dol_print_error('', "Method addTimeSpent was called with wrong parameter user"); 1151 return -1; 1152 } 1153 1154 // Clean parameters 1155 if (isset($this->timespent_note)) { 1156 $this->timespent_note = trim($this->timespent_note); 1157 } 1158 if (empty($this->timespent_datehour)) { 1159 $this->timespent_datehour = $this->timespent_date; 1160 } 1161 1162 $this->db->begin(); 1163 1164 $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet_task_time ("; 1165 $sql .= "fk_task"; 1166 $sql .= ", task_date"; 1167 $sql .= ", task_datehour"; 1168 $sql .= ", task_date_withhour"; 1169 $sql .= ", task_duration"; 1170 $sql .= ", fk_user"; 1171 $sql .= ", note"; 1172 $sql .= ") VALUES ("; 1173 $sql .= $this->id; 1174 $sql .= ", '".$this->db->idate($this->timespent_date)."'"; 1175 $sql .= ", '".$this->db->idate($this->timespent_datehour)."'"; 1176 $sql .= ", ".(empty($this->timespent_withhour) ? 0 : 1); 1177 $sql .= ", ".$this->timespent_duration; 1178 $sql .= ", ".$this->timespent_fk_user; 1179 $sql .= ", ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null"); 1180 $sql .= ")"; 1181 1182 $resql = $this->db->query($sql); 1183 if ($resql) { 1184 $tasktime_id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet_task_time"); 1185 $ret = $tasktime_id; 1186 $this->timespent_id = $ret; 1187 1188 if (!$notrigger) { 1189 // Call trigger 1190 $result = $this->call_trigger('TASK_TIMESPENT_CREATE', $user); 1191 if ($result < 0) { 1192 $ret = -1; 1193 } 1194 // End call triggers 1195 } 1196 } else { 1197 $this->error = $this->db->lasterror(); 1198 $ret = -1; 1199 } 1200 1201 if ($ret > 0) { 1202 // Recalculate amount of time spent for task and update denormalized field 1203 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task"; 1204 $sql .= " SET duration_effective = (SELECT SUM(task_duration) FROM ".MAIN_DB_PREFIX."projet_task_time as ptt where ptt.fk_task = ".((int) $this->id).")"; 1205 if (isset($this->progress)) { 1206 $sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided 1207 } 1208 $sql .= " WHERE rowid = ".((int) $this->id); 1209 1210 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG); 1211 if (!$this->db->query($sql)) { 1212 $this->error = $this->db->lasterror(); 1213 $ret = -2; 1214 } 1215 1216 // Update hourly rate of this time spent entry 1217 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time"; 1218 $sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user 1219 $sql .= " WHERE rowid = ".((int) $tasktime_id); 1220 1221 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG); 1222 if (!$this->db->query($sql)) { 1223 $this->error = $this->db->lasterror(); 1224 $ret = -2; 1225 } 1226 } 1227 1228 if ($ret > 0) { 1229 $this->db->commit(); 1230 } else { 1231 $this->db->rollback(); 1232 } 1233 return $ret; 1234 } 1235 1236 /** 1237 * Calculate total of time spent for task 1238 * 1239 * @param User|int $userobj Filter on user. null or 0=No filter 1240 * @param string $morewherefilter Add more filter into where SQL request (must start with ' AND ...') 1241 * @return array Array of info for task array('min_date', 'max_date', 'total_duration', 'total_amount', 'nblines', 'nblinesnull') 1242 */ 1243 public function getSummaryOfTimeSpent($userobj = null, $morewherefilter = '') 1244 { 1245 global $langs; 1246 1247 if (is_object($userobj)) { 1248 $userid = $userobj->id; 1249 } else { 1250 $userid = $userobj; // old method 1251 } 1252 1253 $id = $this->id; 1254 if (empty($id) && empty($userid)) { 1255 dol_syslog("getSummaryOfTimeSpent called on a not loaded task without user param defined", LOG_ERR); 1256 return -1; 1257 } 1258 1259 $result = array(); 1260 1261 $sql = "SELECT"; 1262 $sql .= " MIN(t.task_datehour) as min_date,"; 1263 $sql .= " MAX(t.task_datehour) as max_date,"; 1264 $sql .= " SUM(t.task_duration) as total_duration,"; 1265 $sql .= " SUM(t.task_duration / 3600 * ".$this->db->ifsql("t.thm IS NULL", 0, "t.thm").") as total_amount,"; 1266 $sql .= " COUNT(t.rowid) as nblines,"; 1267 $sql .= " SUM(".$this->db->ifsql("t.thm IS NULL", 1, 0).") as nblinesnull"; 1268 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t"; 1269 $sql .= " WHERE 1 = 1"; 1270 if ($morewherefilter) { 1271 $sql .= $morewherefilter; 1272 } 1273 if ($id > 0) { 1274 $sql .= " AND t.fk_task = ".((int) $id); 1275 } 1276 if ($userid > 0) { 1277 $sql .= " AND t.fk_user = ".((int) $userid); 1278 } 1279 1280 dol_syslog(get_class($this)."::getSummaryOfTimeSpent", LOG_DEBUG); 1281 $resql = $this->db->query($sql); 1282 if ($resql) { 1283 $obj = $this->db->fetch_object($resql); 1284 1285 $result['min_date'] = $obj->min_date; // deprecated. use the ->timespent_xxx instead 1286 $result['max_date'] = $obj->max_date; // deprecated. use the ->timespent_xxx instead 1287 $result['total_duration'] = $obj->total_duration; // deprecated. use the ->timespent_xxx instead 1288 1289 $this->timespent_min_date = $this->db->jdate($obj->min_date); 1290 $this->timespent_max_date = $this->db->jdate($obj->max_date); 1291 $this->timespent_total_duration = $obj->total_duration; 1292 $this->timespent_total_amount = $obj->total_amount; 1293 $this->timespent_nblinesnull = ($obj->nblinesnull ? $obj->nblinesnull : 0); 1294 $this->timespent_nblines = ($obj->nblines ? $obj->nblines : 0); 1295 1296 $this->db->free($resql); 1297 } else { 1298 dol_print_error($this->db); 1299 } 1300 return $result; 1301 } 1302 1303 /** 1304 * Calculate quantity and value of time consumed using the thm (hourly amount value of work for user entering time) 1305 * 1306 * @param User $fuser Filter on a dedicated user 1307 * @param string $dates Start date (ex 00:00:00) 1308 * @param string $datee End date (ex 23:59:59) 1309 * @return array Array of info for task array('amount','nbseconds','nblinesnull') 1310 */ 1311 public function getSumOfAmount($fuser = '', $dates = '', $datee = '') 1312 { 1313 global $langs; 1314 1315 if (empty($id)) { 1316 $id = $this->id; 1317 } 1318 1319 $result = array(); 1320 1321 $sql = "SELECT"; 1322 $sql .= " SUM(t.task_duration) as nbseconds,"; 1323 $sql .= " SUM(t.task_duration / 3600 * ".$this->db->ifsql("t.thm IS NULL", 0, "t.thm").") as amount, SUM(".$this->db->ifsql("t.thm IS NULL", 1, 0).") as nblinesnull"; 1324 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t"; 1325 $sql .= " WHERE t.fk_task = ".((int) $id); 1326 if (is_object($fuser) && $fuser->id > 0) { 1327 $sql .= " AND fk_user = ".((int) $fuser->id); 1328 } 1329 if ($dates > 0) { 1330 $datefieldname = "task_datehour"; 1331 $sql .= " AND (".$datefieldname." >= '".$this->db->idate($dates)."' OR ".$datefieldname." IS NULL)"; 1332 } 1333 if ($datee > 0) { 1334 $datefieldname = "task_datehour"; 1335 $sql .= " AND (".$datefieldname." <= '".$this->db->idate($datee)."' OR ".$datefieldname." IS NULL)"; 1336 } 1337 //print $sql; 1338 1339 dol_syslog(get_class($this)."::getSumOfAmount", LOG_DEBUG); 1340 $resql = $this->db->query($sql); 1341 if ($resql) { 1342 $obj = $this->db->fetch_object($resql); 1343 1344 $result['amount'] = $obj->amount; 1345 $result['nbseconds'] = $obj->nbseconds; 1346 $result['nblinesnull'] = $obj->nblinesnull; 1347 1348 $this->db->free($resql); 1349 return $result; 1350 } else { 1351 dol_print_error($this->db); 1352 return $result; 1353 } 1354 } 1355 1356 /** 1357 * Load properties of timespent of a task from the time spent ID. 1358 * 1359 * @param int $id Id in time spent table 1360 * @return int <0 if KO, >0 if OK 1361 */ 1362 public function fetchTimeSpent($id) 1363 { 1364 global $langs; 1365 1366 $sql = "SELECT"; 1367 $sql .= " t.rowid,"; 1368 $sql .= " t.fk_task,"; 1369 $sql .= " t.task_date,"; 1370 $sql .= " t.task_datehour,"; 1371 $sql .= " t.task_date_withhour,"; 1372 $sql .= " t.task_duration,"; 1373 $sql .= " t.fk_user,"; 1374 $sql .= " t.thm,"; 1375 $sql .= " t.note"; 1376 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t"; 1377 $sql .= " WHERE t.rowid = ".((int) $id); 1378 1379 dol_syslog(get_class($this)."::fetchTimeSpent", LOG_DEBUG); 1380 $resql = $this->db->query($sql); 1381 if ($resql) { 1382 if ($this->db->num_rows($resql)) { 1383 $obj = $this->db->fetch_object($resql); 1384 1385 $this->timespent_id = $obj->rowid; 1386 $this->id = $obj->fk_task; 1387 $this->timespent_date = $this->db->jdate($obj->task_date); 1388 $this->timespent_datehour = $this->db->jdate($obj->task_datehour); 1389 $this->timespent_withhour = $obj->task_date_withhour; 1390 $this->timespent_duration = $obj->task_duration; 1391 $this->timespent_fk_user = $obj->fk_user; 1392 $this->timespent_thm = $obj->thm; // hourly rate 1393 $this->timespent_note = $obj->note; 1394 } 1395 1396 $this->db->free($resql); 1397 1398 return 1; 1399 } else { 1400 $this->error = "Error ".$this->db->lasterror(); 1401 return -1; 1402 } 1403 } 1404 1405 /** 1406 * Load all records of time spent 1407 * 1408 * @param User $userobj User object 1409 * @param string $morewherefilter Add more filter into where SQL request (must start with ' AND ...') 1410 * @return int <0 if KO, array of time spent if OK 1411 */ 1412 public function fetchAllTimeSpent(User $userobj, $morewherefilter = '') 1413 { 1414 global $langs; 1415 1416 $arrayres = array(); 1417 1418 $sql = "SELECT"; 1419 $sql .= " s.rowid as socid,"; 1420 $sql .= " s.nom as thirdparty_name,"; 1421 $sql .= " s.email as thirdparty_email,"; 1422 $sql .= " ptt.rowid,"; 1423 $sql .= " ptt.fk_task,"; 1424 $sql .= " ptt.task_date,"; 1425 $sql .= " ptt.task_datehour,"; 1426 $sql .= " ptt.task_date_withhour,"; 1427 $sql .= " ptt.task_duration,"; 1428 $sql .= " ptt.fk_user,"; 1429 $sql .= " ptt.note,"; 1430 $sql .= " ptt.thm,"; 1431 $sql .= " pt.rowid as task_id,"; 1432 $sql .= " pt.ref as task_ref,"; 1433 $sql .= " pt.label as task_label,"; 1434 $sql .= " p.rowid as project_id,"; 1435 $sql .= " p.ref as project_ref,"; 1436 $sql .= " p.title as project_label,"; 1437 $sql .= " p.public as public"; 1438 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as ptt, ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet as p"; 1439 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid"; 1440 $sql .= " WHERE ptt.fk_task = pt.rowid AND pt.fk_projet = p.rowid"; 1441 $sql .= " AND ptt.fk_user = ".((int) $userobj->id); 1442 $sql .= " AND pt.entity IN (".getEntity('project').")"; 1443 if ($morewherefilter) { 1444 $sql .= $morewherefilter; 1445 } 1446 1447 dol_syslog(get_class($this)."::fetchAllTimeSpent", LOG_DEBUG); 1448 $resql = $this->db->query($sql); 1449 if ($resql) { 1450 $num = $this->db->num_rows($resql); 1451 1452 $i = 0; 1453 while ($i < $num) { 1454 $obj = $this->db->fetch_object($resql); 1455 1456 $newobj = new stdClass(); 1457 1458 $newobj->socid = $obj->socid; 1459 $newobj->thirdparty_name = $obj->thirdparty_name; 1460 $newobj->thirdparty_email = $obj->thirdparty_email; 1461 1462 $newobj->fk_project = $obj->project_id; 1463 $newobj->project_ref = $obj->project_ref; 1464 $newobj->project_label = $obj->project_label; 1465 $newobj->public = $obj->project_public; 1466 1467 $newobj->fk_task = $obj->task_id; 1468 $newobj->task_ref = $obj->task_ref; 1469 $newobj->task_label = $obj->task_label; 1470 1471 $newobj->timespent_id = $obj->rowid; 1472 $newobj->timespent_date = $this->db->jdate($obj->task_date); 1473 $newobj->timespent_datehour = $this->db->jdate($obj->task_datehour); 1474 $newobj->timespent_withhour = $obj->task_date_withhour; 1475 $newobj->timespent_duration = $obj->task_duration; 1476 $newobj->timespent_fk_user = $obj->fk_user; 1477 $newobj->timespent_thm = $obj->thm; // hourly rate 1478 $newobj->timespent_note = $obj->note; 1479 1480 $arrayres[] = $newobj; 1481 1482 $i++; 1483 } 1484 1485 $this->db->free($resql); 1486 } else { 1487 dol_print_error($this->db); 1488 $this->error = "Error ".$this->db->lasterror(); 1489 return -1; 1490 } 1491 1492 return $arrayres; 1493 } 1494 1495 /** 1496 * Update time spent 1497 * 1498 * @param User $user User id 1499 * @param int $notrigger 0=launch triggers after, 1=disable triggers 1500 * @return int <0 if KO, >0 if OK 1501 */ 1502 public function updateTimeSpent($user, $notrigger = 0) 1503 { 1504 global $conf, $langs; 1505 1506 $ret = 0; 1507 1508 // Check parameters 1509 if ($this->timespent_date == '') { 1510 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("Date")); 1511 return -1; 1512 } 1513 if (!($this->timespent_fk_user > 0)) { 1514 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("User")); 1515 return -1; 1516 } 1517 1518 // Clean parameters 1519 if (empty($this->timespent_datehour)) { 1520 $this->timespent_datehour = $this->timespent_date; 1521 } 1522 if (isset($this->timespent_note)) { 1523 $this->timespent_note = trim($this->timespent_note); 1524 } 1525 1526 $this->db->begin(); 1527 1528 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time SET"; 1529 $sql .= " task_date = '".$this->db->idate($this->timespent_date)."',"; 1530 $sql .= " task_datehour = '".$this->db->idate($this->timespent_datehour)."',"; 1531 $sql .= " task_date_withhour = ".(empty($this->timespent_withhour) ? 0 : 1).","; 1532 $sql .= " task_duration = ".((int) $this->timespent_duration).","; 1533 $sql .= " fk_user = ".((int) $this->timespent_fk_user).","; 1534 $sql .= " note = ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null"); 1535 $sql .= " WHERE rowid = ".((int) $this->timespent_id); 1536 1537 dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG); 1538 if ($this->db->query($sql)) { 1539 if (!$notrigger) { 1540 // Call trigger 1541 $result = $this->call_trigger('TASK_TIMESPENT_MODIFY', $user); 1542 if ($result < 0) { 1543 $this->db->rollback(); 1544 $ret = -1; 1545 } else { 1546 $ret = 1; 1547 } 1548 // End call triggers 1549 } else { 1550 $ret = 1; 1551 } 1552 } else { 1553 $this->error = $this->db->lasterror(); 1554 $this->db->rollback(); 1555 $ret = -1; 1556 } 1557 1558 if ($ret == 1 && ($this->timespent_old_duration != $this->timespent_duration)) { 1559 // Recalculate amount of time spent for task and update denormalized field 1560 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task"; 1561 $sql .= " SET duration_effective = (SELECT SUM(task_duration) FROM ".MAIN_DB_PREFIX."projet_task_time as ptt where ptt.fk_task = ".((int) $this->id).")"; 1562 if (isset($this->progress)) { 1563 $sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided 1564 } 1565 $sql .= " WHERE rowid = ".((int) $this->id); 1566 1567 dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG); 1568 if (!$this->db->query($sql)) { 1569 $this->error = $this->db->lasterror(); 1570 $this->db->rollback(); 1571 $ret = -2; 1572 } 1573 1574 // Update hourly rate of this time spent entry, but only if it was not set initialy 1575 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time"; 1576 $sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user 1577 $sql .= " WHERE (thm IS NULL OR thm = 0) AND rowid = ".((int) $this->timespent_id); 1578 1579 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG); 1580 if (!$this->db->query($sql)) { 1581 $this->error = $this->db->lasterror(); 1582 $ret = -2; 1583 } 1584 } 1585 1586 if ($ret >= 0) { 1587 $this->db->commit(); 1588 } 1589 return $ret; 1590 } 1591 1592 /** 1593 * Delete time spent 1594 * 1595 * @param User $user User that delete 1596 * @param int $notrigger 0=launch triggers after, 1=disable triggers 1597 * @return int <0 if KO, >0 if OK 1598 */ 1599 public function delTimeSpent($user, $notrigger = 0) 1600 { 1601 global $conf, $langs; 1602 1603 $error = 0; 1604 1605 $this->db->begin(); 1606 1607 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_time"; 1608 $sql .= " WHERE rowid = ".$this->timespent_id; 1609 1610 dol_syslog(get_class($this)."::delTimeSpent", LOG_DEBUG); 1611 $resql = $this->db->query($sql); 1612 if (!$resql) { 1613 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 1614 } 1615 1616 if (!$error) { 1617 if (!$notrigger) { 1618 // Call trigger 1619 $result = $this->call_trigger('TASK_TIMESPENT_DELETE', $user); 1620 if ($result < 0) { 1621 $error++; 1622 } 1623 // End call triggers 1624 } 1625 } 1626 1627 if (!$error) { 1628 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task"; 1629 $sql .= " SET duration_effective = duration_effective - ".$this->db->escape($this->timespent_duration ? $this->timespent_duration : 0); 1630 $sql .= " WHERE rowid = ".$this->id; 1631 1632 dol_syslog(get_class($this)."::delTimeSpent", LOG_DEBUG); 1633 if ($this->db->query($sql)) { 1634 $result = 0; 1635 } else { 1636 $this->error = $this->db->lasterror(); 1637 $result = -2; 1638 } 1639 } 1640 1641 // Commit or rollback 1642 if ($error) { 1643 foreach ($this->errors as $errmsg) { 1644 dol_syslog(get_class($this)."::delTimeSpent ".$errmsg, LOG_ERR); 1645 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 1646 } 1647 $this->db->rollback(); 1648 return -1 * $error; 1649 } else { 1650 $this->db->commit(); 1651 return 1; 1652 } 1653 } 1654 1655 /** Load an object from its id and create a new one in database 1656 * 1657 * @param User $user User making the clone 1658 * @param int $fromid Id of object to clone 1659 * @param int $project_id Id of project to attach clone task 1660 * @param int $parent_task_id Id of task to attach clone task 1661 * @param bool $clone_change_dt recalculate date of task regarding new project start date 1662 * @param bool $clone_affectation clone affectation of project 1663 * @param bool $clone_time clone time of project 1664 * @param bool $clone_file clone file of project 1665 * @param bool $clone_note clone note of project 1666 * @param bool $clone_prog clone progress of project 1667 * @return int New id of clone 1668 */ 1669 public function createFromClone(User $user, $fromid, $project_id, $parent_task_id, $clone_change_dt = false, $clone_affectation = false, $clone_time = false, $clone_file = false, $clone_note = false, $clone_prog = false) 1670 { 1671 global $langs, $conf; 1672 1673 $error = 0; 1674 1675 //Use 00:00 of today if time is use on task. 1676 $now = dol_mktime(0, 0, 0, dol_print_date(dol_now(), '%m'), dol_print_date(dol_now(), '%d'), dol_print_date(dol_now(), '%Y')); 1677 1678 $datec = $now; 1679 1680 $clone_task = new Task($this->db); 1681 $origin_task = new Task($this->db); 1682 1683 $clone_task->context['createfromclone'] = 'createfromclone'; 1684 1685 $this->db->begin(); 1686 1687 // Load source object 1688 $clone_task->fetch($fromid); 1689 $clone_task->fetch_optionals(); 1690 //var_dump($clone_task->array_options);exit; 1691 1692 $origin_task->fetch($fromid); 1693 1694 $defaultref = ''; 1695 $obj = empty($conf->global->PROJECT_TASK_ADDON) ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON; 1696 if (!empty($conf->global->PROJECT_TASK_ADDON) && is_readable(DOL_DOCUMENT_ROOT."/core/modules/project/task/".$conf->global->PROJECT_TASK_ADDON.".php")) { 1697 require_once DOL_DOCUMENT_ROOT."/core/modules/project/task/".$conf->global->PROJECT_TASK_ADDON.'.php'; 1698 $modTask = new $obj; 1699 $defaultref = $modTask->getNextValue(0, $clone_task); 1700 } 1701 1702 $ori_project_id = $clone_task->fk_project; 1703 1704 $clone_task->id = 0; 1705 $clone_task->ref = $defaultref; 1706 $clone_task->fk_project = $project_id; 1707 $clone_task->fk_task_parent = $parent_task_id; 1708 $clone_task->date_c = $datec; 1709 $clone_task->planned_workload = $origin_task->planned_workload; 1710 $clone_task->rang = $origin_task->rang; 1711 1712 //Manage Task Date 1713 if ($clone_change_dt) { 1714 $projectstatic = new Project($this->db); 1715 $projectstatic->fetch($ori_project_id); 1716 1717 //Origin project strat date 1718 $orign_project_dt_start = $projectstatic->date_start; 1719 1720 //Calcultate new task start date with difference between origin proj start date and origin task start date 1721 if (!empty($clone_task->date_start)) { 1722 $clone_task->date_start = $now + $clone_task->date_start - $orign_project_dt_start; 1723 } 1724 1725 //Calcultate new task end date with difference between origin proj end date and origin task end date 1726 if (!empty($clone_task->date_end)) { 1727 $clone_task->date_end = $now + $clone_task->date_end - $orign_project_dt_start; 1728 } 1729 } 1730 1731 if (!$clone_prog) { 1732 $clone_task->progress = 0; 1733 } 1734 1735 // Create clone 1736 $result = $clone_task->create($user); 1737 1738 // Other options 1739 if ($result < 0) { 1740 $this->error = $clone_task->error; 1741 $error++; 1742 } 1743 1744 // End 1745 if (!$error) { 1746 $clone_task_id = $clone_task->id; 1747 $clone_task_ref = $clone_task->ref; 1748 1749 //Note Update 1750 if (!$clone_note) { 1751 $clone_task->note_private = ''; 1752 $clone_task->note_public = ''; 1753 } else { 1754 $this->db->begin(); 1755 $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_public, ENT_QUOTES | ENT_HTML5), '_public'); 1756 if ($res < 0) { 1757 $this->error .= $clone_task->error; 1758 $error++; 1759 $this->db->rollback(); 1760 } else { 1761 $this->db->commit(); 1762 } 1763 1764 $this->db->begin(); 1765 $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_private, ENT_QUOTES | ENT_HTML5), '_private'); 1766 if ($res < 0) { 1767 $this->error .= $clone_task->error; 1768 $error++; 1769 $this->db->rollback(); 1770 } else { 1771 $this->db->commit(); 1772 } 1773 } 1774 1775 //Duplicate file 1776 if ($clone_file) { 1777 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 1778 1779 //retrieve project origin ref to know folder to copy 1780 $projectstatic = new Project($this->db); 1781 $projectstatic->fetch($ori_project_id); 1782 $ori_project_ref = $projectstatic->ref; 1783 1784 if ($ori_project_id != $project_id) { 1785 $projectstatic->fetch($project_id); 1786 $clone_project_ref = $projectstatic->ref; 1787 } else { 1788 $clone_project_ref = $ori_project_ref; 1789 } 1790 1791 $clone_task_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($clone_project_ref)."/".dol_sanitizeFileName($clone_task_ref); 1792 $ori_task_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($ori_project_ref)."/".dol_sanitizeFileName($fromid); 1793 1794 $filearray = dol_dir_list($ori_task_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1); 1795 foreach ($filearray as $key => $file) { 1796 if (!file_exists($clone_task_dir)) { 1797 if (dol_mkdir($clone_task_dir) < 0) { 1798 $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir'; 1799 $error++; 1800 } 1801 } 1802 1803 $rescopy = dol_copy($ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name'], 0, 1); 1804 if (is_numeric($rescopy) && $rescopy < 0) { 1805 $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name']); 1806 $error++; 1807 } 1808 } 1809 } 1810 1811 // clone affectation 1812 if ($clone_affectation) { 1813 $origin_task = new Task($this->db); 1814 $origin_task->fetch($fromid); 1815 1816 foreach (array('internal', 'external') as $source) { 1817 $tab = $origin_task->liste_contact(-1, $source); 1818 $num = count($tab); 1819 $i = 0; 1820 while ($i < $num) { 1821 $clone_task->add_contact($tab[$i]['id'], $tab[$i]['code'], $tab[$i]['source']); 1822 if ($clone_task->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') { 1823 $langs->load("errors"); 1824 $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType"); 1825 $error++; 1826 } else { 1827 if ($clone_task->error != '') { 1828 $this->error .= $clone_task->error; 1829 $error++; 1830 } 1831 } 1832 $i++; 1833 } 1834 } 1835 } 1836 1837 if ($clone_time) { 1838 //TODO clone time of affectation 1839 } 1840 } 1841 1842 unset($clone_task->context['createfromclone']); 1843 1844 if (!$error) { 1845 $this->db->commit(); 1846 return $clone_task_id; 1847 } else { 1848 $this->db->rollback(); 1849 dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR); 1850 return -1; 1851 } 1852 } 1853 1854 1855 /** 1856 * Return status label of object 1857 * 1858 * @param integer $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto 1859 * @return string Label 1860 */ 1861 public function getLibStatut($mode = 0) 1862 { 1863 return $this->LibStatut($this->fk_statut, $mode); 1864 } 1865 1866 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1867 /** 1868 * Return status label for an object 1869 * 1870 * @param int $status Id status 1871 * @param integer $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto 1872 * @return string Label 1873 */ 1874 public function LibStatut($status, $mode = 0) 1875 { 1876 // phpcs:enable 1877 global $langs; 1878 1879 // list of Statut of the task 1880 $this->statuts[0] = 'Draft'; 1881 $this->statuts[1] = 'ToDo'; 1882 $this->statuts[2] = 'Running'; 1883 $this->statuts[3] = 'Finish'; 1884 $this->statuts[4] = 'Transfered'; 1885 $this->statuts_short[0] = 'Draft'; 1886 $this->statuts_short[1] = 'ToDo'; 1887 $this->statuts_short[2] = 'Running'; 1888 $this->statuts_short[3] = 'Completed'; 1889 $this->statuts_short[4] = 'Transfered'; 1890 1891 if ($mode == 0) { 1892 return $langs->trans($this->statuts[$status]); 1893 } elseif ($mode == 1) { 1894 return $langs->trans($this->statuts_short[$status]); 1895 } elseif ($mode == 2) { 1896 if ($status == 0) { 1897 return img_picto($langs->trans($this->statuts_short[$status]), 'statut0').' '.$langs->trans($this->statuts_short[$status]); 1898 } elseif ($status == 1) { 1899 return img_picto($langs->trans($this->statuts_short[$status]), 'statut1').' '.$langs->trans($this->statuts_short[$status]); 1900 } elseif ($status == 2) { 1901 return img_picto($langs->trans($this->statuts_short[$status]), 'statut3').' '.$langs->trans($this->statuts_short[$status]); 1902 } elseif ($status == 3) { 1903 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts_short[$status]); 1904 } elseif ($status == 4) { 1905 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts_short[$status]); 1906 } elseif ($status == 5) { 1907 return img_picto($langs->trans($this->statuts_short[$status]), 'statut5').' '.$langs->trans($this->statuts_short[$status]); 1908 } 1909 } elseif ($mode == 3) { 1910 if ($status == 0) { 1911 return img_picto($langs->trans($this->statuts_short[$status]), 'statut0'); 1912 } elseif ($status == 1) { 1913 return img_picto($langs->trans($this->statuts_short[$status]), 'statut1'); 1914 } elseif ($status == 2) { 1915 return img_picto($langs->trans($this->statuts_short[$status]), 'statut3'); 1916 } elseif ($status == 3) { 1917 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6'); 1918 } elseif ($status == 4) { 1919 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6'); 1920 } elseif ($status == 5) { 1921 return img_picto($langs->trans($this->statuts_short[$status]), 'statut5'); 1922 } 1923 } elseif ($mode == 4) { 1924 if ($status == 0) { 1925 return img_picto($langs->trans($this->statuts_short[$status]), 'statut0').' '.$langs->trans($this->statuts[$status]); 1926 } elseif ($status == 1) { 1927 return img_picto($langs->trans($this->statuts_short[$status]), 'statut1').' '.$langs->trans($this->statuts[$status]); 1928 } elseif ($status == 2) { 1929 return img_picto($langs->trans($this->statuts_short[$status]), 'statut3').' '.$langs->trans($this->statuts[$status]); 1930 } elseif ($status == 3) { 1931 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts[$status]); 1932 } elseif ($status == 4) { 1933 return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts[$status]); 1934 } elseif ($status == 5) { 1935 return img_picto($langs->trans($this->statuts_short[$status]), 'statut5').' '.$langs->trans($this->statuts[$status]); 1936 } 1937 } elseif ($mode == 5) { 1938 /*if ($status==0) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut0'); 1939 elseif ($status==1) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut1'); 1940 elseif ($status==2) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut3'); 1941 elseif ($status==3) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6'); 1942 elseif ($status==4) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6'); 1943 elseif ($status==5) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut5'); 1944 */ 1945 //else return $this->progress.' %'; 1946 return ' '; 1947 } elseif ($mode == 6) { 1948 /*if ($status==0) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut0'); 1949 elseif ($status==1) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut1'); 1950 elseif ($status==2) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut3'); 1951 elseif ($status==3) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6'); 1952 elseif ($status==4) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6'); 1953 elseif ($status==5) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut5'); 1954 */ 1955 //else return $this->progress.' %'; 1956 return ' '; 1957 } 1958 } 1959 1960 /** 1961 * Create an intervention document on disk using template defined into PROJECT_TASK_ADDON_PDF 1962 * 1963 * @param string $modele force le modele a utiliser ('' par defaut) 1964 * @param Translate $outputlangs objet lang a utiliser pour traduction 1965 * @param int $hidedetails Hide details of lines 1966 * @param int $hidedesc Hide description 1967 * @param int $hideref Hide ref 1968 * @return int 0 if KO, 1 if OK 1969 */ 1970 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0) 1971 { 1972 global $conf; 1973 1974 $outputlangs->load("projects"); 1975 1976 if (!dol_strlen($modele)) { 1977 $modele = 'nodefault'; 1978 1979 if (!empty($this->model_pdf)) { 1980 $modele = $this->model_pdf; 1981 } elseif (!empty($this->modelpdf)) { // deprecated 1982 $modele = $this->modelpdf; 1983 } elseif (!empty($conf->global->PROJECT_TASK_ADDON_PDF)) { 1984 $modele = $conf->global->PROJECT_TASK_ADDON_PDF; 1985 } 1986 } 1987 1988 $modelpath = "core/modules/project/task/doc/"; 1989 1990 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref); 1991 } 1992 1993 1994 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1995 /** 1996 * Load indicators for dashboard (this->nbtodo and this->nbtodolate) 1997 * 1998 * @param User $user Objet user 1999 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK 2000 */ 2001 public function load_board($user) 2002 { 2003 // phpcs:enable 2004 global $conf, $langs; 2005 2006 // For external user, no check is done on company because readability is managed by public status of project and assignement. 2007 //$socid = $user->socid; 2008 $socid = 0; 2009 2010 $projectstatic = new Project($this->db); 2011 $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, $socid); 2012 2013 // List of tasks (does not care about permissions. Filtering will be done later) 2014 $sql = "SELECT p.rowid as projectid, p.fk_statut as projectstatus,"; 2015 $sql .= " t.rowid as taskid, t.progress as progress, t.fk_statut as status,"; 2016 $sql .= " t.dateo as date_start, t.datee as datee"; 2017 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; 2018 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid"; 2019 //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid"; 2020 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; 2021 $sql .= " WHERE p.entity IN (".getEntity('project', 0).')'; 2022 $sql .= " AND p.fk_statut = 1"; 2023 $sql .= " AND t.fk_projet = p.rowid"; 2024 $sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do 2025 if (!$user->rights->projet->all->lire) { 2026 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")"; 2027 } 2028 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser 2029 //if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")"; 2030 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser 2031 // if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))"; 2032 2033 //print $sql; 2034 $resql = $this->db->query($sql); 2035 if ($resql) { 2036 $task_static = new Task($this->db); 2037 2038 $response = new WorkboardResponse(); 2039 $response->warning_delay = $conf->projet->task->warning_delay / 60 / 60 / 24; 2040 $response->label = $langs->trans("OpenedTasks"); 2041 if ($user->rights->projet->all->lire) { 2042 $response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mainmenu=project'; 2043 } else { 2044 $response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mode=mine&mainmenu=project'; 2045 } 2046 $response->img = img_object('', "task"); 2047 2048 // This assignment in condition is not a bug. It allows walking the results. 2049 while ($obj = $this->db->fetch_object($resql)) { 2050 $response->nbtodo++; 2051 2052 $task_static->projectstatus = $obj->projectstatus; 2053 $task_static->progress = $obj->progress; 2054 $task_static->fk_statut = $obj->status; 2055 $task_static->date_end = $this->db->jdate($obj->datee); 2056 2057 if ($task_static->hasDelay()) { 2058 $response->nbtodolate++; 2059 } 2060 } 2061 2062 return $response; 2063 } else { 2064 $this->error = $this->db->error(); 2065 return -1; 2066 } 2067 } 2068 2069 2070 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2071 /** 2072 * Charge indicateurs this->nb de tableau de bord 2073 * 2074 * @return int <0 if ko, >0 if ok 2075 */ 2076 public function load_state_board() 2077 { 2078 // phpcs:enable 2079 global $user; 2080 2081 $mine = 0; $socid = $user->socid; 2082 2083 $projectstatic = new Project($this->db); 2084 $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, $mine, 1, $socid); 2085 2086 // List of tasks (does not care about permissions. Filtering will be done later) 2087 $sql = "SELECT count(p.rowid) as nb"; 2088 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p"; 2089 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid"; 2090 if (!$user->rights->societe->client->voir && !$socid) { 2091 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid"; 2092 } 2093 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t"; 2094 $sql .= " WHERE p.entity IN (".getEntity('project', 0).')'; 2095 $sql .= " AND t.fk_projet = p.rowid"; // tasks to do 2096 if ($mine || !$user->rights->projet->all->lire) { 2097 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")"; 2098 } 2099 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser 2100 //if ($socid || ! $user->rights->societe->client->voir) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")"; 2101 if ($socid) { 2102 $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")"; 2103 } 2104 if (!$user->rights->societe->client->voir && !$socid) { 2105 $sql .= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))"; 2106 } 2107 2108 $resql = $this->db->query($sql); 2109 if ($resql) { 2110 // This assignment in condition is not a bug. It allows walking the results. 2111 while ($obj = $this->db->fetch_object($resql)) { 2112 $this->nb["tasks"] = $obj->nb; 2113 } 2114 $this->db->free($resql); 2115 return 1; 2116 } else { 2117 dol_print_error($this->db); 2118 $this->error = $this->db->error(); 2119 return -1; 2120 } 2121 } 2122 2123 /** 2124 * Is the task delayed? 2125 * 2126 * @return bool 2127 */ 2128 public function hasDelay() 2129 { 2130 global $conf; 2131 2132 if (!($this->progress >= 0 && $this->progress < 100)) { 2133 return false; 2134 } 2135 2136 $now = dol_now(); 2137 2138 $datetouse = ($this->date_end > 0) ? $this->date_end : ((isset($this->datee) && $this->datee > 0) ? $this->datee : 0); 2139 2140 return ($datetouse > 0 && ($datetouse < ($now - $conf->projet->task->warning_delay))); 2141 } 2142} 2143