dolibarr 23.0.4
task.class.php
Go to the documentation of this file.
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-2024 Frédéric France <frederic.france@free.fr>
6 * Copyright (C) 2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2022-2025 Charlene Benke <charlene@patas-monkey.com>
8 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
9 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
10 * Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
32require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
33require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
34require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
35require_once DOL_DOCUMENT_ROOT.'/core/class/timespent.class.php';
36
37
42{
46 public $element = 'project_task';
47
51 public $table_element = 'projet_task';
52
56 public $fk_element = 'fk_element';
57
61 public $picto = 'projecttask';
62
66 protected $childtables = array(
67 'element_time' => array('name' => 'Task', 'parent' => 'projet_task', 'parentkey' => 'fk_element', 'parenttypefield' => 'elementtype', 'parenttypevalue' => 'task')
68 );
69
73 public $fk_task_parent = 0;
74
78 public $label;
79
83 public $description;
84
88 public $duration_effective;
89
93 public $planned_workload;
94
99 public $date_c;
100
104 public $progress;
105
111 public $dateo;
112
117 public $date_start;
118
124 public $datee;
125
130 public $date_end;
131
137 public $fk_statut;
138
142 public $status;
143
147 public $priority;
148
152 public $fk_user_creat;
153
157 public $fk_user_valid;
158
162 public $rang;
163
167 public $timespent_min_date;
171 public $timespent_max_date;
175 public $timespent_total_duration;
179 public $timespent_total_amount;
183 public $timespent_nblinesnull;
187 public $timespent_nblines;
188 // For detail of lines of timespent record, there is the property ->lines in common
189
190 // Var used to call method addTimeSpent(). Bad practice.
194 public $timespent_id;
198 public $timespent_duration;
202 public $timespent_old_duration;
206 public $timespent_date;
210 public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds)
211
215 public $timespent_withhour; // 0 or 1 = we have entered also start hours for timesheet line
220 public $timespent_fk_user;
224 public $timespent_thm;
228 public $timespent_note;
232 public $timespent_fk_product;
236 public $timespent_invoiceid;
240 public $timespent_invoicelineid;
241
242 public $comments = array();
243
244 // Properties calculated from sum of llx_element_time linked to task
248 public $tobill;
249
253 public $billed;
254
255 // Properties to store project information
259 public $projectref;
260
264 public $projectstatus;
265
269 public $projectlabel;
270
274 public $opp_amount;
275
279 public $opp_percent;
280
284 public $fk_opp_status;
285
289 public $usage_bill_time;
290
294 public $public;
295
299 public $array_options_project;
300
301 // Properties to store thirdparty of project information
302
308 public $socid;
309
313 public $thirdparty_id;
314
318 public $thirdparty_name;
319
323 public $thirdparty_email;
324
325 // store parent ref and position
329 public $task_parent_ref;
330
334 public $task_parent_position;
335
340 public $billable = 1;
341
345 public $budget_amount;
346
350 public $project_budget_amount;
351
355 const STATUS_DRAFT = 0;
356
361
365 const STATUS_ONGOING = 2;
366
370 const STATUS_CLOSED = 3;
371
376
381
382
388 public function __construct($db)
389 {
390 $this->db = $db;
391 // list of status of the task
392 $this->labelStatus = array(
393 0 => 'Draft',
394 1 => 'Validated',
395 2 => 'InProgress',
396 3 => 'Closed',
397 //4 => 'Transferred',
398 );
399 $this->labelStatusShort = array(
400 0 => 'Draft',
401 1 => 'Validated',
402 2 => 'InProgress',
403 3 => 'Closed',
404 //4 => 'Transferred',
405 );
406 }
407
408
416 public function create($user, $notrigger = 0)
417 {
418 global $conf, $langs;
419
420 //For the date
421 $now = dol_now();
422
423 $error = 0;
424
425 // Clean parameters
426 $this->label = trim($this->label);
427 $this->description = trim($this->description);
428 $this->note_public = trim($this->note_public);
429 $this->note_private = trim($this->note_private);
430
431 if (!empty($this->date_start) && !empty($this->date_end) && $this->date_start > $this->date_end) {
432 $this->errors[] = $langs->trans('StartDateCannotBeAfterEndDate');
433 return -1;
434 }
435
436 // Insert request
437 $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet_task (";
438 $sql .= "entity";
439 $sql .= ", fk_projet";
440 $sql .= ", ref";
441 $sql .= ", fk_task_parent";
442 $sql .= ", label";
443 $sql .= ", description";
444 $sql .= ", note_public";
445 $sql .= ", note_private";
446 $sql .= ", datec";
447 $sql .= ", fk_user_creat";
448 $sql .= ", dateo";
449 $sql .= ", datee";
450 $sql .= ", planned_workload";
451 $sql .= ", progress";
452 $sql .= ", budget_amount";
453 $sql .= ", priority";
454 $sql .= ", billable";
455 $sql .= ", fk_statut";
456 $sql .= ", rang";
457 $sql .= ") VALUES (";
458 $sql .= (!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
459 $sql .= ", ".((int) $this->fk_project);
460 $sql .= ", ".(!empty($this->ref) ? "'".$this->db->escape($this->ref)."'" : 'null');
461 $sql .= ", ".((int) $this->fk_task_parent);
462 $sql .= ", '".$this->db->escape($this->label)."'";
463 $sql .= ", '".$this->db->escape($this->description)."'";
464 $sql .= ", '".$this->db->escape($this->note_public)."'";
465 $sql .= ", '".$this->db->escape($this->note_private)."'";
466 $sql .= ", '".$this->db->idate($now)."'";
467 $sql .= ", ".((int) $user->id);
468 $sql .= ", ".(isDolTms($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : 'null');
469 $sql .= ", ".(isDolTms($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : 'null');
470 $sql .= ", ".(($this->planned_workload != '' && $this->planned_workload >= 0) ? ((int) $this->planned_workload) : 'null');
471 $sql .= ", ".(($this->progress != '' && $this->progress >= 0) ? ((int) $this->progress) : 'null');
472 $sql .= ", ".(($this->budget_amount != '' && $this->budget_amount >= 0) ? ((int) $this->budget_amount) : 'null');
473 $sql .= ", ".(($this->priority != '' && $this->priority >= 0) ? (int) $this->priority : 'null');
474 $sql .= ", ".((int) $this->billable);
475 $sql .= ", ".((int) $this->status);
476 $sql .= ", ".((!empty($this->rang)) ? ((int) $this->rang) : "0");
477 $sql .= ")";
478
479 $this->db->begin();
480
481 dol_syslog(get_class($this)."::create", LOG_DEBUG);
482 $resql = $this->db->query($sql);
483 if (!$resql) {
484 $error++;
485 $this->errors[] = "Error ".$this->db->lasterror();
486 }
487
488 if (!$error) {
489 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet_task");
490 // Update extrafield
491 $result = $this->insertExtraFields();
492 if ($result < 0) {
493 $error++;
494 }
495 }
496
497 if (!$error) {
498 if (!$notrigger) {
499 // Call trigger
500 $result = $this->call_trigger('TASK_CREATE', $user);
501 if ($result < 0) {
502 $error++;
503 }
504 // End call triggers
505 }
506 }
507
508 // Commit or rollback
509 if ($error) {
510 foreach ($this->errors as $errmsg) {
511 dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
512 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
513 }
514 $this->db->rollback();
515 return -1 * $error;
516 } else {
517 $this->db->commit();
518 return $this->id;
519 }
520 }
521
522
531 public function fetch($id, $ref = '', $loadparentdata = 0)
532 {
533 $sql = "SELECT";
534 $sql .= " t.rowid,";
535 $sql .= " t.ref,";
536 $sql .= " t.entity,";
537 $sql .= " t.fk_projet as fk_project,";
538 $sql .= " t.fk_task_parent,";
539 $sql .= " t.label,";
540 $sql .= " t.description,";
541 $sql .= " t.duration_effective,";
542 $sql .= " t.planned_workload,";
543 $sql .= " t.datec,";
544 $sql .= " t.dateo as date_start,";
545 $sql .= " t.datee as date_end,";
546 $sql .= " t.fk_user_creat,";
547 $sql .= " t.fk_user_valid,";
548 $sql .= " t.fk_statut as status,";
549 $sql .= " t.progress,";
550 $sql .= " t.budget_amount,";
551 $sql .= " t.priority,";
552 $sql .= " t.note_private,";
553 $sql .= " t.note_public,";
554 $sql .= " t.rang,";
555 $sql .= " t.billable";
556 if (!empty($loadparentdata)) {
557 $sql .= ", t2.ref as task_parent_ref";
558 $sql .= ", t2.rang as task_parent_position";
559 }
560 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task as t";
561 if (!empty($loadparentdata)) {
562 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t2 ON t.fk_task_parent = t2.rowid";
563 }
564 $sql .= " WHERE ";
565 if (!empty($ref)) {
566 $sql .= "entity IN (".getEntity('project').")";
567 $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
568 } else {
569 $sql .= "t.rowid = ".((int) $id);
570 }
571
572 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
573 $resql = $this->db->query($sql);
574 if ($resql) {
575 $num_rows = $this->db->num_rows($resql);
576
577 if ($num_rows) {
578 $obj = $this->db->fetch_object($resql);
579
580 $this->id = $obj->rowid;
581 $this->ref = $obj->ref;
582 $this->entity = $obj->entity;
583 $this->fk_project = $obj->fk_project;
584 $this->fk_task_parent = $obj->fk_task_parent;
585 $this->label = $obj->label;
586 $this->description = $obj->description;
587 $this->duration_effective = $obj->duration_effective;
588 $this->planned_workload = $obj->planned_workload;
589 $this->date_c = $this->db->jdate($obj->datec);
590 $this->date_start = $this->db->jdate($obj->date_start);
591 $this->date_end = $this->db->jdate($obj->date_end);
592 $this->fk_user_creat = $obj->fk_user_creat;
593 $this->fk_user_valid = $obj->fk_user_valid;
594 $this->status = $obj->status;
595 $this->progress = $obj->progress;
596 $this->budget_amount = $obj->budget_amount;
597 $this->priority = $obj->priority;
598 $this->note_private = $obj->note_private;
599 $this->note_public = $obj->note_public;
600 $this->rang = $obj->rang;
601
602 if (!empty($loadparentdata)) {
603 $this->task_parent_ref = $obj->task_parent_ref;
604 $this->task_parent_position = $obj->task_parent_position;
605 }
606 $this->billable = $obj->billable;
607
608 // Retrieve all extrafield
609 $this->fetch_optionals();
610 }
611
612 $this->db->free($resql);
613
614 if ($num_rows) {
615 return 1;
616 } else {
617 return 0;
618 }
619 } else {
620 $this->error = "Error ".$this->db->lasterror();
621 return -1;
622 }
623 }
624
625
633 public function update($user = null, $notrigger = 0)
634 {
635 global $conf, $langs;
636 $error = 0;
637
638 // Clean parameters
639 if (isset($this->fk_project)) {
640 $this->fk_project = (int) $this->fk_project;
641 }
642 if (isset($this->ref)) {
643 $this->ref = trim($this->ref);
644 }
645 if (isset($this->fk_task_parent)) {
646 $this->fk_task_parent = (int) $this->fk_task_parent;
647 }
648 if (isset($this->label)) {
649 $this->label = trim($this->label);
650 }
651 if (isset($this->description)) {
652 $this->description = trim($this->description);
653 }
654 if (isset($this->note_public)) {
655 $this->note_public = trim($this->note_public);
656 }
657 if (isset($this->note_private)) {
658 $this->note_private = trim($this->note_private);
659 }
660 if (isset($this->duration_effective)) {
661 $this->duration_effective = trim($this->duration_effective);
662 }
663 if (isset($this->planned_workload)) {
664 $this->planned_workload = trim($this->planned_workload);
665 }
666 if (isset($this->budget_amount)) {
667 $this->budget_amount = (float) $this->budget_amount;
668 }
669
670 if (!empty($this->date_start) && !empty($this->date_end) && $this->date_start > $this->date_end) {
671 $this->errors[] = $langs->trans('StartDateCannotBeAfterEndDate');
672 return -1;
673 }
674
675 // Check parameters
676 // Put here code to add control on parameters values
677
678 // Update request
679 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task SET";
680 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
681 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "'".$this->db->escape((string) $this->id)."'").",";
682 $sql .= " fk_task_parent=".(isset($this->fk_task_parent) ? $this->fk_task_parent : "null").",";
683 $sql .= " label=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
684 $sql .= " description=".(isset($this->description) ? "'".$this->db->escape($this->description)."'" : "null").",";
685 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
686 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
687 $sql .= " duration_effective=".(isset($this->duration_effective) ? $this->duration_effective : "null").",";
688 $sql .= " planned_workload=".((isset($this->planned_workload) && $this->planned_workload != '') ? $this->planned_workload : "null").",";
689 $sql .= " datec=".(isDolTms($this->date_c) ? "'".$this->db->idate($this->date_c)."'" : 'null').",";
690 $sql .= " dateo=".(isDolTms($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : 'null').",";
691 $sql .= " datee=".(isDolTms($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : 'null').",";
692 $sql .= " progress=".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null').",";
693 $sql .= " budget_amount=".(($this->budget_amount != '' && $this->budget_amount >= 0) ? $this->budget_amount : 'null').",";
694 $sql .= " rang=".((!empty($this->rang)) ? ((int) $this->rang) : "0").",";
695 $sql .= " priority=".((!empty($this->priority)) ? ((int) $this->priority) : "0").",";
696 $sql .= " billable=".((int) $this->billable).",";
697 $sql .= " fk_statut=".((int) $this->status);
698 $sql .= " WHERE rowid=".((int) $this->id);
699
700 $this->db->begin();
701
702 dol_syslog(get_class($this)."::update", LOG_DEBUG);
703 $resql = $this->db->query($sql);
704 if (!$resql) {
705 $error++;
706 $this->errors[] = "Error ".$this->db->lasterror();
707 }
708
709 // Update extrafield
710 if (!$error) {
711 $result = $this->insertExtraFields();
712 if ($result < 0) {
713 $error++;
714 }
715 }
716
717 if (!$error && getDolGlobalString('PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE')) {
718 // Close the parent project if it is open (validated) and its tasks are 100% completed
719 $project = new Project($this->db);
720 if ($project->fetch($this->fk_project) > 0) {
721 if ($project->statut == Project::STATUS_VALIDATED) {
722 $project->getLinesArray(null); // this method does not return <= 0 if fails
723 $projectCompleted = array_reduce(
724 $project->lines,
730 static function ($allTasksCompleted, $task) {
731 return $allTasksCompleted && $task->progress >= 100;
732 },
733 1
734 );
735 if ($projectCompleted) {
736 if ($project->setClose($user) <= 0) {
737 $error++;
738 }
739 }
740 }
741 } else {
742 $error++;
743 }
744 if ($error) {
745 $this->errors[] = $project->error;
746 }
747 }
748
749 if (!$error) {
750 if (!$notrigger) {
751 // Call trigger
752 $result = $this->call_trigger('TASK_MODIFY', $user);
753 if ($result < 0) {
754 $error++;
755 }
756 // End call triggers
757 }
758 }
759
760 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
761 // We remove directory
762 if ($conf->project->dir_output) {
763 $project = new Project($this->db);
764 $project->fetch($this->fk_project);
765
766 $olddir = $conf->project->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->oldcopy->ref);
767 $newdir = $conf->project->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->ref);
768 if (file_exists($olddir)) {
769 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
770 $res = dol_move_dir($olddir, $newdir);
771 if (!$res) {
772 $langs->load("errors");
773 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
774 $error++;
775 }
776 }
777 }
778 }
779
780 // Commit or rollback
781 if ($error) {
782 foreach ($this->errors as $errmsg) {
783 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
784 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
785 }
786 $this->db->rollback();
787 return -1 * $error;
788 } else {
789 $this->db->commit();
790 return 1;
791 }
792 }
793
801 public function delete($user, $notrigger = 0)
802 {
803 global $conf;
804 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
805
806 $error = 0;
807
808 $this->db->begin();
809
810 if ($this->hasChildren() > 0) {
811 dol_syslog(get_class($this)."::delete Can't delete record as it has some sub tasks", LOG_WARNING);
812 $this->error = 'ErrorRecordHasSubTasks';
813 $this->db->rollback();
814 return 0;
815 }
816
817 $objectisused = $this->isObjectUsed($this->id);
818 if (!empty($objectisused)) {
819 dol_syslog(get_class($this)."::delete Can't delete record as it has some child", LOG_WARNING);
820 $this->error = 'ErrorRecordHasChildren';
821 $this->db->rollback();
822 return 0;
823 }
824
825 if (!$error) {
826 // Delete linked contacts
827 $res = $this->delete_linked_contact();
828 if ($res < 0) {
829 $this->error = 'ErrorFailToDeleteLinkedContact';
830 //$error++;
831 $this->db->rollback();
832 return 0;
833 }
834 }
835
836 if (!$error) {
837 $sql = "DELETE FROM ".MAIN_DB_PREFIX."element_time";
838 $sql .= " WHERE fk_element = ".((int) $this->id)." AND elementtype = 'task'";
839
840 $resql = $this->db->query($sql);
841 if (!$resql) {
842 $error++;
843 $this->errors[] = "Error ".$this->db->lasterror();
844 }
845 }
846
847 if (!$error) {
848 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_extrafields";
849 $sql .= " WHERE fk_object = ".((int) $this->id);
850
851 $resql = $this->db->query($sql);
852 if (!$resql) {
853 $error++;
854 $this->errors[] = "Error ".$this->db->lasterror();
855 }
856 }
857
858 if (!$error) {
859 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task";
860 $sql .= " WHERE rowid=".((int) $this->id);
861
862 $resql = $this->db->query($sql);
863 if (!$resql) {
864 $error++;
865 $this->errors[] = "Error ".$this->db->lasterror();
866 }
867 }
868
869 if (!$error) {
870 if (!$notrigger) {
871 // Call trigger
872 $result = $this->call_trigger('TASK_DELETE', $user);
873 if ($result < 0) {
874 $error++;
875 }
876 // End call triggers
877 }
878 }
879
880 // Commit or rollback
881 if ($error) {
882 foreach ($this->errors as $errmsg) {
883 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
884 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
885 }
886 $this->db->rollback();
887 return -1 * $error;
888 } else {
889 //Delete associated link file
890 if ($conf->project->dir_output) {
891 $projectstatic = new Project($this->db);
892 $projectstatic->fetch($this->fk_project);
893
894 $dir = $conf->project->dir_output."/".dol_sanitizeFileName($projectstatic->ref).'/'.dol_sanitizeFileName((string) $this->id);
895 dol_syslog(get_class($this)."::delete dir=".$dir, LOG_DEBUG);
896 if (file_exists($dir)) {
897 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
898 $res = @dol_delete_dir_recursive($dir);
899 if (!$res) {
900 $this->error = 'ErrorFailToDeleteDir';
901 $this->db->rollback();
902 return 0;
903 }
904 }
905 }
906
907 $this->db->commit();
908
909 return 1;
910 }
911 }
912
918 public function hasChildren()
919 {
920 $error = 0;
921 $ret = 0;
922
923 $sql = "SELECT COUNT(*) as nb";
924 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task";
925 $sql .= " WHERE fk_task_parent = ".((int) $this->id);
926
927 dol_syslog(get_class($this)."::hasChildren", LOG_DEBUG);
928 $resql = $this->db->query($sql);
929 if (!$resql) {
930 $error++;
931 $this->errors[] = "Error ".$this->db->lasterror();
932 } else {
933 $obj = $this->db->fetch_object($resql);
934 if ($obj) {
935 $ret = $obj->nb;
936 }
937 $this->db->free($resql);
938 }
939
940 if (!$error) {
941 return $ret;
942 } else {
943 return -1;
944 }
945 }
946
952 public function hasTimeSpent()
953 {
954 $error = 0;
955 $ret = 0;
956
957 $sql = "SELECT COUNT(*) as nb";
958 $sql .= " FROM ".MAIN_DB_PREFIX."element_time";
959 $sql .= " WHERE fk_element = ".((int) $this->id);
960 $sql .= " AND elementtype = 'task'";
961
962 dol_syslog(get_class($this)."::hasTimeSpent", LOG_DEBUG);
963 $resql = $this->db->query($sql);
964 if (!$resql) {
965 $error++;
966 $this->errors[] = "Error ".$this->db->lasterror();
967 } else {
968 $obj = $this->db->fetch_object($resql);
969 if ($obj) {
970 $ret = $obj->nb;
971 }
972 $this->db->free($resql);
973 }
974
975 if (!$error) {
976 return $ret;
977 } else {
978 return -1;
979 }
980 }
981
982
989 public function getTooltipContentArray($params)
990 {
991 global $langs;
992
993 $langs->load('projects');
994
995 $datas = [];
996 $datas['picto'] = img_picto('', $this->picto).' <u>'.$langs->trans("Task").'</u>';
997 if (!empty($this->ref)) {
998 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
999 }
1000 if (!empty($this->label)) {
1001 $datas['label'] = '<br><b>'.$langs->trans('LabelTask').':</b> '.$this->label;
1002 }
1003 if ($this->date_start || $this->date_end) {
1004 $datas['range'] = "<br>".get_date_range($this->date_start, $this->date_end, '', $langs, 0);
1005 }
1006
1007 return $datas;
1008 }
1009
1022 public function getNomUrl($withpicto = 0, $option = '', $mode = 'task', $addlabel = 0, $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1)
1023 {
1024 global $action, $conf, $hookmanager, $langs;
1025
1026 if (!empty($conf->dol_no_mouse_hover)) {
1027 $notooltip = 1; // Force disable tooltips
1028 }
1029
1030 $result = '';
1031 $params = [
1032 'id' => $this->id,
1033 'objecttype' => $this->element,
1034 ];
1035 $classfortooltip = 'classfortooltip';
1036 $dataparams = '';
1037 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1038 $classfortooltip = 'classforajaxtooltip';
1039 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1040 $label = '';
1041 } else {
1042 $label = implode($this->getTooltipContentArray($params));
1043 }
1044
1045 $url = DOL_URL_ROOT.'/projet/tasks/'.$mode.'.php?id='.$this->id.($option == 'withproject' ? '&withproject=1' : '');
1046 // Add param to save lastsearch_values or not
1047 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1048 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1049 $add_save_lastsearch_values = 1;
1050 }
1051 if ($add_save_lastsearch_values) {
1052 $url .= '&save_lastsearch_values=1';
1053 }
1054
1055 $linkclose = '';
1056 if (empty($notooltip)) {
1057 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1058 $label = $langs->trans("ShowTask");
1059 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1060 }
1061 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1062 $linkclose .= $dataparams.' class="'.$classfortooltip.' nowraponall"';
1063 } else {
1064 $linkclose .= ' class="nowraponall"';
1065 }
1066
1067 $linkstart = '<a href="'.$url.'"';
1068 $linkstart .= $linkclose.'>';
1069 $linkend = '</a>';
1070
1071 $picto = 'projecttask';
1072
1073 $result .= $linkstart;
1074 if ($withpicto) {
1075 $result .= img_object(($notooltip ? '' : $label), $picto, 'class="paddingright"', 0, 0, $notooltip ? 0 : 1);
1076 }
1077 if ($withpicto != 2) {
1078 if ($addlabel >= 0) {
1079 $result .= $this->ref;
1080 } else {
1081 $result .= $this->label;
1082 }
1083 }
1084 $result .= $linkend;
1085 if ($withpicto != 2) {
1086 $result .= (($addlabel > 0 && $this->label) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1087 }
1088
1089 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1090 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1091 if ($reshook > 0) {
1092 $result = $hookmanager->resPrint;
1093 } else {
1094 $result .= $hookmanager->resPrint;
1095 }
1096
1097 return $result;
1098 }
1099
1107 public function initAsSpecimen()
1108 {
1109 global $user;
1110
1111 $this->id = 0;
1112
1113 $this->fk_project = 0;
1114 $this->ref = 'TK01';
1115 $this->fk_task_parent = 0;
1116 $this->label = 'Specimen task TK01';
1117 $this->duration_effective = '';
1118 $this->fk_user_creat = $user->id;
1119 $this->progress = 25;
1120 $this->status = 0;
1121 $this->priority = 0;
1122 $this->note_private = 'This is a specimen private note';
1123 $this->note_public = 'This is a specimen public note';
1124 $this->billable = 1;
1125
1126 return 1;
1127 }
1128
1152 public function getTasksArray($usert = null, $userp = null, $projectid = 0, $socid = 0, $mode = 0, $filteronproj = '', $filteronprojstatus = '-1', $morewherefilter = '', $filteronprojuser = 0, $filterontaskuser = 0, $extrafields = null, $includebilltime = 0, $search_array_options = array(), $loadextras = 0, $loadRoleMode = 1, $sortfield = '', $sortorder = '')
1153 {
1154 global $hookmanager;
1155
1156 $tasks = array();
1157
1158 //print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'<br>';
1159
1160 // List of tasks (does not care about permissions. Filtering will be done later)
1161 $sql = "SELECT ";
1162 if ($filteronprojuser > 0 || $filterontaskuser > 0) {
1163 $sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task
1164 }
1165 $sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,";
1166 $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,";
1167 $sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang, t.priority,";
1168 $sql .= " t.budget_amount, t.billable,";
1169 $sql .= " t.note_public, t.note_private,";
1170 $sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,";
1171 $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount as project_budget_amount";
1172 if ($loadextras) { // TODO Replace this with a fetch_optionnal() on the project after the fetch_object of line.
1173 if (!empty($extrafields->attributes['projet']['label'])) {
1174 foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1175 $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key." as options_".$key : '');
1176 }
1177 }
1178 if (!empty($extrafields->attributes['projet_task']['label'])) {
1179 foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
1180 $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key." as options_".$key : '');
1181 }
1182 }
1183 }
1184 if ($includebilltime) {
1185 $sql .= ", SUM(tt.element_duration * ".$this->db->ifsql("invoice_id IS NULL", "1", "0").") as tobill, SUM(tt.element_duration * ".$this->db->ifsql("invoice_id IS NULL", "0", "1").") as billed";
1186 }
1187
1188 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1189 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
1190 if ($loadextras) {
1191 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_extrafields as efp ON (p.rowid = efp.fk_object)";
1192 }
1193
1194 if ($mode == 0) {
1195 if ($filteronprojuser > 0) {
1196 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1197 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
1198 }
1199 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
1200 if ($loadextras) {
1201 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)";
1202 }
1203 if ($includebilltime) {
1204 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1205 }
1206 if ($filterontaskuser > 0) {
1207 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2";
1208 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2";
1209 }
1210 $sql .= " WHERE p.entity IN (".getEntity('project').")";
1211 $sql .= " AND t.fk_projet = p.rowid";
1212 } elseif ($mode == 1) {
1213 if ($filteronprojuser > 0) {
1214 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1215 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
1216 }
1217 if ($filterontaskuser > 0) {
1218 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
1219 if ($includebilltime) {
1220 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1221 }
1222 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2";
1223 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2";
1224 } else {
1225 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t on t.fk_projet = p.rowid";
1226 if ($includebilltime) {
1227 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype = 'task')";
1228 }
1229 }
1230 $sql .= " WHERE p.entity IN (".getEntity('project').")";
1231 } else {
1232 return 'BadValueForParameterMode';
1233 }
1234
1235 if ($filteronprojuser > 0) {
1236 $sql .= " AND p.rowid = ec.element_id";
1237 $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1238 $sql .= " AND ctc.element = 'project'";
1239 $sql .= " AND ec.fk_socpeople = ".((int) $filteronprojuser);
1240 $sql .= " AND ec.statut = 4";
1241 $sql .= " AND ctc.source = 'internal'";
1242 }
1243 if ($filterontaskuser > 0) {
1244 $sql .= " AND t.fk_projet = p.rowid";
1245 $sql .= " AND p.rowid = ec2.element_id";
1246 $sql .= " AND ctc2.rowid = ec2.fk_c_type_contact";
1247 $sql .= " AND ctc2.element = 'project_task'";
1248 $sql .= " AND ec2.fk_socpeople = ".((int) $filterontaskuser);
1249 $sql .= " AND ec2.statut = 4";
1250 $sql .= " AND ctc2.source = 'internal'";
1251 }
1252 if ($socid) {
1253 $sql .= " AND p.fk_soc = ".((int) $socid);
1254 }
1255 if ($projectid) {
1256 $sql .= " AND p.rowid IN (".$this->db->sanitize((string) $projectid).")";
1257 }
1258 if ($filteronproj) {
1259 $sql .= natural_search(array("p.ref", "p.title"), $filteronproj);
1260 }
1261 if ($filteronprojstatus && (int) $filteronprojstatus != '-1') {
1262 $sql .= " AND p.fk_statut IN (".$this->db->sanitize($filteronprojstatus).")";
1263 }
1264 if ($morewherefilter) {
1265 $sql .= $morewherefilter;
1266 }
1267
1268 // Add where from extra fields
1269 $extrafieldsobjectkey = 'projet_task';
1270 $extrafieldsobjectprefix = 'efpt.';
1271 $search_options_pattern = 'search_task_options_'; // Aligned with perweek.php/perday.php that build $search_array_options with this prefix (via extrafields->getOptionalsFromPost('projet_task', '', 'search_task_')). Without it, the SQL template falls back to 'search_options_' and the preg_replace fails to strip the actual prefix, generating phantom column names like efpt.search_task_options_<field>.
1272 global $db, $conf; // needed for extrafields_list_search_sql.tpl
1273 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
1274
1275 // Add where from hooks
1276 $parameters = array();
1277 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
1278 $sql .= $hookmanager->resPrint;
1279 if ($includebilltime) {
1280 $sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,";
1281 $sql .= " t.datec, t.dateo, t.datee, t.tms,";
1282 $sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,";
1283 $sql .= " t.dateo, t.datee, t.planned_workload, t.rang, t.priority,";
1284 $sql .= " t.budget_amount, t.billable,";
1285 $sql .= " t.note_public, t.note_private,";
1286 $sql .= " s.rowid, s.nom, s.email,";
1287 $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
1288 if ($loadextras) {
1289 if (!empty($extrafields->attributes['projet']['label'])) {
1290 foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1291 $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key : '');
1292 }
1293 }
1294 if (!empty($extrafields->attributes['projet_task']['label'])) {
1295 foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
1296 $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key : '');
1297 }
1298 }
1299 }
1300 }
1301
1302 if ($sortfield && $sortorder) {
1303 $sql .= $this->db->order($sortfield, $sortorder);
1304 } else {
1305 $sql .= " ORDER BY p.ref, t.rang, t.dateo";
1306 }
1307
1308 //print $sql;exit;
1309 dol_syslog(get_class($this)."::getTasksArray", LOG_DEBUG);
1310 $resql = $this->db->query($sql);
1311 if ($resql) {
1312 $num = $this->db->num_rows($resql);
1313 $i = 0;
1314 // Loop on each record found, so each couple (project id, task id)
1315 while ($i < $num) {
1316 $error = 0;
1317
1318 $obj = $this->db->fetch_object($resql);
1319
1320 if ($loadRoleMode) {
1321 if ((!$obj->public) && (is_object($userp))) { // If not public project and we ask a filter on project owned by a user
1322 if (!$this->getUserRolesForProjectsOrTasks($userp, null, $obj->projectid, 0)) {
1323 $error++;
1324 }
1325 }
1326 if (is_object($usert)) { // If we ask a filter on a user affected to a task
1327 if (!$this->getUserRolesForProjectsOrTasks(null, $usert, $obj->projectid, $obj->taskid)) {
1328 $error++;
1329 }
1330 }
1331 }
1332
1333 if (!$error) {
1334 $tasks[$i] = new Task($this->db);
1335 $tasks[$i]->id = $obj->taskid;
1336 $tasks[$i]->ref = $obj->taskref;
1337 $tasks[$i]->fk_project = $obj->projectid;
1338
1339 // Data from project
1340 $tasks[$i]->projectref = $obj->ref;
1341 $tasks[$i]->projectlabel = $obj->plabel;
1342 $tasks[$i]->projectstatus = $obj->projectstatus;
1343 $tasks[$i]->fk_opp_status = $obj->fk_opp_status;
1344 $tasks[$i]->opp_amount = $obj->opp_amount;
1345 $tasks[$i]->opp_percent = $obj->opp_percent;
1346 $tasks[$i]->budget_amount = $obj->budget_amount;
1347 $tasks[$i]->project_budget_amount = $obj->project_budget_amount;
1348 $tasks[$i]->usage_bill_time = $obj->usage_bill_time;
1349
1350 $tasks[$i]->label = $obj->label;
1351 $tasks[$i]->description = $obj->description;
1352
1353 $tasks[$i]->fk_task_parent = $obj->fk_task_parent;
1354 $tasks[$i]->note_public = $obj->note_public;
1355 $tasks[$i]->note_private = $obj->note_private;
1356 $tasks[$i]->duration_effective = $obj->duration_effective;
1357 $tasks[$i]->planned_workload = $obj->planned_workload;
1358
1359 if ($includebilltime) {
1360 // Data summed from element_time linked to task
1361 $tasks[$i]->tobill = $obj->tobill;
1362 $tasks[$i]->billed = $obj->billed;
1363 }
1364
1365 $tasks[$i]->progress = $obj->progress;
1366 $tasks[$i]->fk_statut = $obj->status;
1367 $tasks[$i]->status = $obj->status;
1368 $tasks[$i]->public = $obj->public;
1369 $tasks[$i]->date_start = $this->db->jdate($obj->date_start);
1370 $tasks[$i]->date_end = $this->db->jdate($obj->date_end);
1371 $tasks[$i]->rang = $obj->rang;
1372 $tasks[$i]->priority = $obj->priority;
1373
1374 $tasks[$i]->socid = $obj->thirdparty_id; // For backward compatibility
1375 $tasks[$i]->thirdparty_id = $obj->thirdparty_id;
1376 $tasks[$i]->thirdparty_name = $obj->thirdparty_name;
1377 $tasks[$i]->thirdparty_email = $obj->thirdparty_email;
1378
1379 $tasks[$i]->billable = $obj->billable;
1380
1381 if ($loadextras) {
1382 if (!empty($extrafields->attributes['projet']['label'])) {
1383 foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1384 if ($extrafields->attributes['projet']['type'][$key] != 'separate') {
1385 $tmpvar = 'options_'.$key;
1386 $tasks[$i]->array_options_project['options_'.$key] = $obj->$tmpvar;
1387 }
1388 }
1389 }
1390 }
1391
1392 if ($loadextras) {
1393 $tasks[$i]->fetch_optionals();
1394 }
1395 }
1396
1397 $i++;
1398 }
1399 $this->db->free($resql);
1400 } else {
1401 dol_print_error($this->db);
1402 }
1403
1404 return $tasks;
1405 }
1406
1417 public function getUserRolesForProjectsOrTasks($userp, $usert, $projectid = '', $taskid = 0, $filteronprojstatus = -1)
1418 {
1419 $arrayroles = array();
1420
1421 dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks userp=".json_encode(is_object($userp))." usert=".json_encode(is_object($usert))." projectid=".$projectid." taskid=".$taskid);
1422
1423 // We want role of user for a projet or role of user for a task. Both are not possible.
1424 if (empty($userp) && empty($usert)) {
1425 $this->error = "CallWithWrongParameters";
1426 return -1;
1427 }
1428 if (!empty($userp) && !empty($usert)) {
1429 $this->error = "CallWithWrongParameters";
1430 return -1;
1431 }
1432
1433 /* Liste des taches et role sur les projects ou taches */
1434 $sql = "SELECT ";
1435 if ($userp) {
1436 $sql .= " p.rowid as pid,";
1437 } else {
1438 $sql .= " pt.rowid as pid,";
1439 }
1440 $sql .= " ec.element_id, ctc.code, ctc.source";
1441 if ($userp) {
1442 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1443 }
1444 if ($usert && $filteronprojstatus > -1) {
1445 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p, ".MAIN_DB_PREFIX."projet_task as pt";
1446 }
1447 if ($usert && $filteronprojstatus <= -1) {
1448 $sql .= " FROM ".MAIN_DB_PREFIX."projet_task as pt";
1449 }
1450 $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1451 $sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
1452 if ($userp) {
1453 $sql .= " WHERE p.rowid = ec.element_id";
1454 } else {
1455 $sql .= " WHERE pt.rowid = ec.element_id";
1456 }
1457 if ($userp && $filteronprojstatus > -1) {
1458 $sql .= " AND p.fk_statut = ".((int) $filteronprojstatus);
1459 }
1460 if ($usert && $filteronprojstatus > -1) {
1461 $sql .= " AND pt.fk_projet = p.rowid AND p.fk_statut = ".((int) $filteronprojstatus);
1462 }
1463 if ($userp) {
1464 $sql .= " AND ctc.element = 'project'";
1465 }
1466 if ($usert) {
1467 $sql .= " AND ctc.element = 'project_task'";
1468 }
1469 $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1470 if ($userp) {
1471 $sql .= " AND ec.fk_socpeople = ".((int) $userp->id);
1472 }
1473 if ($usert) {
1474 $sql .= " AND ec.fk_socpeople = ".((int) $usert->id);
1475 }
1476 $sql .= " AND ec.statut = 4";
1477 $sql .= " AND ctc.source = 'internal'";
1478 if ($projectid) {
1479 if ($userp) {
1480 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectid).")";
1481 }
1482 if ($usert) {
1483 $sql .= " AND pt.fk_projet IN (".$this->db->sanitize($projectid).")";
1484 }
1485 }
1486 if ($taskid) {
1487 if ($userp) {
1488 $sql .= " ERROR SHOULD NOT HAPPENS";
1489 }
1490 if ($usert) {
1491 $sql .= " AND pt.rowid = ".((int) $taskid);
1492 }
1493 }
1494 //print $sql;
1495
1496 dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks execute request", LOG_DEBUG);
1497 $resql = $this->db->query($sql);
1498 if ($resql) {
1499 $num = $this->db->num_rows($resql);
1500 $i = 0;
1501 while ($i < $num) {
1502 $obj = $this->db->fetch_object($resql);
1503 if (empty($arrayroles[$obj->pid])) {
1504 $arrayroles[$obj->pid] = $obj->code;
1505 } else {
1506 $arrayroles[$obj->pid] .= ','.$obj->code;
1507 }
1508 $i++;
1509 }
1510 $this->db->free($resql);
1511 } else {
1512 dol_print_error($this->db);
1513 }
1514
1515 return $arrayroles;
1516 }
1517
1518
1525 public function getListContactId($source = 'internal')
1526 {
1527 $contactAlreadySelected = array();
1528 $tab = $this->liste_contact(-1, $source);
1529 //var_dump($tab);
1530 $num = count($tab);
1531 $i = 0;
1532 while ($i < $num) {
1533 if ($source == 'thirdparty') {
1534 $contactAlreadySelected[$i] = $tab[$i]['socid'];
1535 } else {
1536 $contactAlreadySelected[$i] = $tab[$i]['id'];
1537 }
1538 $i++;
1539 }
1540 return $contactAlreadySelected;
1541 }
1542
1550 public function mergeContactTask($origin_id, $dest_id)
1551 {
1552 $error = 0;
1553 $origintask = new Task($this->db);
1554 $result = $origintask->fetch($origin_id);
1555 if ($result <= 0) {
1556 return false;
1557 }
1558
1559 //Get list of origin contacts
1560 $arraycontactorigin = array_merge($origintask->liste_contact(-1, 'internal'), $origintask->liste_contact(-1, 'external'));
1561 if (is_array($arraycontactorigin)) {
1562 foreach ($arraycontactorigin as $key => $contact) {
1563 $result = $this->add_contact($contact["id"], $contact["fk_c_type_contact"], $contact["source"]);
1564 if ($result < 0) {
1565 return false;
1566 }
1567 }
1568 }
1569 return true;
1570 }
1571
1579 public function mergeTimeSpentTask($origin_id, $dest_id)
1580 {
1581 $ret = true;
1582
1583 $this->db->begin();
1584
1585 $sql = "UPDATE ".MAIN_DB_PREFIX."element_time as et";
1586 $sql .= " SET et.fk_element = ".((int) $dest_id);
1587 $sql .= " WHERE et.elementtype = 'task'";
1588 $sql .= " AND et.fk_element = ".((int) $origin_id);
1589
1590 dol_syslog(get_class($this)."::mergeTimeSpentTask", LOG_DEBUG);
1591 if (!$this->db->query($sql)) {
1592 $this->error = $this->db->lasterror();
1593 $ret = false;
1594 }
1595
1596 if ($ret) {
1597 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
1598 $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM ".MAIN_DB_PREFIX."element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = ".((int) $dest_id).")";
1599 $sql .= " WHERE rowid = ".((int) $dest_id);
1600
1601 dol_syslog(get_class($this)."::mergeTimeSpentTask update project_task", LOG_DEBUG);
1602 if (!$this->db->query($sql)) {
1603 $this->error = $this->db->lasterror();
1604 $ret = false;
1605 }
1606 }
1607
1608 if ($ret) {
1609 $this->db->commit();
1610 } else {
1611 $this->db->rollback();
1612 }
1613 return $ret;
1614 }
1615
1623 public function addTimeSpent($user, $notrigger = 0)
1624 {
1625 global $langs;
1626
1627 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1628
1629 $ret = 0;
1630 $now = dol_now();
1631
1632 // Check parameters
1633 if (!is_object($user)) {
1634 dol_print_error(null, "Method addTimeSpent was called with wrong parameter user");
1635 return -1;
1636 }
1637
1638 // Clean parameters
1639 if (isset($this->timespent_note)) {
1640 $this->timespent_note = trim($this->timespent_note);
1641 }
1642 if (empty($this->timespent_datehour) || empty($this->timespent_withhour)) {
1643 // Sync datehour from the day-level date when no start hour was provided (withhour = 0), or when
1644 // datehour is empty. When a start hour was entered (withhour = 1), keep datehour untouched so the
1645 // hour is not discarded (#39276). This still resynchronizes datehour when only the day changes.
1646 $this->timespent_datehour = $this->timespent_date;
1647 }
1648
1649 if (getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1650 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
1651 $restrictBefore = dol_time_plus_duree(dol_now(), - getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'), 'm');
1652
1653 if ($this->timespent_date < $restrictBefore) {
1654 $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1655 $this->errors[] = $this->error;
1656 return -1;
1657 }
1658 }
1659
1660 $this->db->begin();
1661
1662 $timespent = new TimeSpent($this->db);
1663 $timespent->fk_element = $this->id;
1664 $timespent->elementtype = 'task';
1665 $timespent->element_date = $this->timespent_date;
1666 $timespent->element_datehour = $this->timespent_datehour;
1667 $timespent->element_date_withhour = $this->timespent_withhour;
1668 $timespent->element_duration = $this->timespent_duration;
1669 $timespent->fk_user = $this->timespent_fk_user;
1670 $timespent->fk_product = $this->timespent_fk_product;
1671 $timespent->note = $this->timespent_note;
1672 $timespent->datec = $now;
1673
1674 $result = $timespent->create($user);
1675 if ($result > 0) {
1676 $ret = $result;
1677 $this->timespent_id = $result;
1678
1679 if (!$notrigger) {
1680 // Call trigger
1681 $result = $this->call_trigger('TASK_TIMESPENT_CREATE', $user);
1682 if ($result < 0) {
1683 $ret = -1;
1684 }
1685 // End call triggers
1686 }
1687 } else {
1688 $this->error = $this->db->lasterror();
1689 $ret = -1;
1690 }
1691
1692 // Propagate trigger handler messages from $this->errors (plural array)
1693 // to $this->error (singular string) so callers like the REST API can
1694 // surface a useful message instead of an empty error tail.
1695 if ($ret <= 0 && empty($this->error) && !empty($this->errors)) {
1696 foreach ($this->errors as $errmsg) {
1697 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
1698 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1699 }
1700 }
1701
1702 if ($ret > 0) {
1703 // Recalculate amount of time spent for task and update denormalized field
1704 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
1705 $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM ".MAIN_DB_PREFIX."element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = ".((int) $this->id).")";
1706 if (isset($this->progress)) {
1707 $sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided
1708 if ($this->progress == 100) {
1709 $this->status = Task::STATUS_CLOSED;
1710 } elseif ($this->progress != 0) {
1712 } else {
1714 }
1715 $sql .= ", fk_statut = ".((int) $this->status);
1716 } else {
1718 $sql .= ", fk_statut = ".((int) $this->status);
1719 }
1720 $sql .= " WHERE rowid = ".((int) $this->id);
1721
1722 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1723 if (!$this->db->query($sql)) {
1724 $this->error = $this->db->lasterror();
1725 $ret = -2;
1726 }
1727
1728 // Update hourly rate of this time spent entry
1729 $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
1730 if (!empty($resql_thm_user)) {
1731 $obj_thm_user = $this->db->fetch_object($resql_thm_user);
1732 $timespent->thm = $obj_thm_user->thm;
1733 }
1734 $res_update = $timespent->update($user);
1735
1736 dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1737 if ($res_update <= 0) {
1738 $this->error = $this->db->lasterror();
1739 $ret = -2;
1740 }
1741 }
1742
1743 if ($ret > 0) {
1744 $this->db->commit();
1745 } else {
1746 $this->db->rollback();
1747 }
1748 return $ret;
1749 }
1750
1757 public function fetchTimeSpentOnTask($morewherefilter = '')
1758 {
1759 $arrayres = array();
1760
1761 $sql = "SELECT";
1762 $sql .= " s.rowid as socid,";
1763 $sql .= " s.nom as thirdparty_name,";
1764 $sql .= " s.email as thirdparty_email,";
1765 $sql .= " ptt.rowid,";
1766 $sql .= " ptt.ref_ext,";
1767 $sql .= " ptt.fk_element as fk_task,";
1768 $sql .= " ptt.element_date as task_date,";
1769 $sql .= " ptt.element_datehour as task_datehour,";
1770 $sql .= " ptt.element_date_withhour as task_date_withhour,";
1771 $sql .= " ptt.element_duration as task_duration,";
1772 $sql .= " ptt.fk_user,";
1773 $sql .= " ptt.fk_product,";
1774 $sql .= " ptt.note,";
1775 $sql .= " ptt.thm,";
1776 $sql .= " pt.rowid as task_id,";
1777 $sql .= " pt.ref as task_ref,";
1778 $sql .= " pt.label as task_label,";
1779 $sql .= " p.rowid as project_id,";
1780 $sql .= " p.ref as project_ref,";
1781 $sql .= " p.title as project_label,";
1782 $sql .= " p.public as project_public";
1783 $sql .= " FROM ".MAIN_DB_PREFIX."element_time as ptt, ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet as p";
1784 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
1785 $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
1786 $sql .= " AND ptt.elementtype = 'task'";
1787 $sql .= " AND pt.rowid = ".((int) $this->id);
1788 $sql .= " AND pt.entity IN (".getEntity('project').")";
1789 if ($morewherefilter) {
1790 $sql .= $morewherefilter;
1791 }
1792
1793 dol_syslog(get_class($this)."::fetchAllTimeSpent", LOG_DEBUG);
1794 $resql = $this->db->query($sql);
1795 if ($resql) {
1796 $num = $this->db->num_rows($resql);
1797
1798 $i = 0;
1799 while ($i < $num) {
1800 $obj = $this->db->fetch_object($resql);
1801
1802 $newobj = new stdClass();
1803
1804 $newobj->socid = $obj->socid;
1805 $newobj->thirdparty_name = $obj->thirdparty_name;
1806 $newobj->thirdparty_email = $obj->thirdparty_email;
1807
1808 $newobj->fk_project = $obj->project_id;
1809 $newobj->project_ref = $obj->project_ref;
1810 $newobj->project_label = $obj->project_label;
1811 $newobj->project_public = $obj->project_public;
1812 $newobj->public = $obj->project_public; // deprecated
1813
1814 $newobj->fk_task = $obj->task_id;
1815 $newobj->task_ref = $obj->task_ref;
1816 $newobj->task_label = $obj->task_label;
1817
1818 $newobj->timespent_line_id = $obj->rowid;
1819 $newobj->timespent_line_ref_ext = $obj->ref_ext;
1820 $newobj->timespent_line_date = $this->db->jdate($obj->task_date);
1821 $newobj->timespent_line_datehour = $this->db->jdate($obj->task_datehour);
1822 $newobj->timespent_line_withhour = $obj->task_date_withhour;
1823 $newobj->timespent_line_duration = $obj->task_duration;
1824 $newobj->timespent_line_fk_user = $obj->fk_user;
1825 $newobj->timespent_line_fk_product = $obj->fk_product;
1826 $newobj->timespent_line_thm = $obj->thm; // hourly rate
1827 $newobj->timespent_line_note = $obj->note;
1828
1829 $arrayres[] = $newobj;
1830
1831 $i++;
1832 }
1833
1834 $this->db->free($resql);
1835
1836 $this->lines = $arrayres;
1837 return 1;
1838 } else {
1839 dol_print_error($this->db);
1840 $this->error = "Error ".$this->db->lasterror();
1841 return -1;
1842 }
1843 }
1844
1845
1853 public function getSummaryOfTimeSpent($userobj = null, $morewherefilter = '')
1854 {
1855 if (is_object($userobj)) {
1856 $userid = $userobj->id;
1857 } else {
1858 $userid = $userobj; // old method
1859 }
1860
1861 $id = $this->id;
1862 if (empty($id) && empty($userid)) {
1863 dol_syslog("getSummaryOfTimeSpent called on a not loaded task without user param defined", LOG_ERR);
1864 return -1;
1865 }
1866
1867 $result = array();
1868
1869 $sql = "SELECT";
1870 $sql .= " MIN(t.element_datehour) as min_date,";
1871 $sql .= " MAX(t.element_datehour) as max_date,";
1872 $sql .= " SUM(t.element_duration) as total_duration,";
1873 $sql .= " SUM(t.element_duration / 3600 * ".$this->db->ifsql("t.thm IS NULL", '0', "t.thm").") as total_amount,";
1874 $sql .= " COUNT(t.rowid) as nblines,";
1875 $sql .= " SUM(".$this->db->ifsql("t.thm IS NULL", '1', '0').") as nblinesnull";
1876 $sql .= " FROM ".MAIN_DB_PREFIX."element_time as t";
1877 $sql .= " WHERE t.elementtype='task'";
1878 if ($morewherefilter) {
1879 $sql .= $morewherefilter;
1880 }
1881 if ($id > 0) {
1882 $sql .= " AND t.fk_element = ".((int) $id);
1883 }
1884 if ($userid > 0) {
1885 $sql .= " AND t.fk_user = ".((int) $userid);
1886 }
1887
1888 dol_syslog(get_class($this)."::getSummaryOfTimeSpent", LOG_DEBUG);
1889 $resql = $this->db->query($sql);
1890 if ($resql) {
1891 $obj = $this->db->fetch_object($resql);
1892
1893 $result['min_date'] = $obj->min_date; // deprecated. use the ->timespent_xxx instead
1894 $result['max_date'] = $obj->max_date; // deprecated. use the ->timespent_xxx instead
1895 $result['total_duration'] = $obj->total_duration; // deprecated. use the ->timespent_xxx instead
1896
1897 $this->timespent_min_date = $this->db->jdate($obj->min_date);
1898 $this->timespent_max_date = $this->db->jdate($obj->max_date);
1899 $this->timespent_total_duration = $obj->total_duration;
1900 $this->timespent_total_amount = $obj->total_amount;
1901 $this->timespent_nblinesnull = ($obj->nblinesnull ? $obj->nblinesnull : 0);
1902 $this->timespent_nblines = ($obj->nblines ? $obj->nblines : 0);
1903
1904 $this->db->free($resql);
1905 } else {
1906 dol_print_error($this->db);
1907 }
1908 return $result;
1909 }
1910
1919 public function getSumOfAmount($fuser = '', $dates = '', $datee = '')
1920 {
1921 $id = $this->id;
1922
1923 $result = array();
1924
1925 $sql = "SELECT";
1926 $sql .= " SUM(t.element_duration) as nbseconds,";
1927 $sql .= " SUM(".$this->db->ifsql("u.thm IS NULL", '1', '0').") as nbuserthmnull,";
1928 $sql .= " SUM(t.element_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";
1929 $sql .= " FROM ".MAIN_DB_PREFIX."element_time as t";
1930 $sql .= " JOIN ".MAIN_DB_PREFIX."user as u ON u.rowid = t.fk_user";
1931 $sql .= " WHERE t.elementtype='task' AND t.fk_element = ".((int) $id);
1932 if (is_object($fuser) && $fuser->id > 0) {
1933 $sql .= " AND fk_user = ".((int) $fuser->id);
1934 }
1935 if ($dates > 0) {
1936 $datefieldname = "element_datehour";
1937 $sql .= " AND (".$datefieldname." >= '".$this->db->idate((int) $dates)."' OR ".$datefieldname." IS NULL)";
1938 }
1939 if ($datee > 0) {
1940 $datefieldname = "element_datehour";
1941 $sql .= " AND (".$datefieldname." <= '".$this->db->idate((int) $datee)."' OR ".$datefieldname." IS NULL)";
1942 }
1943 //print $sql;
1944
1945 dol_syslog(get_class($this)."::getSumOfAmount", LOG_DEBUG);
1946 $resql = $this->db->query($sql);
1947 if ($resql) {
1948 $obj = $this->db->fetch_object($resql);
1949
1950 $result['amount'] = $obj->amount;
1951 $result['nbseconds'] = $obj->nbseconds;
1952 $result['nblinesnull'] = $obj->nblinesnull;
1953 $result['nbuserthmnull'] = $obj->nbuserthmnull;
1954
1955 $this->db->free($resql);
1956 return $result;
1957 } else {
1958 dol_print_error($this->db);
1959 return $result;
1960 }
1961 }
1962
1969 public function fetchTimeSpent($id)
1970 {
1971 $timespent = new TimeSpent($this->db);
1972 $timespent->fetch($id);
1973
1974 dol_syslog(get_class($this)."::fetchTimeSpent", LOG_DEBUG);
1975
1976 if ($timespent->id > 0) {
1977 $this->timespent_id = $timespent->id;
1978 $this->id = $timespent->fk_element;
1979 $this->timespent_date = $timespent->element_date;
1980 $this->timespent_datehour = $timespent->element_datehour;
1981 $this->timespent_withhour = $timespent->element_date_withhour;
1982 $this->timespent_duration = $timespent->element_duration;
1983 $this->timespent_fk_user = $timespent->fk_user;
1984 $this->timespent_fk_product = $timespent->fk_product;
1985 $this->timespent_thm = $timespent->thm; // hourly rate
1986 $this->timespent_note = $timespent->note;
1987
1988 return 1;
1989 }
1990
1991 return 0;
1992 }
1993
2001 public function fetchAllTimeSpent(User $userobj, $morewherefilter = '')
2002 {
2003 $arrayres = array();
2004
2005 $sql = "SELECT";
2006 $sql .= " s.rowid as socid,";
2007 $sql .= " s.nom as thirdparty_name,";
2008 $sql .= " s.email as thirdparty_email,";
2009 $sql .= " ptt.rowid,";
2010 $sql .= " ptt.fk_element as fk_task,";
2011 $sql .= " ptt.element_date as task_date,";
2012 $sql .= " ptt.element_datehour as task_datehour,";
2013 $sql .= " ptt.element_date_withhour as task_date_withhour,";
2014 $sql .= " ptt.element_duration as task_duration,";
2015 $sql .= " ptt.fk_user,";
2016 $sql .= " ptt.note,";
2017 $sql .= " ptt.thm,";
2018 $sql .= " pt.rowid as task_id,";
2019 $sql .= " pt.ref as task_ref,";
2020 $sql .= " pt.label as task_label,";
2021 $sql .= " p.rowid as project_id,";
2022 $sql .= " p.ref as project_ref,";
2023 $sql .= " p.title as project_label,";
2024 $sql .= " p.public as public";
2025 $sql .= " FROM ".MAIN_DB_PREFIX."element_time as ptt, ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet as p";
2026 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
2027 $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
2028 $sql .= " AND ptt.elementtype = 'task'";
2029 $sql .= " AND ptt.fk_user = ".((int) $userobj->id);
2030 $sql .= " AND pt.entity IN (".getEntity('project').")";
2031 if ($morewherefilter) {
2032 $sql .= $morewherefilter;
2033 }
2034
2035 dol_syslog(get_class($this)."::fetchAllTimeSpent", LOG_DEBUG);
2036 $resql = $this->db->query($sql);
2037 if ($resql) {
2038 $num = $this->db->num_rows($resql);
2039
2040 $i = 0;
2041 while ($i < $num) {
2042 $obj = $this->db->fetch_object($resql);
2043
2044 $newobj = new stdClass();
2045
2046 $newobj->socid = $obj->socid;
2047 $newobj->thirdparty_name = $obj->thirdparty_name;
2048 $newobj->thirdparty_email = $obj->thirdparty_email;
2049
2050 $newobj->fk_project = $obj->project_id;
2051 $newobj->project_ref = $obj->project_ref;
2052 $newobj->project_label = $obj->project_label;
2053 $newobj->public = $obj->project_public;
2054
2055 $newobj->fk_task = $obj->task_id;
2056 $newobj->task_ref = $obj->task_ref;
2057 $newobj->task_label = $obj->task_label;
2058
2059 $newobj->timespent_id = $obj->rowid;
2060 $newobj->timespent_date = $this->db->jdate($obj->task_date);
2061 $newobj->timespent_datehour = $this->db->jdate($obj->task_datehour);
2062 $newobj->timespent_withhour = $obj->task_date_withhour;
2063 $newobj->timespent_duration = $obj->task_duration;
2064 $newobj->timespent_fk_user = $obj->fk_user;
2065 $newobj->timespent_thm = $obj->thm; // hourly rate
2066 $newobj->timespent_note = $obj->note;
2067
2068 $arrayres[] = $newobj;
2069
2070 $i++;
2071 }
2072
2073 $this->db->free($resql);
2074 } else {
2075 dol_print_error($this->db);
2076 $this->error = "Error ".$this->db->lasterror();
2077 return -1;
2078 }
2079
2080 return $arrayres;
2081 }
2082
2090 public function updateTimeSpent($user, $notrigger = 0)
2091 {
2092 global $conf, $langs;
2093
2094 $ret = 0;
2095
2096 // Check parameters
2097 if ($this->timespent_date == '') {
2098 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("Date"));
2099 return -1;
2100 }
2101
2102 // Clean parameters
2103 if (empty($this->timespent_datehour) || empty($this->timespent_withhour)) {
2104 // Sync datehour from the day-level date when no start hour was provided (withhour = 0), or when
2105 // datehour is empty. When a start hour was entered (withhour = 1), keep datehour untouched so the
2106 // hour is not discarded (#39276). This still resynchronizes datehour when only the day changes.
2107 $this->timespent_datehour = $this->timespent_date;
2108 }
2109 if (isset($this->timespent_note)) {
2110 $this->timespent_note = trim($this->timespent_note);
2111 }
2112
2113 if (getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
2114 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2115 $restrictBefore = dol_time_plus_duree(dol_now(), - getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'), 'm');
2116
2117 if ($this->timespent_date < $restrictBefore) {
2118 $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
2119 $this->errors[] = $this->error;
2120 return -1;
2121 }
2122 }
2123
2124 $this->db->begin();
2125
2126 $timespent = new TimeSpent($this->db);
2127 $timespent->fetch($this->timespent_id);
2128 $old_fk_element = $timespent->fk_element; // Store old task ID before potential change
2129
2130 $timespent->element_date = $this->timespent_date;
2131 $timespent->element_datehour = $this->timespent_datehour;
2132
2133 $timespent->element_date_withhour = $this->timespent_withhour; // 0 or 1
2134 $timespent->element_duration = $this->timespent_duration;
2135 if ($this->timespent_fk_user > 0) {
2136 $timespent->fk_user = $this->timespent_fk_user;
2137 }
2138 $timespent->fk_product = $this->timespent_fk_product;
2139 $timespent->fk_element = $this->id; // Update task assignment (may be changed)
2140 $timespent->note = $this->timespent_note;
2141 $timespent->invoice_id = $this->timespent_invoiceid;
2142 $timespent->invoice_line_id = $this->timespent_invoicelineid;
2143
2144 dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG);
2145
2146 $resupdate = $timespent->update($user);
2147 if ($resupdate > 0) {
2148 if (!$notrigger) {
2149 // Call trigger
2150 $result = $this->call_trigger('TASK_TIMESPENT_MODIFY', $user);
2151 if ($result < 0) {
2152 $this->db->rollback();
2153 $ret = -1;
2154 } else {
2155 $ret = 1;
2156 }
2157 // End call triggers
2158 } else {
2159 $ret = 1;
2160 }
2161 } else {
2162 $this->error = $this->db->lasterror();
2163 $this->db->rollback();
2164 $ret = -1;
2165 }
2166
2167 // Propagate trigger handler messages from $this->errors (plural array)
2168 // to $this->error (singular string) so callers like the REST API can
2169 // surface a useful message instead of an empty error tail.
2170 if ($ret < 0 && empty($this->error) && !empty($this->errors)) {
2171 foreach ($this->errors as $errmsg) {
2172 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2173 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2174 }
2175 }
2176
2177 if ($ret == 1 && (($this->timespent_old_duration != $this->timespent_duration) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM'))) {
2178 if ($this->timespent_old_duration != $this->timespent_duration) {
2179 // Recalculate amount of time spent for task and update denormalized field
2180 $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
2181 $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $this->id) . ")";
2182 if (isset($this->progress)) {
2183 $sql .= ", progress = " . ((float) $this->progress); // Do not overwrite value if not provided
2184 }
2185 $sql .= " WHERE rowid = " . ((int) $this->id);
2186
2187 dol_syslog(get_class($this) . "::updateTimeSpent", LOG_DEBUG);
2188 if (!$this->db->query($sql)) {
2189 $this->error = $this->db->lasterror();
2190 $this->db->rollback();
2191 $ret = -2;
2192 }
2193 }
2194
2195 // Update hourly rate of this time spent entry, but only if it was not set initially
2196 $res_update = 1;
2197 if (empty($timespent->thm) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM')) {
2198 $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
2199 if (!empty($resql_thm_user)) {
2200 $obj_thm_user = $this->db->fetch_object($resql_thm_user);
2201 $timespent->thm = $obj_thm_user->thm;
2202 }
2203 $res_update = $timespent->update($user);
2204 }
2205
2206 dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG);
2207 if ($res_update <= 0) {
2208 $this->error = $this->db->lasterror();
2209 $ret = -2;
2210 }
2211 }
2212
2213 // If task assignment changed, recalculate duration_effective for both old and new tasks
2214 if ($ret == 1 && $old_fk_element != $this->id) {
2215 // Recalculate duration_effective for the OLD task
2216 $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
2217 $sql .= " SET duration_effective = (SELECT COALESCE(SUM(element_duration), 0) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $old_fk_element) . ")";
2218 $sql .= " WHERE rowid = " . ((int) $old_fk_element);
2219 dol_syslog(get_class($this) . "::updateTimeSpent update old task", LOG_DEBUG);
2220 if (!$this->db->query($sql)) {
2221 $this->error = $this->db->lasterror();
2222 $this->db->rollback();
2223 $ret = -2;
2224 }
2225 // Recalculate duration_effective for the NEW task
2226 if ($ret == 1) {
2227 $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
2228 $sql .= " SET duration_effective = (SELECT COALESCE(SUM(element_duration), 0) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $this->id) . ")";
2229 $sql .= " WHERE rowid = " . ((int) $this->id);
2230 dol_syslog(get_class($this) . "::updateTimeSpent update new task", LOG_DEBUG);
2231 if (!$this->db->query($sql)) {
2232 $this->error = $this->db->lasterror();
2233 $this->db->rollback();
2234 $ret = -2;
2235 }
2236 }
2237 }
2238
2239 if ($ret >= 0) {
2240 $this->db->commit();
2241 }
2242 return $ret;
2243 }
2244
2252 public function delTimeSpent($user, $notrigger = 0)
2253 {
2254 global $conf, $langs;
2255
2256 $error = 0;
2257
2258 if (getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
2259 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2260 $restrictBefore = dol_time_plus_duree(dol_now(), - getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'), 'm');
2261
2262 if ($this->timespent_date < $restrictBefore) {
2263 $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
2264 $this->errors[] = $this->error;
2265 return -1;
2266 }
2267 }
2268
2269 $this->db->begin();
2270
2271 if (!$notrigger) {
2272 // Call trigger
2273 $result = $this->call_trigger('TASK_TIMESPENT_DELETE', $user);
2274 if ($result < 0) {
2275 $error++;
2276 }
2277 // End call triggers
2278 }
2279
2280 if (!$error) {
2281 $timespent = new TimeSpent($this->db);
2282 $timespent->fetch($this->timespent_id);
2283
2284 $res_del = $timespent->delete($user);
2285
2286 if ($res_del < 0) {
2287 $error++;
2288 $this->errors[] = "Error ".$this->db->lasterror();
2289 }
2290 }
2291
2292 if (!$error) {
2293 $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
2294 $sql .= " SET duration_effective = duration_effective - ".((int) $this->timespent_duration);
2295 $sql .= " WHERE rowid = ".((int) $this->id);
2296
2297 dol_syslog(get_class($this)."::delTimeSpent", LOG_DEBUG);
2298 if ($this->db->query($sql)) {
2299 $result = 0;
2300 } else {
2301 $this->error = $this->db->lasterror();
2302 $result = -2;
2303 }
2304 }
2305
2306 // Commit or rollback
2307 if ($error) {
2308 foreach ($this->errors as $errmsg) {
2309 dol_syslog(get_class($this)."::delTimeSpent ".$errmsg, LOG_ERR);
2310 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2311 }
2312 $this->db->rollback();
2313 return -1 * $error;
2314 } else {
2315 $this->db->commit();
2316 return 1;
2317 }
2318 }
2319
2334 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)
2335 {
2336 global $langs, $conf;
2337
2338 $error = 0;
2339
2340 //Use 00:00 of today if time is use on task.
2341 $now = dol_mktime(0, 0, 0, (int) dol_print_date(dol_now(), '%m'), (int) dol_print_date(dol_now(), '%d'), (int) dol_print_date(dol_now(), '%Y'));
2342
2343 $datec = $now;
2344
2345 $clone_task = new Task($this->db);
2346 $origin_task = new Task($this->db);
2347
2348 $clone_task->context['createfromclone'] = 'createfromclone';
2349
2350 $this->db->begin();
2351
2352 // Load source object
2353 $clone_task->fetch($fromid);
2354 $clone_task->fetch_optionals();
2355 //var_dump($clone_task->array_options);exit;
2356
2357 $origin_task->fetch($fromid);
2358
2359 $clone_task->date_c = $datec; // Set the new creation date before computing the ref, else the numbering mask uses the source task year
2360
2361 $defaultref = '';
2362 $obj = !getDolGlobalString('PROJECT_TASK_ADDON') ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON;
2363 if (getDolGlobalString('PROJECT_TASK_ADDON') && is_readable(DOL_DOCUMENT_ROOT."/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON').".php")) {
2364 require_once DOL_DOCUMENT_ROOT."/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON').'.php';
2365 $modTask = new $obj();
2366 '@phan-var-force ModeleNumRefTask $modTask';
2367 $defaultref = $modTask->getNextValue(null, $clone_task);
2368 }
2369
2370 $ori_project_id = $clone_task->fk_project;
2371
2372 $clone_task->id = 0;
2373 $clone_task->ref = $defaultref;
2374 $clone_task->fk_project = $project_id;
2375 $clone_task->fk_task_parent = $parent_task_id;
2376 $clone_task->planned_workload = $origin_task->planned_workload;
2377 $clone_task->rang = $origin_task->rang;
2378 $clone_task->priority = $origin_task->priority;
2379
2380 //Manage Task Date
2381 if ($clone_change_dt) {
2382 $projectstatic = new Project($this->db);
2383 $projectstatic->fetch($ori_project_id);
2384
2385 // Origin project start date
2386 $orign_project_dt_start = (!isset($projectstatic->date_start) || $projectstatic->date_start == '') ? $projectstatic->date_c : $projectstatic->date_start;
2387
2388 // Calculate new task start date with difference between origin proj start date and origin task start date
2389 if (!empty($clone_task->date_start)) {
2390 $clone_task->date_start = $now + $clone_task->date_start - $orign_project_dt_start;
2391 }
2392
2393 // Calculate new task end date with difference between origin proj start date and origin task end date
2394 if (!empty($clone_task->date_end)) {
2395 $clone_task->date_end = $now + $clone_task->date_end - $orign_project_dt_start;
2396 }
2397 }
2398
2399 if (!$clone_prog) {
2400 $clone_task->progress = 0;
2401 }
2402
2403 // Create clone
2404 $result = $clone_task->create($user);
2405
2406 // Other options
2407 if ($result < 0) {
2408 $this->error = $clone_task->error;
2409 $error++;
2410 }
2411 // End
2412 if ($error) {
2413 $clone_task_id = 0; // For static tool check
2414 } else {
2415 $clone_task_id = $clone_task->id;
2416 $clone_task_ref = $clone_task->ref;
2417
2418 //Note Update
2419 if (!$clone_note) {
2420 $clone_task->note_private = '';
2421 $clone_task->note_public = '';
2422 } else {
2423 $this->db->begin();
2424 $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_public, ENT_QUOTES | ENT_HTML5), '_public');
2425 if ($res < 0) {
2426 $this->error .= $clone_task->error;
2427 $error++;
2428 $this->db->rollback();
2429 } else {
2430 $this->db->commit();
2431 }
2432
2433 $this->db->begin();
2434 $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_private, ENT_QUOTES | ENT_HTML5), '_private');
2435 if ($res < 0) {
2436 $this->error .= $clone_task->error;
2437 $error++;
2438 $this->db->rollback();
2439 } else {
2440 $this->db->commit();
2441 }
2442 }
2443
2444 //Duplicate file
2445 if ($clone_file) {
2446 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2447
2448 //retrieve project origin ref to know folder to copy
2449 $projectstatic = new Project($this->db);
2450 $projectstatic->fetch($ori_project_id);
2451 $ori_project_ref = $projectstatic->ref;
2452
2453 if ($ori_project_id != $project_id) {
2454 $projectstatic->fetch($project_id);
2455 $clone_project_ref = $projectstatic->ref;
2456 } else {
2457 $clone_project_ref = $ori_project_ref;
2458 }
2459
2460 $clone_task_dir = $conf->project->dir_output."/".dol_sanitizeFileName($clone_project_ref)."/".dol_sanitizeFileName($clone_task_ref);
2461 $ori_task_dir = $conf->project->dir_output."/".dol_sanitizeFileName($ori_project_ref)."/".dol_sanitizeFileName((string) $fromid);
2462
2463 $filearray = dol_dir_list($ori_task_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
2464 foreach ($filearray as $file) {
2465 if (!file_exists($clone_task_dir)) {
2466 if (dol_mkdir($clone_task_dir) < 0) {
2467 $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
2468 $error++;
2469 }
2470 }
2471
2472 $rescopy = dol_copy($ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name'], '0', 1);
2473 if (is_numeric($rescopy) && $rescopy < 0) {
2474 $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name']);
2475 $error++;
2476 }
2477 }
2478 }
2479
2480 // clone affectation
2481 if ($clone_affectation) {
2482 $origin_task = new Task($this->db);
2483 $origin_task->fetch($fromid);
2484
2485 foreach (array('internal', 'external') as $source) {
2486 $tab = $origin_task->liste_contact(-1, $source);
2487 $num = count($tab);
2488 $i = 0;
2489 while ($i < $num) {
2490 $clone_task->add_contact($tab[$i]['id'], $tab[$i]['code'], $tab[$i]['source']);
2491 if ($clone_task->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2492 $langs->load("errors");
2493 $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
2494 $error++;
2495 } else {
2496 if ($clone_task->error != '') {
2497 $this->error .= $clone_task->error;
2498 $error++;
2499 }
2500 }
2501 $i++;
2502 }
2503 }
2504 }
2505
2506 if ($clone_time) {
2507 //TODO clone time of affectation
2508 }
2509 }
2510
2511 unset($clone_task->context['createfromclone']);
2512
2513 if (!$error) {
2514 $this->db->commit();
2515 return $clone_task_id;
2516 } else {
2517 $this->db->rollback();
2518 dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
2519 return -1;
2520 }
2521 }
2522
2523
2530 public function getLibStatut($mode = 0)
2531 {
2532 return $this->LibStatut($this->status, $mode);
2533 }
2534
2535 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2543 public function LibStatut($status, $mode = 0)
2544 {
2545 // phpcs:enable
2546 global $langs;
2547
2548 if ($mode == 0) {
2549 return $langs->trans($this->labelStatus[$status]);
2550 } elseif ($mode == 1) {
2551 return $langs->trans($this->labelStatusShort[$status]);
2552 } elseif ($mode == 2) {
2553 switch ($status) {
2554 case 0: // draft
2555 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0').' '.$langs->trans($this->labelStatusShort[$status]);
2556 case 1: // validated
2557 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1').' '.$langs->trans($this->labelStatusShort[$status]);
2558 case 2: // in progress
2559 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3').' '.$langs->trans($this->labelStatusShort[$status]);
2560 case 3: // closed
2561 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6').' '.$langs->trans($this->labelStatusShort[$status]);
2562 case 4: // transferred ?
2563 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6').' '.$langs->trans($this->labelStatusShort[$status]);
2564 case 5: // ???
2565 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5').' '.$langs->trans($this->labelStatusShort[$status]);
2566 }
2567 } elseif ($mode == 3) {
2568 switch ($status) {
2569 case 0:
2570 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0');
2571 case 1:
2572 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1');
2573 case 2:
2574 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3');
2575 case 3:
2576 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2577 case 4:
2578 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2579 case 5:
2580 return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5');
2581 }
2582 } elseif ($mode == 4) {
2583 switch ($status) {
2584 case 0:
2585 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status0', $mode);
2586 case 1:
2587 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status1', $mode);
2588 case 2:
2589 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status3', $mode);
2590 case 3:
2591 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status6', $mode);
2592 case 4:
2593 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status6', $mode);
2594 case 5:
2595 return dolGetStatus($langs->trans($this->labelStatus[$status]), $langs->trans($this->labelStatusShort[$status]), '', 'status5', $mode);
2596 }
2597 } elseif ($mode == 5) {
2598 /*if ($status==0) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2599 elseif ($status==1) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2600 elseif ($status==2) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2601 elseif ($status==3) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2602 elseif ($status==4) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2603 elseif ($status==5) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2604 */
2605 //else return $this->progress.' %';
2606 return '&nbsp;';
2607 } elseif ($mode == 6) {
2608 /*if ($status==0) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2609 elseif ($status==1) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2610 elseif ($status==2) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2611 elseif ($status==3) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2612 elseif ($status==4) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2613 elseif ($status==5) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2614 */
2615 //else return $this->progress.' %';
2616 return '&nbsp;';
2617 }
2618 return "";
2619 }
2620
2631 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2632 {
2633 $outputlangs->load("projects");
2634
2635 if (!dol_strlen($modele)) {
2636 $modele = 'nodefault';
2637
2638 if (!empty($this->model_pdf)) {
2639 $modele = $this->model_pdf;
2640 } elseif (getDolGlobalString('PROJECT_TASK_ADDON_PDF')) {
2641 $modele = getDolGlobalString('PROJECT_TASK_ADDON_PDF');
2642 }
2643 }
2644
2645 $modelpath = "core/modules/project/task/doc/";
2646
2647 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2648 }
2649
2650
2651 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2658 public function load_board($user)
2659 {
2660 // phpcs:enable
2661 global $conf, $langs;
2662
2663 // For external user, no check is done on company because readability is managed by public status of project and assignment.
2664 //$socid = $user->socid;
2665 $socid = 0;
2666
2667 $projectstatic = new Project($this->db);
2668 $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, $socid);
2669
2670 // List of tasks (does not care about permissions. Filtering will be done later)
2671 $sql = "SELECT p.rowid as projectid, p.fk_statut as projectstatus,";
2672 $sql .= " t.rowid as taskid, t.progress as progress, t.fk_statut as status,";
2673 $sql .= " t.dateo as date_start, t.datee as date_end";
2674 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2675 //$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2676 //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2677 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
2678 $sql .= " WHERE p.entity IN (".getEntity('project', 0).')';
2679 $sql .= " AND p.fk_statut = 1";
2680 $sql .= " AND t.fk_projet = p.rowid";
2681 $sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do
2682 if (!$user->hasRight('projet', 'all', 'lire')) {
2683 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2684 }
2685 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2686 //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).")";
2687 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2688 // 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))";
2689
2690 //print $sql;
2691 $resql = $this->db->query($sql);
2692 if ($resql) {
2693 $task_static = new Task($this->db);
2694
2695 $response = new WorkboardResponse();
2696 $response->warning_delay = $conf->project->task->warning_delay / 60 / 60 / 24;
2697 $response->label = $langs->trans("OpenedTasks");
2698 if ($user->hasRight("projet", "all", "lire")) {
2699 $response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mainmenu=project';
2700 } else {
2701 $response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mode=mine&amp;mainmenu=project';
2702 }
2703 $response->img = img_object('', "task");
2704
2705 // This assignment in condition is not a bug. It allows walking the results.
2706 while ($obj = $this->db->fetch_object($resql)) {
2707 $response->nbtodo++;
2708
2709 $task_static->projectstatus = $obj->projectstatus;
2710 $task_static->progress = $obj->progress;
2711 $task_static->fk_statut = $obj->status;
2712 $task_static->status = $obj->status;
2713 $task_static->date_start = $this->db->jdate($obj->date_start);
2714 $task_static->date_end = $this->db->jdate($obj->date_end);
2715
2716 if ($task_static->hasDelay()) {
2717 $response->nbtodolate++;
2718 }
2719 }
2720
2721 return $response;
2722 } else {
2723 $this->error = $this->db->error();
2724 return -1;
2725 }
2726 }
2727
2728
2734 public function loadStateBoard()
2735 {
2736 global $user;
2737
2738 $mine = 0;
2739 $socid = $user->socid;
2740
2741 $projectstatic = new Project($this->db);
2742 $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, $mine, 1, $socid);
2743
2744 // List of tasks (does not care about permissions. Filtering will be done later)
2745 $sql = "SELECT count(p.rowid) as nb";
2746 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2747 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2748 if (!$user->hasRight('societe', 'client', 'voir')) {
2749 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2750 }
2751 $sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
2752 $sql .= " WHERE p.entity IN (".getEntity('project', 0).')';
2753 $sql .= " AND t.fk_projet = p.rowid"; // tasks to do
2754 if ($mine || !$user->hasRight('projet', 'all', 'lire')) {
2755 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2756 }
2757 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2758 //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).")";
2759 if ($socid) {
2760 $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2761 }
2762 if (!$user->hasRight('societe', 'client', 'voir')) {
2763 $sql .= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
2764 }
2765
2766 $resql = $this->db->query($sql);
2767 if ($resql) {
2768 // This assignment in condition is not a bug. It allows walking the results.
2769 while ($obj = $this->db->fetch_object($resql)) {
2770 $this->nb["tasks"] = $obj->nb;
2771 }
2772 $this->db->free($resql);
2773 return 1;
2774 } else {
2775 dol_print_error($this->db);
2776 $this->error = $this->db->error();
2777 return -1;
2778 }
2779 }
2780
2786 public function hasDelay()
2787 {
2788 global $conf;
2789
2790 if (!($this->progress >= 0 && $this->progress < 100)) {
2791 return false;
2792 }
2793
2794 $now = dol_now();
2795
2796 $datetouse = ($this->date_end > 0) ? $this->date_end : ((isset($this->datee) && $this->datee > 0) ? $this->datee : 0);
2797
2798 return ($datetouse > 0 && ($datetouse < ($now - $conf->project->task->warning_delay)));
2799 }
2800
2808 public function getKanbanView($option = '', $arraydata = null)
2809 {
2810 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2811
2812 $return = '<div class="box-flex-item box-flex-grow-zero">';
2813 $return .= '<div class="info-box info-box-sm info-box-kanban">';
2814 $return .= '<span class="info-box-icon bg-infobox-action">';
2815 $return .= img_picto('', $this->picto);
2816 //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2817 $return .= '</span>';
2818 $return .= '<div class="info-box-content">';
2819 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
2820 if ($selected >= 0) {
2821 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2822 }
2823 if (!empty($arraydata['projectlink'])) {
2824 //$tmpproject = $arraydata['project'];
2825 //$return .= '<br><span class="info-box-status ">'.$tmpproject->getNomProject().'</span>';
2826 $return .= '<br><span class="info-box-status ">'.$arraydata['projectlink'].'</span>';
2827 }
2828 //if (property_exists($this, 'budget_amount')) {
2829 //$return .= '<br><span class="info-box-label amount">'.$langs->trans("Budget").' : '.price($this->budget_amount, 0, $langs, 1, 0, 0, $conf->currency).'</span>';
2830 //}
2831 if (property_exists($this, 'duration_effective')) {
2832 $return .= '<br><div class="info-box-label progressinkanban paddingtop">'.getTaskProgressView($this, false, true).'</div>';
2833 }
2834 $return .= '</div>';
2835 $return .= '</div>';
2836 $return .= '</div>';
2837
2838 return $return;
2839 }
2840
2848 public function mergeTask($task_origin_id)
2849 {
2850 global $langs, $hookmanager, $user, $action;
2851
2852 $error = 0;
2853 $task_origin = new Task($this->db); // The thirdparty that we will delete
2854
2855 dol_syslog("mergeTask merge task id=".$task_origin_id." (will be deleted) into the task id=".$this->id);
2856
2857 $langs->load('error');
2858
2859 if (!$error && $task_origin->fetch($task_origin_id) < 1) {
2860 $this->error = $langs->trans('ErrorRecordNotFound');
2861 $error++;
2862 }
2863
2864 if (!$error) {
2865 $this->db->begin();
2866
2867 // Recopy some data
2868 $listofproperties = array(
2869 'label', 'description', 'duration_effective', 'planned_workload', 'datec', 'date_start',
2870 'date_end', 'fk_user_creat', 'fk_user_valid', 'fk_statut', 'progress', 'budget_amount',
2871 'priority', 'rang', 'fk_projet', 'fk_task_parent'
2872 );
2873 foreach ($listofproperties as $property) {
2874 if (empty($this->$property)) {
2875 $this->$property = $task_origin->$property;
2876 }
2877 }
2878
2879 // Concat some data
2880 $listofproperties = array(
2881 'note_public', 'note_private'
2882 );
2883 foreach ($listofproperties as $property) {
2884 $this->$property = dol_concatdesc($this->$property, $task_origin->$property);
2885 }
2886
2887 // Merge extrafields
2888 if (is_array($task_origin->array_options)) {
2889 foreach ($task_origin->array_options as $key => $val) {
2890 if (empty($this->array_options[$key])) {
2891 $this->array_options[$key] = $val;
2892 }
2893 }
2894 }
2895
2896 // Update
2897 $result = $this->update($user);
2898
2899 if ($result < 0) {
2900 $error++;
2901 }
2902
2903 // Merge time spent
2904 if (!$error) {
2905 $result = $this->mergeTimeSpentTask($task_origin_id, $this->id);
2906 if ($result != true) {
2907 $error++;
2908 }
2909 }
2910
2911 // Merge contacts
2912 if (!$error) {
2913 $result = $this->mergeContactTask($task_origin_id, $this->id);
2914 if ($result != true) {
2915 $error++;
2916 }
2917 }
2918
2919 // External modules should update their ones too
2920 if (!$error) {
2921 $parameters = array('task_origin' => $task_origin->id, 'task_dest' => $this->id);
2922 $reshook = $hookmanager->executeHooks('replaceThirdparty', $parameters, $this, $action);
2923
2924 if ($reshook < 0) {
2925 $this->error = $hookmanager->error;
2926 $this->errors = $hookmanager->errors;
2927 $error++;
2928 }
2929 }
2930
2931
2932 if (!$error) {
2933 $this->context = array('merge' => 1, 'mergefromid' => $task_origin->id, 'mergefromref' => $task_origin->ref);
2934
2935 // Call trigger
2936 $result = $this->call_trigger('TASK_MODIFY', $user);
2937 if ($result < 0) {
2938 $error++;
2939 }
2940 // End call triggers
2941 }
2942
2943 if (!$error) {
2944 // We finally remove the old task
2945 if ($task_origin->delete($user) < 1) {
2946 $this->error = $task_origin->error;
2947 $this->errors = $task_origin->errors;
2948 $error++;
2949 }
2950 }
2951
2952 if (!$error) {
2953 $this->db->commit();
2954 return 0;
2955 } else {
2956 $langs->load("errors");
2957 $this->error = $langs->trans('ErrorsTaskMerge');
2958 $this->db->rollback();
2959 return -1;
2960 }
2961 }
2962
2963 return -1;
2964 }
2965
2976 public function setCategories($categories)
2977 {
2978 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2979 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT_TASK);
2980 }
2981}
$object ref
Definition info.php:90
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
isObjectUsed($id=0, $entity=0)
Function to check if an object is used by others (by children).
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage projects.
const STATUS_VALIDATED
Open/Validated status.
Class to manage tasks.
create($user, $notrigger=0)
Create into database.
fetch($id, $ref='', $loadparentdata=0)
Load object in memory from database.
getLibStatut($mode=0)
Return status label of object.
mergeContactTask($origin_id, $dest_id)
Merge contact of tasks.
const STATUS_TRANSFERRED
Transferred status.
getSumOfAmount($fuser='', $dates='', $datee='')
Calculate quantity and value of time consumed using the thm (hourly amount value of work for user ent...
mergeTask($task_origin_id)
Merge a task with another one, deleting the given task.
__construct($db)
Constructor.
fetchTimeSpentOnTask($morewherefilter='')
Fetch records of time spent of this task.
setCategories($categories)
Sets object to task categories.
const STATUS_VALIDATED
Validated status (To do).
hasTimeSpent()
Return nb of time spent.
getNomUrl($withpicto=0, $option='', $mode='task', $addlabel=0, $sep=' - ', $notooltip=0, $save_lastsearch_value=-1)
Return clickable name (with picto eventually)
getListContactId($source='internal')
Return list of id of contacts of task.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
delTimeSpent($user, $notrigger=0)
Delete time spent.
mergeTimeSpentTask($origin_id, $dest_id)
Merge time spent of tasks.
const STATUS_DRAFT
Draft status.
fetchAllTimeSpent(User $userobj, $morewherefilter='')
Load all records of time spent.
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
getUserRolesForProjectsOrTasks($userp, $usert, $projectid='', $taskid=0, $filteronprojstatus=-1)
Return list of roles for a user for each projects or each tasks (or a particular project or a particu...
update($user=null, $notrigger=0)
Update database.
getSummaryOfTimeSpent($userobj=null, $morewherefilter='')
Calculate total of time spent for task.
getTooltipContentArray($params)
getTooltipContentArray
const STATUS_CANCELED
status canceled
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create an intervention document on disk using template defined into PROJECT_TASK_ADDON_PDF.
fetchTimeSpent($id)
Load properties of timespent of a task from the time spent ID.
updateTimeSpent($user, $notrigger=0)
Update time spent line with id $this->timespent_id.
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)
Load an object from its id and create a new one in database.
loadStateBoard()
Load indicators this->nb for state board.
const STATUS_ONGOING
Ongoing status (In progress).
initAsSpecimen()
Initialise an instance with random values.
getTasksArray($usert=null, $userp=null, $projectid=0, $socid=0, $mode=0, $filteronproj='', $filteronprojstatus='-1', $morewherefilter='', $filteronprojuser=0, $filterontaskuser=0, $extrafields=null, $includebilltime=0, $search_array_options=array(), $loadextras=0, $loadRoleMode=1, $sortfield='', $sortorder='')
Return list of tasks for all projects or for one particular project Sort order is on project,...
addTimeSpent($user, $notrigger=0)
Add time spent.
hasDelay()
Is the task delayed?
LibStatut($status, $mode=0)
Return status label for an object.
const STATUS_CLOSED
Finished status.
hasChildren()
Return nb of children.
Class for TimeSpent.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0, $tz='')
Add a delay to a date.
Definition date.lib.php:128
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_copy($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=0)
Copy a file to another file.
dol_move_dir($srcdir, $destdir, $overwriteifexists=1, $indexdatabase=1, $renamedircontent=1)
Move a directory into another name.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)