dolibarr 24.0.2
project.class.php
Go to the documentation of this file.
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 * Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
11 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
12 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
13 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 3 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program. If not, see <https://www.gnu.org/licenses/>.
27 */
28
34require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
35
39class Project extends CommonObject
40{
44 public $element = 'project';
45
49 public $table_element = 'projet';
50
54 public $table_element_line = 'projet_task';
55
59 public $table_element_date;
60
64 public $fk_element = 'fk_projet';
65
69 public $picto = 'project';
70
74 protected $table_ref_field = 'ref';
75
79 public $fk_project;
80
84 public $description;
85
89 public $title;
90
95 public $dateo;
96
100 public $date_start;
101
106 public $datee;
107
111 public $date_end;
112
116 public $date_start_event;
117
121 public $date_end_event;
122
126 public $location;
127
131 public $date_close;
132
136 public $socid; // To store id of thirdparty
137
141 public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
142
146 public $user_author_id;
147
151 public $fk_user_close;
152
156 public $public;
157
161 public $budget_amount;
162
166 public $usage_opportunity;
167
171 public $usage_task;
172
176 public $usage_bill_time; // Is the time spent on project must be invoiced or not
177
181 public $usage_organize_event;
182
186 public $accept_conference_suggestions;
187
191 public $accept_booth_suggestions;
192
196 public $price_registration;
197
201 public $price_booth;
202
206 public $max_attendees;
207
212 public $statut; // 0=draft, 1=opened, 2=closed
213
217 public $opp_status; // opportunity status, into table llx_c_lead_status
218
222 public $opp_status_code;
223
227 public $fk_opp_status; // opportunity status, into table llx_c_lead_status
228
232 public $opp_amount; // opportunity amount
233
237 public $opp_percent; // opportunity probability
238
242 public $opp_weighted_amount; // opportunity weighted amount
243
247 public $email_msgid;
248
252 public $weekWorkLoad;
256 public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
257
261 public $monthWorkLoad;
262
266 public $monthWorkLoadPerTask;
267
272 public $datec;
273
277 public $date_c;
278
283 public $datem;
284
288 public $date_m;
289
293 public $ip;
294
298 public $lines;
299
340 // BEGIN MODULEBUILDER PROPERTIES
344 public $fields = array(
345 'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
346 'fk_project' => array('type' => 'integer', 'label' => 'Parent', 'enabled' => 1, 'visible' => -1, 'notnull' => 0, 'position' => 12),
347 'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 15, 'searchall' => 1),
348 'title' => array('type' => 'varchar(255)', 'label' => 'ProjectLabel', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 17, 'showoncombobox' => 2, 'searchall' => 1, 'csslist' => 'tdoverflowmax250'),
349 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 19),
350 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 1, 'visible' => 0, 'position' => 20),
351 'dateo' => array('type' => 'date', 'label' => 'DateStart', 'enabled' => 1, 'visible' => -1, 'position' => 30),
352 'datee' => array('type' => 'date', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => 1, 'position' => 35),
353 'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => 3, 'position' => 55, 'searchall' => 1),
354 'public' => array('type' => 'integer', 'label' => 'Visibility', 'enabled' => 1, 'visible' => -1, 'position' => 65),
355 'fk_opp_status' => array('type' => 'integer:CLeadStatus:core/class/cleadstatus.class.php', 'label' => 'OpportunityStatusShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 75),
356 'opp_percent' => array('type' => 'double(5,2)', 'label' => 'OpportunityProbabilityShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 80),
357 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 85, 'searchall' => 1),
358 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 90, 'searchall' => 1),
359 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 95),
360 'date_close' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => 0, 'position' => 105),
361 'fk_user_close' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => 0, 'position' => 110),
362 'opp_amount' => array('type' => 'double(24,8)', 'label' => 'OpportunityAmountShort', 'enabled' => 1, 'visible' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position' => 115),
363 'budget_amount' => array('type' => 'double(24,8)', 'label' => 'Budget', 'enabled' => 1, 'visible' => -1, 'position' => 119),
364 'usage_opportunity' => array('type' => 'integer', 'label' => 'UsageOpportunity', 'enabled' => 1, 'visible' => -1, 'position' => 130),
365 'usage_task' => array('type' => 'integer', 'label' => 'UsageTasks', 'enabled' => 1, 'visible' => -1, 'position' => 135),
366 'usage_bill_time' => array('type' => 'integer', 'label' => 'UsageBillTimeShort', 'enabled' => 1, 'visible' => -1, 'position' => 140),
367 'usage_organize_event' => array('type' => 'integer', 'label' => 'UsageOrganizeEvent', 'enabled' => 1, 'visible' => -1, 'position' => 145),
368 // Properties for event organization
369 'date_start_event' => array('type' => 'date', 'label' => 'DateStartEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 200),
370 'date_end_event' => array('type' => 'date', 'label' => 'DateEndEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 201),
371 'location' => array('type' => 'text', 'label' => 'Location', 'enabled' => 1, 'visible' => 3, 'position' => 202, 'searchall' => 1),
372 'accept_conference_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestConf', 'enabled' => 1, 'visible' => -1, 'position' => 210),
373 'accept_booth_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestBooth', 'enabled' => 1, 'visible' => -1, 'position' => 211),
374 'price_registration' => array('type' => 'double(24,8)', 'label' => 'PriceOfRegistration', 'enabled' => 1, 'visible' => -1, 'position' => 212),
375 'price_booth' => array('type' => 'double(24,8)', 'label' => 'PriceOfBooth', 'enabled' => 1, 'visible' => -1, 'position' => 215),
376 'max_attendees' => array('type' => 'integer', 'label' => 'MaxNbOfAttendees', 'enabled' => 1, 'visible' => -1, 'position' => 215),
377 // Generic
378 'datec' => array('type' => 'datetime', 'label' => 'DateCreationShort', 'enabled' => 1, 'visible' => -2, 'position' => 400),
379 'tms' => array('type' => 'timestamp', 'label' => 'DateModificationShort', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 405),
380 'fk_user_creat' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 410),
381 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModification', 'enabled' => 1, 'visible' => 0, 'position' => 415),
382 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 420),
383 'email_msgid' => array('type' => 'varchar(255)', 'label' => 'EmailMsgID', 'enabled' => 1, 'visible' => -1, 'position' => 450, 'help' => 'EmailMsgIDWhenSourceisEmail', 'csslist' => 'tdoverflowmax125'),
384 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'alias' => 'status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 500, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Closed')),
385 );
386 // END MODULEBUILDER PROPERTIES
387
391 const STATUS_DRAFT = 0;
392
397
401 const STATUS_CLOSED = 2;
402
403
409 public function __construct($db)
410 {
411 global $conf;
412
413 $this->db = $db;
414
415 $this->ismultientitymanaged = 1;
416 $this->isextrafieldmanaged = 1;
417
418 $this->labelStatusShort = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
419 $this->labelStatus = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
420
421 global $conf;
422
423 if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID')) {
424 $this->fields['rowid']['visible'] = 0;
425 }
426
427 if (!getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
428 $this->fields['fk_opp_status']['enabled'] = 0;
429 $this->fields['opp_percent']['enabled'] = 0;
430 $this->fields['opp_amount']['enabled'] = 0;
431 $this->fields['usage_opportunity']['enabled'] = 0;
432 }
433
434 if (getDolGlobalString('PROJECT_HIDE_TASKS')) {
435 $this->fields['usage_bill_time']['visible'] = 0;
436 $this->fields['usage_task']['visible'] = 0;
437 }
438
439 if (empty($conf->eventorganization->enabled)) {
440 $this->fields['usage_organize_event']['visible'] = 0;
441 $this->fields['accept_conference_suggestions']['enabled'] = 0;
442 $this->fields['accept_booth_suggestions']['enabled'] = 0;
443 $this->fields['price_registration']['enabled'] = 0;
444 $this->fields['price_booth']['enabled'] = 0;
445 $this->fields['max_attendees']['enabled'] = 0;
446 }
447 }
448
456 public function create($user, $notrigger = 0)
457 {
458 $error = 0;
459 $ret = 0;
460
461 $now = dol_now();
462
463 // Clean parameters
464 $this->note_private = dol_substr($this->note_private, 0, 65535);
465 $this->note_public = dol_substr($this->note_public, 0, 65535);
466
467 // Check parameters
468 if (!trim($this->ref)) {
469 $this->error = 'ErrorFieldsRequired';
470 dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
471 return -1;
472 }
473 if (getDolGlobalString('PROJECT_THIRDPARTY_REQUIRED') && !($this->socid > 0)) {
474 $this->error = 'ErrorFieldsRequired';
475 dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
476 return -1;
477 }
478
479 // Create project
480 $this->db->begin();
481
482 $sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
483 $sql .= "ref";
484 $sql .= ", fk_project";
485 $sql .= ", title";
486 $sql .= ", description";
487 $sql .= ", fk_soc";
488 $sql .= ", fk_user_creat";
489 $sql .= ", fk_statut";
490 $sql .= ", fk_opp_status";
491 $sql .= ", opp_percent";
492 $sql .= ", public";
493 $sql .= ", datec";
494 $sql .= ", dateo";
495 $sql .= ", datee";
496 $sql .= ", opp_amount";
497 $sql .= ", budget_amount";
498 $sql .= ", usage_opportunity";
499 $sql .= ", usage_task";
500 $sql .= ", usage_bill_time";
501 $sql .= ", usage_organize_event";
502 $sql .= ", accept_conference_suggestions";
503 $sql .= ", accept_booth_suggestions";
504 $sql .= ", price_registration";
505 $sql .= ", price_booth";
506 $sql .= ", max_attendees";
507 $sql .= ", date_start_event";
508 $sql .= ", date_end_event";
509 $sql .= ", location";
510 $sql .= ", email_msgid";
511 $sql .= ", note_private";
512 $sql .= ", note_public";
513 $sql .= ", entity";
514 $sql .= ", ip";
515 $sql .= ") VALUES (";
516 $sql .= "'".$this->db->escape($this->ref)."'";
517 $sql .= ", ".($this->fk_project ? ((int) $this->fk_project) : "null");
518 $sql .= ", '".$this->db->escape($this->title)."'";
519 $sql .= ", '".$this->db->escape($this->description)."'";
520 $sql .= ", ".($this->socid > 0 ? ((int) $this->socid) : "null");
521 $sql .= ", ".((int) $user->id);
522 $sql .= ", ".(is_numeric($this->status) ? ((int) $this->status) : '0');
523 $sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
524 $sql .= ", ".(is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
525 $sql .= ", ".($this->public ? 1 : 0);
526 $sql .= ", '".$this->db->idate($now)."'";
527 $sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
528 $sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
529 $sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
530 $sql .= ", ".(strcmp((string) $this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
531 $sql .= ", ".($this->usage_opportunity ? 1 : 0);
532 $sql .= ", ".($this->usage_task ? 1 : 0);
533 $sql .= ", ".($this->usage_bill_time ? 1 : 0);
534 $sql .= ", ".($this->usage_organize_event ? 1 : 0);
535 $sql .= ", ".($this->accept_conference_suggestions ? 1 : 0);
536 $sql .= ", ".($this->accept_booth_suggestions ? 1 : 0);
537 $sql .= ", ".(strcmp((string) $this->price_registration, '') ? price2num($this->price_registration) : 'null');
538 $sql .= ", ".(strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : 'null');
539 $sql .= ", ".(strcmp((string) $this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
540 $sql .= ", ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
541 $sql .= ", ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
542 $sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : 'null');
543 $sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
544 $sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
545 $sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
546 $sql .= ", ".setEntity($this);
547 $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
548 $sql .= ")";
549
550 dol_syslog(get_class($this)."::create", LOG_DEBUG);
551 $resql = $this->db->query($sql);
552 if ($resql) {
553 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
554 $ret = $this->id;
555
556 if (!$notrigger) {
557 // Call trigger
558 $result = $this->call_trigger('PROJECT_CREATE', $user);
559 if ($result < 0) {
560 $error++;
561 }
562 // End call triggers
563 }
564 } else {
565 $this->error = $this->db->lasterror();
566 $error++;
567 }
568
569 // Update extrafield
570 if (!$error) {
571 $result = $this->insertExtraFields();
572 if ($result < 0) {
573 $error++;
574 }
575 }
576
577 if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
578 $res = $this->setValid($user);
579 if ($res < 0) {
580 $error++;
581 }
582 }
583
584 if (!$error) {
585 $this->db->commit();
586 return $ret;
587 } else {
588 $this->db->rollback();
589 return -1;
590 }
591 }
592
600 public function update($user, $notrigger = 0)
601 {
602 global $langs, $conf;
603
604 $error = 0;
605
606 // Clean parameters
607 $this->title = trim($this->title);
608 $this->description = trim($this->description);
609 if ($this->opp_amount < 0) {
610 $this->opp_amount = '';
611 }
612 if ($this->opp_percent < 0) {
613 $this->opp_percent = '';
614 }
615 if ($this->date_end && $this->date_end < $this->date_start) {
616 $langs->load("errors");
617 $this->error = $langs->trans("ErrorStartDateGreaterEnd");
618 $this->errors[] = $this->error;
619 $this->db->rollback();
620 dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
621 return -3;
622 }
623
624 $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
625
626 if (dol_strlen(trim($this->ref)) > 0) {
627 $this->db->begin();
628
629 $sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
630 $sql .= " ref='".$this->db->escape($this->ref)."'";
631 $sql .= ", fk_project=".($this->fk_project ? ((int) $this->fk_project) : "null");
632 $sql .= ", title = '".$this->db->escape($this->title)."'";
633 $sql .= ", description = '".$this->db->escape($this->description)."'";
634 $sql .= ", fk_soc = ".($this->socid > 0 ? ((int) $this->socid) : "null");
635 $sql .= ", fk_statut = ".((int) $this->status);
636 $sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'null');
637 $sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? ((float) $this->opp_percent) : 'null');
638 $sql .= ", public = ".($this->public ? 1 : 0);
639 $sql .= ", datec = ".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
640 $sql .= ", dateo = ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
641 $sql .= ", datee = ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
642 $sql .= ", date_close = ".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
643 $sql .= ", note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
644 $sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
645 $sql .= ", fk_user_close = ".($this->fk_user_close > 0 ? ((int) $this->fk_user_close) : "null");
646 $sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
647 $sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
648 $sql .= ", fk_user_modif = ".((int) $user->id);
649 $sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
650 $sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
651 $sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
652 $sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
653 $sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0);
654 $sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0);
655 $sql .= ", price_registration = ".(isset($this->price_registration) && strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
656 $sql .= ", price_booth = ".(isset($this->price_booth) && strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : "null");
657 $sql .= ", max_attendees = ".(strcmp((string) $this->max_attendees, '') ? (int) $this->max_attendees : "null");
658 $sql .= ", date_start_event = ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
659 $sql .= ", date_end_event = ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
660 $sql .= ", location = '".$this->db->escape($this->location)."'";
661 $sql .= ", entity = ".((int) $this->entity);
662 $sql .= " WHERE rowid = ".((int) $this->id);
663
664 dol_syslog(get_class($this)."::update", LOG_DEBUG);
665 $resql = $this->db->query($sql);
666 if ($resql) {
667 // Update extrafield
668 if (!$error) {
669 $result = $this->insertExtraFields();
670 if ($result < 0) {
671 $error++;
672 }
673 }
674
675 if (!$error && !$notrigger) {
676 // Call trigger
677 $result = $this->call_trigger('PROJECT_MODIFY', $user);
678 if ($result < 0) {
679 $error++;
680 }
681 // End call triggers
682 }
683
684 if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
685 // We remove directory
686 if ($conf->project->dir_output) {
687 $olddir = $conf->project->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
688 $newdir = $conf->project->dir_output."/".dol_sanitizeFileName($this->ref);
689 if (file_exists($olddir)) {
690 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
691 $res = @rename($olddir, $newdir);
692 if (!$res) {
693 $langs->load("errors");
694 $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
695 $error++;
696 }
697 }
698 }
699 }
700 if (!$error) {
701 $this->db->commit();
702 $result = 1;
703 } else {
704 $this->db->rollback();
705 $result = -1;
706 }
707 } else {
708 $this->error = $this->db->lasterror();
709 $this->errors[] = $this->error;
710 $this->db->rollback();
711 if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
712 $result = -4;
713 } else {
714 $result = -2;
715 }
716 dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
717 }
718 } else {
719 dol_syslog(get_class($this)."::update ref null");
720 $result = -1;
721 }
722
723 return $result;
724 }
725
735 public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
736 {
737 if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
738 dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING);
739 return -1;
740 }
741
742 $sql = "SELECT rowid, entity, fk_project, ref, title, description, public, datec, opp_amount, budget_amount,";
743 $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,";
744 $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
745 $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location, extraparams";
746 $sql .= " FROM ".MAIN_DB_PREFIX."projet";
747 if (!empty($id)) {
748 $sql .= " WHERE rowid = ".((int) $id);
749 } else {
750 $sql .= " WHERE entity IN (".getEntity('project').")";
751 if (!empty($ref)) {
752 $sql .= " AND ref = '".$this->db->escape($ref)."'";
753 } elseif (!empty($ref_ext)) {
754 $sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
755 } else {
756 $sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
757 }
758 }
759
760 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
761 $resql = $this->db->query($sql);
762 if ($resql) {
763 $num_rows = $this->db->num_rows($resql);
764
765 if ($num_rows) {
766 $obj = $this->db->fetch_object($resql);
767
768 $this->id = $obj->rowid;
769 $this->entity = $obj->entity;
770 $this->ref = $obj->ref;
771 $this->fk_project = $obj->fk_project;
772 $this->title = $obj->title;
773 $this->description = $obj->description;
774 $this->date_c = $this->db->jdate($obj->datec);
775 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
776 $this->date_m = $this->db->jdate($obj->tms);
777 $this->datem = $this->db->jdate($obj->tms); // TODO deprecated
778 $this->date_start = $this->db->jdate($obj->date_start);
779 $this->date_end = $this->db->jdate($obj->date_end);
780 $this->date_close = $this->db->jdate($obj->date_close);
781 $this->note_private = $obj->note_private;
782 $this->note_public = $obj->note_public;
783 $this->socid = $obj->fk_soc;
784 $this->user_author_id = $obj->fk_user_creat;
785 $this->user_modification_id = $obj->fk_user_modif;
786 $this->user_closing_id = $obj->fk_user_close;
787 $this->public = $obj->public;
788 $this->statut = $obj->status; // deprecated
789 $this->status = $obj->status;
790 $this->opp_status = $obj->fk_opp_status;
791 $this->opp_amount = $obj->opp_amount;
792 $this->opp_percent = $obj->opp_percent;
793 $this->budget_amount = $obj->budget_amount;
794 $this->model_pdf = $obj->model_pdf;
795 $this->usage_opportunity = (int) $obj->usage_opportunity;
796 $this->usage_task = (int) $obj->usage_task;
797 $this->usage_bill_time = (int) $obj->usage_bill_time;
798 $this->usage_organize_event = (int) $obj->usage_organize_event;
799 $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
800 $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
801 $this->price_registration = $obj->price_registration;
802 $this->price_booth = $obj->price_booth;
803 $this->max_attendees = $obj->max_attendees;
804 $this->date_start_event = $this->db->jdate($obj->date_start_event);
805 $this->date_end_event = $this->db->jdate($obj->date_end_event);
806 $this->location = $obj->location;
807 $this->email_msgid = $obj->email_msgid;
808 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
809
810 $this->db->free($resql);
811
812 // Retrieve all extrafield
813 // fetch optionals attributes and labels
814 $this->fetch_optionals();
815
816 return 1;
817 }
818
819 $this->db->free($resql);
820
821 return 0;
822 } else {
823 $this->error = $this->db->lasterror();
824 $this->errors[] = $this->db->lasterror();
825 return -1;
826 }
827 }
828
837 public function fetchAndSetSubstitution($id, $key, $fetched = false)
838 {
839 $substitution = '';
840
841 if ($fetched === false) {
842 $res = $this->fetch($id);
843 if ($res > 0) {
844 $fetched = true;
845 }
846 }
847
848 if ($fetched === true) {
849 if ($key == '__PROJECT_ID__') {
850 $substitution = ($this->id > 0 ? $this->id : '');
851 } elseif ($key == '__PROJECT_REF__') {
852 $substitution = $this->ref;
853 } elseif ($key == '__PROJECT_NAME__') {
854 $substitution = $this->title;
855 }
856 }
857
858 return (string) $substitution;
859 }
860
861 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
873 public function get_element_list($type, $tablename, $datefieldname = '', $date_start = null, $date_end = null, $projectkey = 'fk_projet')
874 {
875 // phpcs:enable
876
877 global $hookmanager;
878
879 $elements = array();
880
881 if ($this->id <= 0) {
882 return $elements;
883 }
884
885 $ids = $this->id;
886
887 if ($type == 'agenda') {
888 $sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize((string) $ids).") AND entity IN (".getEntity('agenda').")";
889 } elseif ($type == 'expensereport') {
890 $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((string) $ids).")";
891 } elseif ($type == 'project_task') {
892 $sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize((string) $ids).")";
893 } elseif ($type == 'element_time') { // Case we want to duplicate line foreach user
894 $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet IN (".$this->db->sanitize((string) $ids).")";
895 } elseif ($type == 'stocktransfer_stocktransfer') {
896 $sql = "SELECT ms.rowid, ms.fk_user_author as fk_user FROM ".MAIN_DB_PREFIX."stocktransfer_stocktransfer 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((string) $ids).") AND ms.type_mouvement = 1";
897 } elseif ($type == 'stock_mouvement') {
898 // Stock movements have no entity field (the warehouse has one), and the project is stored into fk_projet
899 $sql = "SELECT ms.rowid 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.fk_projet IN (".$this->db->sanitize((string) $ids).")";
900 } elseif ($type == 'loan') {
901 $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((string) $ids).")";
902 } else {
903 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$this->db->sanitize($tablename)." WHERE ".$this->db->sanitize($projectkey)." IN (".$this->db->sanitize((string) $ids).") AND entity IN (".getEntity($type).")";
904 }
905
906 if (isDolTms($date_start) && $type == 'loan') {
907 $sql .= " AND (dateend > '".$this->db->idate((int) $date_start)."' OR dateend IS NULL)";
908 } elseif (isDolTms($date_start) && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
909 if (empty($datefieldname) && !empty($this->table_element_date)) {
910 $datefieldname = $this->table_element_date;
911 }
912 if (empty($datefieldname)) {
913 return 'Error this object has no date field defined';
914 }
915 $sql .= " AND (".$this->db->sanitize($datefieldname)." >= '".$this->db->idate((int) $date_start)."' OR ".$this->db->sanitize($datefieldname)." IS NULL)";
916 }
917
918 if (isDolTms($date_end) && $type == 'loan') {
919 $sql .= " AND (datestart < '".$this->db->idate((int) $date_end)."' OR datestart IS NULL)";
920 } elseif (isDolTms($date_end) && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
921 if (empty($datefieldname) && !empty($this->table_element_date)) {
922 $datefieldname = $this->table_element_date;
923 }
924 if (empty($datefieldname)) {
925 return 'Error this object has no date field defined';
926 }
927 $sql .= " AND (".$this->db->sanitize($datefieldname)." <= '".$this->db->idate((int) $date_end)."' OR ".$this->db->sanitize($datefieldname)." IS NULL)";
928 }
929
930 $parameters = array(
931 'sql' => $sql,
932 'type' => $type,
933 'tablename' => $tablename,
934 'datefieldname' => $datefieldname,
935 'dates' => $date_start,
936 'datee' => $date_end,
937 'fk_projet' => $projectkey,
938 'ids' => $ids,
939 );
940 $reshook = $hookmanager->executeHooks('getElementList', $parameters);
941 if ($reshook > 0) {
942 $sql = $hookmanager->resPrint;
943 } else {
944 $sql .= $hookmanager->resPrint;
945 }
946
947 if (!$sql) {
948 return -1;
949 }
950
951 //print $sql;
952 dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
953 $result = $this->db->query($sql);
954 if ($result) {
955 $nump = $this->db->num_rows($result);
956 if ($nump) {
957 $i = 0;
958 while ($i < $nump) {
959 $obj = $this->db->fetch_object($result);
960
961 $elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
962
963 $i++;
964 }
965 $this->db->free($result);
966 }
967
968 /* Return array even if empty*/
969 return $elements;
970 } else {
971 dol_print_error($this->db);
972 }
973 return -1;
974 }
975
983 public function delete($user, $notrigger = 0)
984 {
985 global $langs, $conf;
986 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
987
988 $error = 0;
989
990 $this->db->begin();
991
992 if (!$error) {
993 // Delete linked contacts
994 $res = $this->delete_linked_contact();
995 if ($res < 0) {
996 $this->error = 'ErrorFailToDeleteLinkedContact';
997 //$error++;
998 $this->db->rollback();
999 return 0;
1000 }
1001 }
1002
1003 // Set fk_projet into elements to null
1004 $listoftables = array(
1005 'propal' => 'fk_projet',
1006 'commande' => 'fk_projet',
1007 'facture' => 'fk_projet',
1008 'supplier_proposal' => 'fk_projet',
1009 'commande_fournisseur' => 'fk_projet',
1010 'facture_fourn' => 'fk_projet',
1011 'expensereport_det' => 'fk_projet',
1012 'contrat' => 'fk_projet',
1013 'fichinter' => 'fk_projet',
1014 'don' => array('field' => 'fk_projet', 'module' => 'don'),
1015 'actioncomm' => 'fk_project',
1016 'mrp_mo' => array('field' => 'fk_project', 'module' => 'mrp'),
1017 'entrepot' => 'fk_project',
1018 );
1019 foreach ($listoftables as $key => $value) {
1020 if (is_array($value)) {
1021 if (!isModEnabled($value['module'])) {
1022 continue;
1023 }
1024 $fieldname = $value['field'];
1025 } else {
1026 $fieldname = $value;
1027 }
1028 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($key)." SET ".$this->db->sanitize($fieldname)." = NULL WHERE ".$this->db->sanitize($fieldname)." = ".((int) $this->id);
1029
1030 $resql = $this->db->query($sql);
1031 if (!$resql) {
1032 $this->errors[] = $this->db->lasterror();
1033 $error++;
1034 break;
1035 }
1036 }
1037
1038 // Remove linked categories.
1039 if (!$error) {
1040 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
1041 $sql .= " WHERE fk_project = ".((int) $this->id);
1042
1043 $result = $this->db->query($sql);
1044 if (!$result) {
1045 $error++;
1046 $this->errors[] = $this->db->lasterror();
1047 }
1048 }
1049
1050 // Fetch tasks
1051 $this->getLinesArray($user, 0);
1052
1053 // Delete tasks
1054 $ret = $this->deleteTasks($user);
1055 if ($ret < 0) {
1056 $error++;
1057 }
1058
1059
1060 // Delete all child tables
1061 if (!$error) {
1062 $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
1063 foreach ($elements as $table) {
1064 if (!$error) {
1065 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->db->sanitize($table);
1066 $sql .= " WHERE fk_project = ".((int) $this->id);
1067
1068 $result = $this->db->query($sql);
1069 if (!$result) {
1070 $error++;
1071 $this->errors[] = $this->db->lasterror();
1072 }
1073 }
1074 }
1075 }
1076
1077 if (!$error) {
1078 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
1079 $sql .= " WHERE fk_object = ".((int) $this->id);
1080
1081 $resql = $this->db->query($sql);
1082 if (!$resql) {
1083 $this->errors[] = $this->db->lasterror();
1084 $error++;
1085 }
1086 }
1087
1088 // Delete project
1089 if (!$error) {
1090 $sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
1091 $sql .= " WHERE rowid=".((int) $this->id);
1092
1093 $resql = $this->db->query($sql);
1094 if (!$resql) {
1095 $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
1096 $error++;
1097 }
1098 }
1099
1100
1101
1102 if (empty($error)) {
1103 // We remove directory
1104 $projectref = dol_sanitizeFileName($this->ref);
1105 if ($conf->project->dir_output && !empty($projectref)) {
1106 $dir = $conf->project->dir_output."/".$projectref;
1107 if (file_exists($dir)) {
1108 $res = @dol_delete_dir_recursive($dir);
1109 if (!$res) {
1110 $this->errors[] = 'ErrorFailToDeleteDir';
1111 $error++;
1112 }
1113 }
1114 }
1115
1116 if (!$notrigger) {
1117 // Call trigger
1118 $result = $this->call_trigger('PROJECT_DELETE', $user);
1119
1120 if ($result < 0) {
1121 $error++;
1122 }
1123 // End call triggers
1124 }
1125 }
1126
1127 if (empty($error)) {
1128 $this->db->commit();
1129 return 1;
1130 } else {
1131 foreach ($this->errors as $errmsg) {
1132 dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1133 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1134 }
1135 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
1136 $this->db->rollback();
1137 return -1;
1138 }
1139 }
1140
1149 public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
1150 {
1151 if ($this->id <= 0) {
1152 return 0;
1153 }
1154
1155 if ($type == 'agenda') {
1156 $sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".((int) $this->id)." AND entity IN (".getEntity('agenda').")";
1157 } elseif ($type == 'expensereport') {
1158 $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);
1159 } elseif ($type == 'project_task') {
1160 $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id);
1161 } elseif ($type == 'element_time') { // Case we want to duplicate line foreach user
1162 $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet = ".((int) $this->id);
1163 } elseif ($type == 'stock_mouvement') {
1164 $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";
1165 } elseif ($type == 'loan') {
1166 $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);
1167 } else {
1168 $sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$this->db->sanitize($tablename)." WHERE ".$this->db->sanitize($projectkey)." = ".((int) $this->id)." AND entity IN (".getEntity($type).")";
1169 }
1170
1171 $result = $this->db->query($sql);
1172
1173 if (!$result) {
1174 return 0;
1175 }
1176
1177 $obj = $this->db->fetch_object($result);
1178
1179 $this->db->free($result);
1180
1181 return $obj->nb;
1182 }
1183
1190 public function deleteTasks($user)
1191 {
1192 $countTasks = count($this->lines);
1193 $deleted = false;
1194 if ($countTasks) {
1195 foreach ($this->lines as $task) {
1196 if ($task->hasChildren() <= 0) { // If there is no children (or error to detect them)
1197 $deleted = true;
1198 $ret = $task->delete($user);
1199 if ($ret <= 0) {
1200 $this->errors[] = $this->db->lasterror();
1201 return -1;
1202 }
1203 }
1204 }
1205 }
1206 $this->getLinesArray($user);
1207 if ($deleted && count($this->lines) < $countTasks) {
1208 if (count($this->lines)) {
1209 $this->deleteTasks($user);
1210 }
1211 }
1212
1213 return 1;
1214 }
1215
1223 public function setValid($user, $notrigger = 0)
1224 {
1225 global $langs;
1226
1227 $error = 0;
1228
1229 // Protection
1230 if ($this->status == self::STATUS_VALIDATED) {
1231 dol_syslog(get_class($this)."::validate action abandoned: already validated", LOG_WARNING);
1232 return 0;
1233 }
1234
1235 // Check parameters
1236 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ', '/').'/', $this->title)) {
1237 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1238 return -1;
1239 }
1240
1241 $this->db->begin();
1242
1243 $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1244 $sql .= " SET fk_statut = ".self::STATUS_VALIDATED;
1245 $sql .= " WHERE rowid = ".((int) $this->id);
1246 //$sql .= " AND entity = ".((int) $conf->entity); // Disabled, when we use the ID for the where, we must not add any other search condition
1247
1248 dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
1249 $resql = $this->db->query($sql);
1250 if ($resql) {
1251 // Call trigger
1252 if (empty($notrigger)) {
1253 $result = $this->call_trigger('PROJECT_VALIDATE', $user);
1254 if ($result < 0) {
1255 $error++;
1256 }
1257 // End call triggers
1258 }
1259
1260 if (!$error) {
1261 $this->statut = 1;
1262 $this->status = 1;
1263 $this->db->commit();
1264 return 1;
1265 } else {
1266 $this->db->rollback();
1267 $this->error = implode(',', $this->errors);
1268 dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
1269 return -1;
1270 }
1271 } else {
1272 $this->db->rollback();
1273 $this->error = $this->db->lasterror();
1274 return -1;
1275 }
1276 }
1277
1284 public function setClose($user)
1285 {
1286 $now = dol_now();
1287
1288 $error = 0;
1289
1290 if ($this->status != self::STATUS_CLOSED) {
1291 $this->db->begin();
1292
1293 $sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1294 $sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'";
1295 $sql .= " WHERE rowid = ".((int) $this->id);
1296 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1297
1298 if (getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
1299 // TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1300 }
1301
1302 dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
1303 $resql = $this->db->query($sql);
1304 if ($resql) {
1305 // Call trigger
1306 $result = $this->call_trigger('PROJECT_CLOSE', $user);
1307 if ($result < 0) {
1308 $error++;
1309 }
1310 // End call triggers
1311
1312 if (!$error) {
1313 $this->status = 2;
1314 $this->db->commit();
1315 return 1;
1316 } else {
1317 $this->db->rollback();
1318 $this->error = implode(',', $this->errors);
1319 dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
1320 return -1;
1321 }
1322 } else {
1323 $this->db->rollback();
1324 $this->error = $this->db->lasterror();
1325 return -1;
1326 }
1327 }
1328
1329 return 0;
1330 }
1331
1338 public function getLibStatut($mode = 0)
1339 {
1340 return $this->LibStatut(isset($this->status) ? $this->status : $this->statut, $mode);
1341 }
1342
1343 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1351 public function LibStatut($status, $mode = 0)
1352 {
1353 // phpcs:enable
1354 global $langs;
1355
1356 if (is_null($status)) {
1357 return '';
1358 }
1359
1360 $statustrans = array(
1361 0 => 'status0',
1362 1 => 'status4',
1363 2 => 'status6',
1364 );
1365
1366 $statusClass = 'status0';
1367 if (!empty($statustrans[$status])) {
1368 $statusClass = $statustrans[$status];
1369 }
1370
1371 return dolGetStatus($langs->transnoentitiesnoconv($this->labelStatus[$status]), $langs->transnoentitiesnoconv($this->labelStatusShort[$status]), '', $statusClass, $mode);
1372 }
1373
1380 public function getTooltipContentArray($params)
1381 {
1382 global $conf, $langs;
1383
1384 $langs->load('projects');
1385 $option = $params['option'] ?? '';
1386 $moreinpopup = $params['moreinpopup'] ?? '';
1387
1388 $datas = [];
1389 if ($option != 'nolink') {
1390 $datas['picto'] = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1391 }
1392 if (isset($this->status)) {
1393 $datas['picto'] .= ' '.$this->getLibStatut(5);
1394 }
1395 $datas['ref'] = (isset($datas['picto']) ? '<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
1396 $datas['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
1397 if (isset($this->public)) {
1398 $datas['visibility'] = '<br><b>'.$langs->trans("Visibility").":</b> ";
1399 $datas['visibility'] .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
1400 }
1401 if (!empty($this->thirdparty_name)) {
1402 $datas['thirdparty'] = '<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
1403 }
1404 if (!empty($this->date_start)) {
1405 $datas['datestart'] = '<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
1406 }
1407 if (!empty($this->date_end)) {
1408 $datas['dateend'] = '<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
1409 }
1410 if ($moreinpopup) {
1411 $datas['moreinpopup'] = '<br>'.$moreinpopup;
1412 }
1413
1414 return $datas;
1415 }
1416
1431 public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '', $save_pageforbacktolist = '')
1432 {
1433 global $conf, $langs, $user, $hookmanager;
1434
1435 if (!empty($conf->dol_no_mouse_hover)) {
1436 $notooltip = 1; // Force disable tooltips
1437 }
1438
1439 $result = '';
1440 if (getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB')) {
1441 $option = getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB');
1442 }
1443 $params = [
1444 'id' => $this->id,
1445 'objecttype' => $this->element,
1446 'moreinpopup' => $moreinpopup,
1447 'option' => $option,
1448 ];
1449 $classfortooltip = 'classfortooltip';
1450 $dataparams = '';
1451 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1452 $classfortooltip = 'classforajaxtooltip';
1453 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1454 $label = '';
1455 } else {
1456 $label = implode($this->getTooltipContentArray($params));
1457 }
1458
1459 $url = '';
1460 if ($option != 'nolink') {
1461 if (preg_match('/\.php$/', $option)) {
1462 $url = dol_buildpath($option, 1).'?id='.$this->id;
1463 } elseif ($option == 'task') {
1464 $url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1465 } elseif ($option == 'preview') {
1466 $url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1467 } elseif ($option == 'eventorganization') {
1468 $url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
1469 } elseif ($option == 'mailing') {
1470 $url = DOL_URL_ROOT.'/comm/mailing/list.php?projectid='.$this->id;
1471 } else {
1472 $url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1473 }
1474 // Add param to save lastsearch_values or not
1475 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1476 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1477 $add_save_lastsearch_values = 1;
1478 }
1479 if ($add_save_lastsearch_values) {
1480 $url .= '&save_lastsearch_values=1';
1481 }
1482 $add_save_backpagefor = ($save_pageforbacktolist ? 1 : 0);
1483 if ($add_save_backpagefor) {
1484 $url .= "&save_pageforbacktolist=".urlencode($save_pageforbacktolist);
1485 }
1486 }
1487
1488 $linkclose = '';
1489 if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1490 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1491 $label = $langs->trans("ShowProject");
1492 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1493 }
1494 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1495 $linkclose .= $dataparams.' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1496 } else {
1497 $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1498 }
1499
1500 $picto = 'projectpub';
1501 if (!$this->public) {
1502 $picto = 'project';
1503 }
1504
1505 $linkstart = '<a href="'.$url.'"';
1506 $linkstart .= $linkclose.'>';
1507 $linkend = '</a>';
1508
1509 $result .= $linkstart;
1510 if ($withpicto) {
1511 $result .= img_object(($notooltip ? '' : $label), $picto, 'class="pictofixedwidth em088"', 0, 0, $notooltip ? 0 : 1);
1512 }
1513 if ($withpicto != 2) {
1514 if ($addlabel >= 0) {
1515 $result .= $this->ref;
1516 } else {
1517 $result .= $this->title;
1518 }
1519 }
1520 $result .= $linkend;
1521 if ($withpicto != 2) {
1522 $result .= (($addlabel > 0 && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1523 }
1524
1525 global $action;
1526 $hookmanager->initHooks(array('projectdao'));
1527 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1528 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1529 if ($reshook > 0) {
1530 $result = $hookmanager->resPrint;
1531 } else {
1532 $result .= $hookmanager->resPrint;
1533 }
1534
1535 return $result;
1536 }
1537
1545 public function initAsSpecimen()
1546 {
1547 global $user, $langs, $conf;
1548
1549 $now = dol_now();
1550
1551 // Initialise parameters
1552 $this->id = 0;
1553 $this->ref = 'SPECIMEN';
1554 $this->entity = $conf->entity;
1555 $this->specimen = 1;
1556 $this->socid = 1;
1557 $this->date_c = $now;
1558 $this->date_m = $now;
1559 $this->date_start = $now;
1560 $this->date_end = $now + (3600 * 24 * 365);
1561 $this->note_public = 'SPECIMEN';
1562 $this->note_private = 'Private Note';
1563 $this->fk_project = 0;
1564 $this->opp_amount = 20000;
1565 $this->budget_amount = 10000;
1566
1567 $this->usage_opportunity = 1;
1568 $this->usage_task = 1;
1569 $this->usage_bill_time = 1;
1570 $this->usage_organize_event = 1;
1571
1572 /*
1573 $nbp = mt_rand(1, 9);
1574 $xnbp = 0;
1575 while ($xnbp < $nbp)
1576 {
1577 $line = new Task($this->db);
1578 $line->fk_project = 0;
1579 $line->label = $langs->trans("Label") . " " . $xnbp;
1580 $line->description = $langs->trans("Description") . " " . $xnbp;
1581
1582 $this->lines[]=$line;
1583 $xnbp++;
1584 }
1585 */
1586
1587 return 1;
1588 }
1589
1597 public function restrictedProjectArea(User $user, $mode = 'read')
1598 {
1599 // To verify role of users
1600 $userAccess = 0;
1601 if (($mode == 'read' && $user->hasRight('projet', 'all', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'all', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'all', 'supprimer'))) {
1602 $userAccess = 1;
1603 } elseif ($this->public && (($mode == 'read' && $user->hasRight('projet', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'supprimer')))) {
1604 $userAccess = 1;
1605 } else { // No access due to permission to read all projects, so we check if we are a contact of project
1606 foreach (array('internal', 'external') as $source) {
1607 $userRole = $this->liste_contact(4, $source);
1608 $num = count($userRole);
1609
1610 $nblinks = 0;
1611 while ($nblinks < $num) {
1612 if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) { // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1613 if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1614 $userAccess++;
1615 }
1616 if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1617 $userAccess++;
1618 }
1619 if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1620 $userAccess++;
1621 }
1622 }
1623 if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) { // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1624 if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1625 $userAccess++;
1626 }
1627 if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1628 $userAccess++;
1629 }
1630 if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1631 $userAccess++;
1632 }
1633 }
1634 $nblinks++;
1635 }
1636 }
1637 //if (empty($nblinks)) // If nobody has permission, we grant creator
1638 //{
1639 // if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1640 // {
1641 // $userAccess = 1;
1642 // }
1643 //}
1644 }
1645
1646 return ($userAccess ? $userAccess : -1);
1647 }
1648
1659 public function getProjectsAuthorizedForUser($fuser, $mode = 0, $list = 0, $socid = 0, $filter = '')
1660 {
1661 $projects = array();
1662 $temp = array();
1663
1664 // Get id of types of contacts for projects (This list never contains a lot of elements)
1665 $listofprojectcontacttype = array();
1666 $sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1667 $sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1668 $sql2 .= " AND ctc.source = 'internal'";
1669 $resql = $this->db->query($sql2);
1670 if ($resql) {
1671 while ($obj = $this->db->fetch_object($resql)) {
1672 $listofprojectcontacttype[$obj->rowid] = $obj->code;
1673 }
1674 } else {
1675 dol_print_error($this->db);
1676 }
1677 if (count($listofprojectcontacttype) == 0) {
1678 $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1679 }
1680
1681
1682 $sql = "SELECT p.rowid, p.ref";
1683 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1684 $sql .= " WHERE p.entity IN (".getEntity('project').")";
1685 // Internal users must see project he is contact to even if project is linked to a third party he can't see.
1686 if ($socid > 0) {
1687 $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1688 }
1689
1690 if ($mode == 0) {
1691 $sql .= " AND (p.public = 1";
1692 $sql .= " OR EXISTS (SELECT ec.rowid FROM ".MAIN_DB_PREFIX."element_contact as ec";
1693 $sql .= " WHERE ec.element_id = p.rowid AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))).")";
1694 $sql .= " AND ec.fk_socpeople = ".((int) $fuser->id).")";
1695 $sql .= ")";
1696 } elseif ($mode == 1) {
1697 $sql .= " AND EXISTS (SELECT ec.rowid FROM ".MAIN_DB_PREFIX."element_contact as ec";
1698 $sql .= " WHERE ec.element_id = p.rowid AND ec.fk_c_type_contact IN (".$this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))).")";
1699 $sql .= " AND ec.fk_socpeople = ".((int) $fuser->id).")";
1700 } // elseif ($mode == 2) {
1701 // No filter. Use this if fuser has permission to see all project
1702 //}
1703
1704 // Manage filter
1705 $errormessage = '';
1706 $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
1707 if ($errormessage) {
1708 $this->errors[] = $errormessage;
1709 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
1710 $sql .= $filter; // @phan-suppress-current-line SqlInjection
1711 }
1712
1713 $resql = $this->db->query($sql);
1714 if ($resql) {
1715 $num = $this->db->num_rows($resql);
1716 $i = 0;
1717 while ($i < $num) {
1718 $row = $this->db->fetch_row($resql);
1719 $projects[$row[0]] = $row[1];
1720 $temp[] = $row[0];
1721 $i++;
1722 }
1723
1724 $this->db->free($resql);
1725
1726 if ($list) {
1727 if (empty($temp)) {
1728 return '0';
1729 }
1730 $result = implode(',', $temp);
1731 return $result;
1732 }
1733 } else {
1734 dol_print_error($this->db);
1735 }
1736
1737 return $projects;
1738 }
1739
1755 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)
1756 {
1757 global $langs, $conf;
1758
1759 $error = 0;
1760 $clone_project_id = 0; // For static toolcheck
1761
1762 dol_syslog("createFromClone clone_contact=".json_encode($clone_contact)." clone_task=".json_encode($clone_task)." clone_project_file=".json_encode($clone_project_file)." clone_note=".json_encode($clone_note)." move_date=".json_encode($move_date), LOG_DEBUG);
1763
1764 $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1765
1766 $clone_project = new Project($this->db);
1767
1768 $clone_project->context['createfromclone'] = 'createfromclone';
1769
1770 $this->db->begin();
1771
1772 // Load source object
1773 $clone_project->fetch($fromid);
1774 $clone_project->fetch_optionals();
1775 $clone_project->socid = ($newthirdpartyid > 0 ? $newthirdpartyid : 0);
1776 $clone_project->fetch_thirdparty();
1777
1778 $orign_dt_start = $clone_project->date_start;
1779 $orign_project_ref = $clone_project->ref;
1780
1781 $clone_project->id = 0;
1782 if ($move_date) {
1783 $clone_project->date_start = $now;
1784 if (!empty($clone_project->date_end)) {
1785 if (!empty($orign_dt_start)) {
1786 $clone_project->date_end += ($now - (int) $orign_dt_start);
1787 } elseif (!empty($clone_project->date_c)) {
1788 $clone_project->date_end += ($now - (int) $clone_project->date_c);
1789 }
1790 }
1791 }
1792
1793 $clone_project->date_c = $now;
1794
1795 if (!$clone_note) {
1796 $clone_project->note_private = '';
1797 $clone_project->note_public = '';
1798 }
1799
1800 //Generate next ref
1801 $defaultref = '';
1802 $obj = getDolGlobalString('PROJECT_ADDON', 'mod_project_simple');
1803 // Search template files
1804 $file = '';
1805 $classname = '';
1806 $filefound = 0;
1807 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1808 foreach ($dirmodels as $reldir) {
1809 $file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1810 if (file_exists($file)) {
1811 $filefound = 1;
1812 dol_include_once($reldir."core/modules/project/".$obj.'.php');
1813 $modProject = new $obj();
1814 '@phan-var-force ModeleNumRefProjects $modProject';
1815 $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1816 break;
1817 }
1818 }
1819 if (is_numeric($defaultref) && $defaultref <= 0) {
1820 $defaultref = '';
1821 }
1822
1823 $clone_project->ref = $defaultref;
1824 $clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1825
1826 // Create clone
1827 $result = $clone_project->create($user, $notrigger);
1828
1829 // Other options
1830 if ($result < 0) {
1831 $this->error .= $clone_project->error;
1832 $error++;
1833 }
1834
1835 if (!$error) {
1836 //Get the new project id
1837 $clone_project_id = $clone_project->id;
1838
1839 //Note Update
1840 if (!$clone_note) {
1841 $clone_project->note_private = '';
1842 $clone_project->note_public = '';
1843 } else {
1844 $this->db->begin();
1845 $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1846 if ($res < 0) {
1847 $this->error .= $clone_project->error;
1848 $error++;
1849 $this->db->rollback();
1850 } else {
1851 $this->db->commit();
1852 }
1853
1854 $this->db->begin();
1855 $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1856 if ($res < 0) {
1857 $this->error .= $clone_project->error;
1858 $error++;
1859 $this->db->rollback();
1860 } else {
1861 $this->db->commit();
1862 }
1863 }
1864
1865 //Duplicate contact
1866 if ($clone_contact) {
1867 $origin_project = new Project($this->db);
1868 $origin_project->fetch($fromid);
1869
1870 foreach (array('internal', 'external') as $source) {
1871 $tab = $origin_project->liste_contact(-1, $source);
1872 if (is_array($tab) && count($tab) > 0) {
1873 foreach ($tab as $contacttoadd) {
1874 $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1875 if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1876 $langs->load("errors");
1877 $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1878 $error++;
1879 } else {
1880 if ($clone_project->error != '') {
1881 $this->error .= $clone_project->error;
1882 $error++;
1883 }
1884 }
1885 }
1886 } elseif ($tab < 0) {
1887 $this->error .= $origin_project->error;
1888 $error++;
1889 }
1890 }
1891 }
1892
1893 //Duplicate file
1894 if ($clone_project_file) {
1895 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1896
1897 $clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
1898 $ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1899
1900 if (dol_mkdir($clone_project_dir) >= 0) {
1901 $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1902 foreach ($filearray as $key => $file) {
1903 $rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], '0', 1);
1904 if (is_numeric($rescopy) && $rescopy < 0) {
1905 $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1906 $error++;
1907 }
1908 }
1909 } else {
1910 $this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1911 $error++;
1912 }
1913 }
1914
1915 //Duplicate task
1916 if ($clone_task) {
1917 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1918
1919 $taskstatic = new Task($this->db);
1920
1921 // Security check
1922 $socid = 0;
1923 if ($user->socid > 0) {
1924 $socid = $user->socid;
1925 }
1926
1927 $tasksarray = $taskstatic->getTasksArray(null, null, $fromid, $socid, 0);
1928
1929 $tab_conv_child_parent = array();
1930 $result_clone = 0;
1931
1932 // Loop on each task, to clone it
1933 foreach ($tasksarray as $tasktoclone) {
1934 $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_task_parent, $move_date, true, false, $clone_task_file, true, false);
1935 if ($result_clone <= 0) {
1936 $this->error .= $taskstatic->error;
1937 $error++;
1938 } else {
1939 $new_task_id = $result_clone;
1940 $taskstatic->fetch($tasktoclone->id);
1941
1942 //manage new parent clone task id
1943 // if the current task has child we store the original task id and the equivalent clone task id
1944 if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1945 $tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1946 }
1947 }
1948 }
1949
1950 //Parse all clone node to be sure to update new parent
1951 $tasksarray = $taskstatic->getTasksArray(null, null, $clone_project_id, $socid, 0);
1952 foreach ($tasksarray as $task_cloned) {
1953 $taskstatic->fetch($task_cloned->id);
1954 if ($taskstatic->fk_task_parent != 0) {
1955 $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1956 }
1957 $res = $taskstatic->update($user, $notrigger);
1958 if ($result_clone <= 0) {
1959 $this->error .= $taskstatic->error;
1960 $error++;
1961 }
1962 }
1963 }
1964 }
1965
1966 unset($clone_project->context['createfromclone']);
1967
1968 if (!$error && $clone_project_id != 0) {
1969 $this->db->commit();
1970 return $clone_project_id;
1971 } else {
1972 $this->db->rollback();
1973 dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1974 return -1;
1975 }
1976 }
1977
1978
1985 public function shiftTaskDate($old_project_dt_start)
1986 {
1987 global $user;
1988
1989 $error = 0;
1990 $result = 0;
1991
1992 $taskstatic = new Task($this->db);
1993
1994 // Security check
1995 $socid = 0;
1996 if ($user->socid > 0) {
1997 $socid = $user->socid;
1998 }
1999
2000 $tasksarray = $taskstatic->getTasksArray(null, null, $this->id, $socid, 0);
2001
2002 foreach ($tasksarray as $tasktoshiftdate) {
2003 $to_update = false;
2004 // Fetch only if update of date will be made
2005 if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
2006 //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
2007 $to_update = true;
2008 $task = new Task($this->db);
2009 $result = $task->fetch($tasktoshiftdate->id);
2010 if (!$result) {
2011 $error++;
2012 $this->error .= $task->error;
2013 }
2014 } else {
2015 continue;
2016 }
2017 //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
2018
2019 //Calculate new task start date with difference between old proj start date and origin task start date
2020 if (!empty($tasktoshiftdate->date_start)) {
2021 $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
2022 }
2023
2024 //Calculate new task end date with difference between origin proj end date and origin task end date
2025 if (!empty($tasktoshiftdate->date_end)) {
2026 $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
2027 }
2028
2029 if ($to_update) {
2030 $result = $task->update($user);
2031 if (!$result) {
2032 $error++;
2033 $this->error .= $task->error;
2034 }
2035 }
2036 }
2037 if ($error != 0) {
2038 return -1;
2039 }
2040 return $result;
2041 }
2042
2043
2044 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2052 public function update_element($tableName, $elementSelectId)
2053 {
2054 // phpcs:enable
2055 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName);
2056
2057 if ($tableName == "actioncomm") {
2058 $sql .= " SET fk_project = ".((int) $this->id);
2059 $sql .= " WHERE id = ".((int) $elementSelectId);
2060 } elseif (in_array($tableName, ["entrepot", "mrp_mo", "stocktransfer_stocktransfer"])) {
2061 $sql .= " SET fk_project = ".((int) $this->id);
2062 $sql .= " WHERE rowid = ".((int) $elementSelectId);
2063 } else {
2064 $sql .= " SET fk_projet = ".((int) $this->id);
2065 $sql .= " WHERE rowid = ".((int) $elementSelectId);
2066 }
2067
2068 dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
2069
2070 $resql = $this->db->query($sql);
2071 if (!$resql) {
2072 $this->error = $this->db->lasterror();
2073 return -1;
2074 } else {
2075 return 1;
2076 }
2077 }
2078
2079 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2088 public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
2089 {
2090 // phpcs:enable
2091 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->db->sanitize($tableName);
2092
2093 if ($tableName == "actioncomm") {
2094 $sql .= " SET fk_project = NULL";
2095 $sql .= " WHERE id = ".((int) $elementSelectId);
2096 $sql .= " AND fk_project = ".((int) $this->id);
2097 } else {
2098 $sql .= " SET ".$this->db->sanitize($projectfield)." = NULL";
2099 $sql .= " WHERE rowid = ".((int) $elementSelectId);
2100 $sql .= " AND ".$this->db->sanitize($projectfield)." = ".((int) $this->id);
2101 }
2102
2103 dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
2104
2105 $resql = $this->db->query($sql);
2106 if (!$resql) {
2107 $this->error = $this->db->lasterror();
2108 return -1;
2109 } else {
2110 return 1;
2111 }
2112 }
2113
2124 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2125 {
2126 global $conf, $langs;
2127
2128 $langs->load("projects");
2129
2130 if (!dol_strlen($modele)) {
2131 $modele = 'baleine';
2132
2133 if ($this->model_pdf) {
2134 $modele = $this->model_pdf;
2135 } elseif (getDolGlobalString('PROJECT_ADDON_PDF')) {
2136 $modele = getDolGlobalString('PROJECT_ADDON_PDF');
2137 }
2138 }
2139
2140 $modelpath = "core/modules/project/doc/";
2141
2142 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2143 }
2144
2145
2155 public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
2156 {
2157 $this->weekWorkLoad = array();
2158 $this->weekWorkLoadPerTask = array();
2159
2160 if (empty($datestart)) {
2161 dol_print_error(null, 'Error datestart parameter is empty');
2162 }
2163
2164 $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2165 $sql .= " FROM ".MAIN_DB_PREFIX."element_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2166 $sql .= " WHERE ptt.fk_element = pt.rowid";
2167 $sql .= " AND ptt.elementtype = 'task'";
2168 $sql .= " AND pt.fk_projet = ".((int) $this->id);
2169 $sql .= " AND (ptt.element_date >= '".$this->db->idate($datestart)."' ";
2170 $sql .= " AND ptt.element_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
2171 if ($taskid) {
2172 $sql .= " AND ptt.fk_element=".((int) $taskid);
2173 }
2174 if (is_numeric($userid)) {
2175 $sql .= " AND ptt.fk_user=".((int) $userid);
2176 }
2177
2178 //print $sql;
2179 $resql = $this->db->query($sql);
2180 if ($resql) {
2181 $dayallreadyfound = array();
2182
2183 $num = $this->db->num_rows($resql);
2184 $i = 0;
2185 // Loop on each record found, so each couple (project id, task id)
2186 while ($i < $num) {
2187 $obj = $this->db->fetch_object($resql);
2188 $day = $this->db->jdate($obj->element_date); // task_date is date without hours
2189
2190 if (empty($dayallreadyfound[$day])) {
2191 $this->weekWorkLoad[$day] = (int) $obj->element_duration; // Float in db used as int
2192 $this->weekWorkLoadPerTask[$day][$obj->fk_element] = (int) $obj->element_duration;
2193 } else {
2194 $this->weekWorkLoad[$day] += (int) $obj->element_duration; // Float in db used as int
2195 $this->weekWorkLoadPerTask[$day][$obj->fk_element] += (int) $obj->element_duration;
2196 }
2197 $dayallreadyfound[$day] = 1;
2198 $i++;
2199 }
2200 $this->db->free($resql);
2201 return $num;
2202 } else {
2203 $this->error = "Error ".$this->db->lasterror();
2204 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2205 return -1;
2206 }
2207 }
2208
2218 public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2219 {
2220 $this->monthWorkLoad = array();
2221 $this->monthWorkLoadPerTask = array();
2222
2223 if (empty($datestart)) {
2224 dol_print_error(null, 'Error datestart parameter is empty');
2225 }
2226
2227 $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2228 $sql .= " FROM ".MAIN_DB_PREFIX."element_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2229 $sql .= " WHERE ptt.fk_element = pt.rowid";
2230 $sql .= " AND ptt.elementtype = 'task'";
2231 $sql .= " AND pt.fk_projet = ".((int) $this->id);
2232 $sql .= " AND (ptt.element_date >= '".$this->db->idate($datestart)."' ";
2233 $sql .= " AND ptt.element_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
2234 if ($taskid) {
2235 $sql .= " AND ptt.fk_element=".((int) $taskid);
2236 }
2237 if (is_numeric($userid)) {
2238 $sql .= " AND ptt.fk_user=".((int) $userid);
2239 }
2240
2241 //print $sql;
2242 $resql = $this->db->query($sql);
2243 if ($resql) {
2244 $weekalreadyfound = array();
2245
2246 $num = $this->db->num_rows($resql);
2247 $i = 0;
2248 $week_number = ''; // Initialisation for static analysis
2249 // Loop on each record found, so each couple (project id, task id)
2250 while ($i < $num) {
2251 $obj = $this->db->fetch_object($resql);
2252 if (!empty($obj->element_date)) {
2253 $date = explode('-', $obj->element_date);
2254 $week_number = getWeekNumber((int) $date[2], (int) $date[1], (int) $date[0]);
2255 }
2256 if (empty($weekalreadyfound[$week_number])) {
2257 $this->monthWorkLoad[$week_number] = (int) $obj->element_duration;
2258 $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] = (int) $obj->element_duration;
2259 } else {
2260 $this->monthWorkLoad[$week_number] += (int) $obj->element_duration;
2261 if (!isset($this->monthWorkLoadPerTask[$week_number][$obj->fk_element])) {
2262 $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] = 0;
2263 }
2264 $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] += (int) $obj->element_duration;
2265 }
2266 $weekalreadyfound[$week_number] = 1;
2267 $i++;
2268 }
2269 $this->db->free($resql);
2270 return 1;
2271 } else {
2272 $this->error = "Error ".$this->db->lasterror();
2273 dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2274 return -1;
2275 }
2276 }
2277
2278 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2285 public function load_board($user)
2286 {
2287 // phpcs:enable
2288 global $conf, $langs;
2289
2290 // For external user, no check is done on company because readability is managed by public status of project and assignment.
2291 //$socid=$user->socid;
2292
2293 $response = new WorkboardResponse();
2294 $response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2295 $response->label = $langs->trans("OpenedProjects");
2296 $response->labelShort = $langs->trans("Opened");
2297 $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2298 $response->url_late = DOL_URL_ROOT.'/projet/list.php?search_option=late&mainmenu=project';
2299 $response->img = img_object('', "projectpub");
2300 $response->nbtodo = 0;
2301 $response->nbtodolate = 0;
2302
2303 $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2304 $sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
2305 $sql .= ")";
2306 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2307 // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2308 //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2309 $sql .= " WHERE p.fk_statut = 1";
2310 $sql .= " AND p.entity IN (".getEntity('project').')';
2311
2312
2313 $projectsListId = null;
2314 if (!$user->hasRight("projet", "all", "lire")) {
2315 $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
2316 $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2317 if (empty($projectsListId)) {
2318 return $response;
2319 }
2320
2321 $sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2322 }
2323
2324 // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2325 //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).")";
2326 // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2327 //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))";
2328
2329 //print $sql;
2330 $resql = $this->db->query($sql);
2331 if ($resql) {
2332 $project_static = new Project($this->db);
2333
2334
2335 // This assignment in condition is not a bug. It allows walking the results.
2336 while ($obj = $this->db->fetch_object($resql)) {
2337 $response->nbtodo++;
2338
2339 $project_static->statut = $obj->status;
2340 $project_static->status = $obj->status;
2341 $project_static->opp_status = $obj->fk_opp_status;
2342 $project_static->date_end = $this->db->jdate($obj->datee);
2343
2344 if ($project_static->hasDelay()) {
2345 $response->nbtodolate++;
2346 }
2347 }
2348
2349 return $response;
2350 }
2351
2352 $this->error = $this->db->error();
2353 return -1;
2354 }
2355
2364 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2365 {
2366 $tables = array(
2367 'projet'
2368 );
2369
2370 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2371 }
2372
2373
2379 public function loadStateBoard()
2380 {
2381 global $user;
2382
2383 $this->nb = array();
2384
2385 $sql = "SELECT count(p.rowid) as nb";
2386 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2387 $sql .= " WHERE";
2388 $sql .= " p.entity IN (".getEntity('project').")";
2389 if (!$user->hasRight('projet', 'all', 'lire')) {
2390 $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2391 $sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2392 }
2393
2394 $resql = $this->db->query($sql);
2395 if ($resql) {
2396 while ($obj = $this->db->fetch_object($resql)) {
2397 $this->nb["projects"] = $obj->nb;
2398 }
2399 $this->db->free($resql);
2400 return 1;
2401 } else {
2402 dol_print_error($this->db);
2403 $this->error = $this->db->error();
2404 return -1;
2405 }
2406 }
2407
2408
2414 public function hasDelay()
2415 {
2416 global $conf;
2417
2418 if (!($this->status == self::STATUS_VALIDATED)) {
2419 return false;
2420 }
2421 if (!$this->date_end) {
2422 return false;
2423 }
2424
2425 $now = dol_now();
2426
2427 return ($this->date_end) < ($now - $conf->project->warning_delay);
2428 }
2429
2430
2437 public function info($id)
2438 {
2439 $sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2440 $sql .= ' date_close as datecloture,';
2441 $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_user_cloture';
2442 $sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
2443 $sql .= ' WHERE c.rowid = '.((int) $id);
2444 $result = $this->db->query($sql);
2445 if ($result) {
2446 if ($this->db->num_rows($result)) {
2447 $obj = $this->db->fetch_object($result);
2448
2449 $this->id = $obj->rowid;
2450
2451 $this->user_creation_id = $obj->fk_user_author;
2452 $this->user_closing_id = $obj->fk_user_cloture;
2453
2454 $this->date_creation = $this->db->jdate($obj->datec);
2455 $this->date_modification = $this->db->jdate($obj->datem);
2456 $this->date_cloture = $this->db->jdate($obj->datecloture);
2457 }
2458
2459 $this->db->free($result);
2460 } else {
2461 dol_print_error($this->db);
2462 }
2463 }
2464
2475 public function setCategories($categories)
2476 {
2477 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2478 return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2479 }
2480
2481
2489 public function getLinesArray($user, $loadRoleMode = 1)
2490 {
2491 require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2492 $taskstatic = new Task($this->db);
2493
2494 $this->lines = $taskstatic->getTasksArray(null, $user, $this->id, 0, 0, '', '-1', '', 0, 0, null, 0, array(), 0, $loadRoleMode);
2495 return 1;
2496 }
2497
2516 public function sendEmail($text, $subject, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = -1, $errors_to = '', $moreinheader = '')
2517 {
2518 // TODO EMAIL
2519
2520 return 1;
2521 }
2522
2531 public function getKanbanView($option = '', $arraydata = null, $size = '')
2532 {
2533 global $conf, $langs;
2534
2535 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2536
2537 if (empty($size)) {
2538 if (empty($conf->dol_optimize_smallscreen)) {
2539 $size = 'large';
2540 } else {
2541 $size = 'small';
2542 }
2543 }
2544
2545 $return = '<div class="box-flex-item '.($size == 'small' ? 'box-flex-item-small' : '').' box-flex-grow-zero '.($arraydata['mode'] == 'kanbangroupby' ? 'kanban-draggable" data-itemid="'.$this->id.'" data-element="'.$this->element.'" data-tableelement="'.$this->table_element.'"' : '"').'>';
2546 $return .= '<div class="info-box info-box-sm">';
2547 $return .= '<span class="info-box-icon bg-infobox-action">';
2548 $return .= img_picto('', $this->public ? 'projectpub' : $this->picto);
2549 //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2550 $return .= '</span>';
2551 $return .= '<div class="info-box-content">';
2552 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref);
2553 if ($this->hasDelay()) {
2554 $return .= img_warning($langs->trans('Late'));
2555 }
2556 $return .= '</span>';
2557 if ($selected >= 0) {
2558 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2559 }
2560 // Date
2561 /*
2562 if (property_exists($this, 'date_start') && $this->date_start) {
2563 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_start, 'day').'</>';
2564 }
2565 if (property_exists($this, 'date_end') && $this->date_end) {
2566 if ($this->date_start) {
2567 $return .= ' - ';
2568 } else {
2569 $return .= '<br>';
2570 }
2571 $return .= '<span class="info-box-label">'.dol_print_date($this->date_end, 'day').'</span>';
2572 }*/
2573 if (property_exists($this, 'thirdparty') && !is_null($this->thirdparty) && is_object($this->thirdparty) && $this->thirdparty instanceof Societe && $this->thirdparty->id > 0) {
2574 $return .= '<br><div class="info-box-ref tdoverflowmax125 inline-block valignmiddle">'.$this->thirdparty->getNomUrl(1);
2575 $return .= '</div>';
2576 if (!empty($this->thirdparty->phone)) {
2577 $return .= '<div class="inline-block valignmiddle">';
2578 $return .= dol_print_phone($this->thirdparty->phone, $this->thirdparty->country_code, 0, $this->thirdparty->id, 'tel', 'hidenum', 'phone', $this->thirdparty->phone, 0, 'paddingleft paddingright'); // @phan-suppress-current-line PhanPluginSuspiciousParamPosition
2579 $return .= '</div>';
2580 }
2581 if (!empty($this->thirdparty->email)) {
2582 $return .= '<div class="inline-block valignmiddle">';
2583 $return .= dol_print_email($this->thirdparty->email, 0, $this->thirdparty->id, 'thirdparty', -1, 1, 2, 'paddingleft paddingright');
2584 $return .= '</div>';
2585 }
2586 }
2587 if (!empty($arraydata['assignedusers'])) {
2588 $return .= '<br>';
2589 if ($this->public) {
2590 $return .= img_picto($langs->trans('Visibility').': '.$langs->trans('SharedProject'), 'world', 'class="paddingrightonly valignmiddle"');
2591 //print $langs->trans('SharedProject');
2592 } else {
2593 $return .= img_picto($langs->trans('Visibility').': '.$langs->trans('PrivateProject'), 'private', 'class="paddingrightonly valignmiddle"');
2594 //print $langs->trans('PrivateProject');
2595 }
2596
2597 $return .= ' <span class="small valignmiddle">'.$arraydata['assignedusers'].'</span>';
2598 }
2599 /*if (property_exists($this, 'user_author_id')) {
2600 $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Author").'</span>';
2601 $return .= '<span> : '.$user->getNomUrl(1).'</span>';
2602 }*/
2603 $return .= '<br><div>'; // start div line status
2604 if ($this->usage_opportunity && $this->opp_status_code) {
2605 //$return .= '<br><span class="info-bo-label opacitymedium">'.$langs->trans("OpportunityStatusShort").'</span>';
2606 //$return .= '<div class="small inline-block">'.dol_trunc($langs->trans("OppStatus".$this->opp_status_code), 5).'</div>';
2607 $return .= '<div class="opacitymedium small marginrightonly inline-block" title="'.dol_escape_htmltag($langs->trans("OppStatus".$this->opp_status_code)).'">'.round($this->opp_percent).'%</div>';
2608 $return .= ' <div class="amount small marginrightonly inline-block">'.price($this->opp_amount).'</div>';
2609 }
2610 if (method_exists($this, 'getLibStatut')) {
2611 $return .= '<div class="info-box-status small inline-block valignmiddle">'.$this->getLibStatut(3).'</div>';
2612 }
2613 $return .= '</div>'; // end div line status
2614
2615 $return .= '</div>';
2616 $return .= '</div>';
2617 $return .= '</div>';
2618
2619 return $return;
2620 }
2621
2627 public function getChildren()
2628 {
2629 $children = [];
2630 $sql = 'SELECT rowid,title';
2631 $sql .= ' FROM '.MAIN_DB_PREFIX.'projet';
2632 $sql .= ' WHERE fk_project = '.((int) $this->id);
2633 $sql .= ' ORDER BY title';
2634 $result = $this->db->query($sql);
2635 if ($result) {
2636 $n = $this->db->num_rows($result);
2637 while ($n) {
2638 $children[] = $this->db->fetch_object($result);
2639 $n--;
2640 }
2641 $this->db->free($result);
2642 } else {
2643 dol_print_error($this->db);
2644 }
2645
2646 return $children;
2647 }
2648
2654 public function createWeeklyReport()
2655 {
2656 global $mysoc, $user, $langs;
2657
2658 $now = dol_now();
2659 $nowDate = dol_getdate($now, true);
2660
2661 $errormesg = '';
2662 $errorsMsg = array();
2663
2664 $firstDayOfWeekTS = dol_get_first_day_week($nowDate['mday'], $nowDate['mon'], $nowDate['year']);
2665
2666 $firstDayOfWeekDate = dol_mktime(0, 0, 0, $nowDate['mon'], $firstDayOfWeekTS['first_day'], $nowDate['year']);
2667
2668 $lastWeekStartTS = dol_time_plus_duree($firstDayOfWeekDate, -7, 'd');
2669
2670 $lastWeekEndTS = dol_time_plus_duree($lastWeekStartTS, 6, 'd');
2671
2672 $startDate = dol_print_date($lastWeekStartTS, '%Y-%m-%d 00:00:00');
2673 $endDate = dol_print_date($lastWeekEndTS, '%Y-%m-%d 23:59:59');
2674
2675 $sql = "SELECT
2676 u.rowid AS user_id,
2677 CONCAT(u.firstname, ' ', u.lastname) AS name,
2678 u.email,u.weeklyhours,
2679 SUM(et.element_duration) AS total_seconds
2680 FROM
2681 ".MAIN_DB_PREFIX."element_time AS et
2682 JOIN
2683 ".MAIN_DB_PREFIX."user AS u ON et.fk_user = u.rowid
2684 WHERE
2685 et.element_date BETWEEN '".$this->db->escape($startDate)."' AND '".$this->db->escape($endDate)."'
2686 AND et.elementtype = 'task'
2687 GROUP BY
2688 et.fk_user";
2689
2690 $resql = $this->db->query($sql);
2691 if (!$resql) {
2692 dol_print_error($this->db);
2693 return -1;
2694 } else {
2695 $reportContent = "<span>Weekly time report from $startDate to $endDate </span><br><br>";
2696 $reportContent .= '<table border="1" style="border-collapse: collapse;">';
2697 $reportContent .= '<tr><th>'.$langs->trans("User").'</th><th>Temps saisi (heures)</th><th>Temps travaillé par semaine (heures)</th></tr>';
2698
2699 $to = '';
2700 $nbMailSend = 0;
2701 $error = 0;
2702 $errors_to = '';
2703 while ($obj = $this->db->fetch_object($resql)) {
2704 $to = $obj->email;
2705
2706 $weekendEnabled = 0;
2707 $numHolidays = num_public_holiday($lastWeekStartTS, $lastWeekEndTS, $mysoc->country_code, 1);
2708 if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_MONDAY')) {
2709 $numHolidays -= 1;
2710 $weekendEnabled += 1;
2711 }
2712 if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_TUESDAY')) {
2713 $numHolidays -= 1;
2714 $weekendEnabled += 1;
2715 }
2716 if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY')) {
2717 $numHolidays -= 1;
2718 $weekendEnabled += 1;
2719 }
2720 if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY')) {
2721 $numHolidays -= 1;
2722 $weekendEnabled += 1;
2723 }
2724
2725 $dailyHours = $obj->weeklyhours / (7 - $weekendEnabled);
2726
2727 // Adjust total on seconde
2728 $adjustedSeconds = $obj->total_seconds + ($numHolidays * $dailyHours * 3600);
2729
2730 $totalHours = round($adjustedSeconds / 3600, 2);
2731
2732 $reportContent .= "<tr><td>{$obj->name}</td><td>{$totalHours}</td><td>".round($obj->weeklyhours, 2)."</td></tr>";
2733
2734 $reportContent .= '</table>';
2735
2736 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
2737
2738 // PREPARE EMAIL
2739 $errormesg = '';
2740
2741 $subject = 'Rapport hebdomadaire des temps travaillés';
2742
2743 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
2744 if (empty($from)) {
2745 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
2746 $error++;
2747 }
2748
2749 $mail = new CMailFile($subject, $to, $from, $reportContent, array(), array(), array(), '', '', 0, -1, '', '', '', 'text/html');
2750
2751 if ($mail->sendfile()) {
2752 $nbMailSend++;
2753
2754 // Add a line into event table
2755 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2756
2757 // Insert record of emails sent
2758 $actioncomm = new ActionComm($this->db);
2759
2760 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2761 $actioncomm->socid = $this->thirdparty->id; // To link to a company
2762 $actioncomm->contact_id = 0;
2763
2764 $actioncomm->code = 'AC_EMAIL';
2765 $actioncomm->label = 'createWeeklyReportOK()';
2766 $actioncomm->fk_project = $this->id;
2767 $actioncomm->datep = dol_now();
2768 $actioncomm->datef = $actioncomm->datep;
2769 $actioncomm->percentage = -1; // Not applicable
2770 $actioncomm->authorid = $user->id; // User saving action
2771 $actioncomm->userownerid = $user->id; // Owner of action
2772 // Fields when action is an email (content should be added into note)
2773 $actioncomm->email_msgid = $mail->msgid;
2774 $actioncomm->email_subject = $subject;
2775 $actioncomm->email_from = $from;
2776 $actioncomm->email_sender = '';
2777 $actioncomm->email_to = $to;
2778
2779 $actioncomm->errors_to = $errors_to;
2780
2781 $actioncomm->elementtype = 'project_task';
2782 $actioncomm->fk_element = (int) $this->element;
2783
2784 $actioncomm->create($user);
2785 } else {
2786 $errormesg = $mail->error.' : '.$to;
2787 $error++;
2788
2789 // Add a line into event table
2790 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2791
2792 // Insert record of emails sent
2793 $actioncomm = new ActionComm($this->db);
2794
2795 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2796 $actioncomm->socid = $this->thirdparty->id; // To link to a company
2797 $actioncomm->contact_id = 0;
2798
2799 $actioncomm->code = 'AC_EMAIL';
2800 $actioncomm->label = 'createWeeklyReportKO()';
2801 $actioncomm->note_private = $errormesg;
2802 $actioncomm->fk_project = $this->id;
2803 $actioncomm->datep = dol_now();
2804 $actioncomm->datef = $actioncomm->datep;
2805 $actioncomm->authorid = $user->id; // User saving action
2806 $actioncomm->userownerid = $user->id; // Owner of action
2807 // Fields when action is an email (content should be added into note)
2808 $actioncomm->email_msgid = $mail->msgid;
2809 $actioncomm->email_from = $from;
2810 $actioncomm->email_sender = '';
2811 $actioncomm->email_to = $to;
2812
2813 $actioncomm->errors_to = $errors_to;
2814
2815 $actioncomm->elementtype = 'project_task';
2816 $actioncomm->fk_element = (int) $this->element;
2817
2818 $actioncomm->create($user);
2819 }
2820 $this->db->commit();
2821 }
2822 }
2823 if (!empty($errormesg)) {
2824 $errorsMsg[] = $errormesg;
2825 }
2826
2827 if (!$error) {
2828 $this->output .= 'Nb of emails sent : '.$nbMailSend;
2829 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
2830 return 0;
2831 } else {
2832 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
2833 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
2834 return $error;
2835 }
2836 }
2837}
$object ref
Definition info.php:90
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
liste_contact($statusoflink=-1, $source='external', $list=0, $code='', $status=-1, $arrayoftcids=array())
Get array of all contacts for an object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
Class to manage Dolibarr database access.
Class to manage projects.
LibStatut($status, $mode=0)
Renvoi status label for a status.
getLibStatut($mode=0)
Return status label of object.
getElementCount($type, $tablename, $projectkey='fk_projet')
Return the count of a type of linked elements of this project.
getNomUrl($withpicto=0, $option='', $addlabel=0, $moreinpopup='', $sep=' - ', $notooltip=0, $save_lastsearch_value=-1, $morecss='', $save_pageforbacktolist='')
Return clickable name (with optional picto)
fetchAndSetSubstitution($id, $key, $fetched=false)
Fetch object and substitute key.
getChildren()
Return array of sub-projects of the current project.
setValid($user, $notrigger=0)
Validate a project.
createWeeklyReport()
Method for calculating weekly hours worked and generating a report.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
const STATUS_VALIDATED
Open/Validated status.
create($user, $notrigger=0)
Create a project into database.
const STATUS_CLOSED
Closed status.
const STATUS_DRAFT
Draft status.
loadTimeSpentMonth($datestart, $taskid=0, $userid=0)
Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of projec...
__construct($db)
Constructor.
fetch($id, $ref='', $ref_ext='', $email_msgid='')
Get object from database.
shiftTaskDate($old_project_dt_start)
Shift project task date from current date to delta.
hasDelay()
Is the project delayed?
remove_element($tableName, $elementSelectId, $projectfield='fk_projet')
Associate element to a project.
setCategories($categories)
Sets object to supplied categories.
update_element($tableName, $elementSelectId)
Associate element to a project.
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)
Load an object from its id and create a new one in database.
loadStateBoard()
Load indicators this->nb for the state board.
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
getLinesArray($user, $loadRoleMode=1)
Create an array of tasks of current project.
get_element_list($type, $tablename, $datefieldname='', $date_start=null, $date_end=null, $projectkey='fk_projet')
Return list of elements for type, linked to a project.
getKanbanView($option='', $arraydata=null, $size='')
Return clickable link of object (with eventually picto)
info($id)
Charge les information d'ordre info dans l'objet commande.
getProjectsAuthorizedForUser($fuser, $mode=0, $list=0, $socid=0, $filter='')
Return array of projects a user has permission on, is affected to, or all projects.
getTooltipContentArray($params)
getTooltipContentArray
sendEmail($text, $subject, $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array(), $addr_cc="", $addr_bcc="", $deliveryreceipt=0, $msgishtml=-1, $errors_to='', $moreinheader='')
Function sending an email to the current project with the text supplied in parameter.
initAsSpecimen()
Initialise an instance with random values.
setClose($user)
Close a project.
loadTimeSpent($datestart, $taskid=0, $userid=0)
Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of projec...
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create an intervention document on disk using template defined into PROJECT_ADDON_PDF.
restrictedProjectArea(User $user, $mode='read')
Check if user has permission on current project.
deleteTasks($user)
Delete tasks with no children first, then task with children recursively.
update($user, $notrigger=0)
Update a project.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage tasks.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
global $mysoc
dol_get_first_day_week($day, $month, $year, $gm=false)
Return first day of week for a date.
Definition date.lib.php:695
getWeekNumber($day, $month, $year)
Return week number.
num_public_holiday($timestampStart, $timestampEnd, $countryCodeOrId='', $lastday=0, $includesaturday=-1, $includesunday=-1, $includefriday=-1, $includemonday=-1)
Return the number of non working days including Friday, Saturday and Sunday (or not) between 2 dates ...
Definition date.lib.php:787
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0, $tz='')
Add a delay to a date.
Definition date.lib.php:129
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_copy($srcfile, $destfile, $newmask='0', $overwriteifexists=1, $testvirus=0, $indexdatabase=0)
Copy a file to another file.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_warning($titlealt='default', $moreatt='', $morecss='pictowarning')
Show warning logo.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)