task.class.php 80 KB

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