task.class.php 80 KB

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