dolibarr 25.0.0-alpha
contrat.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2012 Destailleur Laurent <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
7 * Copyright (C) 2010-2016 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2013 Christophe Battarel <christophe.battarel@altairis.fr>
9 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
10 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
12 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
13 * Copyright (C) 2015-2018 Ferran Marcet <fmarcet@2byte.es>
14 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
15 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
16 * Copyright (C) 2026 Alexandre Spangaro <alexandre@inovea-conseil.com
17 * Copyright (C) 2026 Lionel Vessiller <lvessiller@open-dsi.fr>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
40require_once DOL_DOCUMENT_ROOT.'/contrat/class/contratligne.class.php';
41require_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
42require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
43require_once DOL_DOCUMENT_ROOT.'/core/class/commonsignedobject.class.php';
44
51class Contrat extends CommonObject
52{
53 use CommonSignedObject;
54
58 public $element = 'contrat';
59
63 public $table_element = 'contrat';
64
68 public $table_element_line = 'contratdet';
69
73 public $fk_element = 'fk_contrat';
74
78 public $picto = 'contract';
79
84 public $restrictiononfksoc = 1;
85
89 protected $table_ref_field = 'ref';
90
95 public $ref_customer;
96
100 public $from;
101
106 public $ref_supplier;
107
112 public $fk_contract_type = 0;
113
118 public $socid;
119
125 public $fk_soc;
126
130 public $societe;
131
137 public $statut = 0;
138
143 public $status = 0;
144
148 public $product;
149
153 public $fk_user_author;
154
160 public $user_author_id;
161
162
167 public $user_creation;
168
172 public $user_cloture;
173
177 public $date_contrat;
178
182 public $commercial_signature_id;
183
188 public $fk_commercial_signature;
189
193 public $commercial_suivi_id;
194
200 public $fk_commercial_suivi;
201
205 public $extraparams = array();
206
210 public $lines = array();
211
215 public $nbofservices;
219 public $nbofserviceswait;
223 public $nbofservicesopened;
227 public $nbofservicesexpired;
231 public $nbofservicesclosed;
232 //public $lower_planned_end_date;
233 //public $higher_planner_end_date;
234
239 protected $lines_id_index_mapper = array();
240
241
266 // BEGIN MODULEBUILDER PROPERTIES
270 public $fields = array(
271 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
272 'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'showoncombobox' => 1, 'position' => 15, 'searchall' => 1),
273 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 20),
274 'ref_customer' => array('type' => 'varchar(50)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 25, 'searchall' => 1),
275 'ref_supplier' => array('type' => 'varchar(50)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 26, 'searchall' => 1),
276 'fk_contract_type' => array('type' => 'smallint(6)', 'label' => 'ContractType', 'enabled' => 1, 'visible' => -1, 'position' => 27, 'default' => '0', 'arrayofkeyval' => array(0 => 'CustomerContract', 1 => 'SupplierContract')),
277 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 30, 'index' => 1),
278 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35),
279 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 40),
280 'date_contrat' => array('type' => 'datetime', 'label' => 'Date contrat', 'enabled' => 1, 'visible' => -1, 'position' => 45),
281 'signed_status' => array('type' => 'smallint(6)', 'label' => 'SignedStatus', 'enabled' => 1, 'visible' => -1, 'position' => 50, 'arrayofkeyval' => array(0 => 'NoSignature', 1 => 'SignedSender', 2 => 'SignedReceiver', 3 => 'SignedReceiverOnline', 9 => 'SignedAll')),
282 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 70),
283 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 75),
284 'fk_commercial_signature' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'SaleRepresentative Signature', 'enabled' => 1, 'visible' => -1, 'position' => 80),
285 'fk_commercial_suivi' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'SaleRepresentative follower', 'enabled' => 1, 'visible' => -1, 'position' => 85),
286 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 90),
287 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 105, 'searchall' => 1),
288 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 110, 'searchall' => 1),
289 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 115),
290 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 120),
291 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 125),
292 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 135),
293 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 140),
294 'statut' => array('type' => 'smallint(6)', 'label' => 'Statut', 'enabled' => 1, 'visible' => -1, 'position' => 500, 'notnull' => 1, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Closed'))
295 );
296 // END MODULEBUILDER PROPERTIES
297
298 const STATUS_DRAFT = 0;
299 const STATUS_VALIDATED = 1;
300 const STATUS_CLOSED = 2;
301
307 public function __construct($db)
308 {
309 $this->db = $db;
310
311 $this->ismultientitymanaged = 1;
312 $this->isextrafieldmanaged = 1;
313 }
314
321 public function getNextNumRef($soc)
322 {
323 global $db, $langs, $conf;
324 $langs->load("contracts");
325
326 if (getDolGlobalString('CONTRACT_ADDON')) {
327 $mybool = false;
328
329 $file = getDolGlobalString('CONTRACT_ADDON') . ".php";
330 $classname = getDolGlobalString('CONTRACT_ADDON');
331
332 // Include file with class
333 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
334
335 foreach ($dirmodels as $reldir) {
336 $dir = dol_buildpath($reldir."core/modules/contract/");
337
338 // Load file with numbering class (if found)
339 $mybool = ((bool) @include_once $dir.$file) || $mybool;
340 }
341
342 if (!$mybool) {
343 dol_print_error(null, "Failed to include file ".$file);
344 return '';
345 }
346
347 $obj = new $classname();
348 '@phan-var-force ModelNumRefContracts $obj';
350 $numref = $obj->getNextValue($soc, $this);
351
352 if ($numref != "") {
353 return $numref;
354 } else {
355 $this->error = $obj->error;
356 dol_print_error($db, get_class($this)."::getNextValue ".$obj->error);
357 return "";
358 }
359 } else {
360 $langs->load("errors");
361 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Contract"));
362 return "";
363 }
364 }
365
366 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
377 public function active_line($user, $line_id, $date_start, $date_end = '', $comment = '')
378 {
379 // phpcs:enable
380 $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->active_line($user, $date_start, $date_end, $comment);
381 if ($result < 0) {
382 $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error;
383 $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors;
384 }
385 return $result;
386 }
387
388
389 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
399 public function close_line($user, $line_id, $date_end, $comment = '')
400 {
401 // phpcs:enable
402 $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->close_line($user, $date_end, $comment);
403 if ($result < 0) {
404 $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error;
405 $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors;
406 }
407 return $result;
408 }
409
410
422 public function activateAll($user, $date_start = '', $notrigger = 0, $comment = '', $date_end = '')
423 {
424 if (empty($date_start)) {
426 }
427
428 $this->db->begin();
429
430 $error = 0;
431
432 // Load lines
433 $this->fetch_lines();
434
435 foreach ($this->lines as $contratline) {
436 // Open lines not already open
437 if ($contratline->status != ContratLigne::STATUS_OPEN) {
438 $contratline->context = $this->context;
439
440 $result = $contratline->active_line($user, $date_start, !empty($date_end) ? $date_end : -1, $comment); // This call trigger LINECONTRACT_ACTIVATE
441 if ($result < 0) {
442 $error++;
443 $this->setErrorsFromObject($contratline);
444 break;
445 }
446 }
447 }
448
449 if (!$error && $this->status == 0) {
450 $result = $this->validate($user, '', $notrigger);
451 if ($result < 0) {
452 $error++;
453 }
454 }
455
456 if (!$error) {
457 $this->db->commit();
458 return 1;
459 } else {
460 $this->db->rollback();
461 return -1;
462 }
463 }
464
475 public function closeAll(User $user, $notrigger = 0, $comment = '', $nofetchlines = 0)
476 {
477 dol_syslog("closeAll begin", LOG_DEBUG, 1);
478
479 $this->db->begin();
480
481 // Load lines
482 if (empty($nofetchlines)) {
483 $this->fetch_lines();
484 }
485
486 $now = dol_now();
487
488 $error = 0;
489
490 foreach ($this->lines as $contratline) {
491 // Close lines not already closed
492 if ($contratline->status != ContratLigne::STATUS_CLOSED) {
493 $contratline->date_end_real = $now;
494 $contratline->date_cloture = $now; // For backward compatibility
495 $contratline->user_closing_id = $user->id;
496 $contratline->status = ContratLigne::STATUS_CLOSED;
497
498 $result = $contratline->close_line($user, $now, $comment, $notrigger);
499
500 if ($result < 0) {
501 $error++;
502 $this->error = $contratline->error.($contratline->error ? ' ('.$this->ref.')' : '');
503 $this->errors = $contratline->errors;
504 break;
505 }
506 }
507 }
508
509 if (!$error && $this->status == 0) {
510 $result = $this->validate($user, '', $notrigger);
511 if ($result < 0) {
512 $error++;
513 }
514 }
515
516 if (!$error) {
517 $this->db->commit();
518
519 dol_syslog("closeAll end", LOG_DEBUG, -1);
520
521 return 1;
522 } else {
523 $this->db->rollback();
524
525 dol_syslog("closeAll end", LOG_DEBUG, -1);
526
527 return -1;
528 }
529 }
530
539 public function validate(User $user, $force_number = '', $notrigger = 0)
540 {
541 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
542 global $conf;
543
544 $now = dol_now();
545
546 $error = 0;
547 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number);
548
549
550 $this->db->begin();
551
552 $this->fetch_thirdparty();
553
554 // A contract is validated so we can move thirdparty to status customer
555 if (!getDolGlobalString('CONTRACT_DISABLE_AUTOSET_AS_CLIENT_ON_CONTRACT_VALIDATION') && $this->thirdparty->fournisseur == 0) {
556 $result = $this->thirdparty->setAsCustomer();
557 }
558
559 // Define new ref
560 if ($force_number) {
561 $num = $force_number;
562 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
563 $num = $this->getNextNumRef($this->thirdparty);
564 } else {
565 $num = (string) $this->ref;
566 }
567 $this->newref = dol_sanitizeFileName($num);
568
569 if ($num) {
570 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET ref = '".$this->db->escape($num)."', statut = 1";
571 $sql .= " WHERE rowid = ".((int) $this->id)." AND statut = 0";
572
573 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
574 $resql = $this->db->query($sql);
575 if (!$resql) {
576 dol_print_error($this->db);
577 $error++;
578 $this->error = $this->db->lasterror();
579 }
580
581 // Trigger calls
582 if (!$error && !$notrigger) {
583 // Call trigger
584 $result = $this->call_trigger('CONTRACT_VALIDATE', $user);
585 if ($result < 0) {
586 $error++;
587 }
588 // End call triggers
589 }
590
591 if (!$error) {
592 $this->oldref = $this->ref;
593
594 // Rename directory if dir was a temporary ref
595 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
596 // Now we rename also files into index
597 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files SET filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'contract/".$this->db->escape($this->newref)."'";
598 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'contract/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
599 $resql = $this->db->query($sql);
600 if (!$resql) {
601 $error++;
602 $this->error = $this->db->lasterror();
603 }
604 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files SET filepath = 'contract/".$this->db->escape($this->newref)."'";
605 $sql .= " WHERE filepath = 'contract/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
606 $resql = $this->db->query($sql);
607 if (!$resql) {
608 $error++;
609 $this->error = $this->db->lasterror();
610 }
611
612 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
613 $oldref = dol_sanitizeFileName($this->ref);
614 $newref = dol_sanitizeFileName($num);
615 $dirsource = $conf->contract->dir_output.'/'.$oldref;
616 $dirdest = $conf->contract->dir_output.'/'.$newref;
617 if (!$error && file_exists($dirsource)) {
618 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
619
620 if (@rename($dirsource, $dirdest)) {
621 dol_syslog("Rename ok");
622 // Rename docs starting with $oldref with $newref
623 $listoffiles = dol_dir_list($conf->contract->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
624 foreach ($listoffiles as $fileentry) {
625 $dirsource = $fileentry['name'];
626 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
627 $dirsource = $fileentry['path'].'/'.$dirsource;
628 $dirdest = $fileentry['path'].'/'.$dirdest;
629 @rename($dirsource, $dirdest);
630 }
631 }
632 }
633 }
634 }
635
636 // Set new ref and define current status
637 if (!$error) {
638 $this->ref = $num;
639 $this->status = self::STATUS_VALIDATED;
640 $this->statut = self::STATUS_VALIDATED; // deprecated
641 $this->date_validation = $now;
642 }
643 } else {
644 $error++;
645 }
646
647 if (!$error) {
648 $this->db->commit();
649 return 1;
650 } else {
651 $this->db->rollback();
652 return -1;
653 }
654 }
655
663 public function reopen($user, $notrigger = 0)
664 {
665 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
666
667 $now = dol_now();
668
669 $error = 0;
670 dol_syslog(get_class($this).'::reopen user='.$user->id);
671
672 $this->db->begin();
673
674 $this->fetch_thirdparty();
675
676 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET statut = 0";
677 //$sql.= ", fk_user_valid = null, date_valid = null";
678 $sql .= " WHERE rowid = ".((int) $this->id)." AND statut = 1";
679
680 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
681 $resql = $this->db->query($sql);
682 if (!$resql) {
683 dol_print_error($this->db);
684 $error++;
685 $this->error = $this->db->lasterror();
686 }
687
688 // Trigger calls
689 if (!$error && !$notrigger) {
690 // Call trigger
691 $result = $this->call_trigger('CONTRACT_REOPEN', $user);
692 if ($result < 0) {
693 $error++;
694 }
695 // End call triggers
696 }
697
698 // Set new ref and define current status
699 if (!$error) {
700 $this->statut = self::STATUS_DRAFT;
701 $this->status = self::STATUS_DRAFT;
702 $this->date_validation = $now;
703 }
704
705 if (!$error) {
706 $this->db->commit();
707 return 1;
708 } else {
709 $this->db->rollback();
710 return -1;
711 }
712 }
713
725 public function fetch($id, $ref = '', $ref_customer = '', $ref_supplier = '', $noextrafields = 0, $nolines = 0)
726 {
727 $result = -10;
728
729 $sql = "SELECT rowid, statut as status, ref, fk_soc as thirdpartyid,";
730 $sql .= " ref_supplier, ref_customer,";
731 $sql .= " ref_ext,";
732 $sql .= " entity,";
733 $sql .= " signed_status,";
734 $sql .= " date_contrat as datecontrat,";
735 $sql .= " fk_user_author,";
736 $sql .= " fk_projet as fk_project,";
737 $sql .= " fk_commercial_signature, fk_commercial_suivi,";
738 $sql .= " note_private, note_public, model_pdf, last_main_doc, extraparams, fk_contract_type";
739 $sql .= " FROM ".MAIN_DB_PREFIX."contrat";
740 if (!$id) {
741 $sql .= " WHERE entity IN (".getEntity('contract').")";
742 } else {
743 $sql .= " WHERE rowid = ".(int) $id;
744 }
745 if ($ref_customer) {
746 $sql .= " AND ref_customer = '".$this->db->escape($ref_customer)."'";
747 }
748 if ($ref_supplier) {
749 $sql .= " AND ref_supplier = '".$this->db->escape($ref_supplier)."'";
750 }
751 if ($ref) {
752 $sql .= " AND ref = '".$this->db->escape($ref)."'";
753 }
754
755 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
756
757 $resql = $this->db->query($sql);
758 if ($resql) {
759 $num = $this->db->num_rows($resql);
760 if ($num > 1) {
761 $this->error = 'Fetch found several records.';
762 dol_syslog($this->error, LOG_ERR);
763 $result = -2;
764 return 0;
765 } elseif ($num) { // $num = 1
766 $obj = $this->db->fetch_object($resql);
767 if ($obj) {
768 $this->id = $obj->rowid;
769 $this->ref = (!isset($obj->ref) || !$obj->ref) ? $obj->rowid : $obj->ref;
770 $this->ref_customer = $obj->ref_customer;
771 $this->ref_supplier = $obj->ref_supplier;
772 $this->ref_ext = $obj->ref_ext;
773 $this->entity = $obj->entity;
774 $this->statut = $obj->status;
775 $this->status = $obj->status;
776 $this->signed_status = $obj->signed_status;
777
778 $this->date_contrat = $this->db->jdate($obj->datecontrat);
779 $this->date_creation = $this->db->jdate($obj->datecontrat);
780
781 $this->user_author_id = $obj->fk_user_author;
782
783 $this->commercial_signature_id = $obj->fk_commercial_signature;
784 $this->commercial_suivi_id = $obj->fk_commercial_suivi;
785
786 $this->note_private = $obj->note_private;
787 $this->note_public = $obj->note_public;
788 $this->model_pdf = $obj->model_pdf;
789
790 $this->fk_projet = $obj->fk_project; // deprecated
791 $this->fk_project = $obj->fk_project;
792
793 $this->socid = $obj->thirdpartyid;
794 $this->fk_soc = $obj->thirdpartyid;
795 $this->last_main_doc = $obj->last_main_doc;
796 $this->extraparams = (isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : null);
797 $this->fk_contract_type = (int) $obj->fk_contract_type;
798
799 $this->db->free($resql);
800
801 // Retrieve all extrafields
802 // fetch optionals attributes and labels
803 if (empty($noextrafields)) {
804 $result = $this->fetch_optionals();
805 if ($result < 0) {
806 $this->error = $this->db->lasterror();
807 return -4;
808 }
809 }
810
811 // Lines
812 if (empty($nolines)) {
813 if ($result >= 0 && !empty($this->table_element_line)) {
814 $result = $this->fetch_lines();
815 if ($result < 0) {
816 $this->error = $this->db->lasterror();
817 return -3;
818 }
819 }
820 }
821
822 return $this->id;
823 } else {
824 dol_syslog(get_class($this)."::fetch Contract failed");
825 $this->error = "Fetch contract failed";
826 return -1;
827 }
828 } else {
829 dol_syslog(get_class($this)."::fetch Contract not found");
830 $this->error = "Contract not found";
831 return 0;
832 }
833 } else {
834 dol_syslog(get_class($this)."::fetch Error searching contract");
835 $this->error = $this->db->error();
836 return -1;
837 }
838 }
839
840 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
850 public function fetch_lines($only_services = 0, $loadalsotranslation = 0, $noextrafields = 0)
851 {
852 // phpcs:enable
853 $this->nbofservices = 0;
854 $this->nbofserviceswait = 0;
855 $this->nbofservicesopened = 0;
856 $this->nbofservicesexpired = 0;
857 $this->nbofservicesclosed = 0;
858
859 $total_ttc = 0;
860 $total_vat = 0;
861 $total_ht = 0;
862
863 $now = dol_now();
864
865 $this->lines = array();
866 $pos = 0;
867
868 // Selects contract lines related to a product
869 $sql = "SELECT p.label as product_label, p.description as product_desc, p.ref as product_ref, p.fk_product_type as product_type,";
870 $sql .= " d.rowid, d.fk_contrat, d.statut as status, d.description, d.subprice, d.subprice_ttc, d.vat_src_code, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.localtax1_type, d.localtax2_type, d.qty, d.remise_percent, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht,";
871 $sql .= " d.total_ht,";
872 $sql .= " d.total_tva,";
873 $sql .= " d.total_localtax1,";
874 $sql .= " d.total_localtax2,";
875 $sql .= " d.total_ttc,";
876 $sql .= " d.info_bits, d.fk_product,";
877 $sql .= " d.date_ouverture_prevue as date_start,";
878 $sql .= " d.date_ouverture as date_start_real,";
879 $sql .= " d.date_fin_validite as date_end,";
880 $sql .= " d.date_cloture as date_end_real,";
881 $sql .= " d.fk_user_author,";
882 $sql .= " d.fk_user_ouverture,";
883 $sql .= " d.fk_user_cloture,";
884 $sql .= " d.fk_unit,";
885 $sql .= " d.extraparams,";
886 $sql .= " d.product_type as type,";
887 $sql .= " d.rang";
888 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as d LEFT JOIN ".MAIN_DB_PREFIX."product as p ON d.fk_product = p.rowid";
889 $sql .= " WHERE d.fk_contrat = ".((int) $this->id);
890 if ($only_services == 1) {
891 $sql .= " AND d.product_type = 1";
892 }
893 $sql .= " ORDER by d.rang ASC";
894
895 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
896 $result = $this->db->query($sql);
897 if ($result) {
898 $num = $this->db->num_rows($result);
899 $i = 0;
900
901 while ($i < $num) {
902 $objp = $this->db->fetch_object($result);
903
904 $line = new ContratLigne($this->db);
905
906 $line->id = $objp->rowid;
907 $line->ref = $objp->rowid;
908 $line->fk_contrat = $objp->fk_contrat;
909 $line->desc = $objp->description; // Description line
910 $line->qty = $objp->qty;
911 $line->vat_src_code = $objp->vat_src_code;
912 $line->tva_tx = $objp->tva_tx;
913 $line->localtax1_tx = $objp->localtax1_tx;
914 $line->localtax2_tx = $objp->localtax2_tx;
915 $line->localtax1_type = $objp->localtax1_type;
916 $line->localtax2_type = $objp->localtax2_type;
917 $line->subprice = $objp->subprice;
918 $line->subprice_ttc = $objp->subprice_ttc;
919 $line->statut = $objp->status; // For backward compatibility
920 $line->status = $objp->status;
921 $line->remise_percent = $objp->remise_percent;
922 $line->total_ht = $objp->total_ht;
923 $line->total_tva = $objp->total_tva;
924 $line->total_localtax1 = $objp->total_localtax1;
925 $line->total_localtax2 = $objp->total_localtax2;
926 $line->total_ttc = $objp->total_ttc;
927 $line->fk_product = (($objp->fk_product > 0) ? $objp->fk_product : 0);
928 $line->info_bits = $objp->info_bits;
929 $line->type = $objp->type;
930
931 $line->fk_fournprice = $objp->fk_fournprice;
932 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $objp->fk_fournprice, $objp->pa_ht);
933 $line->pa_ht = $marginInfos[0];
934
935 $line->fk_user_author = $objp->fk_user_author;
936 $line->fk_user_ouverture = $objp->fk_user_ouverture;
937 $line->fk_user_cloture = $objp->fk_user_cloture;
938 $line->fk_unit = $objp->fk_unit;
939
940 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
941
942 $line->ref = $objp->product_ref; // deprecated
943 $line->product_ref = $objp->product_ref; // Product Ref
944 $line->product_type = $objp->product_type; // Product Type
945 $line->product_desc = $objp->product_desc; // Product Description
946 $line->product_label = $objp->product_label; // Product Label
947
948 $line->description = $objp->description;
949
950 $line->date_start = $this->db->jdate($objp->date_start);
951 $line->date_start_real = $this->db->jdate($objp->date_start_real);
952 $line->date_end = $this->db->jdate($objp->date_end);
953 $line->date_end_real = $this->db->jdate($objp->date_end_real);
954 // For backward compatibility
955 //$line->date_ouverture_prevue = $this->db->jdate($objp->date_ouverture_prevue);
956 //$line->date_ouverture = $this->db->jdate($objp->date_ouverture);
957 //$line->date_fin_validite = $this->db->jdate($objp->date_fin_validite);
958 //$line->date_cloture = $this->db->jdate($objp->date_cloture);
959 //$line->date_debut_prevue = $this->db->jdate($objp->date_ouverture_prevue);
960 //$line->date_debut_reel = $this->db->jdate($objp->date_ouverture);
961 //$line->date_fin_prevue = $this->db->jdate($objp->date_fin_validite);
962 //$line->date_fin_reel = $this->db->jdate($objp->date_cloture);
963
964 $line->rang = $objp->rang;
965
966 // Retrieve all extrafields for contract line
967 // fetch optionals attributes and labels
968 if (empty($noextrafields)) {
969 $line->fetch_optionals();
970 }
971
972 // multilangs
973 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
974 $tmpproduct = new Product($this->db);
975 $tmpproduct->fetch($objp->fk_product);
976 $tmpproduct->getMultiLangs();
977
978 $line->multilangs = $tmpproduct->multilangs;
979 }
980
981 $this->lines[$pos] = $line;
982
983 $this->lines_id_index_mapper[$line->id] = $pos;
984
985 //dol_syslog("1 ".$line->desc);
986 //dol_syslog("2 ".$line->product_desc);
987
988 if ($line->statut == ContratLigne::STATUS_INITIAL) {
989 $this->nbofserviceswait++;
990 }
991 if ($line->statut == ContratLigne::STATUS_OPEN && (empty($line->date_end) || $line->date_end >= $now)) {
992 $this->nbofservicesopened++;
993 }
994 if ($line->statut == ContratLigne::STATUS_OPEN && (!empty($line->date_end) && $line->date_end < $now)) {
995 $this->nbofservicesexpired++;
996 }
997 if ($line->statut == ContratLigne::STATUS_CLOSED) {
998 $this->nbofservicesclosed++;
999 }
1000
1001 // TODO Not saved into database
1002 $total_ttc += $objp->total_ttc;
1003 $total_vat += $objp->total_tva;
1004 $total_ht += $objp->total_ht;
1005
1006 $i++;
1007 $pos++;
1008 }
1009 $this->db->free($result);
1010 } else {
1011 dol_syslog(get_class($this)."::Fetch Error when reading lines of contracts linked to products");
1012 return -3;
1013 }
1014
1015 // Now set the global properties on contract not stored into database.
1016 $this->nbofservices = count($this->lines);
1017 $this->total_ttc = (float) price2num($total_ttc);
1018 $this->total_tva = (float) price2num($total_vat);
1019 $this->total_ht = (float) price2num($total_ht);
1020
1021 return $this->lines;
1022 }
1023
1031 public function create($user, $notrigger = 0)
1032 {
1033 global $conf, $langs;
1034
1035 // Check parameters
1036 $paramsok = 1;
1037 if ($this->commercial_signature_id <= 0) {
1038 $langs->load("commercial");
1039 $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeSignature"));
1040 $paramsok = 0;
1041 }
1042 if ($this->commercial_suivi_id <= 0) {
1043 $langs->load("commercial");
1044 $this->error .= ($this->error ? "<br>" : '');
1045 $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeFollowUp"));
1046 $paramsok = 0;
1047 }
1048 $this->entity = setEntity($this);
1049 if (!$paramsok) {
1050 return -1;
1051 }
1052
1053
1054 $this->db->begin();
1055
1056 $now = dol_now();
1057
1058 // Insert contract
1059 $sql = "INSERT INTO ".MAIN_DB_PREFIX."contrat (datec, fk_soc, fk_user_author, date_contrat,";
1060 $sql .= " fk_commercial_signature, fk_commercial_suivi, fk_projet,";
1061 $sql .= " ref, entity, signed_status, note_private, note_public, ref_customer, ref_supplier, ref_ext, fk_contract_type)";
1062 $sql .= " VALUES ('".$this->db->idate($now)."', ".((int) $this->socid).", ".((int) $user->id);
1063 $sql .= ", ".(dol_strlen($this->date_contrat) != 0 ? "'".$this->db->idate($this->date_contrat)."'" : "NULL");
1064 $sql .= ",".($this->commercial_signature_id > 0 ? ((int) $this->commercial_signature_id) : "NULL");
1065 $sql .= ",".($this->commercial_suivi_id > 0 ? ((int) $this->commercial_suivi_id) : "NULL");
1066 $sql .= ",".($this->fk_project > 0 ? ((int) $this->fk_project) : "NULL");
1067 $sql .= ", ".(dol_strlen($this->ref) <= 0 ? "null" : "'".$this->db->escape($this->ref)."'");
1068 $sql .= ", ".((int) $this->entity);
1069 $sql .= ", ".((int) $this->signed_status);
1070 $sql .= ", ".(!empty($this->note_private) ? ("'".$this->db->escape($this->note_private)."'") : "NULL");
1071 $sql .= ", ".(!empty($this->note_public) ? ("'".$this->db->escape($this->note_public)."'") : "NULL");
1072 $sql .= ", ".(!empty($this->ref_customer) ? ("'".$this->db->escape($this->ref_customer)."'") : "NULL");
1073 $sql .= ", ".(!empty($this->ref_supplier) ? ("'".$this->db->escape($this->ref_supplier)."'") : "NULL");
1074 $sql .= ", ".(!empty($this->ref_ext) ? ("'".$this->db->escape($this->ref_ext)."'") : "NULL");
1075 $sql .= ", ".((int) $this->fk_contract_type);
1076 $sql .= ")";
1077 $resql = $this->db->query($sql);
1078
1079 if ($resql) {
1080 $error = 0;
1081
1082 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."contrat");
1083
1084 // Load object modContract
1085 $module = getDolGlobalString('CONTRACT_ADDON', 'mod_contract_serpis');
1086 if (substr($module, 0, 13) == 'mod_contract_' && substr($module, -3) == 'php') {
1087 $module = substr($module, 0, dol_strlen($module) - 4);
1088 }
1089 $result = dol_include_once('/core/modules/contract/'.$module.'.php');
1090 if ($result > 0) {
1091 $modCodeContract = new $module();
1092 '@phan-var-force ModelNumRefContracts $modCodeContract';
1095 if (!empty($modCodeContract->code_auto)) {
1096 // Force the ref to a draft value if numbering module is an automatic numbering
1097 $sql = 'UPDATE '.MAIN_DB_PREFIX."contrat SET ref='(PROV".((int) $this->id).")' WHERE rowid=".((int) $this->id);
1098 if ($this->db->query($sql)) {
1099 if ($this->id) {
1100 $this->ref = "(PROV".$this->id.")";
1101 }
1102 }
1103 }
1104 }
1105
1106 $result = $this->insertExtraFields();
1107 if ($result < 0) {
1108 $error++;
1109 }
1110
1111 // Insert business contacts ('SALESREPSIGN','contrat')
1112 if (!$error && !empty($this->commercial_signature_id)) {
1113 $result = $this->add_contact($this->commercial_signature_id, 'SALESREPSIGN', 'internal');
1114 if ($result < 0) {
1115 $error++;
1116 }
1117 }
1118
1119 // Insert business contacts ('SALESREPFOLL','contrat')
1120 if (!$error && !empty($this->commercial_suivi_id)) {
1121 $result = $this->add_contact($this->commercial_suivi_id, 'SALESREPFOLL', 'internal');
1122 if ($result < 0) {
1123 $error++;
1124 }
1125 }
1126
1127 if (!$error) {
1128 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1129 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1130 }
1131
1132 // Add object linked
1133 if ($this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1134 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1135 if (is_array($tmp_origin_id)) { // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
1136 foreach ($tmp_origin_id as $origin_id) {
1137 $ret = $this->add_object_linked($origin, $origin_id);
1138 if (!$ret) {
1139 $this->error = $this->db->lasterror();
1140 $error++;
1141 }
1142 }
1143 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1144 $origin_id = $tmp_origin_id;
1145 $ret = $this->add_object_linked($origin, $origin_id);
1146 if (!$ret) {
1147 $this->error = $this->db->lasterror();
1148 $error++;
1149 }
1150 }
1151 }
1152 }
1153
1154 if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
1155 $originforcontact = $this->origin;
1156 $originidforcontact = $this->origin_id;
1157 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
1158 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
1159 $exp = new Expedition($this->db);
1160 $exp->fetch($this->origin_id);
1161 $exp->fetchObjectLinked();
1162 if (count($exp->linkedObjectsIds['commande']) > 0) {
1163 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1164 $originforcontact = 'commande';
1165 $originidforcontact = $value;
1166 break; // We take first one
1167 }
1168 }
1169 }
1170
1171 $sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
1172 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
1173
1174 $resqlcontact = $this->db->query($sqlcontact);
1175 if ($resqlcontact) {
1176 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1177 if ($objcontact->source == 'internal' && in_array($objcontact->code, array('SALESREPSIGN', 'SALESREPFOLL'))) {
1178 continue; // ignore this, already forced previously
1179 }
1180
1181 $this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
1182 }
1183 } else {
1184 dol_print_error($this->db);
1185 }
1186 }
1187 }
1188
1189 if (!$error && !$notrigger) {
1190 // Call trigger
1191 $result = $this->call_trigger('CONTRACT_CREATE', $user);
1192 if ($result < 0) {
1193 $error++;
1194 }
1195 // End call triggers
1196 }
1197
1198 if (!$error) {
1199 $this->db->commit();
1200 return $this->id;
1201 } else {
1202 // Keep the error reported by the failing step, it is the only actionable one.
1203 // Only fall back to a generic message when nothing was set, as propal.class.php does.
1204 if (empty($this->error) && empty($this->errors)) {
1205 $this->error = "Failed to add contract";
1206 }
1207 dol_syslog(get_class($this)."::create - 20 - ".$this->error, LOG_ERR);
1208 $this->db->rollback();
1209 return -2;
1210 }
1211 } else {
1212 $this->error = $langs->trans("UnknownError").": ".$this->db->error();
1213 dol_syslog(get_class($this)."::create - 10 - ".$this->error, LOG_ERR);
1214
1215 $this->db->rollback();
1216 return -1;
1217 }
1218 }
1219
1220
1227 public function delete($user)
1228 {
1229 global $conf;
1230
1231 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1232
1233 $error = 0;
1234
1235 $this->db->begin();
1236
1237 // Call trigger
1238 $result = $this->call_trigger('CONTRACT_DELETE', $user);
1239 if ($result < 0) {
1240 $error++;
1241 }
1242 // End call triggers
1243
1244 if (!$error) {
1245 // Delete linked contacts
1246 $res = $this->delete_linked_contact();
1247 if ($res < 0) {
1248 dol_syslog(get_class($this)."::delete error", LOG_ERR);
1249 $error++;
1250 }
1251 }
1252
1253 if (!$error) {
1254 // Delete linked object
1255 $res = $this->deleteObjectLinked();
1256 if ($res < 0) {
1257 $error++;
1258 }
1259 }
1260
1261 // Delete lines
1262 if (!$error) {
1263 // Delete contratdet extrafields
1264 $sql_main_table = MAIN_DB_PREFIX.'contratdet';
1265 $ef = $sql_main_table."_extrafields";
1266 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$sql_main_table." WHERE fk_contrat = ".((int) $this->id).")";
1267
1268 dol_syslog(get_class($this)."::delete contratdet_extrafields", LOG_DEBUG);
1269 $resql = $this->db->query($sql);
1270 if (!$resql) {
1271 $this->error = $this->db->error();
1272 $error++;
1273 }
1274 }
1275
1276 if (!$error) {
1277 // Delete contratdet
1278 $sql = "DELETE FROM ".MAIN_DB_PREFIX."contratdet";
1279 $sql .= " WHERE fk_contrat = ".((int) $this->id);
1280
1281 dol_syslog(get_class($this)."::delete contratdet", LOG_DEBUG);
1282 $resql = $this->db->query($sql);
1283 if (!$resql) {
1284 $this->error = $this->db->error();
1285 $error++;
1286 }
1287 }
1288
1289 // Delete record into ECM index and physically
1290 if (!$error) {
1291 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1292 if ($res) {
1293 $res = $this->deleteEcmFiles(1);
1294 } // Deleting files physically is done later with the dol_delete_dir_recursive
1295 if (!$res) {
1296 $error++;
1297 }
1298 }
1299
1300 // Delete contract
1301 if (!$error) {
1302 $sql = "DELETE FROM ".MAIN_DB_PREFIX."contrat";
1303 $sql .= " WHERE rowid=".((int) $this->id);
1304
1305 dol_syslog(get_class($this)."::delete contrat", LOG_DEBUG);
1306 $resql = $this->db->query($sql);
1307 if (!$resql) {
1308 $this->error = $this->db->error();
1309 $error++;
1310 }
1311 }
1312
1313 // Removed extrafields
1314 if (!$error) {
1315 $result = $this->deleteExtraFields();
1316 if ($result < 0) {
1317 $error++;
1318 dol_syslog(get_class($this)."::delete error -3 ".$this->error, LOG_ERR);
1319 }
1320 }
1321
1322 if (!$error) {
1323 // We remove directory
1324 $ref = dol_sanitizeFileName($this->ref);
1325 if ($conf->contrat->dir_output && !empty($ref)) {
1326 $dir = $conf->contrat->multidir_output[$this->entity]."/".$ref;
1327 if (file_exists($dir)) {
1328 $res = @dol_delete_dir_recursive($dir);
1329 if (!$res) {
1330 $this->error = 'ErrorFailToDeleteDir';
1331 $error++;
1332 }
1333 }
1334 }
1335 }
1336
1337 if (!$error) {
1338 $this->db->commit();
1339 return 1;
1340 } else {
1341 $this->error = $this->db->lasterror();
1342 $this->db->rollback();
1343 return -1;
1344 }
1345 }
1346
1354 public function update($user, $notrigger = 0)
1355 {
1356 global $conf;
1357 $error = 0;
1358
1359 // Clean parameters
1360 if (empty($this->fk_commercial_signature) && $this->commercial_signature_id > 0) {
1361 $this->fk_commercial_signature = $this->commercial_signature_id;
1362 }
1363 if (empty($this->fk_commercial_suivi) && $this->commercial_suivi_id > 0) {
1364 $this->fk_commercial_suivi = $this->commercial_suivi_id;
1365 }
1366 if (empty($this->socid) && $this->fk_soc > 0) {
1367 $this->socid = (int) $this->fk_soc;
1368 }
1369
1370 if (isset($this->ref)) {
1371 $this->ref = trim($this->ref);
1372 }
1373 if (isset($this->ref_customer)) {
1374 $this->ref_customer = trim($this->ref_customer);
1375 }
1376 if (isset($this->ref_supplier)) {
1377 $this->ref_supplier = trim($this->ref_supplier);
1378 }
1379 if (isset($this->ref_ext)) {
1380 $this->ref_ext = trim($this->ref_ext);
1381 }
1382 if (isset($this->entity)) {
1383 $this->entity = (int) $this->entity;
1384 }
1385 if (isset($this->status)) {
1386 $this->status = (int) $this->status;
1387 }
1388 if ((int) $this->socid > 0) {
1389 $this->socid = (int) $this->socid;
1390 }
1391 if (isset($this->fk_commercial_signature)) {
1392 $this->fk_commercial_signature = (int) $this->fk_commercial_signature;
1393 }
1394 if (isset($this->fk_commercial_suivi)) {
1395 $this->fk_commercial_suivi = (int) $this->fk_commercial_suivi;
1396 }
1397 if (isset($this->note_private)) {
1398 $this->note_private = trim($this->note_private);
1399 }
1400 if (isset($this->note_public)) {
1401 $this->note_public = trim($this->note_public);
1402 }
1403 if (isset($this->import_key)) {
1404 $this->import_key = trim($this->import_key);
1405 }
1406
1407 $extraparams = (!empty($this->extraparams) ? json_encode($this->extraparams) : null);
1408 $extraparams = dol_trunc($extraparams, 250);
1409
1410 // $this->oldcopy must have been set by the caller of update
1411
1412 // Update request
1413 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET";
1414 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1415 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").",";
1416 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1417 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1418 $sql .= " entity=".((int) $conf->entity).",";
1419 $sql .= " date_contrat=".(dol_strlen($this->date_contrat) != 0 ? "'".$this->db->idate($this->date_contrat)."'" : 'null').",";
1420 $sql .= " statut=".(isset($this->statut) ? ((int) $this->statut) : (isset($this->status) ? ((int) $this->status) : "null")).",";
1421 $sql .= " fk_soc=".($this->socid > 0 ? ((int) $this->socid) : "null").",";
1422 $sql .= " fk_projet=".($this->fk_project > 0 ? ((int) $this->fk_project) : "null").",";
1423 $sql .= " fk_commercial_signature=".(isset($this->fk_commercial_signature) ? ((int) $this->fk_commercial_signature) : "null").",";
1424 $sql .= " fk_commercial_suivi=".(isset($this->fk_commercial_suivi) ? ((int) $this->fk_commercial_suivi) : "null").",";
1425 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1426 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1427 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
1428 $sql .= " fk_user_modif=".(isset($user->id) ? ((int) $user->id) : "null").",";
1429 $sql .= " extraparams=".(isset($extraparams) ? "'".$this->db->escape($extraparams)."'" : "null");
1430 $sql .= " WHERE rowid=".((int) $this->id);
1431
1432 $this->db->begin();
1433
1434 $resql = $this->db->query($sql);
1435 if (!$resql) {
1436 $error++;
1437 $this->errors[] = "Error ".$this->db->lasterror();
1438 }
1439
1440 if (!$error) {
1441 $result = $this->insertExtraFields(); // This delete and reinsert extrafields
1442 if ($result < 0) {
1443 $error++;
1444 }
1445 }
1446
1447 if (!$error && !$notrigger) {
1448 // Call triggers
1449 $result = $this->call_trigger('CONTRACT_MODIFY', $user);
1450 if ($result < 0) {
1451 $error++;
1452 }
1453 // End call triggers
1454 }
1455
1456 // Commit or rollback
1457 if ($error) {
1458 foreach ($this->errors as $errmsg) {
1459 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1460 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1461 }
1462 $this->db->rollback();
1463 return -1 * $error;
1464 } else {
1465 if (isset($user->id)) {
1466 $this->fk_user_modif = (int) $user->id;
1467 }
1468 $this->db->commit();
1469 return 1;
1470 }
1471 }
1472
1473
1497 public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $remise_percent, $date_start, $date_end, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $fk_fournprice = null, $pa_ht = 0, $array_options = array(), $fk_unit = null, $rang = 0)
1498 {
1499 global $user, $langs, $mysoc;
1500 $error = 0;
1501
1502 dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $remise_percent, $date_start, $date_end, $price_base_type, $pu_ttc, $info_bits, $rang");
1503
1504 // Check parameters
1505 if ($fk_product <= 0 && empty($desc)) {
1506 $this->error = "ErrorDescRequiredForFreeProductLines";
1507 return -1;
1508 }
1509
1510 if ($this->status >= 0) {
1511 // Clean parameters
1512 $pu_ht = price2num($pu_ht);
1513 $pu_ttc = price2num($pu_ttc);
1514 $pa_ht = price2num($pa_ht);
1515
1516 // Clean vat code
1517 $reg = array();
1518 $vat_src_code = '';
1519 if (preg_match('/\‍((.*)\‍)/', (string) $txtva, $reg)) {
1520 $vat_src_code = $reg[1];
1521 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', (string) $txtva); // Remove code into vatrate.
1522 }
1523 $txtva = price2num($txtva);
1524 $txlocaltax1 = price2num($txlocaltax1);
1525 $txlocaltax2 = price2num($txlocaltax2);
1526
1527 $remise_percent = price2num($remise_percent);
1528 $qty = price2num($qty);
1529 if (empty($qty)) {
1530 $qty = 1;
1531 }
1532 if (empty($info_bits)) {
1533 $info_bits = 0;
1534 }
1535 if (empty($pu_ht) || !is_numeric($pu_ht)) {
1536 $pu_ht = 0;
1537 }
1538 if (empty($pu_ttc)) {
1539 $pu_ttc = 0;
1540 }
1541 if (empty($txtva) || !is_numeric($txtva)) {
1542 $txtva = 0;
1543 }
1544 if (empty($txlocaltax1) || !is_numeric($txlocaltax1)) {
1545 $txlocaltax1 = 0;
1546 }
1547 if (empty($txlocaltax2) || !is_numeric($txlocaltax2)) {
1548 $txlocaltax2 = 0;
1549 }
1550
1551 if ($price_base_type == 'HT') {
1552 $pu = $pu_ht;
1553 } else {
1554 $pu = $pu_ttc;
1555 }
1556
1557 // Check parameters
1558 if (empty($remise_percent)) {
1559 $remise_percent = 0;
1560 }
1561 if (empty($rang)) {
1562 $rang = 0;
1563 }
1564
1565 if ($date_start && $date_end && $date_start > $date_end) {
1566 $langs->load("errors");
1567 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1568 return -1;
1569 }
1570
1571 $this->db->begin();
1572
1573 $localtaxes_type = getLocalTaxesFromRate($txtva.($vat_src_code ? ' ('.$vat_src_code.')' : ''), 0, $this->societe, $mysoc);
1574
1575 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1576 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1577 // and this is done at the line level, which has its own VAT rate
1578
1579 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type);
1580 $total_ht = $tabprice[0];
1581 $total_tva = $tabprice[1];
1582 $total_ttc = $tabprice[2];
1583 $total_localtax1 = $tabprice[9];
1584 $total_localtax2 = $tabprice[10];
1585 $pu_ht = $tabprice[3];
1586 $pu_ttc = $tabprice[5];
1587
1588 if (count($localtaxes_type) > 0) {
1589 $localtax1_type = $localtaxes_type[0];
1590 $localtax2_type = $localtaxes_type[2];
1591 } else {
1592 $localtax1_type = "";
1593 $localtax2_type = "";
1594 }
1595
1596 if (empty($pa_ht)) {
1597 $pa_ht = 0;
1598 }
1599
1600
1601 // if buy price not defined, define buyprice as configured in margin admin
1602 if ($pa_ht == 0) {
1603 $result = $this->defineBuyPrice((float) $pu_ht, $remise_percent, $fk_product, $qty);
1604 if ($result < 0) {
1605 return -1;
1606 } else {
1607 $pa_ht = $result;
1608 }
1609 }
1610
1611 // Insert into database
1612 $sql = "INSERT INTO ".MAIN_DB_PREFIX."contratdet";
1613 $sql .= " (fk_contrat, label, description, fk_product, qty, tva_tx, vat_src_code,";
1614 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice, subprice_ttc,";
1615 $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc,";
1616 $sql .= " info_bits,";
1617 $sql .= " fk_product_fournisseur_price, buy_price_ht";
1618 if ($date_start > 0) {
1619 $sql .= ",date_ouverture_prevue";
1620 }
1621 if ($date_end > 0) {
1622 $sql .= ",date_fin_validite";
1623 }
1624 $sql .= ", fk_unit";
1625 $sql .= ", rang";
1626 $sql .= ") VALUES (";
1627 $sql .= ((int) $this->id).", '', '".$this->db->escape($desc)."',";
1628 $sql .= ($fk_product > 0 ? ((int) $fk_product) : "null").",";
1629 $sql .= " ".((float) $qty).",";
1630 $sql .= " ".((float) $txtva).",";
1631 $sql .= " ".($vat_src_code ? "'".$this->db->escape($vat_src_code)."'" : "null").",";
1632 $sql .= " ".((float) $txlocaltax1).",";
1633 $sql .= " ".((float) $txlocaltax2).",";
1634 $sql .= " '".$this->db->escape($localtax1_type)."',";
1635 $sql .= " '".$this->db->escape($localtax2_type)."',";
1636 $sql .= " ".price2num($remise_percent).",";
1637 $sql .= " ".price2num($pu_ht).",";
1638 $sql .= " ".($price_base_type === 'TTC' ? (float) price2num($pu_ttc) : 0).",";
1639 $sql .= " ".price2num($total_ht).",".price2num($total_tva).",".price2num($total_localtax1).",".price2num($total_localtax2).",".price2num($total_ttc).",";
1640 $sql .= " ".((int) $info_bits).",";
1641 if (isset($fk_fournprice)) {
1642 $sql .= ' '.((int) $fk_fournprice).',';
1643 } else {
1644 $sql .= ' null,';
1645 }
1646 if (isset($pa_ht)) {
1647 $sql .= ' '.price2num($pa_ht);
1648 } else {
1649 $sql .= ' null';
1650 }
1651 if ($date_start > 0) {
1652 $sql .= ",'".$this->db->idate($date_start)."'";
1653 }
1654 if ($date_end > 0) {
1655 $sql .= ",'".$this->db->idate($date_end)."'";
1656 }
1657 $sql .= ", ".($fk_unit ? "'".$this->db->escape((string) $fk_unit)."'" : "null");
1658 $sql .= ", ".(!empty($rang) ? (int) $rang : "0");
1659 $sql .= ")";
1660
1661 $resql = $this->db->query($sql);
1662 if ($resql) {
1663 $contractlineid = $this->db->last_insert_id(MAIN_DB_PREFIX."contratdet");
1664
1665 $contractline = new ContratLigne($this->db);
1666 $contractline->array_options = $array_options;
1667 $contractline->id = $contractlineid;
1668 $result = $contractline->insertExtraFields();
1669 if ($result < 0) {
1670 $this->setErrorsFromObject($contractline);
1671 $error++;
1672 }
1673
1674 if (empty($error)) {
1675 // Call trigger
1676 $this->context['line_id'] = $contractlineid;
1677 $result = $this->call_trigger('LINECONTRACT_INSERT', $user);
1678 if ($result < 0) {
1679 $error++;
1680 }
1681 // End call triggers
1682 }
1683
1684 if ($error) {
1685 $this->db->rollback();
1686 return -1;
1687 } else {
1688 $this->db->commit();
1689 return $contractlineid;
1690 }
1691 } else {
1692 $this->db->rollback();
1693 $this->error = $this->db->error()." sql=".$sql;
1694 return -1;
1695 }
1696 } else {
1697 dol_syslog(get_class($this)."::addline ErrorTryToAddLineOnValidatedContract", LOG_ERR);
1698 return -2;
1699 }
1700 }
1701
1726 public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $tvatx, $localtax1tx = 0.0, $localtax2tx = 0.0, $date_start_real = '', $date_end_real = '', $price_base_type = 'HT', $info_bits = 0, $fk_fournprice = null, $pa_ht = 0, $array_options = array(), $fk_unit = null, $rang = 0)
1727 {
1728 global $user, $langs, $mysoc;
1729
1730 if (!$this->isLineOfObject($rowid)) {
1731 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1732 return -1;
1733 }
1734
1735 $error = 0;
1736
1737 // Clean parameters
1738 $qty = trim((string) $qty);
1739 $desc = trim($desc);
1740
1741 $tvatx = price2num($tvatx);
1742 $localtax1tx = price2num($localtax1tx);
1743 $localtax2tx = price2num($localtax2tx);
1744 $pa_ht = price2num($pa_ht);
1745 if (empty($fk_fournprice)) {
1746 $fk_fournprice = 0;
1747 }
1748 if (empty($rang)) {
1749 $rang = 0;
1750 }
1751
1752 if ($date_start && $date_end && $date_start > $date_end) {
1753 $langs->load("errors");
1754 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1755 return -1;
1756 }
1757
1758 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $date_start_real, $date_end_real, $tvatx, $localtax1tx, $localtax2tx, $price_base_type, $info_bits, $rang");
1759
1760 $this->db->begin();
1761
1762 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1763 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1764 // and this is done at the line level, which has its own VAT rate
1765
1766 $localtaxes_type = getLocalTaxesFromRate($tvatx, 0, $this->societe, $mysoc);
1767 $tvatx = preg_replace('/\s*\‍(.*\‍)/', '', $tvatx); // Remove code into vatrate.
1768
1769 $tabprice = calcul_price_total((float) $qty, $pu, $remise_percent, (float) price2num($tvatx), (float) $localtax1tx, (float) $localtax2tx, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type);
1770 $total_ht = $tabprice[0];
1771 $total_tva = $tabprice[1];
1772 $total_ttc = $tabprice[2];
1773 $total_localtax1 = $tabprice[9];
1774 $total_localtax2 = $tabprice[10];
1775 $pu_ht = $tabprice[3];
1776 $pu_ttc = $tabprice[5];
1777
1778 $localtax1_type = (empty($localtaxes_type[0]) ? '' : $localtaxes_type[0]);
1779 $localtax2_type = (empty($localtaxes_type[2]) ? '' : $localtaxes_type[2]);
1780
1781 if (empty($pa_ht)) {
1782 $pa_ht = 0;
1783 }
1784
1785 // if buy price not defined, define buyprice as configured in margin admin
1786 if ($pa_ht == 0) {
1787 $result = $this->defineBuyPrice($pu, $remise_percent);
1788 if ($result < 0) {
1789 return -1;
1790 } else {
1791 $pa_ht = $result;
1792 }
1793 }
1794
1795 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET description = '".$this->db->escape($desc)."'";
1796 $sql .= ",subprice = ".((float) price2num($pu_ht));
1797 // Persist the original entry mode of the line so a no-op edit can preserve it later.
1798 $sql .= ",subprice_ttc = ".($price_base_type === 'TTC' ? (float) price2num($pu_ttc) : 0);
1799 $sql .= ",remise_percent = ".((float) price2num($remise_percent));
1800 $sql .= ",qty = ".((float) $qty);
1801 $sql .= ",tva_tx = ".((float) price2num($tvatx));
1802 $sql .= ",localtax1_tx = ".((float) price2num($localtax1tx));
1803 $sql .= ",localtax2_tx = ".((float) price2num($localtax2tx));
1804 $sql .= ",localtax1_type='".$this->db->escape($localtax1_type)."'";
1805 $sql .= ",localtax2_type='".$this->db->escape($localtax2_type)."'";
1806 $sql .= ", total_ht = ".((float) price2num($total_ht));
1807 $sql .= ", total_tva = ".((float) price2num($total_tva));
1808 $sql .= ", total_localtax1 = ".((float) price2num($total_localtax1));
1809 $sql .= ", total_localtax2 = ".((float) price2num($total_localtax2));
1810 $sql .= ", total_ttc = ".((float) price2num($total_ttc));
1811 $sql .= ", fk_product_fournisseur_price=".($fk_fournprice > 0 ? ((int) $fk_fournprice) : "null");
1812 $sql .= ", buy_price_ht = ".((float) price2num($pa_ht));
1813 if ($date_start > 0) {
1814 $sql .= ",date_ouverture_prevue = '".$this->db->idate($date_start)."'";
1815 } else {
1816 $sql .= ",date_ouverture_prevue = null";
1817 }
1818 if ($date_end > 0) {
1819 $sql .= ",date_fin_validite = '".$this->db->idate($date_end)."'";
1820 } else {
1821 $sql .= ",date_fin_validite = null";
1822 }
1823 if ($date_start_real > 0) {
1824 $sql .= ",date_ouverture = '".$this->db->idate($date_start_real)."'";
1825 } else {
1826 $sql .= ",date_ouverture = null";
1827 }
1828 if ($date_end_real > 0) {
1829 $sql .= ",date_cloture = '".$this->db->idate($date_end_real)."'";
1830 } else {
1831 $sql .= ",date_cloture = null";
1832 }
1833 $sql .= ", fk_unit = ".($fk_unit > 0 ? ((int) $fk_unit) : "null");
1834 $sql .= ", rang = ".(!empty($rang) ? ((int) $rang) : "0");
1835 $sql .= " WHERE rowid = ".((int) $rowid);
1836
1837 dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
1838 $result = $this->db->query($sql);
1839 if ($result) {
1840 if (is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used
1841 $contractline = new ContratLigne($this->db);
1842 $contractline->fetch($rowid);
1843
1844 // We replace values in $contractline->array_options only for entries defined into $array_options
1845 foreach ($array_options as $key => $value) {
1846 $contractline->array_options[$key] = $array_options[$key];
1847 }
1848
1849 $result = $contractline->insertExtraFields();
1850 if ($result < 0) {
1851 $this->errors[] = $contractline->error;
1852 $error++;
1853 }
1854 }
1855
1856 // Update column llx_contrat.denormalized_lower_panned_end_date with next expiration date of an open contract
1857 $sqltoupdatecontract = "UPDATE ".MAIN_DB_PREFIX."contrat";
1858 $sqltoupdatecontract .= " SET denormalized_lower_planned_end_date = (SELECT MIN(date_fin_validite) FROM ".MAIN_DB_PREFIX."contratdet as cd WHERE cd.fk_contrat = ".((int) $this->id)." AND cd.statut = ".ContratLigne::STATUS_OPEN.")";
1859 $sqltoupdatecontract .= " WHERE rowid = ".((int) $this->id);
1860 $resqltoupdatecontract = $this->db->query($sqltoupdatecontract);
1861 if (!$resqltoupdatecontract) {
1862 $this->error = $this->db->lasterror();
1863 $this->db->rollback();
1864 return -1;
1865 }
1866
1867 if (empty($error)) {
1868 // Call trigger
1869 $this->context['line_id'] = $rowid;
1870 $result = $this->call_trigger('LINECONTRACT_MODIFY', $user);
1871 if ($result < 0) {
1872 $this->db->rollback();
1873 return -3;
1874 }
1875 // End call triggers
1876
1877 $this->db->commit();
1878 return 1;
1879 } else {
1880 $this->db->rollback();
1881 return -1;
1882 }
1883 } else {
1884 $this->db->rollback();
1885 $this->error = $this->db->error();
1886 dol_syslog(get_class($this)."::updateline Erreur -1");
1887 return -1;
1888 }
1889 }
1890
1898 public function deleteLine($idline, User $user)
1899 {
1900 $error = 0;
1901
1902 if ($this->statut >= 0) {
1903 if ($this->id > 0) {
1904 // The line must belong to this contract
1905 $contractline = new ContratLigne($this->db);
1906 if ($contractline->fetch($idline) <= 0 || (int) $contractline->fk_contrat !== (int) $this->id) {
1907 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1908 return -1;
1909 }
1910 }
1911
1912 // Call trigger
1913 $this->context['line_id'] = $idline;
1914 $result = $this->call_trigger('LINECONTRACT_DELETE', $user);
1915 if ($result < 0) {
1916 return -1;
1917 }
1918 // End call triggers
1919
1920 $this->db->begin();
1921
1922 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element_line;
1923 $sql .= " WHERE rowid = ".((int) $idline);
1924
1925 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
1926 $resql = $this->db->query($sql);
1927 if (!$resql) {
1928 $this->error = "Error ".$this->db->lasterror();
1929 $error++;
1930 }
1931
1932 if (!$error) {
1933 // Remove extrafields
1934 $contractline = new ContratLigne($this->db);
1935 $contractline->id = $idline;
1936 $result = $contractline->deleteExtraFields();
1937 if ($result < 0) {
1938 $error++;
1939 $this->error = "Error ".get_class($this)."::deleteline deleteExtraFields error -4 ".$contractline->error;
1940 }
1941 }
1942
1943 if (!$error) {
1944 // Renumber remaining lines so rang stays a contiguous 1..N sequence.
1945 // Without this, a deleted line leaves a permanent gap that breaks
1946 // the up/down swap logic (updateLineUp/updateLineDown) for any pair
1947 // of lines that no longer sit at an exact rang+/-1 from each other.
1948 $result = $this->line_order(true, 'ASC', false);
1949 if ($result < 0) {
1950 $error++;
1951 $this->error = "Error ".get_class($this)."::deleteline line_order error";
1952 }
1953 }
1954
1955 if (empty($error)) {
1956 $this->db->commit();
1957 return 1;
1958 } else {
1959 dol_syslog(get_class($this)."::deleteline ERROR:".$this->error, LOG_ERR);
1960 $this->db->rollback();
1961 return -1;
1962 }
1963 } else {
1964 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1965 return -2;
1966 }
1967 }
1968
1969
1970 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1978 public function update_statut($user)
1979 {
1980 // phpcs:enable
1981 dol_syslog(__METHOD__." is deprecated", LOG_WARNING);
1982
1983 // If draft, we keep it (should not happen)
1984 if ($this->statut == 0) {
1985 return 1;
1986 }
1987
1988 // Load $this->lines array
1989 // $this->fetch_lines();
1990
1991 // $newstatut=1;
1992 // foreach($this->lines as $key => $contractline)
1993 // {
1994 // // if ($contractline) // Loop on each service
1995 // }
1996
1997 return 1;
1998 }
1999
2000
2007 public function getLibStatut($mode)
2008 {
2009 return $this->LibStatut($this->statut, $mode);
2010 }
2011
2012 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2020 public function LibStatut($status, $mode)
2021 {
2022 // phpcs:enable
2023 global $langs;
2024
2025 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
2026 global $langs;
2027 $langs->load("contracts");
2028 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft');
2029 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated');
2030 $this->labelStatus[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed');
2031 $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft');
2032 $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated');
2033 $this->labelStatusShort[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed');
2034 }
2035
2036 $statusType = 'status'.$status;
2037 if ($status == self::STATUS_VALIDATED) {
2038 $statusType = 'status6';
2039 }
2040
2041 if ($mode == 4 || $mode == 6 || $mode == 7) {
2042 $text = '';
2043 if ($mode == 4) {
2044 $text = '<span class="hideonsmartphone">';
2045 $text .= ($this->nbofserviceswait + $this->nbofservicesopened + $this->nbofservicesexpired + $this->nbofservicesclosed);
2046 $text .= ' '.$langs->trans("Services");
2047 $text .= ': &nbsp; &nbsp; ';
2048 $text .= '</span>';
2049 }
2050 $text .= ($mode == 7 ? '<span class="nowraponall">' : '');
2051 $text .= ($mode != 7 || $this->nbofserviceswait > 0) ? ($this->nbofserviceswait.ContratLigne::LibStatut(0, 3, -1, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesopened || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2052 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2053 $text .= ($mode != 7 || $this->nbofservicesopened > 0) ? ($this->nbofservicesopened.ContratLigne::LibStatut(4, 3, 0, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2054 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2055 $text .= ($mode != 7 || $this->nbofservicesexpired > 0) ? ($this->nbofservicesexpired.ContratLigne::LibStatut(4, 3, 1, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesclosed) ? ' &nbsp; ' : '') : '';
2056 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : '');
2057 $text .= ($mode != 7 || $this->nbofservicesclosed > 0) ? ($this->nbofservicesclosed.ContratLigne::LibStatut(5, 3, -1, 'class="marginleft2"')) : '';
2058 $text .= ($mode == 7 ? '</span>' : '');
2059 if (getDolGlobalString('CONTRACT_SHOW_SIGNATURE_STATUS_WITH_SERVICE_STATUS')) {
2060 $text .= is_null($this->signed_status) ? '' : ' '.$this->getLibSignedStatus(5);
2061 }
2062 return $text;
2063 } else {
2064 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
2065 }
2066 }
2067
2074 public function getTooltipContentArray($params)
2075 {
2076 global $conf, $langs, $user;
2077
2078 $langs->load('contracts');
2079
2080 $datas = [];
2081 $nofetch = !empty($params['nofetch']);
2082
2083 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2084 return ['optimize' => $langs->trans("ShowContract")];
2085 }
2086 if ($user->hasRight('contrat', 'lire')) {
2087 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Contract").'</u>';
2088 /* Status of a contract is status of all services, so disabled
2089 if (isset($this->statut)) {
2090 $label .= ' '.$this->getLibStatut(5);
2091 }*/
2092 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.($this->ref ? $this->ref : $this->id);
2093 if (!$nofetch) {
2094 $langs->load('companies');
2095 if (empty($this->thirdparty)) {
2096 $this->fetch_thirdparty();
2097 }
2098 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
2099 }
2100 $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '. $this->ref_customer;
2101 if (!$nofetch) {
2102 $langs->load('project');
2103 if (is_null($this->project) || (is_object($this->project) && empty($this->project->id))) {
2104 $res = $this->fetchProject();
2105 if ($res > 0 && $this->project instanceof Project) {
2106 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
2107 }
2108 }
2109 }
2110 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
2111 if (!empty($this->total_ht)) {
2112 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2113 }
2114 if (!empty($this->total_tva)) {
2115 $datas['vatamount'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2116 }
2117 if (!empty($this->total_ttc)) {
2118 $datas['amounttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2119 }
2120 }
2121 return $datas;
2122 }
2123
2133 public function getNomUrl($withpicto = 0, $maxlength = 0, $notooltip = 0, $save_lastsearch_value = -1)
2134 {
2135 global $langs, $user, $hookmanager;
2136
2137 $result = '';
2138
2139 $baseurl = DOL_URL_ROOT . '/contrat/card.php';
2140 $query = ['id' => $this->id];
2141
2142 //if ($option !== 'nolink')
2143 //{
2144 // Add param to save lastsearch_values or not
2145 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2146 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2147 $add_save_lastsearch_values = 1;
2148 }
2149 if ($add_save_lastsearch_values) {
2150 $query = array_merge($query, ['save_lastsearch_values' => 1]);
2151 }
2152 //}
2153 $url = dolBuildUrl($baseurl, $query);
2154 $params = [
2155 'id' => $this->id,
2156 'objecttype' => $this->element,
2157 'nofetch' => 1,
2158 ];
2159 $classfortooltip = 'classfortooltip';
2160 $dataparams = '';
2161 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2162 $classfortooltip = 'classforajaxtooltip';
2163 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2164 $label = '';
2165 } else {
2166 $label = implode($this->getTooltipContentArray($params));
2167 }
2168
2169 $linkclose = '';
2170 if (empty($notooltip) && $user->hasRight('contrat', 'lire')) {
2171 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2172 $label = $langs->trans("ShowContract");
2173 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2174 }
2175 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2176 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2177 }
2178 $linkstart = '<a href="'.$url.'"';
2179 $linkstart .= $linkclose.'>';
2180 $linkend = '</a>';
2181
2182 $result .= $linkstart;
2183 if ($withpicto) {
2184 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
2185 }
2186 if ($withpicto != 2) {
2187 $result .= ($this->ref ? $this->ref : $this->id);
2188 }
2189 $result .= $linkend;
2190
2191 global $action;
2192 $hookmanager->initHooks(array('contractdao'));
2193 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2194 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2195 if ($reshook > 0) {
2196 $result = $hookmanager->resPrint;
2197 } else {
2198 $result .= $hookmanager->resPrint;
2199 }
2200
2201 return $result;
2202 }
2203
2210 public function info($id)
2211 {
2212 $sql = "SELECT c.rowid, c.ref, c.datec,";
2213 $sql .= " c.tms as date_modification,";
2214 $sql .= " fk_user_author";
2215 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c";
2216 $sql .= " WHERE c.rowid = ".((int) $id);
2217
2218 $result = $this->db->query($sql);
2219 if ($result) {
2220 if ($this->db->num_rows($result)) {
2221 $obj = $this->db->fetch_object($result);
2222
2223 $this->id = $obj->rowid;
2224 $this->ref = (!$obj->ref) ? $obj->rowid : $obj->ref;
2225
2226 $this->user_creation_id = $obj->fk_user_author;
2227 $this->date_creation = $this->db->jdate($obj->datec);
2228 $this->date_modification = $this->db->jdate($obj->date_modification);
2229 }
2230
2231 $this->db->free($result);
2232 } else {
2233 dol_print_error($this->db);
2234 }
2235 }
2236
2237 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2244 public function array_detail($status = -1)
2245 {
2246 // phpcs:enable
2247 $tab = array();
2248
2249 $sql = "SELECT cd.rowid";
2250 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
2251 $sql .= " WHERE fk_contrat =".((int) $this->id);
2252 if ($status >= 0) {
2253 $sql .= " AND statut = ".((int) $status);
2254 }
2255
2256 dol_syslog(get_class($this)."::array_detail()", LOG_DEBUG);
2257 $resql = $this->db->query($sql);
2258 if ($resql) {
2259 $num = $this->db->num_rows($resql);
2260 $i = 0;
2261 while ($i < $num) {
2262 $obj = $this->db->fetch_object($resql);
2263 $tab[$i] = $obj->rowid;
2264 $i++;
2265 }
2266 return $tab;
2267 } else {
2268 $this->error = $this->db->error();
2269 return -1;
2270 }
2271 }
2272
2282 public function getListOfContracts($option = 'all', $status = [], $product_categories = [], $line_status = [])
2283 {
2284 $tab = array();
2285
2286 $sql = "SELECT c.rowid";
2287 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c";
2288 if (!empty($product_categories) || !empty($line_status)) {
2289 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."contratdet as cd ON cd.fk_contrat = c.rowid";
2290 }
2291 if (!empty($product_categories)) {
2292 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."categorie_product as cp ON cp.fk_product = cd.fk_product AND cp.fk_categorie IN (".$this->db->sanitize(implode(', ', $product_categories)).")";
2293 }
2294 $sql .= " WHERE c.fk_soc =".((int) $this->socid);
2295 $sql .= ($option == 'others') ? " AND c.rowid <> ".((int) $this->id) : "";
2296 $sql .= (!empty($status)) ? " AND c.statut IN (".$this->db->sanitize(implode(', ', $status)).")" : "";
2297 $sql .= (!empty($line_status)) ? " AND cd.statut IN (".$this->db->sanitize(implode(', ', $line_status)).")" : "";
2298 $sql .= " GROUP BY c.rowid";
2299
2300 dol_syslog(get_class($this)."::getOtherContracts()", LOG_DEBUG);
2301 $resql = $this->db->query($sql);
2302 if ($resql) {
2303 $num = $this->db->num_rows($resql);
2304 $i = 0;
2305 while ($i < $num) {
2306 $obj = $this->db->fetch_object($resql);
2307 $contrat = new Contrat($this->db);
2308 $contrat->fetch($obj->rowid);
2309 $tab[$contrat->id] = $contrat;
2310 $i++;
2311 }
2312 return $tab;
2313 } else {
2314 $this->error = $this->db->lasterror();
2315 return -1;
2316 }
2317 }
2318
2319
2320 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2328 public function load_board($user, $mode)
2329 {
2330 // phpcs:enable
2331 global $conf, $langs;
2332
2333 $this->from = " FROM ".MAIN_DB_PREFIX."contrat as c";
2334 $this->from .= ", ".MAIN_DB_PREFIX."contratdet as cd";
2335 $this->from .= ", ".MAIN_DB_PREFIX."societe as s";
2336 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2337 $this->from .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2338 }
2339
2340 $now = dol_now();
2341 if ($mode == 'inactive') {
2342 $warning_delay = $conf->contract->services->inactifs->warning_delay;
2343 $datefield = 'cd.date_ouverture_prevue';
2344 } elseif ($mode == 'expired' || $mode == 'active') {
2345 $warning_delay = $conf->contract->services->expires->warning_delay;
2346 $datefield = 'cd.date_fin_validite';
2347 } else {
2348 return -1;
2349 }
2350
2351 // The count and the number of late services are computed by the database instead of reading every service. A service
2352 // is late when it has a date (planned start for inactive services, end of validity for the others) and that date is
2353 // before now minus the warning delay.
2354 $sql = "SELECT COUNT(cd.rowid) as nb,";
2355 $sql .= " SUM(CASE WHEN ".$this->db->sanitize($datefield)." IS NOT NULL AND ".$this->db->sanitize($datefield)." < '".$this->db->idate($now - $warning_delay)."' THEN 1 ELSE 0 END) as nblate";
2356 if ($mode == 'inactive') {
2357 $sql .= $this->from;
2358 $sql .= " WHERE c.statut = 1";
2359 $sql .= " AND c.rowid = cd.fk_contrat";
2360 $sql .= " AND cd.statut = 0";
2361 } elseif ($mode == 'expired') {
2362 $sql .= $this->from;
2363 $sql .= " WHERE c.statut = 1";
2364 $sql .= " AND c.rowid = cd.fk_contrat";
2365 $sql .= " AND cd.statut = 4";
2366 $sql .= " AND cd.date_fin_validite < '".$this->db->idate($now)."'";
2367 } else {
2368 $sql .= $this->from;
2369 $sql .= " WHERE c.statut = 1";
2370 $sql .= " AND c.rowid = cd.fk_contrat";
2371 $sql .= " AND cd.statut = 4";
2372 }
2373 $sql .= " AND c.fk_soc = s.rowid";
2374 $sql .= " AND c.entity = ".((int) $conf->entity);
2375 if ($user->socid) {
2376 $sql .= " AND c.fk_soc = ".((int) $user->socid);
2377 }
2378 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2379 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2380 }
2381
2382 $resql = $this->db->query($sql);
2383 if ($resql) {
2384 $langs->load("contracts");
2385
2386 if ($mode == 'inactive') {
2387 $label = $langs->trans("BoardNotActivatedServices");
2388 $labelShort = $langs->trans("BoardNotActivatedServicesShort");
2389 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=0&sortfield=cd.date_fin_validite&sortorder=asc';
2390 $url_late = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=0&search_option=late';
2391 } elseif ($mode == 'expired') {
2392 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=4&filter=expired&sortfield=cd.date_fin_validite&sortorder=asc';
2393 $url_late = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=4&search_option=late';
2394 $label = $langs->trans("BoardExpiredServices");
2395 $labelShort = $langs->trans("BoardExpiredServicesShort");
2396 } else {
2397 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_status=4&sortfield=cd.date_fin_validite&sortorder=asc';
2398 $url_late = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&search_option=late';
2399 $label = $langs->trans("BoardRunningServices");
2400 $labelShort = $langs->trans("BoardRunningServicesShort");
2401 }
2402
2403 $response = new WorkboardResponse();
2404 $response->warning_delay = $warning_delay / 60 / 60 / 24;
2405 $response->label = $label;
2406 $response->labelShort = $labelShort;
2407 $response->url = $url;
2408 $response->url_late = $url_late;
2409 $response->img = img_object('', "contract");
2410
2411 $obj = $this->db->fetch_object($resql);
2412 if ($obj) {
2413 $response->nbtodo = (int) $obj->nb;
2414 $response->nbtodolate = (int) $obj->nblate;
2415 }
2416
2417 return $response;
2418 } else {
2419 dol_print_error($this->db);
2420 $this->error = $this->db->error();
2421 return -1;
2422 }
2423 }
2424
2430 public function loadStateBoard()
2431 {
2432 global $conf, $user;
2433
2434 $this->nb = array();
2435 $sanitizedclause = "WHERE";
2436
2437 $sql = "SELECT count(c.rowid) as nb";
2438 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c";
2439 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid";
2440 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2441 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
2442 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
2443 $sanitizedclause = "AND";
2444 }
2445 $sql .= " ".$sanitizedclause." c.entity = ".((int) $conf->entity);
2446
2447 $resql = $this->db->query($sql);
2448 if ($resql) {
2449 while ($obj = $this->db->fetch_object($resql)) {
2450 $this->nb["contracts"] = $obj->nb;
2451 }
2452 $this->db->free($resql);
2453 return 1;
2454 } else {
2455 dol_print_error($this->db);
2456 $this->error = $this->db->error();
2457 return -1;
2458 }
2459 }
2460
2461
2462 /* gestion des contacts d'un contrat */
2463
2469 public function getIdBillingContact()
2470 {
2471 return $this->getIdContact('external', 'BILLING');
2472 }
2473
2479 public function getIdServiceContact()
2480 {
2481 return $this->getIdContact('external', 'SERVICE');
2482 }
2483
2484
2492 public function initAsSpecimen()
2493 {
2494 global $user, $langs, $conf;
2495
2496 // Load array of products prodids
2497 $num_prods = 0;
2498 $prodids = array();
2499 $sql = "SELECT rowid";
2500 $sql .= " FROM ".MAIN_DB_PREFIX."product";
2501 $sql .= " WHERE entity IN (".getEntity('product').")";
2502 $sql .= " AND tosell = 1";
2503 $sql .= $this->db->plimit(100);
2504
2505 $resql = $this->db->query($sql);
2506 if ($resql) {
2507 $num_prods = $this->db->num_rows($resql);
2508 $i = 0;
2509 while ($i < $num_prods) {
2510 $i++;
2511 $row = $this->db->fetch_row($resql);
2512 $prodids[$i] = $row[0];
2513 }
2514 }
2515
2516 // Initialise parameters
2517 $this->id = 0;
2518 $this->specimen = 1;
2519
2520 $this->ref = 'SPECIMEN';
2521 $this->ref_customer = 'SPECIMENCUST';
2522 $this->ref_supplier = 'SPECIMENSUPP';
2523 $this->socid = 1;
2524 $this->status = 0;
2525 $this->date_creation = (dol_now() - 3600 * 24 * 7);
2526 $this->date_contrat = dol_now();
2527 $this->commercial_signature_id = 1;
2528 $this->commercial_suivi_id = 1;
2529 $this->note_private = 'This is a comment (private)';
2530 $this->note_public = 'This is a comment (public)';
2531 $this->fk_project = 0;
2532 // Lines
2533 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
2534 $xnbp = 0;
2535 while ($xnbp < $nbp) {
2536 $line = new ContratLigne($this->db);
2537 $line->qty = 1;
2538 $line->subprice = 100;
2539 $line->tva_tx = 19.6;
2540 $line->remise_percent = 10;
2541 $line->total_ht = 90;
2542 $line->total_ttc = 107.64; // 90 * 1.196
2543 $line->total_tva = 17.64;
2544 $line->date_start = dol_now() - 500000;
2545 $line->date_start_real = dol_now() - 200000;
2546 $line->date_end = dol_now() + 500000;
2547 $line->date_end_real = dol_now() - 100000;
2548 if ($num_prods > 0) {
2549 $prodid = mt_rand(1, $num_prods);
2550 $line->fk_product = $prodids[$prodid];
2551 }
2552 $this->lines[$xnbp] = $line;
2553 $xnbp++;
2554 }
2555
2556 return 1;
2557 }
2558
2564 public function getLinesArray()
2565 {
2566 return $this->fetch_lines();
2567 }
2568
2574 public function getTicketsArray()
2575 {
2576 global $user;
2577
2578 $ticket = new Ticket($this->db);
2579 $nbTicket = $ticket->fetchAll($user, 'ASC', 't.datec', 0, 0, 0, array('t.fk_contract' => $this->id));
2580
2581 return ($nbTicket < 0 ? $nbTicket : $ticket->lines);
2582 }
2583
2584
2596 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2597 {
2598 global $langs;
2599
2600 if (!dol_strlen($modele)) {
2601 $modele = ''; // No doc template/generation by default
2602
2603 if (!empty($this->model_pdf)) {
2604 $modele = $this->model_pdf;
2605 } elseif (getDolGlobalString('CONTRACT_ADDON_PDF')) {
2606 $modele = getDolGlobalString('CONTRACT_ADDON_PDF');
2607 }
2608 }
2609
2610 if (empty($modele)) {
2611 return 0;
2612 } else {
2613 $langs->load("contracts");
2614 $outputlangs->load("products");
2615
2616 $modelpath = "core/modules/contract/doc/";
2617 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2618 }
2619 }
2620
2629 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2630 {
2631 $tables = array(
2632 'contrat'
2633 );
2634
2635 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2636 }
2637
2646 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
2647 {
2648 $tables = array(
2649 'contratdet'
2650 );
2651
2652 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
2653 }
2654
2663 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
2664 {
2665 global $db, $langs, $conf, $hookmanager, $extrafields;
2666
2667 dol_include_once('/projet/class/project.class.php');
2668
2669 $error = 0;
2670
2671 $this->fetch($this->id);
2672
2673 // Load dest object
2674 $clonedObj = clone $this;
2675 $clonedObj->socid = $socid;
2676
2677 $this->db->begin();
2678
2679 $objsoc = new Societe($this->db);
2680
2681 $objsoc->fetch($clonedObj->socid);
2682
2683 // Clean data
2684 $clonedObj->statut = 0;
2685 $clonedObj->status = 0;
2686 // Clean extrafields
2687 if (is_array($clonedObj->array_options) && count($clonedObj->array_options) > 0) {
2688 $extrafields->fetch_name_optionals_label($this->table_element);
2689 foreach ($clonedObj->array_options as $key => $option) {
2690 $shortkey = preg_replace('/options_/', '', $key);
2691 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) {
2692 unset($clonedObj->array_options[$key]);
2693 }
2694 }
2695 }
2696
2697 if (!getDolGlobalString('CONTRACT_ADDON') || !is_readable(DOL_DOCUMENT_ROOT."/core/modules/contract/" . getDolGlobalString('CONTRACT_ADDON').".php")) {
2698 $this->error = 'ErrorSetupNotComplete';
2699 dol_syslog($this->error);
2700 return -1;
2701 }
2702
2703 // Set ref
2704 require_once DOL_DOCUMENT_ROOT."/core/modules/contract/" . getDolGlobalString('CONTRACT_ADDON').'.php';
2705 $obj = getDolGlobalString('CONTRACT_ADDON');
2706 $modContract = new $obj();
2707 '@phan-var-force ModelNumRefContracts $modContract';
2709 $clonedObj->ref = $modContract->getNextValue($objsoc, $clonedObj);
2710
2711 // get extrafields so they will be clone
2712 foreach ($this->lines as $line) {
2713 $line->fetch_optionals($line->id);
2714 }
2715
2716 // Create clone
2717 $clonedObj->context['createfromclone'] = 'createfromclone';
2718 $result = $clonedObj->create($user);
2719 if ($result < 0) {
2720 $error++;
2721 $this->setErrorsFromObject($clonedObj);
2722 } else {
2723 // copy external contacts if same company
2724 if ($this->socid == $clonedObj->socid) {
2725 if ($clonedObj->copy_linked_contact($this, 'external') < 0) {
2726 $error++;
2727 }
2728 }
2729 }
2730
2731 if (!$error) {
2732 foreach ($this->lines as $line) {
2733 // Preserve the original entry mode of the line. Contrat::addline() stores subprice from the
2734 // $pu_ht argument as-is (like the card, which pre-computes it), so we pass the stored HT and
2735 // flag TTC + subprice_ttc so the total is computed from the typed value (no rounding drift).
2736 $line_price_base_type = $line->getPriceBaseType();
2737 $result = $clonedObj->addline($line->description, (float) $line->subprice, $line->qty, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, $line->fk_product, $line->remise_percent, $line->date_start, $line->date_cloture, $line_price_base_type, (float) $line->subprice_ttc, $line->info_bits, $line->fk_fournprice, $line->pa_ht, $line->array_options, $line->fk_unit, $line->rang);
2738 if ($result < 0) {
2739 $error++;
2740 $this->setErrorsFromObject($clonedObj);
2741 }
2742 }
2743 }
2744
2745 if (!$error) {
2746 // Hook of thirdparty module
2747 if (is_object($hookmanager)) {
2748 $parameters = array(
2749 'objFrom' => $this,
2750 'clonedObj' => $clonedObj
2751 );
2752 $action = '';
2753 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $clonedObj, $action); // Note that $action and $object may have been modified by some hooks
2754 if ($reshook < 0) {
2755 $this->setErrorsFromObject($hookmanager);
2756 $error++;
2757 }
2758 }
2759 }
2760
2761 unset($clonedObj->context['createfromclone']);
2762
2763 // End
2764 if (!$error) {
2765 $this->db->commit();
2766 return $clonedObj->id;
2767 } else {
2768 $this->db->rollback();
2769 return -1;
2770 }
2771 }
2772
2773
2783 public function doAutoRenewContracts($thirdparty_id = 0, $delayindaysshort = 0)
2784 {
2785 global $langs, $user;
2786
2787 $langs->load("agenda");
2788
2789 $now = dol_now();
2790
2791 $enddatetoscan = dol_time_plus_duree($now, -1 * abs($delayindaysshort), 'd');
2792
2793 $error = 0;
2794 $this->output = '';
2795 $this->error = '';
2796
2797 $contractlineprocessed = array();
2798 $contractignored = array();
2799 $contracterror = array();
2800
2801 dol_syslog(__METHOD__, LOG_DEBUG);
2802
2803 $sql = 'SELECT c.rowid, c.ref_customer, cd.rowid as lid, cd.date_fin_validite, p.duration';
2804 $sql .= ' FROM '.MAIN_DB_PREFIX.'contrat as c, '.MAIN_DB_PREFIX.'contratdet as cd';
2805 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON p.rowid = cd.fk_product';
2806 $sql .= ' WHERE cd.fk_contrat = c.rowid';
2807 $sql .= " AND date_format(cd.date_fin_validite, '%Y-%m-%d') <= date_format('".$this->db->idate($enddatetoscan)."', '%Y-%m-%d')";
2808 $sql .= " AND cd.statut = 4";
2809 if ($thirdparty_id > 0) {
2810 $sql .= " AND c.fk_soc = ".((int) $thirdparty_id);
2811 }
2812 //print $sql;
2813
2814 $resql = $this->db->query($sql);
2815 if ($resql) {
2816 $num = $this->db->num_rows($resql);
2817
2818 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
2819 include_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2820
2821 $i = 0;
2822 while ($i < $num) {
2823 $obj = $this->db->fetch_object($resql);
2824 if ($obj) {
2825 if (!empty($contractlineprocessed[$obj->lid]) || !empty($contractignored[$obj->rowid]) || !empty($contracterror[$obj->rowid])) {
2826 continue;
2827 }
2828
2829 // Load contract
2830 $object = new Contrat($this->db);
2831 $object->fetch($obj->rowid); // fetch also lines
2832
2833 if ($object->id <= 0) {
2834 $error++;
2835 $this->errors[] = 'Failed to load contract with id='.$obj->rowid;
2836 continue;
2837 }
2838
2839 dol_syslog("* Process contract line in doRenewalContracts for contract id=".$object->id." ref=".$object->ref." ref_customer=".$object->ref_customer." contract line id=".$obj->lid);
2840
2841 // Update expiration date of line
2842 $expirationdate = $this->db->jdate($obj->date_fin_validite);
2843 $duration_value = preg_replace('/[^0-9]/', '', $obj->duration);
2844 $duration_unit = preg_replace('/\d/', '', $obj->duration);
2845
2846 // Load linked ->linkedObjects (objects linked)
2847 // @TODO Comment this line and then make the search if there is n open invoice(s) by doing a dedicated SQL COUNT request to fill $contractcanceled.
2848 $object->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 1);
2849
2850 // Test if there is at least 1 open invoice
2851 if (isset($object->linkedObjects['facture']) && is_array($object->linkedObjects['facture']) && count($object->linkedObjects['facture']) > 0) {
2852 // Sort array of linked invoices by ascending date
2853 usort($object->linkedObjects['facture'], array('Contrat', 'contractCmpDate'));
2854 //dol_sort_array($object->linkedObjects['facture'], 'date');
2855
2856 $someinvoicenotpaid = 0;
2857 foreach ($object->linkedObjects['facture'] as $idinvoice => $invoice) {
2858 if ($invoice->statut == Facture::STATUS_DRAFT) {
2859 continue;
2860 } // Draft invoice are not invoice not paid
2861
2862 if (empty($invoice->paye)) {
2863 $someinvoicenotpaid++;
2864 }
2865 }
2866 if ($someinvoicenotpaid) {
2867 $this->output .= 'Contract '.$object->ref.' is qualified for renewal but there is '.$someinvoicenotpaid.' invoice(s) unpayed so we cancel renewal'."\n";
2868 $contractignored[$object->id] = $object->ref;
2869 continue;
2870 }
2871 }
2872
2873 if ($expirationdate && $expirationdate < $enddatetoscan) {
2874 dol_syslog("Define the newdate of end of services from expirationdate=".$expirationdate);
2875 $newdate = $expirationdate;
2876 $protecti = 0; // $protecti is to avoid infinite loop
2877 while ($newdate < $enddatetoscan && $protecti < 1000) {
2878 $newdate = dol_time_plus_duree($newdate, (int) $duration_value, $duration_unit);
2879 $protecti++;
2880 }
2881
2882 if ($protecti < 1000) { // If not, there is a pb
2883 // We will update the end of date of contrat, so first we refresh contract data
2884 dol_syslog("We will update the end of date of contract with newdate = ".dol_print_date($newdate, 'dayhourrfc'));
2885
2886 $this->db->begin();
2887
2888 $errorforlocaltransaction = 0;
2889
2890 $label = 'Renewal of contrat '.$object->ref.' line '.$obj->lid;
2891 $comment = 'Renew date of contract '.$object->ref.' line '.$obj->lid.' by doAutoRenewContracts';
2892
2893 $sqlupdate = 'UPDATE '.MAIN_DB_PREFIX."contratdet SET date_fin_validite = '".$this->db->idate($newdate)."'";
2894 $sqlupdate .= ' WHERE rowid = '.((int) $obj->lid);
2895 $resqlupdate = $this->db->query($sqlupdate);
2896 if ($resqlupdate) {
2897 $contractlineprocessed[$obj->lid] = $object->ref;
2898
2899 // Update column llx_contrat.denormalized_lower_panned_end_date with next expiration date of an open contract
2900 $sqltoupdatecontract = "UPDATE ".MAIN_DB_PREFIX."contrat";
2901 $sqltoupdatecontract .= " SET denormalized_lower_planned_end_date = (SELECT MIN(date_fin_validite) FROM ".MAIN_DB_PREFIX."contratdet as cd WHERE cd.fk_contrat = ".((int) $object->id)." AND cd.statut = ".ContratLigne::STATUS_OPEN.")";
2902 $sqltoupdatecontract .= " WHERE rowid = ".((int) $object->id);
2903 $resqltoupdatecontract = $this->db->query($sqltoupdatecontract);
2904 if (!$resqltoupdatecontract) {
2905 $this->error = $this->db->lasterror();
2906 $this->db->rollback();
2907 return -1;
2908 }
2909
2910 $actioncode = 'RENEW_CONTRACT';
2911 $now = dol_now();
2912
2913 // Create an event
2914 $actioncomm = new ActionComm($this->db);
2915 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2916 $actioncomm->code = 'AC_'.$actioncode;
2917 $actioncomm->label = $label;
2918 $actioncomm->datep = $now;
2919 $actioncomm->datef = $now;
2920 $actioncomm->percentage = -1; // Not applicable
2921 $actioncomm->socid = $object->socid;
2922 $actioncomm->authorid = $user->id; // User saving action
2923 $actioncomm->userownerid = $user->id; // Owner of action
2924 $actioncomm->fk_element = $object->id;
2925 $actioncomm->elementid = $object->id;
2926 $actioncomm->elementtype = 'contract';
2927 $actioncomm->note_private = $comment;
2928
2929 $ret = $actioncomm->create($user); // User creating action
2930 } else {
2931 $contracterror[$object->id] = $object->ref;
2932
2933 $error++;
2934 $errorforlocaltransaction++;
2935 $this->error = $this->db->lasterror();
2936 }
2937
2938 if (! $errorforlocaltransaction) {
2939 $this->db->commit();
2940 } else {
2941 $this->db->rollback();
2942 }
2943 } else {
2944 $error++;
2945 $this->error = "Bad value for newdate in doAutoRenewContracts - expirationdate=".$expirationdate." enddatetoscan=".$enddatetoscan." duration_value=".$duration_value." duration_unit=".$duration_value;
2946 dol_syslog($this->error, LOG_ERR);
2947 }
2948 }
2949 }
2950 $i++;
2951 }
2952 } else {
2953 $error++;
2954 $this->error = $this->db->lasterror();
2955 }
2956
2957 $this->output .= count($contractlineprocessed).' contract line(s) with end date before '.dol_print_date($enddatetoscan, 'day').' were renewed'.(count($contractlineprocessed) > 0 ? ' : '.implode(',', $contractlineprocessed) : '');
2958
2959 return ($error ? 1 : 0);
2960 }
2961
2969 public static function contractCmpDate($a, $b)
2970 {
2971 if ($a->date == $b->date) {
2972 return strcmp((string) $a->id, (string) $b->id);
2973 }
2974 return ($a->date < $b->date) ? -1 : 1;
2975 }
2976
2984 public function getKanbanView($option = '', $arraydata = null)
2985 {
2986 global $langs;
2987
2988 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2989
2990 $return = '<div class="box-flex-item box-flex-grow-zero">';
2991 $return .= '<div class="info-box info-box-sm">';
2992 $return .= '<span class="info-box-icon bg-infobox-action">';
2993 $return .= img_picto('', $this->picto);
2994 $return .= '</span>';
2995 $return .= '<div class="info-box-content">';
2996 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl() . '</span>';
2997 if ($selected >= 0) {
2998 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2999 }
3000 if (!empty($arraydata['thirdparty']) && $arraydata['thirdparty'] instanceof Societe) {
3001 $tmpthirdparty = $arraydata['thirdparty'];
3002 '@phan-var-force Societe $tmpthirdparty';
3003 $return .= '<br><div class="info-box-label inline-block valignmiddle">'.$tmpthirdparty->getNomUrl(1).'</div>';
3004 }
3005 $return .= '<br><span class="opacitymedium valignmiddle">'.$langs->trans("DateContract").' : </span><span class="info-box-label valignmiddle">'.dol_print_date($this->date_contrat, 'day').'</span>';
3006 $return .= '<br><div class="info-box-status valignmiddle">'.$this->getLibStatut(7).'</div>';
3007 $return .= '</div>';
3008 $return .= '</div>';
3009 $return .= '</div>';
3010
3011 return $return;
3012 }
3013
3021 public function getTotalizedLines($statut, int $expired)
3022 {
3023 $sql = "SELECT SUM(cd.qty) as total_qty, SUM(cd.total_ht) as total_ht, SUM(cd.total_tva) as total_tva, SUM(cd.total_ttc) as total_ttc,";
3024 $sql .= " SUM(cd.total_localtax1) as total_localtax1, SUM(cd.total_localtax2) as total_localtax2";
3025 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
3026 $sql .= " WHERE cd.fk_contrat =".((int) $this->id);
3027 if ($statut >= 0) {
3028 $sql .= " AND cd.statut = ".((int) $statut);
3029 if ($expired > 0) {
3030 $sql .= " AND cd.date_fin_validite < '".$this->db->idate(dol_now())."'";
3031 } else {
3032 $sql .= " AND cd.date_fin_validite >= '".$this->db->idate(dol_now())."'";
3033 }
3034 }
3035 $ret = $this->db->query($sql);
3036 $response = array();
3037 if ($ret) {
3038 $obj = $this->db->fetch_object($ret);
3039 $response['total_qty'] = $obj->total_qty;
3040 $response['total_ht'] = $obj->total_ht;
3041 $response['total_tva'] = $obj->total_tva;
3042 $response['total_localtax1'] = $obj->total_localtax1;
3043 $response['total_localtax2'] = $obj->total_localtax2;
3044 $response['total_ttc'] = $obj->total_ttc;
3045 } else {
3046 dol_print_error($this->db);
3047 $this->error = $this->db->lasterror();
3048 return -1;
3049 }
3050 return $response;
3051 }
3052
3053 // @Todo getLibSignedStatus, LibSignedStatus
3054
3064 public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
3065 {
3066 return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
3067 }
3068
3083 public function sendReminderForExpiredServices($daysbeforeendlist = '10', $fk_product = 0)
3084 {
3085 global $conf, $langs, $mysoc, $user;
3086
3087 $error = 0;
3088 $this->output = '';
3089 $this->error = '';
3090
3091 $blockingerrormsg = '';
3092
3093 if (!isModEnabled('contract')) { // Should not happen. If module disabled, cron job should not be visible.
3094 $langs->load("agenda");
3095 $this->output = $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Contract"));
3096 return 0;
3097 }
3098
3099 $now = dol_now();
3100 $nbok = 0;
3101 $nbko = 0;
3102
3103 $listoflinesok = array();
3104 $listoflinesko = array();
3105
3106 $arraydaysbeforeend = explode(';', $daysbeforeendlist);
3107 foreach ($arraydaysbeforeend as $daysbeforeend) { // Loop on each delay
3108 dol_syslog(__METHOD__.' - Process delta = '.$daysbeforeend, LOG_DEBUG);
3109
3110 if (!is_numeric($daysbeforeend)) {
3111 $blockingerrormsg = "Value for delta is not a numeric value";
3112 $nbko++;
3113 break;
3114 }
3115
3116 $tmp = dol_getdate($now);
3117 $datetosearchfor = dol_time_plus_duree(dol_mktime(0, 0, 0, $tmp['mon'], $tmp['mday'], $tmp['year'], 'tzserver'), (int) $daysbeforeend, 'd');
3118 $datetosearchforend = dol_time_plus_duree(dol_mktime(23, 59, 59, $tmp['mon'], $tmp['mday'], $tmp['year'], 'tzserver'), (int) $daysbeforeend, 'd');
3119
3120 $sql = "SELECT cd.rowid, cd.fk_contrat";
3121 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd";
3122 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."contrat as c ON c.rowid = cd.fk_contrat";
3123 $sql .= " WHERE c.entity = ".((int) $conf->entity); // Do not use getEntity('contract') here, we want the batch to be on its entity only
3124 $sql .= " AND cd.statut = ".((int) ContratLigne::STATUS_OPEN);
3125 $sql .= " AND cd.date_fin_validite >= '".$this->db->idate($datetosearchfor)."'";
3126 $sql .= " AND cd.date_fin_validite <= '".$this->db->idate($datetosearchforend)."'";
3127 if ((int) $fk_product > 0) {
3128 $sql .= " AND cd.fk_product = ".((int) $fk_product);
3129 }
3130
3131 $resql = $this->db->query($sql);
3132 if ($resql) {
3133 $num_rows = $this->db->num_rows($resql);
3134
3135 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
3136 include_once DOL_DOCUMENT_ROOT.'/contrat/class/contratligne.class.php';
3137 $formmail = new FormMail($this->db);
3138
3139 $i = 0;
3140 while ($i < $num_rows) {
3141 $obj = $this->db->fetch_object($resql);
3142
3143 $contractline = new ContratLigne($this->db);
3144 $contractline->fetch($obj->rowid);
3145
3146 $contractstatic = new Contrat($this->db);
3147 $contractstatic->fetch($obj->fk_contrat);
3148 $thirdpartyres = $contractstatic->fetch_thirdparty();
3149
3150 if ($thirdpartyres <= 0 || empty($contractstatic->thirdparty->email)) {
3151 $nbko++;
3152 $listoflinesko[$contractline->id] = $contractline->id;
3153 } else {
3154 $languagefromcountrycode = getLanguageCodeFromCountryCode($contractstatic->thirdparty->country_code);
3155 $languagecodetouse = (empty($contractstatic->thirdparty->default_lang) ? ($languagefromcountrycode ? $languagefromcountrycode : $mysoc->default_lang) : $contractstatic->thirdparty->default_lang);
3156
3157 $outputlangs = new Translate('', $conf);
3158 $outputlangs->setDefaultLang($languagecodetouse);
3159 $outputlangs->loadLangs(array("main", "contracts"));
3160 dol_syslog("sendReminderForExpiredServices Language for thirdparty id ".$contractstatic->thirdparty->id." set to ".$outputlangs->defaultlang." mysoc->default_lang=".$mysoc->default_lang);
3161
3162 $arraydefaultmessage = null;
3163 $labeltouse = getDolGlobalString('CONTRACT_EMAIL_TEMPLATE_REMIND_EXPIRATION');
3164
3165 if (!empty($labeltouse)) {
3166 $arraydefaultmessage = $formmail->getEMailTemplate($this->db, 'contrat', $user, $outputlangs, 0, 1, $labeltouse);
3167 }
3168
3169 if (!empty($labeltouse) && is_object($arraydefaultmessage) && $arraydefaultmessage->id > 0) {
3170 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $contractstatic);
3171 complete_substitutions_array($substitutionarray, $outputlangs, $contractstatic);
3172 $substitutionarray['__CONTRACT_LINE_LABEL__'] = ($contractline->label ? $contractline->label : $contractline->description);
3173 $substitutionarray['__CONTRACT_LINE_DATE_END__'] = dol_print_date($contractline->date_end, 'day', 'tzuserrel', $outputlangs);
3174
3175 $subject = make_substitutions($arraydefaultmessage->topic, $substitutionarray, $outputlangs);
3176 $msg = make_substitutions($arraydefaultmessage->content, $substitutionarray, $outputlangs);
3177 $email_from = getDolGlobalString('CONTRACT_MAIL_FROM', $conf->email_from);
3178 $to = (string) $contractstatic->thirdparty->email;
3179 $cc = getDolGlobalString('CONTRACT_CC_MAIL_FROM');
3180
3181 $trackid = 'con'.$contractstatic->id;
3182 $moreinheader = 'X-Dolibarr-Info: sendReminderForExpiredServices'."\r\n";
3183
3184 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3185 $cmail = new CMailFile($subject, $to, $email_from, $msg, array(), array(), array(), $cc, '', 0, 1, '', '', $trackid, $moreinheader);
3186 $result = $cmail->sendfile();
3187 if (!$result) {
3188 $error++;
3189 $this->error .= $cmail->error.' ';
3190 if (!is_null($cmail->errors)) {
3191 $this->errors = array_merge($this->errors, $cmail->errors);
3192 }
3193 $nbko++;
3194 $listoflinesko[$contractline->id] = $contractline->id;
3195 } else {
3196 $nbok++;
3197 $listoflinesok[$contractline->id] = $contractline->id;
3198
3199 // Insert record of email sent, as an agenda event on the contract (same convention as other automated reminder emails)
3200 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3201
3202 $actioncomm = new ActionComm($this->db);
3203 $actioncomm->type_code = 'AC_OTH_AUTO';
3204 $actioncomm->code = 'AC_EMAIL';
3205 $actioncomm->label = $langs->transnoentities('MailSentByTo', CMailFile::getValidAddress($email_from, 4, 0, 1), CMailFile::getValidAddress($to, 4, 0, 1));
3206 $actioncomm->note_private = $msg;
3207 $actioncomm->fk_project = 0;
3208 $actioncomm->datep = $now;
3209 $actioncomm->datef = $now;
3210 $actioncomm->percentage = -1; // Not applicable
3211 $actioncomm->socid = $contractstatic->thirdparty->id;
3212 $actioncomm->contact_id = 0;
3213 $actioncomm->authorid = $user->id;
3214 $actioncomm->userownerid = $user->id;
3215 $actioncomm->email_msgid = $cmail->msgid;
3216 $actioncomm->email_from = $email_from;
3217 $actioncomm->email_sender = '';
3218 $actioncomm->email_to = $to;
3219 $actioncomm->email_subject = $subject;
3220
3221 $actioncomm->fk_element = $contractstatic->id;
3222 $actioncomm->elementid = $contractstatic->id;
3223 $actioncomm->elementtype = $contractstatic->element;
3224
3225 $actioncomm->create($user);
3226 }
3227 } else {
3228 $error++;
3229 $this->error .= "Can't find email template with label=".$labeltouse.", to use for the reminding email ";
3230
3231 $nbko++;
3232 $listoflinesko[$contractline->id] = $contractline->id;
3233
3234 break;
3235 }
3236 }
3237
3238 $i++;
3239 }
3240 } else {
3241 $this->error = $this->db->lasterror();
3242 return 1;
3243 }
3244 }
3245
3246 if ($blockingerrormsg) {
3247 $this->error = $blockingerrormsg;
3248 return 1;
3249 } else {
3250 $this->output = 'Found '.($nbok + $nbko).' contract lines to send reminder for.';
3251 $this->output .= ' Sent email successfully for '.$nbok.' lines';
3252 if ($nbko) {
3253 $this->output .= ' - Canceled for '.$nbko.' line(s) (no thirdparty email, missing template, or send error)';
3254 }
3255 }
3256
3257 if ($error) {
3258 return 1;
3259 }
3260 return 0;
3261 }
3262}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$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,...
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
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...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
getIdContact($source, $code, $status=0)
Return id of contacts for a source and a contact code.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product 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.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0, $qty=0)
Get buy price to use for margin calculation.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
static contractCmpDate($a, $b)
Used to sort lines by date.
getTotalizedLines($statut, int $expired)
Return totalized lines.
fetch($id, $ref='', $ref_customer='', $ref_supplier='', $noextrafields=0, $nolines=0)
Load a contract from database.
loadStateBoard()
Load the indicators this->nb for state board.
update_statut($user)
Update statut of contract according to services.
closeAll(User $user, $notrigger=0, $comment='', $nofetchlines=0)
Close all lines of a contract.
updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $tvatx, $localtax1tx=0.0, $localtax2tx=0.0, $date_start_real='', $date_end_real='', $price_base_type='HT', $info_bits=0, $fk_fournprice=null, $pa_ht=0, $array_options=array(), $fk_unit=null, $rang=0)
Update a contract line.
getTicketsArray()
Create an array of associated tickets.
setSignedStatus(User $user, int $status=0, int $notrigger=0, $triggercode='')
Set signed status.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
active_line($user, $line_id, $date_start, $date_end='', $comment='')
Activate a contract line.
getTooltipContentArray($params)
getTooltipContentArray
getLinesArray()
Create an array of order lines.
validate(User $user, $force_number='', $notrigger=0)
Validate a contract.
getIdBillingContact()
Return id des contacts clients de facturation.
initAsSpecimen()
Initialise an instance with random values.
close_line($user, $line_id, $date_end, $comment='')
Close a contract line.
reopen($user, $notrigger=0)
Unvalidate a contract.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
deleteLine($idline, User $user)
Delete a contract line.
array_detail($status=-1)
Return list of line rowid.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $remise_percent, $date_start, $date_end, $price_base_type='HT', $pu_ttc=0.0, $info_bits=0, $fk_fournprice=null, $pa_ht=0, $array_options=array(), $fk_unit=null, $rang=0)
Insert contract line into database.
update($user, $notrigger=0)
Update object into database.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getListOfContracts($option='all', $status=[], $product_categories=[], $line_status=[])
Return list of other contracts for the same company than current contract.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
LibStatut($status, $mode)
Return the label of a given contrat status.
activateAll($user, $date_start='', $notrigger=0, $comment='', $date_end='')
Open all lines of a contract.
__construct($db)
Constructor.
doAutoRenewContracts($thirdparty_id=0, $delayindaysshort=0)
Action executed by scheduler CAN BE A CRON TASK Loop on each contract lines and update the end of dat...
getNomUrl($withpicto=0, $maxlength=0, $notooltip=0, $save_lastsearch_value=-1)
Return clickable name (with picto eventually)
info($id)
Charge les information d'ordre info dans l'objet contrat.
getLibStatut($mode)
Return label of a contract status.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
fetch_lines($only_services=0, $loadalsotranslation=0, $noextrafields=0)
Load lines array into this->lines.
getIdServiceContact()
Return id des contacts clients de prestation.
sendReminderForExpiredServices($daysbeforeendlist='10', $fk_product=0)
Send reminders by email before a running contract service expires.
Class to manage lines of contracts.
Class to manage Dolibarr database access.
const STATUS_DRAFT
Draft status.
Class to manage a HTML form to send a unitary email Usage: $formail = new FormMail($db) $formmail->pr...
Class to manage products or services.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
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
setSignedStatusCommon(User $user, int $status, int $notrigger=0, string $triggercode='')
Set signed status & call trigger with context message.
global $mysoc
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:126
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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:65
$date_start
Variables from include:
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...
getLanguageCodeFromCountryCode($countrycode)
Return default language from country code.
setEntity($currentobject)
Set entity id to use when to create an object.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
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.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
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.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
Class to generate the form for creating a new ticket.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90