project.class.php 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321
  1. <?php
  2. /* Copyright (C) 2002-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2005-2020 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2005-2010 Regis Houssin <regis.houssin@inodbox.com>
  5. * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
  6. * Copyright (C) 2014-2017 Marcos García <marcosgdf@gmail.com>
  7. * Copyright (C) 2017 Ferran Marcet <fmarcet@2byte.es>
  8. * Copyright (C) 2019 Juanjo Menent <jmenent@2byte.es>
  9. * Copyright (C) 2022 Charlene Benke <charlene@patas-monkey.com>
  10. *
  11. * This program is free software; you can redistribute it and/or modify
  12. * it under the terms of the GNU General Public License as published by
  13. * the Free Software Foundation; either version 3 of the License, or
  14. * (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  23. */
  24. /**
  25. * \file htdocs/projet/class/project.class.php
  26. * \ingroup projet
  27. * \brief File of class to manage projects
  28. */
  29. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  30. /**
  31. * Class to manage projects
  32. */
  33. class Project extends CommonObject
  34. {
  35. /**
  36. * @var string ID to identify managed object
  37. */
  38. public $element = 'project';
  39. /**
  40. * @var string Name of table without prefix where object is stored
  41. */
  42. public $table_element = 'projet';
  43. /**
  44. * @var string Name of subtable line
  45. */
  46. public $table_element_line = 'projet_task';
  47. /**
  48. * @var string Name of field date
  49. */
  50. public $table_element_date;
  51. /**
  52. * @var string Field with ID of parent key if this field has a parent
  53. */
  54. public $fk_element = 'fk_projet';
  55. /**
  56. * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
  57. * @var int
  58. */
  59. public $ismultientitymanaged = 1;
  60. /**
  61. * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
  62. */
  63. public $picto = 'project';
  64. /**
  65. * {@inheritdoc}
  66. */
  67. protected $table_ref_field = 'ref';
  68. /**
  69. * @var string description
  70. */
  71. public $description;
  72. /**
  73. * @var string
  74. */
  75. public $title;
  76. /**
  77. * @var int Date start
  78. * @deprecated
  79. * @see $date_start
  80. */
  81. public $dateo;
  82. /**
  83. * @var int Date start
  84. */
  85. public $date_start;
  86. /**
  87. * @var int Date end
  88. * @deprecated
  89. * @see $date_end
  90. */
  91. public $datee;
  92. /**
  93. * @var int Date end
  94. */
  95. public $date_end;
  96. /**
  97. * @var int Date start event
  98. */
  99. public $date_start_event;
  100. /**
  101. * @var int Date end event
  102. */
  103. public $date_end_event;
  104. /**
  105. * @var string Location
  106. */
  107. public $location;
  108. /**
  109. * @var int Date close
  110. */
  111. public $date_close;
  112. public $socid; // To store id of thirdparty
  113. public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
  114. public $user_author_id; //!< Id of project creator. Not defined if shared project.
  115. /**
  116. * @var int user close id
  117. */
  118. public $fk_user_close;
  119. /**
  120. * @var int user close id
  121. */
  122. public $user_close_id;
  123. public $public; //!< Tell if this is a public or private project
  124. /**
  125. * @var float budget Amount
  126. */
  127. public $budget_amount;
  128. /**
  129. * @var integer Can use projects to follow opportunities
  130. */
  131. public $usage_opportunity;
  132. /**
  133. * @var integer Can follow tasks on project and enter time spent on it
  134. */
  135. public $usage_task;
  136. /**
  137. * @var integer Use to bill task spend time
  138. */
  139. public $usage_bill_time; // Is the time spent on project must be invoiced or not
  140. /**
  141. * @var integer Event organization: Use Event Organization
  142. */
  143. public $usage_organize_event;
  144. /**
  145. * @var integer Event organization: Allow unknown people to suggest new conferences
  146. */
  147. public $accept_conference_suggestions;
  148. /**
  149. * @var integer Event organization: Allow unknown people to suggest new booth
  150. */
  151. public $accept_booth_suggestions;
  152. /**
  153. * @var float Event organization: registration price
  154. */
  155. public $price_registration;
  156. /**
  157. * @var float Event organization: booth price
  158. */
  159. public $price_booth;
  160. /**
  161. * @var float Max attendees
  162. */
  163. public $max_attendees;
  164. public $statuts_short;
  165. public $statuts_long;
  166. public $statut; // 0=draft, 1=opened, 2=closed
  167. public $opp_status; // opportunity status, into table llx_c_lead_status
  168. public $fk_opp_status; // opportunity status, into table llx_c_lead_status
  169. public $opp_percent; // opportunity probability
  170. public $email_msgid;
  171. public $oldcopy;
  172. public $weekWorkLoad; // Used to store workload details of a projet
  173. public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
  174. /**
  175. * @var int Creation date
  176. * @deprecated
  177. * @see $date_c
  178. */
  179. public $datec;
  180. /**
  181. * @var int Creation date
  182. */
  183. public $date_c;
  184. /**
  185. * @var int Modification date
  186. * @deprecated
  187. * @see $date_m
  188. */
  189. public $datem;
  190. /**
  191. * @var int Modification date
  192. */
  193. public $date_m;
  194. /**
  195. * @var Task[]
  196. */
  197. public $lines;
  198. /**
  199. * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
  200. * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
  201. * 'label' the translation key.
  202. * 'enabled' is a condition when the field must be managed.
  203. * 'position' is the sort order of field.
  204. * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
  205. * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
  206. * 'noteditable' says if field is not editable (1 or 0)
  207. * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
  208. * 'index' if we want an index in database.
  209. * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
  210. * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
  211. * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
  212. * 'css' is the CSS style to use on field. For example: 'maxwidth200'
  213. * 'help' is a string visible as a tooltip on field
  214. * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
  215. * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
  216. * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
  217. * 'comment' is not used. You can store here any text of your choice. It is not used by application.
  218. *
  219. * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
  220. */
  221. // BEGIN MODULEBUILDER PROPERTIES
  222. /**
  223. * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
  224. */
  225. public $fields = array(
  226. 'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
  227. 'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>15, 'searchall'=>1),
  228. 'title' =>array('type'=>'varchar(255)', 'label'=>'ProjectLabel', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>17, 'showoncombobox'=>2, 'searchall'=>1),
  229. 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>19),
  230. 'fk_soc' =>array('type'=>'integer', 'label'=>'Thirdparty', 'enabled'=>1, 'visible'=>0, 'position'=>20),
  231. 'dateo' =>array('type'=>'date', 'label'=>'DateStart', 'enabled'=>1, 'visible'=>1, 'position'=>30),
  232. 'datee' =>array('type'=>'date', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>1, 'position'=>35),
  233. 'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>1),
  234. 'public' =>array('type'=>'integer', 'label'=>'Visibility', 'enabled'=>1, 'visible'=>1, 'position'=>65),
  235. 'fk_opp_status' =>array('type'=>'integer', 'label'=>'OpportunityStatusShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>75),
  236. 'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'OpportunityProbabilityShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>80),
  237. 'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85, 'searchall'=>1),
  238. 'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90, 'searchall'=>1),
  239. 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPdf', 'enabled'=>1, 'visible'=>0, 'position'=>95),
  240. 'date_close' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>0, 'position'=>105),
  241. 'fk_user_close' =>array('type'=>'integer', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>0, 'position'=>110),
  242. 'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'OpportunityAmountShort', 'enabled'=>1, 'visible'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position'=>115),
  243. 'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget', 'enabled'=>1, 'visible'=>1, 'position'=>119),
  244. 'usage_bill_time' =>array('type'=>'integer', 'label'=>'UsageBillTimeShort', 'enabled'=>1, 'visible'=>-1, 'position'=>130),
  245. 'usage_opportunity' =>array('type'=>'integer', 'label'=>'UsageOpportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135),
  246. 'usage_task' =>array('type'=>'integer', 'label'=>'UsageTasks', 'enabled'=>1, 'visible'=>-1, 'position'=>140),
  247. 'usage_organize_event' =>array('type'=>'integer', 'label'=>'UsageOrganizeEvent', 'enabled'=>1, 'visible'=>-1, 'position'=>145),
  248. // Properties for event organization
  249. 'date_start_event' =>array('type'=>'date', 'label'=>'DateStartEvent', 'enabled'=>"isModEnabled('eventorganization')", 'visible'=>1, 'position'=>200),
  250. 'date_end_event' =>array('type'=>'date', 'label'=>'DateEndEvent', 'enabled'=>"isModEnabled('eventorganization')", 'visible'=>1, 'position'=>201),
  251. 'location' =>array('type'=>'text', 'label'=>'Location', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>202),
  252. 'accept_conference_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestConf', 'enabled'=>1, 'visible'=>-1, 'position'=>210),
  253. 'accept_booth_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>211),
  254. 'price_registration' =>array('type'=>'double(24,8)', 'label'=>'PriceOfRegistration', 'enabled'=>1, 'visible'=>-1, 'position'=>212),
  255. 'price_booth' =>array('type'=>'double(24,8)', 'label'=>'PriceOfBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
  256. 'max_attendees' =>array('type'=>'integer', 'label'=>'MaxNbOfAttendees', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
  257. // Generic
  258. 'datec' =>array('type'=>'datetime', 'label'=>'DateCreationShort', 'enabled'=>1, 'visible'=>-2, 'position'=>400),
  259. 'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>405),
  260. 'fk_user_creat' =>array('type'=>'integer', 'label'=>'UserCreation', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>410),
  261. 'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModification', 'enabled'=>1, 'visible'=>0, 'position'=>415),
  262. 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-1, 'position'=>420),
  263. 'email_msgid'=>array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'enabled'=>1, 'visible'=>-1, 'position'=>450, 'help'=>'EmailMsgIDWhenSourceisEmail'),
  264. 'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>500),
  265. );
  266. // END MODULEBUILDER PROPERTIES
  267. /**
  268. * Draft status
  269. */
  270. const STATUS_DRAFT = 0;
  271. /**
  272. * Open/Validated status
  273. */
  274. const STATUS_VALIDATED = 1;
  275. /**
  276. * Closed status
  277. */
  278. const STATUS_CLOSED = 2;
  279. /**
  280. * Constructor
  281. *
  282. * @param DoliDB $db Database handler
  283. */
  284. public function __construct($db)
  285. {
  286. global $conf;
  287. $this->db = $db;
  288. $this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
  289. $this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
  290. global $conf;
  291. if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
  292. $this->fields['rowid']['visible'] = 0;
  293. }
  294. if (empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
  295. $this->fields['fk_opp_status']['enabled'] = 0;
  296. $this->fields['opp_percent']['enabled'] = 0;
  297. $this->fields['opp_amount']['enabled'] = 0;
  298. $this->fields['usage_opportunity']['enabled'] = 0;
  299. }
  300. if (!empty($conf->global->PROJECT_HIDE_TASKS)) {
  301. $this->fields['usage_bill_time']['visible'] = 0;
  302. $this->fields['usage_task']['visible'] = 0;
  303. }
  304. if (empty($conf->eventorganization->enabled)) {
  305. $this->fields['usage_organize_event']['visible'] = 0;
  306. $this->fields['accept_conference_suggestions']['enabled'] = 0;
  307. $this->fields['accept_booth_suggestions']['enabled'] = 0;
  308. $this->fields['price_registration']['enabled'] = 0;
  309. $this->fields['price_booth']['enabled'] = 0;
  310. $this->fields['max_attendees']['enabled'] = 0;
  311. }
  312. }
  313. /**
  314. * Create a project into database
  315. *
  316. * @param User $user User making creation
  317. * @param int $notrigger Disable triggers
  318. * @return int <0 if KO, id of created project if OK
  319. */
  320. public function create($user, $notrigger = 0)
  321. {
  322. global $conf, $langs;
  323. $error = 0;
  324. $ret = 0;
  325. $now = dol_now();
  326. // Clean parameters
  327. $this->note_private = dol_substr($this->note_private, 0, 65535);
  328. $this->note_public = dol_substr($this->note_public, 0, 65535);
  329. // Check parameters
  330. if (!trim($this->ref)) {
  331. $this->error = 'ErrorFieldsRequired';
  332. dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
  333. return -1;
  334. }
  335. if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0)) {
  336. $this->error = 'ErrorFieldsRequired';
  337. dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
  338. return -1;
  339. }
  340. // Create project
  341. $this->db->begin();
  342. $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
  343. $sql .= "ref";
  344. $sql .= ", title";
  345. $sql .= ", description";
  346. $sql .= ", fk_soc";
  347. $sql .= ", fk_user_creat";
  348. $sql .= ", fk_statut";
  349. $sql .= ", fk_opp_status";
  350. $sql .= ", opp_percent";
  351. $sql .= ", public";
  352. $sql .= ", datec";
  353. $sql .= ", dateo";
  354. $sql .= ", datee";
  355. $sql .= ", opp_amount";
  356. $sql .= ", budget_amount";
  357. $sql .= ", usage_opportunity";
  358. $sql .= ", usage_task";
  359. $sql .= ", usage_bill_time";
  360. $sql .= ", usage_organize_event";
  361. $sql .= ", accept_conference_suggestions";
  362. $sql .= ", accept_booth_suggestions";
  363. $sql .= ", price_registration";
  364. $sql .= ", price_booth";
  365. $sql .= ", max_attendees";
  366. $sql .= ", date_start_event";
  367. $sql .= ", date_end_event";
  368. $sql .= ", location";
  369. $sql .= ", email_msgid";
  370. $sql .= ", note_private";
  371. $sql .= ", note_public";
  372. $sql .= ", entity";
  373. $sql .= ", ip";
  374. $sql .= ") VALUES (";
  375. $sql .= "'".$this->db->escape($this->ref)."'";
  376. $sql .= ", '".$this->db->escape($this->title)."'";
  377. $sql .= ", '".$this->db->escape($this->description)."'";
  378. $sql .= ", ".($this->socid > 0 ? $this->socid : "null");
  379. $sql .= ", ".((int) $user->id);
  380. $sql .= ", ".(is_numeric($this->statut) ? ((int) $this->statut) : '0');
  381. $sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
  382. $sql .= ", ".(is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
  383. $sql .= ", ".($this->public ? 1 : 0);
  384. $sql .= ", '".$this->db->idate($now)."'";
  385. $sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
  386. $sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
  387. $sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
  388. $sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
  389. $sql .= ", ".($this->usage_opportunity ? 1 : 0);
  390. $sql .= ", ".($this->usage_task ? 1 : 0);
  391. $sql .= ", ".($this->usage_bill_time ? 1 : 0);
  392. $sql .= ", ".($this->usage_organize_event ? 1 : 0);
  393. $sql .= ", ".($this->accept_conference_suggestions ? 1 : 0);
  394. $sql .= ", ".($this->accept_booth_suggestions ? 1 : 0);
  395. $sql .= ", ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : 'null');
  396. $sql .= ", ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : 'null');
  397. $sql .= ", ".(strcmp($this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
  398. $sql .= ", ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
  399. $sql .= ", ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
  400. $sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : 'null');
  401. $sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
  402. $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
  403. $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
  404. $sql .= ", ".((int) $conf->entity);
  405. $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
  406. $sql .= ")";
  407. dol_syslog(get_class($this)."::create", LOG_DEBUG);
  408. $resql = $this->db->query($sql);
  409. if ($resql) {
  410. $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
  411. $ret = $this->id;
  412. if (!$notrigger) {
  413. // Call trigger
  414. $result = $this->call_trigger('PROJECT_CREATE', $user);
  415. if ($result < 0) {
  416. $error++;
  417. }
  418. // End call triggers
  419. }
  420. } else {
  421. $this->error = $this->db->lasterror();
  422. $this->errno = $this->db->lasterrno();
  423. $error++;
  424. }
  425. // Update extrafield
  426. if (!$error) {
  427. $result = $this->insertExtraFields();
  428. if ($result < 0) {
  429. $error++;
  430. }
  431. }
  432. if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
  433. $res = $this->setValid($user);
  434. if ($res < 0) {
  435. $error++;
  436. }
  437. }
  438. if (!$error) {
  439. $this->db->commit();
  440. return $ret;
  441. } else {
  442. $this->db->rollback();
  443. return -1;
  444. }
  445. }
  446. /**
  447. * Update a project
  448. *
  449. * @param User $user User object of making update
  450. * @param int $notrigger 1=Disable all triggers
  451. * @return int <=0 if KO, >0 if OK
  452. */
  453. public function update($user, $notrigger = 0)
  454. {
  455. global $langs, $conf;
  456. $error = 0;
  457. // Clean parameters
  458. $this->title = trim($this->title);
  459. $this->description = trim($this->description);
  460. if ($this->opp_amount < 0) {
  461. $this->opp_amount = '';
  462. }
  463. if ($this->opp_percent < 0) {
  464. $this->opp_percent = '';
  465. }
  466. if ($this->date_end && $this->date_end < $this->date_start) {
  467. $this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
  468. $this->errors[] = $this->error;
  469. $this->db->rollback();
  470. dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
  471. return -3;
  472. }
  473. $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
  474. if (dol_strlen(trim($this->ref)) > 0) {
  475. $this->db->begin();
  476. $sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
  477. $sql .= " ref='".$this->db->escape($this->ref)."'";
  478. $sql .= ", title = '".$this->db->escape($this->title)."'";
  479. $sql .= ", description = '".$this->db->escape($this->description)."'";
  480. $sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
  481. $sql .= ", fk_statut = ".((int) $this->statut);
  482. $sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
  483. $sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
  484. $sql .= ", public = ".($this->public ? 1 : 0);
  485. $sql .= ", datec = ".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
  486. $sql .= ", dateo = ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
  487. $sql .= ", datee = ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
  488. $sql .= ", date_close = ".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
  489. $sql .= ", fk_user_close = ".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
  490. $sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
  491. $sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
  492. $sql .= ", fk_user_modif = ".$user->id;
  493. $sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
  494. $sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
  495. $sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
  496. $sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
  497. $sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0);
  498. $sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0);
  499. $sql .= ", price_registration = ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
  500. $sql .= ", price_booth = ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : "null");
  501. $sql .= ", max_attendees = ".(strcmp($this->max_attendees, '') ? price2num($this->max_attendees) : "null");
  502. $sql .= ", date_start_event = ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
  503. $sql .= ", date_end_event = ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
  504. $sql .= ", location = '".$this->db->escape($this->location)."'";
  505. $sql .= ", entity = ".((int) $this->entity);
  506. $sql .= " WHERE rowid = ".((int) $this->id);
  507. dol_syslog(get_class($this)."::update", LOG_DEBUG);
  508. $resql = $this->db->query($sql);
  509. if ($resql) {
  510. // Update extrafield
  511. if (!$error) {
  512. $result = $this->insertExtraFields();
  513. if ($result < 0) {
  514. $error++;
  515. }
  516. }
  517. if (!$error && !$notrigger) {
  518. // Call trigger
  519. $result = $this->call_trigger('PROJECT_MODIFY', $user);
  520. if ($result < 0) {
  521. $error++;
  522. }
  523. // End call triggers
  524. }
  525. if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
  526. // We remove directory
  527. if ($conf->project->dir_output) {
  528. $olddir = $conf->project->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
  529. $newdir = $conf->project->dir_output."/".dol_sanitizeFileName($this->ref);
  530. if (file_exists($olddir)) {
  531. include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  532. $res = @rename($olddir, $newdir);
  533. if (!$res) {
  534. $langs->load("errors");
  535. $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
  536. $error++;
  537. }
  538. }
  539. }
  540. }
  541. if (!$error) {
  542. $this->db->commit();
  543. $result = 1;
  544. } else {
  545. $this->db->rollback();
  546. $result = -1;
  547. }
  548. } else {
  549. $this->error = $this->db->lasterror();
  550. $this->errors[] = $this->error;
  551. $this->db->rollback();
  552. if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
  553. $result = -4;
  554. } else {
  555. $result = -2;
  556. }
  557. dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
  558. }
  559. } else {
  560. dol_syslog(get_class($this)."::update ref null");
  561. $result = -1;
  562. }
  563. return $result;
  564. }
  565. /**
  566. * Get object from database
  567. *
  568. * @param int $id Id of object to load
  569. * @param string $ref Ref of project
  570. * @param string $ref_ext Ref ext of project
  571. * @param string $email_msgid Email msgid
  572. * @return int >0 if OK, 0 if not found, <0 if KO
  573. */
  574. public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
  575. {
  576. global $conf;
  577. if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
  578. dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING);
  579. return -1;
  580. }
  581. $sql = "SELECT rowid, entity, ref, title, description, public, datec, opp_amount, budget_amount,";
  582. $sql .= " tms, dateo as date_start, datee as date_end, date_close, fk_soc, fk_user_creat, fk_user_modif, fk_user_close, fk_statut as status, fk_opp_status, opp_percent,";
  583. $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
  584. $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location";
  585. $sql .= " FROM ".MAIN_DB_PREFIX."projet";
  586. if (!empty($id)) {
  587. $sql .= " WHERE rowid = ".((int) $id);
  588. } else {
  589. $sql .= " WHERE entity IN (".getEntity('project').")";
  590. if (!empty($ref)) {
  591. $sql .= " AND ref = '".$this->db->escape($ref)."'";
  592. } elseif (!empty($ref_ext)) {
  593. $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
  594. } else {
  595. $sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
  596. }
  597. }
  598. dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
  599. $resql = $this->db->query($sql);
  600. if ($resql) {
  601. $num_rows = $this->db->num_rows($resql);
  602. if ($num_rows) {
  603. $obj = $this->db->fetch_object($resql);
  604. $this->id = $obj->rowid;
  605. $this->entity = $obj->entity;
  606. $this->ref = $obj->ref;
  607. $this->title = $obj->title;
  608. $this->description = $obj->description;
  609. $this->date_c = $this->db->jdate($obj->datec);
  610. $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
  611. $this->date_m = $this->db->jdate($obj->tms);
  612. $this->datem = $this->db->jdate($obj->tms); // TODO deprecated
  613. $this->date_start = $this->db->jdate($obj->date_start);
  614. $this->date_end = $this->db->jdate($obj->date_end);
  615. $this->date_close = $this->db->jdate($obj->date_close);
  616. $this->note_private = $obj->note_private;
  617. $this->note_public = $obj->note_public;
  618. $this->socid = $obj->fk_soc;
  619. $this->user_author_id = $obj->fk_user_creat;
  620. $this->user_modification_id = $obj->fk_user_modif;
  621. $this->user_close_id = $obj->fk_user_close;
  622. $this->public = $obj->public;
  623. $this->statut = $obj->status; // deprecated
  624. $this->status = $obj->status;
  625. $this->opp_status = $obj->fk_opp_status;
  626. $this->opp_amount = $obj->opp_amount;
  627. $this->opp_percent = $obj->opp_percent;
  628. $this->budget_amount = $obj->budget_amount;
  629. $this->model_pdf = $obj->model_pdf;
  630. $this->modelpdf = $obj->model_pdf; // deprecated
  631. $this->usage_opportunity = (int) $obj->usage_opportunity;
  632. $this->usage_task = (int) $obj->usage_task;
  633. $this->usage_bill_time = (int) $obj->usage_bill_time;
  634. $this->usage_organize_event = (int) $obj->usage_organize_event;
  635. $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
  636. $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
  637. $this->price_registration = $obj->price_registration;
  638. $this->price_booth = $obj->price_booth;
  639. $this->max_attendees = $obj->max_attendees;
  640. $this->date_start_event = $this->db->jdate($obj->date_start_event);
  641. $this->date_end_event = $this->db->jdate($obj->date_end_event);
  642. $this->location = $obj->location;
  643. $this->email_msgid = $obj->email_msgid;
  644. $this->db->free($resql);
  645. // Retrieve all extrafield
  646. // fetch optionals attributes and labels
  647. $this->fetch_optionals();
  648. return 1;
  649. }
  650. $this->db->free($resql);
  651. return 0;
  652. } else {
  653. $this->error = $this->db->lasterror();
  654. $this->errors[] = $this->db->lasterror();
  655. return -1;
  656. }
  657. }
  658. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  659. /**
  660. * Return list of elements for type, linked to a project
  661. *
  662. * @param string $type 'propal','order','invoice','order_supplier','invoice_supplier',...
  663. * @param string $tablename name of table associated of the type
  664. * @param string $datefieldname name of date field for filter
  665. * @param int $date_start Start date
  666. * @param int $date_end End date
  667. * @param string $projectkey Equivalent key to fk_projet for actual type
  668. * @return mixed Array list of object ids linked to project, < 0 or string if error
  669. */
  670. public function get_element_list($type, $tablename, $datefieldname = '', $date_start = '', $date_end = '', $projectkey = 'fk_projet')
  671. {
  672. // phpcs:enable
  673. global $hookmanager;
  674. $elements = array();
  675. if ($this->id <= 0) {
  676. return $elements;
  677. }
  678. $ids = $this->id;
  679. if ($type == 'agenda') {
  680. $sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity('agenda').")";
  681. } elseif ($type == 'expensereport') {
  682. $sql = "SELECT ed.rowid FROM ".MAIN_DB_PREFIX."expensereport as e, ".MAIN_DB_PREFIX."expensereport_det as ed WHERE e.rowid = ed.fk_expensereport AND e.entity IN (".getEntity('expensereport').") AND ed.fk_projet IN (".$this->db->sanitize($ids).")";
  683. } elseif ($type == 'project_task') {
  684. $sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize($ids).")";
  685. } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user
  686. $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet IN (".$this->db->sanitize($ids).")";
  687. } elseif ($type == 'stock_mouvement') {
  688. $sql = "SELECT ms.rowid, ms.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin IN (".$this->db->sanitize($ids).") AND ms.type_mouvement = 1";
  689. } elseif ($type == 'loan') {
  690. $sql = "SELECT l.rowid, l.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet IN (".$this->db->sanitize($ids).")";
  691. } else {
  692. $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity($type).")";
  693. }
  694. if ($date_start > 0 && $type == 'loan') {
  695. $sql .= " AND (dateend > '".$this->db->idate($date_start)."' OR dateend IS NULL)";
  696. } elseif ($date_start > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
  697. if (empty($datefieldname) && !empty($this->table_element_date)) {
  698. $datefieldname = $this->table_element_date;
  699. }
  700. if (empty($datefieldname)) {
  701. return 'Error this object has no date field defined';
  702. }
  703. $sql .= " AND (".$datefieldname." >= '".$this->db->idate($date_start)."' OR ".$datefieldname." IS NULL)";
  704. }
  705. if ($date_end > 0 && $type == 'loan') {
  706. $sql .= " AND (datestart < '".$this->db->idate($date_end)."' OR datestart IS NULL)";
  707. } elseif ($date_end > 0 && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
  708. if (empty($datefieldname) && !empty($this->table_element_date)) {
  709. $datefieldname = $this->table_element_date;
  710. }
  711. if (empty($datefieldname)) {
  712. return 'Error this object has no date field defined';
  713. }
  714. $sql .= " AND (".$datefieldname." <= '".$this->db->idate($date_end)."' OR ".$datefieldname." IS NULL)";
  715. }
  716. $parameters = array(
  717. 'sql'=>$sql,
  718. 'type' => $type,
  719. 'tablename' => $tablename,
  720. 'datefieldname' => $datefieldname,
  721. 'dates' => $date_start,
  722. 'datee' => $date_end,
  723. 'fk_projet' => $projectkey,
  724. 'ids' => $ids,
  725. );
  726. $reshook = $hookmanager->executeHooks('getElementList', $parameters);
  727. if ($reshook > 0) {
  728. $sql = $hookmanager->resPrint;
  729. } else {
  730. $sql .= $hookmanager->resPrint;
  731. }
  732. if (!$sql) {
  733. return -1;
  734. }
  735. //print $sql;
  736. dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
  737. $result = $this->db->query($sql);
  738. if ($result) {
  739. $nump = $this->db->num_rows($result);
  740. if ($nump) {
  741. $i = 0;
  742. while ($i < $nump) {
  743. $obj = $this->db->fetch_object($result);
  744. $elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
  745. $i++;
  746. }
  747. $this->db->free($result);
  748. }
  749. /* Return array even if empty*/
  750. return $elements;
  751. } else {
  752. dol_print_error($this->db);
  753. }
  754. }
  755. /**
  756. * Delete a project from database
  757. *
  758. * @param User $user User
  759. * @param int $notrigger Disable triggers
  760. * @return int <0 if KO, 0 if not possible, >0 if OK
  761. */
  762. public function delete($user, $notrigger = 0)
  763. {
  764. global $langs, $conf;
  765. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  766. $error = 0;
  767. $this->db->begin();
  768. if (!$error) {
  769. // Delete linked contacts
  770. $res = $this->delete_linked_contact();
  771. if ($res < 0) {
  772. $this->error = 'ErrorFailToDeleteLinkedContact';
  773. //$error++;
  774. $this->db->rollback();
  775. return 0;
  776. }
  777. }
  778. // Set fk_projet into elements to null
  779. $listoftables = array(
  780. 'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet',
  781. 'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet',
  782. 'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet',
  783. 'fichinter'=>'fk_projet',
  784. 'don'=>array('field'=>'fk_projet', 'module'=>'don'),
  785. 'actioncomm'=>'fk_project',
  786. 'mrp_mo'=>'fk_project',
  787. 'entrepot'=>'fk_project'
  788. );
  789. foreach ($listoftables as $key => $value) {
  790. if (is_array($value)) {
  791. if (!isModEnabled($value['module'])) {
  792. continue;
  793. }
  794. $fieldname = $value['field'];
  795. } else {
  796. $fieldname = $value;
  797. }
  798. $sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$fieldname." = NULL where ".$fieldname." = ".((int) $this->id);
  799. $resql = $this->db->query($sql);
  800. if (!$resql) {
  801. $this->errors[] = $this->db->lasterror();
  802. $error++;
  803. break;
  804. }
  805. }
  806. // Remove linked categories.
  807. if (!$error) {
  808. $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
  809. $sql .= " WHERE fk_project = ".((int) $this->id);
  810. $result = $this->db->query($sql);
  811. if (!$result) {
  812. $error++;
  813. $this->errors[] = $this->db->lasterror();
  814. }
  815. }
  816. // Fetch tasks
  817. $this->getLinesArray($user, 0);
  818. // Delete tasks
  819. $ret = $this->deleteTasks($user);
  820. if ($ret < 0) {
  821. $error++;
  822. }
  823. // Delete all child tables
  824. if (!$error) {
  825. $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
  826. foreach ($elements as $table) {
  827. if (!$error) {
  828. $sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
  829. $sql .= " WHERE fk_project = ".((int) $this->id);
  830. $result = $this->db->query($sql);
  831. if (!$result) {
  832. $error++;
  833. $this->errors[] = $this->db->lasterror();
  834. }
  835. }
  836. }
  837. }
  838. if (!$error) {
  839. $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
  840. $sql .= " WHERE fk_object = ".((int) $this->id);
  841. $resql = $this->db->query($sql);
  842. if (!$resql) {
  843. $this->errors[] = $this->db->lasterror();
  844. $error++;
  845. }
  846. }
  847. // Delete project
  848. if (!$error) {
  849. $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
  850. $sql .= " WHERE rowid=".((int) $this->id);
  851. $resql = $this->db->query($sql);
  852. if (!$resql) {
  853. $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
  854. $error++;
  855. }
  856. }
  857. if (empty($error)) {
  858. // We remove directory
  859. $projectref = dol_sanitizeFileName($this->ref);
  860. if ($conf->project->dir_output) {
  861. $dir = $conf->project->dir_output."/".$projectref;
  862. if (file_exists($dir)) {
  863. $res = @dol_delete_dir_recursive($dir);
  864. if (!$res) {
  865. $this->errors[] = 'ErrorFailToDeleteDir';
  866. $error++;
  867. }
  868. }
  869. }
  870. if (!$notrigger) {
  871. // Call trigger
  872. $result = $this->call_trigger('PROJECT_DELETE', $user);
  873. if ($result < 0) {
  874. $error++;
  875. }
  876. // End call triggers
  877. }
  878. }
  879. if (empty($error)) {
  880. $this->db->commit();
  881. return 1;
  882. } else {
  883. foreach ($this->errors as $errmsg) {
  884. dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
  885. $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
  886. }
  887. dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
  888. $this->db->rollback();
  889. return -1;
  890. }
  891. }
  892. /**
  893. * Return the count of a type of linked elements of this project
  894. *
  895. * @param string $type The type of the linked elements (e.g. 'propal', 'order', 'invoice', 'order_supplier', 'invoice_supplier')
  896. * @param string $tablename The name of table associated of the type
  897. * @param string $projectkey (optional) Equivalent key to fk_projet for actual type
  898. * @return integer The count of the linked elements (the count is zero on request error too)
  899. */
  900. public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
  901. {
  902. if ($this->id <= 0) {
  903. return 0;
  904. }
  905. if ($type == 'agenda') {
  906. $sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".((int) $this->id)." AND entity IN (".getEntity('agenda').")";
  907. } elseif ($type == 'expensereport') {
  908. $sql = "SELECT COUNT(ed.rowid) as nb FROM ".MAIN_DB_PREFIX."expensereport as e, ".MAIN_DB_PREFIX."expensereport_det as ed WHERE e.rowid = ed.fk_expensereport AND e.entity IN (".getEntity('expensereport').") AND ed.fk_projet = ".((int) $this->id);
  909. } elseif ($type == 'project_task') {
  910. $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id);
  911. } elseif ($type == 'project_task_time') { // Case we want to duplicate line foreach user
  912. $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet = ".((int) $this->id);
  913. } elseif ($type == 'stock_mouvement') {
  914. $sql = "SELECT COUNT(ms.rowid) as nb FROM ".MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin = ".((int) $this->id)." AND ms.type_mouvement = 1";
  915. } elseif ($type == 'loan') {
  916. $sql = "SELECT COUNT(l.rowid) as nb FROM ".MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet = ".((int) $this->id);
  917. } else {
  918. $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".((int) $this->id)." AND entity IN (".getEntity($type).")";
  919. }
  920. $result = $this->db->query($sql);
  921. if (!$result) {
  922. return 0;
  923. }
  924. $obj = $this->db->fetch_object($result);
  925. $this->db->free($result);
  926. return $obj->nb;
  927. }
  928. /**
  929. * Delete tasks with no children first, then task with children recursively
  930. *
  931. * @param User $user User
  932. * @return int <0 if KO, 1 if OK
  933. */
  934. public function deleteTasks($user)
  935. {
  936. $countTasks = count($this->lines);
  937. $deleted = false;
  938. if ($countTasks) {
  939. foreach ($this->lines as $task) {
  940. if ($task->hasChildren() <= 0) { // If there is no children (or error to detect them)
  941. $deleted = true;
  942. $ret = $task->delete($user);
  943. if ($ret <= 0) {
  944. $this->errors[] = $this->db->lasterror();
  945. return -1;
  946. }
  947. }
  948. }
  949. }
  950. $this->getLinesArray($user);
  951. if ($deleted && count($this->lines) < $countTasks) {
  952. if (count($this->lines)) {
  953. $this->deleteTasks($this->lines);
  954. }
  955. }
  956. return 1;
  957. }
  958. /**
  959. * Validate a project
  960. *
  961. * @param User $user User that validate
  962. * @param int $notrigger 1=Disable triggers
  963. * @return int <0 if KO, 0=Nothing done, >0 if KO
  964. */
  965. public function setValid($user, $notrigger = 0)
  966. {
  967. global $langs, $conf;
  968. $error = 0;
  969. // Protection
  970. if ($this->status == self::STATUS_VALIDATED) {
  971. dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
  972. return 0;
  973. }
  974. // Check parameters
  975. if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
  976. $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
  977. return -1;
  978. }
  979. $this->db->begin();
  980. $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
  981. $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
  982. $sql .= " WHERE rowid = ".((int) $this->id);
  983. //$sql .= " AND entity = ".((int) $conf->entity); // Disabled, when we use the ID for the where, we must not add any other search condition
  984. dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
  985. $resql = $this->db->query($sql);
  986. if ($resql) {
  987. // Call trigger
  988. if (empty($notrigger)) {
  989. $result = $this->call_trigger('PROJECT_VALIDATE', $user);
  990. if ($result < 0) {
  991. $error++;
  992. }
  993. // End call triggers
  994. }
  995. if (!$error) {
  996. $this->statut = 1;
  997. $this->db->commit();
  998. return 1;
  999. } else {
  1000. $this->db->rollback();
  1001. $this->error = join(',', $this->errors);
  1002. dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
  1003. return -1;
  1004. }
  1005. } else {
  1006. $this->db->rollback();
  1007. $this->error = $this->db->lasterror();
  1008. return -1;
  1009. }
  1010. }
  1011. /**
  1012. * Close a project
  1013. *
  1014. * @param User $user User that close project
  1015. * @return int <0 if KO, 0 if already closed, >0 if OK
  1016. */
  1017. public function setClose($user)
  1018. {
  1019. global $langs, $conf;
  1020. $now = dol_now();
  1021. $error = 0;
  1022. if ($this->statut != self::STATUS_CLOSED) {
  1023. $this->db->begin();
  1024. $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
  1025. $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'";
  1026. $sql .= " WHERE rowid = ".((int) $this->id);
  1027. $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
  1028. if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
  1029. // TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
  1030. }
  1031. dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
  1032. $resql = $this->db->query($sql);
  1033. if ($resql) {
  1034. // Call trigger
  1035. $result = $this->call_trigger('PROJECT_CLOSE', $user);
  1036. if ($result < 0) {
  1037. $error++;
  1038. }
  1039. // End call triggers
  1040. if (!$error) {
  1041. $this->statut = 2;
  1042. $this->db->commit();
  1043. return 1;
  1044. } else {
  1045. $this->db->rollback();
  1046. $this->error = join(',', $this->errors);
  1047. dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
  1048. return -1;
  1049. }
  1050. } else {
  1051. $this->db->rollback();
  1052. $this->error = $this->db->lasterror();
  1053. return -1;
  1054. }
  1055. }
  1056. return 0;
  1057. }
  1058. /**
  1059. * Return status label of object
  1060. *
  1061. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
  1062. * @return string Label
  1063. */
  1064. public function getLibStatut($mode = 0)
  1065. {
  1066. return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
  1067. }
  1068. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1069. /**
  1070. * Renvoi status label for a status
  1071. *
  1072. * @param int $status id status
  1073. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
  1074. * @return string Label
  1075. */
  1076. public function LibStatut($status, $mode = 0)
  1077. {
  1078. // phpcs:enable
  1079. global $langs;
  1080. $statustrans = array(
  1081. 0 => 'status0',
  1082. 1 => 'status4',
  1083. 2 => 'status6',
  1084. );
  1085. $statusClass = 'status0';
  1086. if (!empty($statustrans[$status])) {
  1087. $statusClass = $statustrans[$status];
  1088. }
  1089. return dolGetStatus($langs->transnoentitiesnoconv($this->statuts_long[$status]), $langs->transnoentitiesnoconv($this->statuts_short[$status]), '', $statusClass, $mode);
  1090. }
  1091. /**
  1092. * Return clickable name (with picto eventually)
  1093. *
  1094. * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto
  1095. * @param string $option Variant where the link point to ('', 'nolink')
  1096. * @param int $addlabel 0=Default, 1=Add label into string, >1=Add first chars into string
  1097. * @param string $moreinpopup Text to add into popup
  1098. * @param string $sep Separator between ref and label if option addlabel is set
  1099. * @param int $notooltip 1=Disable tooltip
  1100. * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
  1101. * @param string $morecss More css on a link
  1102. * @return string String with URL
  1103. */
  1104. public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
  1105. {
  1106. global $conf, $langs, $user, $hookmanager;
  1107. if (!empty($conf->dol_no_mouse_hover)) {
  1108. $notooltip = 1; // Force disable tooltips
  1109. }
  1110. $result = '';
  1111. if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
  1112. $option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
  1113. }
  1114. $label = '';
  1115. if ($option != 'nolink') {
  1116. $label = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
  1117. }
  1118. if (isset($this->status)) {
  1119. $label .= ' '.$this->getLibStatut(5);
  1120. }
  1121. $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Ref').': </b>'.$this->ref; // The space must be after the : to not being explode when showing the title in img_picto
  1122. $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Label').': </b>'.$this->title; // The space must be after the : to not being explode when showing the title in img_picto
  1123. if (isset($this->public)) {
  1124. $label .= '<br><b>'.$langs->trans("Visibility").":</b> ";
  1125. $label .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
  1126. }
  1127. if (!empty($this->thirdparty_name)) {
  1128. $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('ThirdParty').': </b>'.$this->thirdparty_name; // The space must be after the : to not being explode when showing the title in img_picto
  1129. }
  1130. if (!empty($this->date_start)) {
  1131. $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateStart').': </b>'.dol_print_date($this->date_start, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
  1132. }
  1133. if (!empty($this->date_end)) {
  1134. $label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateEnd').': </b>'.dol_print_date($this->date_end, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
  1135. }
  1136. if ($moreinpopup) {
  1137. $label .= '<br>'.$moreinpopup;
  1138. }
  1139. $url = '';
  1140. if ($option != 'nolink') {
  1141. if (preg_match('/\.php$/', $option)) {
  1142. $url = dol_buildpath($option, 1).'?id='.$this->id;
  1143. } elseif ($option == 'task') {
  1144. $url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
  1145. } elseif ($option == 'preview') {
  1146. $url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
  1147. } elseif ($option == 'eventorganization') {
  1148. $url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
  1149. } else {
  1150. $url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
  1151. }
  1152. // Add param to save lastsearch_values or not
  1153. $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
  1154. if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
  1155. $add_save_lastsearch_values = 1;
  1156. }
  1157. if ($add_save_lastsearch_values) {
  1158. $url .= '&save_lastsearch_values=1';
  1159. }
  1160. }
  1161. $linkclose = '';
  1162. if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
  1163. if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
  1164. $label = $langs->trans("ShowProject");
  1165. $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
  1166. }
  1167. $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
  1168. $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
  1169. } else {
  1170. $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
  1171. }
  1172. $picto = 'projectpub';
  1173. if (!$this->public) {
  1174. $picto = 'project';
  1175. }
  1176. $linkstart = '<a href="'.$url.'"';
  1177. $linkstart .= $linkclose.'>';
  1178. $linkend = '</a>';
  1179. $result .= $linkstart;
  1180. if ($withpicto) {
  1181. $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="pictofixedwidth"' : '') : 'class="'.(($withpicto != 2) ? 'pictofixedwidth ' : '').'classfortooltip pictofixedwidth em088"'), 0, 0, $notooltip ? 0 : 1);
  1182. }
  1183. if ($withpicto != 2) {
  1184. $result .= $this->ref;
  1185. }
  1186. $result .= $linkend;
  1187. if ($withpicto != 2) {
  1188. $result .= (($addlabel && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
  1189. }
  1190. global $action;
  1191. $hookmanager->initHooks(array('projectdao'));
  1192. $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
  1193. $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
  1194. if ($reshook > 0) {
  1195. $result = $hookmanager->resPrint;
  1196. } else {
  1197. $result .= $hookmanager->resPrint;
  1198. }
  1199. return $result;
  1200. }
  1201. /**
  1202. * Initialise an instance with random values.
  1203. * Used to build previews or test instances.
  1204. * id must be 0 if object instance is a specimen.
  1205. *
  1206. * @return void
  1207. */
  1208. public function initAsSpecimen()
  1209. {
  1210. global $user, $langs, $conf;
  1211. $now = dol_now();
  1212. // Initialise parameters
  1213. $this->id = 0;
  1214. $this->ref = 'SPECIMEN';
  1215. $this->entity = $conf->entity;
  1216. $this->specimen = 1;
  1217. $this->socid = 1;
  1218. $this->date_c = $now;
  1219. $this->date_m = $now;
  1220. $this->date_start = $now;
  1221. $this->date_end = $now + (3600 * 24 * 365);
  1222. $this->note_public = 'SPECIMEN';
  1223. $this->fk_ele = 20000;
  1224. $this->opp_amount = 20000;
  1225. $this->budget_amount = 10000;
  1226. $this->usage_opportunity = 1;
  1227. $this->usage_task = 1;
  1228. $this->usage_bill_time = 1;
  1229. $this->usage_organize_event = 1;
  1230. /*
  1231. $nbp = mt_rand(1, 9);
  1232. $xnbp = 0;
  1233. while ($xnbp < $nbp)
  1234. {
  1235. $line = new Task($this->db);
  1236. $line->fk_project = 0;
  1237. $line->label = $langs->trans("Label") . " " . $xnbp;
  1238. $line->description = $langs->trans("Description") . " " . $xnbp;
  1239. $this->lines[]=$line;
  1240. $xnbp++;
  1241. }
  1242. */
  1243. }
  1244. /**
  1245. * Check if user has permission on current project
  1246. *
  1247. * @param User $user Object user to evaluate
  1248. * @param string $mode Type of permission we want to know: 'read', 'write'
  1249. * @return int >0 if user has permission, <0 if user has no permission
  1250. */
  1251. public function restrictedProjectArea(User $user, $mode = 'read')
  1252. {
  1253. // To verify role of users
  1254. $userAccess = 0;
  1255. if (($mode == 'read' && !empty($user->rights->projet->all->lire)) || ($mode == 'write' && !empty($user->rights->projet->all->creer)) || ($mode == 'delete' && !empty($user->rights->projet->all->supprimer))) {
  1256. $userAccess = 1;
  1257. } elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer)))) {
  1258. $userAccess = 1;
  1259. } else { // No access due to permission to read all projects, so we check if we are a contact of project
  1260. foreach (array('internal', 'external') as $source) {
  1261. $userRole = $this->liste_contact(4, $source);
  1262. $num = count($userRole);
  1263. $nblinks = 0;
  1264. while ($nblinks < $num) {
  1265. if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) { // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
  1266. if ($mode == 'read' && $user->rights->projet->lire) {
  1267. $userAccess++;
  1268. }
  1269. if ($mode == 'write' && $user->rights->projet->creer) {
  1270. $userAccess++;
  1271. }
  1272. if ($mode == 'delete' && $user->rights->projet->supprimer) {
  1273. $userAccess++;
  1274. }
  1275. }
  1276. if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) { // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
  1277. if ($mode == 'read' && $user->rights->projet->lire) {
  1278. $userAccess++;
  1279. }
  1280. if ($mode == 'write' && $user->rights->projet->creer) {
  1281. $userAccess++;
  1282. }
  1283. if ($mode == 'delete' && $user->rights->projet->supprimer) {
  1284. $userAccess++;
  1285. }
  1286. }
  1287. $nblinks++;
  1288. }
  1289. }
  1290. //if (empty($nblinks)) // If nobody has permission, we grant creator
  1291. //{
  1292. // if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
  1293. // {
  1294. // $userAccess = 1;
  1295. // }
  1296. //}
  1297. }
  1298. return ($userAccess ? $userAccess : -1);
  1299. }
  1300. /**
  1301. * Return array of projects a user has permission on, is affected to, or all projects
  1302. *
  1303. * @param User $user User object
  1304. * @param int $mode 0=All project I have permission on (assigned to me or public), 1=Projects assigned to me only, 2=Will return list of all projects with no test on contacts
  1305. * @param int $list 0=Return array, 1=Return string list
  1306. * @param int $socid 0=No filter on third party, id of third party
  1307. * @param string $filter additionnal filter on project (statut, ref, ...)
  1308. * @return array or string Array of projects id, or string with projects id separated with "," if list is 1
  1309. */
  1310. public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
  1311. {
  1312. $projects = array();
  1313. $temp = array();
  1314. $sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
  1315. $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
  1316. if ($mode == 0) {
  1317. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
  1318. } elseif ($mode == 1) {
  1319. $sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
  1320. } elseif ($mode == 2) {
  1321. // No filter. Use this if user has permission to see all project
  1322. }
  1323. $sql .= " WHERE p.entity IN (".getEntity('project').")";
  1324. // Internal users must see project he is contact to even if project linked to a third party he can't see.
  1325. //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).")";
  1326. if ($socid > 0) {
  1327. $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
  1328. }
  1329. // Get id of types of contacts for projects (This list never contains a lot of elements)
  1330. $listofprojectcontacttype = array();
  1331. $sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
  1332. $sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
  1333. $sql2 .= " AND ctc.source = 'internal'";
  1334. $resql = $this->db->query($sql2);
  1335. if ($resql) {
  1336. while ($obj = $this->db->fetch_object($resql)) {
  1337. $listofprojectcontacttype[$obj->rowid] = $obj->code;
  1338. }
  1339. } else {
  1340. dol_print_error($this->db);
  1341. }
  1342. if (count($listofprojectcontacttype) == 0) {
  1343. $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
  1344. }
  1345. if ($mode == 0) {
  1346. $sql .= " AND ( p.public = 1";
  1347. $sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
  1348. $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
  1349. $sql .= " )";
  1350. } elseif ($mode == 1) {
  1351. $sql .= " AND ec.element_id = p.rowid";
  1352. $sql .= " AND (";
  1353. $sql .= " ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
  1354. $sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
  1355. $sql .= " )";
  1356. } elseif ($mode == 2) {
  1357. // No filter. Use this if user has permission to see all project
  1358. }
  1359. $sql .= $filter;
  1360. //print $sql;
  1361. $resql = $this->db->query($sql);
  1362. if ($resql) {
  1363. $num = $this->db->num_rows($resql);
  1364. $i = 0;
  1365. while ($i < $num) {
  1366. $row = $this->db->fetch_row($resql);
  1367. $projects[$row[0]] = $row[1];
  1368. $temp[] = $row[0];
  1369. $i++;
  1370. }
  1371. $this->db->free($resql);
  1372. if ($list) {
  1373. if (empty($temp)) {
  1374. return '0';
  1375. }
  1376. $result = implode(',', $temp);
  1377. return $result;
  1378. }
  1379. } else {
  1380. dol_print_error($this->db);
  1381. }
  1382. return $projects;
  1383. }
  1384. /**
  1385. * Load an object from its id and create a new one in database
  1386. *
  1387. * @param User $user User making the clone
  1388. * @param int $fromid Id of object to clone
  1389. * @param bool $clone_contact Clone contact of project
  1390. * @param bool $clone_task Clone task of project
  1391. * @param bool $clone_project_file Clone file of project
  1392. * @param bool $clone_task_file Clone file of task (if task are copied)
  1393. * @param bool $clone_note Clone note of project
  1394. * @param bool $move_date Move task date on clone
  1395. * @param integer $notrigger No trigger flag
  1396. * @param int $newthirdpartyid New thirdparty id
  1397. * @return int New id of clone
  1398. */
  1399. public function createFromClone(User $user, $fromid, $clone_contact = false, $clone_task = true, $clone_project_file = false, $clone_task_file = false, $clone_note = true, $move_date = true, $notrigger = 0, $newthirdpartyid = 0)
  1400. {
  1401. global $langs, $conf;
  1402. $error = 0;
  1403. dol_syslog("createFromClone clone_contact=".$clone_contact." clone_task=".$clone_task." clone_project_file=".$clone_project_file." clone_note=".$clone_note." move_date=".$move_date, LOG_DEBUG);
  1404. $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
  1405. $clone_project = new Project($this->db);
  1406. $clone_project->context['createfromclone'] = 'createfromclone';
  1407. $this->db->begin();
  1408. // Load source object
  1409. $clone_project->fetch($fromid);
  1410. $clone_project->fetch_optionals();
  1411. if ($newthirdpartyid > 0) {
  1412. $clone_project->socid = $newthirdpartyid;
  1413. }
  1414. $clone_project->fetch_thirdparty();
  1415. $orign_dt_start = $clone_project->date_start;
  1416. $orign_project_ref = $clone_project->ref;
  1417. $clone_project->id = 0;
  1418. if ($move_date) {
  1419. $clone_project->date_start = $now;
  1420. if (!(empty($clone_project->date_end))) {
  1421. $clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
  1422. }
  1423. }
  1424. $clone_project->date_c = $now;
  1425. if (!$clone_note) {
  1426. $clone_project->note_private = '';
  1427. $clone_project->note_public = '';
  1428. }
  1429. //Generate next ref
  1430. $defaultref = '';
  1431. $obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
  1432. // Search template files
  1433. $file = ''; $classname = ''; $filefound = 0;
  1434. $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
  1435. foreach ($dirmodels as $reldir) {
  1436. $file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
  1437. if (file_exists($file)) {
  1438. $filefound = 1;
  1439. dol_include_once($reldir."core/modules/project/".$obj.'.php');
  1440. $modProject = new $obj;
  1441. $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
  1442. break;
  1443. }
  1444. }
  1445. if (is_numeric($defaultref) && $defaultref <= 0) {
  1446. $defaultref = '';
  1447. }
  1448. $clone_project->ref = $defaultref;
  1449. $clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
  1450. // Create clone
  1451. $result = $clone_project->create($user, $notrigger);
  1452. // Other options
  1453. if ($result < 0) {
  1454. $this->error .= $clone_project->error;
  1455. $error++;
  1456. }
  1457. if (!$error) {
  1458. //Get the new project id
  1459. $clone_project_id = $clone_project->id;
  1460. //Note Update
  1461. if (!$clone_note) {
  1462. $clone_project->note_private = '';
  1463. $clone_project->note_public = '';
  1464. } else {
  1465. $this->db->begin();
  1466. $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
  1467. if ($res < 0) {
  1468. $this->error .= $clone_project->error;
  1469. $error++;
  1470. $this->db->rollback();
  1471. } else {
  1472. $this->db->commit();
  1473. }
  1474. $this->db->begin();
  1475. $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
  1476. if ($res < 0) {
  1477. $this->error .= $clone_project->error;
  1478. $error++;
  1479. $this->db->rollback();
  1480. } else {
  1481. $this->db->commit();
  1482. }
  1483. }
  1484. //Duplicate contact
  1485. if ($clone_contact) {
  1486. $origin_project = new Project($this->db);
  1487. $origin_project->fetch($fromid);
  1488. foreach (array('internal', 'external') as $source) {
  1489. $tab = $origin_project->liste_contact(-1, $source);
  1490. if (is_array($tab) && count($tab)>0) {
  1491. foreach ($tab as $contacttoadd) {
  1492. $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
  1493. if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
  1494. $langs->load("errors");
  1495. $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
  1496. $error++;
  1497. } else {
  1498. if ($clone_project->error != '') {
  1499. $this->error .= $clone_project->error;
  1500. $error++;
  1501. }
  1502. }
  1503. }
  1504. } elseif ($tab < 0) {
  1505. $this->error .= $origin_project->error;
  1506. $error++;
  1507. }
  1508. }
  1509. }
  1510. //Duplicate file
  1511. if ($clone_project_file) {
  1512. require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
  1513. $clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
  1514. $ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
  1515. if (dol_mkdir($clone_project_dir) >= 0) {
  1516. $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
  1517. foreach ($filearray as $key => $file) {
  1518. $rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
  1519. if (is_numeric($rescopy) && $rescopy < 0) {
  1520. $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
  1521. $error++;
  1522. }
  1523. }
  1524. } else {
  1525. $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
  1526. $error++;
  1527. }
  1528. }
  1529. //Duplicate task
  1530. if ($clone_task) {
  1531. require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
  1532. $taskstatic = new Task($this->db);
  1533. // Security check
  1534. $socid = 0;
  1535. if ($user->socid > 0) {
  1536. $socid = $user->socid;
  1537. }
  1538. $tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
  1539. $tab_conv_child_parent = array();
  1540. // Loop on each task, to clone it
  1541. foreach ($tasksarray as $tasktoclone) {
  1542. $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false);
  1543. if ($result_clone <= 0) {
  1544. $this->error .= $taskstatic->error;
  1545. $error++;
  1546. } else {
  1547. $new_task_id = $result_clone;
  1548. $taskstatic->fetch($tasktoclone->id);
  1549. //manage new parent clone task id
  1550. // if the current task has child we store the original task id and the equivalent clone task id
  1551. if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
  1552. $tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
  1553. }
  1554. }
  1555. }
  1556. //Parse all clone node to be sure to update new parent
  1557. $tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
  1558. foreach ($tasksarray as $task_cloned) {
  1559. $taskstatic->fetch($task_cloned->id);
  1560. if ($taskstatic->fk_task_parent != 0) {
  1561. $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
  1562. }
  1563. $res = $taskstatic->update($user, $notrigger);
  1564. if ($result_clone <= 0) {
  1565. $this->error .= $taskstatic->error;
  1566. $error++;
  1567. }
  1568. }
  1569. }
  1570. }
  1571. unset($clone_project->context['createfromclone']);
  1572. if (!$error) {
  1573. $this->db->commit();
  1574. return $clone_project_id;
  1575. } else {
  1576. $this->db->rollback();
  1577. dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
  1578. return -1;
  1579. }
  1580. }
  1581. /**
  1582. * Shift project task date from current date to delta
  1583. *
  1584. * @param integer $old_project_dt_start Old project start date
  1585. * @return int 1 if OK or < 0 if KO
  1586. */
  1587. public function shiftTaskDate($old_project_dt_start)
  1588. {
  1589. global $user, $langs, $conf;
  1590. $error = 0;
  1591. $result = 0;
  1592. $taskstatic = new Task($this->db);
  1593. // Security check
  1594. $socid = 0;
  1595. if ($user->socid > 0) {
  1596. $socid = $user->socid;
  1597. }
  1598. $tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
  1599. foreach ($tasksarray as $tasktoshiftdate) {
  1600. $to_update = false;
  1601. // Fetch only if update of date will be made
  1602. if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
  1603. //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
  1604. $to_update = true;
  1605. $task = new Task($this->db);
  1606. $result = $task->fetch($tasktoshiftdate->id);
  1607. if (!$result) {
  1608. $error++;
  1609. $this->error .= $task->error;
  1610. }
  1611. }
  1612. //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
  1613. //Calcultate new task start date with difference between old proj start date and origin task start date
  1614. if (!empty($tasktoshiftdate->date_start)) {
  1615. $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
  1616. }
  1617. //Calcultate new task end date with difference between origin proj end date and origin task end date
  1618. if (!empty($tasktoshiftdate->date_end)) {
  1619. $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
  1620. }
  1621. if ($to_update) {
  1622. $result = $task->update($user);
  1623. if (!$result) {
  1624. $error++;
  1625. $this->error .= $task->error;
  1626. }
  1627. }
  1628. }
  1629. if ($error != 0) {
  1630. return -1;
  1631. }
  1632. return $result;
  1633. }
  1634. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1635. /**
  1636. * Associate element to a project
  1637. *
  1638. * @param string $tableName Table of the element to update
  1639. * @param int $elementSelectId Key-rowid of the line of the element to update
  1640. * @return int 1 if OK or < 0 if KO
  1641. */
  1642. public function update_element($tableName, $elementSelectId)
  1643. {
  1644. // phpcs:enable
  1645. $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
  1646. if ($tableName == "actioncomm") {
  1647. $sql .= " SET fk_project=".$this->id;
  1648. $sql .= " WHERE id=".((int) $elementSelectId);
  1649. } elseif ($tableName == "entrepot") {
  1650. $sql .= " SET fk_project=".$this->id;
  1651. $sql .= " WHERE rowid=".((int) $elementSelectId);
  1652. } else {
  1653. $sql .= " SET fk_projet=".$this->id;
  1654. $sql .= " WHERE rowid=".((int) $elementSelectId);
  1655. }
  1656. dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
  1657. $resql = $this->db->query($sql);
  1658. if (!$resql) {
  1659. $this->error = $this->db->lasterror();
  1660. return -1;
  1661. } else {
  1662. return 1;
  1663. }
  1664. }
  1665. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1666. /**
  1667. * Associate element to a project
  1668. *
  1669. * @param string $tableName Table of the element to update
  1670. * @param int $elementSelectId Key-rowid of the line of the element to update
  1671. * @param string $projectfield The column name that stores the link with the project
  1672. *
  1673. * @return int 1 if OK or < 0 if KO
  1674. */
  1675. public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
  1676. {
  1677. // phpcs:enable
  1678. $sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
  1679. if ($tableName == "actioncomm") {
  1680. $sql .= " SET fk_project=NULL";
  1681. $sql .= " WHERE id=".((int) $elementSelectId);
  1682. } else {
  1683. $sql .= " SET ".$projectfield."=NULL";
  1684. $sql .= " WHERE rowid=".((int) $elementSelectId);
  1685. }
  1686. dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
  1687. $resql = $this->db->query($sql);
  1688. if (!$resql) {
  1689. $this->error = $this->db->lasterror();
  1690. return -1;
  1691. } else {
  1692. return 1;
  1693. }
  1694. }
  1695. /**
  1696. * Create an intervention document on disk using template defined into PROJECT_ADDON_PDF
  1697. *
  1698. * @param string $modele Force template to use ('' by default)
  1699. * @param Translate $outputlangs Objet lang to use for translation
  1700. * @param int $hidedetails Hide details of lines
  1701. * @param int $hidedesc Hide description
  1702. * @param int $hideref Hide ref
  1703. * @return int 0 if KO, 1 if OK
  1704. */
  1705. public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
  1706. {
  1707. global $conf, $langs;
  1708. $langs->load("projects");
  1709. if (!dol_strlen($modele)) {
  1710. $modele = 'baleine';
  1711. if ($this->model_pdf) {
  1712. $modele = $this->model_pdf;
  1713. } elseif (!empty($conf->global->PROJECT_ADDON_PDF)) {
  1714. $modele = $conf->global->PROJECT_ADDON_PDF;
  1715. }
  1716. }
  1717. $modelpath = "core/modules/project/doc/";
  1718. return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
  1719. }
  1720. /**
  1721. * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
  1722. * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
  1723. *
  1724. * @param int $datestart First day of week (use dol_get_first_day to find this date)
  1725. * @param int $taskid Filter on a task id
  1726. * @param int $userid Time spent by a particular user
  1727. * @return int <0 if OK, >0 if KO
  1728. */
  1729. public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
  1730. {
  1731. $error = 0;
  1732. $this->weekWorkLoad = array();
  1733. $this->weekWorkLoadPerTask = array();
  1734. if (empty($datestart)) {
  1735. dol_print_error('', 'Error datestart parameter is empty');
  1736. }
  1737. $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
  1738. $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
  1739. $sql .= " WHERE ptt.fk_task = pt.rowid";
  1740. $sql .= " AND pt.fk_projet = ".((int) $this->id);
  1741. $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
  1742. $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
  1743. if ($taskid) {
  1744. $sql .= " AND ptt.fk_task=".((int) $taskid);
  1745. }
  1746. if (is_numeric($userid)) {
  1747. $sql .= " AND ptt.fk_user=".((int) $userid);
  1748. }
  1749. //print $sql;
  1750. $resql = $this->db->query($sql);
  1751. if ($resql) {
  1752. $daylareadyfound = array();
  1753. $num = $this->db->num_rows($resql);
  1754. $i = 0;
  1755. // Loop on each record found, so each couple (project id, task id)
  1756. while ($i < $num) {
  1757. $obj = $this->db->fetch_object($resql);
  1758. $day = $this->db->jdate($obj->task_date); // task_date is date without hours
  1759. if (empty($daylareadyfound[$day])) {
  1760. $this->weekWorkLoad[$day] = $obj->task_duration;
  1761. $this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration;
  1762. } else {
  1763. $this->weekWorkLoad[$day] += $obj->task_duration;
  1764. $this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration;
  1765. }
  1766. $daylareadyfound[$day] = 1;
  1767. $i++;
  1768. }
  1769. $this->db->free($resql);
  1770. return 1;
  1771. } else {
  1772. $this->error = "Error ".$this->db->lasterror();
  1773. dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
  1774. return -1;
  1775. }
  1776. }
  1777. /**
  1778. * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
  1779. * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
  1780. *
  1781. * @param int $datestart First day of week (use dol_get_first_day to find this date)
  1782. * @param int $taskid Filter on a task id
  1783. * @param int $userid Time spent by a particular user
  1784. * @return int <0 if OK, >0 if KO
  1785. */
  1786. public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
  1787. {
  1788. $error = 0;
  1789. $this->monthWorkLoad = array();
  1790. $this->monthWorkLoadPerTask = array();
  1791. if (empty($datestart)) {
  1792. dol_print_error('', 'Error datestart parameter is empty');
  1793. }
  1794. $sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
  1795. $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
  1796. $sql .= " WHERE ptt.fk_task = pt.rowid";
  1797. $sql .= " AND pt.fk_projet = ".((int) $this->id);
  1798. $sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
  1799. $sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
  1800. if ($taskid) {
  1801. $sql .= " AND ptt.fk_task=".((int) $taskid);
  1802. }
  1803. if (is_numeric($userid)) {
  1804. $sql .= " AND ptt.fk_user=".((int) $userid);
  1805. }
  1806. //print $sql;
  1807. $resql = $this->db->query($sql);
  1808. if ($resql) {
  1809. $weekalreadyfound = array();
  1810. $num = $this->db->num_rows($resql);
  1811. $i = 0;
  1812. // Loop on each record found, so each couple (project id, task id)
  1813. while ($i < $num) {
  1814. $obj = $this->db->fetch_object($resql);
  1815. if (!empty($obj->task_date)) {
  1816. $date = explode('-', $obj->task_date);
  1817. $week_number = getWeekNumber($date[2], $date[1], $date[0]);
  1818. }
  1819. if (empty($weekalreadyfound[$week_number])) {
  1820. $this->monthWorkLoad[$week_number] = $obj->task_duration;
  1821. $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration;
  1822. } else {
  1823. $this->monthWorkLoad[$week_number] += $obj->task_duration;
  1824. $this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration;
  1825. }
  1826. $weekalreadyfound[$week_number] = 1;
  1827. $i++;
  1828. }
  1829. $this->db->free($resql);
  1830. return 1;
  1831. } else {
  1832. $this->error = "Error ".$this->db->lasterror();
  1833. dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
  1834. return -1;
  1835. }
  1836. }
  1837. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1838. /**
  1839. * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
  1840. *
  1841. * @param User $user Objet user
  1842. * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
  1843. */
  1844. public function load_board($user)
  1845. {
  1846. // phpcs:enable
  1847. global $conf, $langs;
  1848. // For external user, no check is done on company because readability is managed by public status of project and assignement.
  1849. //$socid=$user->socid;
  1850. $response = new WorkboardResponse();
  1851. $response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
  1852. $response->label = $langs->trans("OpenedProjects");
  1853. $response->labelShort = $langs->trans("Opened");
  1854. $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
  1855. $response->img = img_object('', "projectpub");
  1856. $response->nbtodo = 0;
  1857. $response->nbtodolate = 0;
  1858. $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
  1859. $sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
  1860. $sql .= ")";
  1861. $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
  1862. // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
  1863. //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
  1864. $sql .= " WHERE p.fk_statut = 1";
  1865. $sql .= " AND p.entity IN (".getEntity('project').')';
  1866. $projectsListId = null;
  1867. if (!$user->hasRight("projet", "all", "lire")) {
  1868. $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
  1869. $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
  1870. if (empty($projectsListId)) {
  1871. return $response;
  1872. }
  1873. $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
  1874. }
  1875. // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
  1876. //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).")";
  1877. // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
  1878. //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))";
  1879. //print $sql;
  1880. $resql = $this->db->query($sql);
  1881. if ($resql) {
  1882. $project_static = new Project($this->db);
  1883. // This assignment in condition is not a bug. It allows walking the results.
  1884. while ($obj = $this->db->fetch_object($resql)) {
  1885. $response->nbtodo++;
  1886. $project_static->statut = $obj->status;
  1887. $project_static->opp_status = $obj->fk_opp_status;
  1888. $project_static->date_end = $this->db->jdate($obj->datee);
  1889. if ($project_static->hasDelay()) {
  1890. $response->nbtodolate++;
  1891. }
  1892. }
  1893. return $response;
  1894. }
  1895. $this->error = $this->db->error();
  1896. return -1;
  1897. }
  1898. /**
  1899. * Function used to replace a thirdparty id with another one.
  1900. *
  1901. * @param DoliDB $db Database handler
  1902. * @param int $origin_id Old thirdparty id
  1903. * @param int $dest_id New thirdparty id
  1904. * @return bool
  1905. */
  1906. public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
  1907. {
  1908. $tables = array(
  1909. 'projet'
  1910. );
  1911. return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
  1912. }
  1913. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  1914. /**
  1915. * Charge indicateurs this->nb pour le tableau de bord
  1916. *
  1917. * @return int <0 if KO, >0 if OK
  1918. */
  1919. public function load_state_board()
  1920. {
  1921. // phpcs:enable
  1922. global $user;
  1923. $this->nb = array();
  1924. $sql = "SELECT count(p.rowid) as nb";
  1925. $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
  1926. $sql .= " WHERE";
  1927. $sql .= " p.entity IN (".getEntity('project').")";
  1928. if (empty($user->rights->projet->all->lire)) {
  1929. $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
  1930. $sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
  1931. }
  1932. $resql = $this->db->query($sql);
  1933. if ($resql) {
  1934. while ($obj = $this->db->fetch_object($resql)) {
  1935. $this->nb["projects"] = $obj->nb;
  1936. }
  1937. $this->db->free($resql);
  1938. return 1;
  1939. } else {
  1940. dol_print_error($this->db);
  1941. $this->error = $this->db->error();
  1942. return -1;
  1943. }
  1944. }
  1945. /**
  1946. * Is the project delayed?
  1947. *
  1948. * @return bool
  1949. */
  1950. public function hasDelay()
  1951. {
  1952. global $conf;
  1953. if (!($this->statut == self::STATUS_VALIDATED)) {
  1954. return false;
  1955. }
  1956. if (!$this->date_end) {
  1957. return false;
  1958. }
  1959. $now = dol_now();
  1960. return ($this->date_end) < ($now - $conf->project->warning_delay);
  1961. }
  1962. /**
  1963. * Charge les informations d'ordre info dans l'objet commande
  1964. *
  1965. * @param int $id Id of order
  1966. * @return void
  1967. */
  1968. public function info($id)
  1969. {
  1970. $sql = 'SELECT c.rowid, datec as datec, tms as datem,';
  1971. $sql .= ' date_close as datecloture,';
  1972. $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture';
  1973. $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
  1974. $sql .= ' WHERE c.rowid = '.((int) $id);
  1975. $result = $this->db->query($sql);
  1976. if ($result) {
  1977. if ($this->db->num_rows($result)) {
  1978. $obj = $this->db->fetch_object($result);
  1979. $this->id = $obj->rowid;
  1980. if ($obj->fk_user_author) {
  1981. $cuser = new User($this->db);
  1982. $cuser->fetch($obj->fk_user_author);
  1983. $this->user_creation = $cuser;
  1984. }
  1985. if (!empty($obj->fk_user_cloture)) {
  1986. $cluser = new User($this->db);
  1987. $cluser->fetch($obj->fk_user_cloture);
  1988. $this->user_cloture = $cluser;
  1989. }
  1990. $this->date_creation = $this->db->jdate($obj->datec);
  1991. $this->date_modification = $this->db->jdate($obj->datem);
  1992. $this->date_cloture = $this->db->jdate($obj->datecloture);
  1993. }
  1994. $this->db->free($result);
  1995. } else {
  1996. dol_print_error($this->db);
  1997. }
  1998. }
  1999. /**
  2000. * Sets object to supplied categories.
  2001. *
  2002. * Deletes object from existing categories not supplied.
  2003. * Adds it to non existing supplied categories.
  2004. * Existing categories are left untouch.
  2005. *
  2006. * @param int[]|int $categories Category or categories IDs
  2007. * @return int <0 if KO, >0 if OK
  2008. */
  2009. public function setCategories($categories)
  2010. {
  2011. require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
  2012. return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
  2013. }
  2014. /**
  2015. * Create an array of tasks of current project
  2016. *
  2017. * @param User $user Object user we want project allowed to
  2018. * @param int $loadRoleMode 1= will test Roles on task; 0 used in delete project action
  2019. * @return int >0 if OK, <0 if KO
  2020. */
  2021. public function getLinesArray($user, $loadRoleMode = 1)
  2022. {
  2023. require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
  2024. $taskstatic = new Task($this->db);
  2025. $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '', '-1', '', 0, 0, array(), 0, array(), 0, $loadRoleMode);
  2026. }
  2027. }