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-2021 Frédéric France <frederic.france@netlogic.fr> 13 * Copyright (C) 2015-2018 Ferran Marcet <fmarcet@2byte.es> 14 * 15 * This program is free software; you can redistribute it and/or modify 16 * it under the terms of the GNU General Public License as published by 17 * the Free Software Foundation; either version 3 of the License, or 18 * (at your option) any later version. 19 * 20 * This program is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU General Public License for more details. 24 * 25 * You should have received a copy of the GNU General Public License 26 * along with this program. If not, see <https://www.gnu.org/licenses/>. 27 */ 28 29/** 30 * \file htdocs/contrat/class/contrat.class.php 31 * \ingroup contrat 32 * \brief File of class to manage contracts 33 */ 34 35require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; 36require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php"; 37require_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php'; 38require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php'; 39 40/** 41 * Class to manage contracts 42 */ 43class Contrat extends CommonObject 44{ 45 /** 46 * @var string ID to identify managed object 47 */ 48 public $element = 'contrat'; 49 50 /** 51 * @var string Name of table without prefix where object is stored 52 */ 53 public $table_element = 'contrat'; 54 55 /** 56 * @var string Name of subtable line 57 */ 58 public $table_element_line = 'contratdet'; 59 60 /** 61 * @var string Fieldname with ID of parent key if this field has a parent 62 */ 63 public $fk_element = 'fk_contrat'; 64 65 /** 66 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png 67 */ 68 public $picto = 'contract'; 69 70 /** 71 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe 72 * @var int 73 */ 74 public $ismultientitymanaged = 1; 75 76 /** 77 * @var int Does object support extrafields ? 0=No, 1=Yes 78 */ 79 public $isextrafieldmanaged = 1; 80 81 /** 82 * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user 83 * @var integer 84 */ 85 public $restrictiononfksoc = 1; 86 87 /** 88 * {@inheritdoc} 89 */ 90 protected $table_ref_field = 'ref'; 91 92 /** 93 * Customer reference of the contract 94 * @var string 95 */ 96 public $ref_customer; 97 98 /** 99 * Supplier reference of the contract 100 * @var string 101 */ 102 public $ref_supplier; 103 104 /** 105 * Entity of the contract 106 * @var int 107 */ 108 public $entity; 109 110 /** 111 * Client id linked to the contract 112 * @var int 113 */ 114 public $socid; 115 116 public $societe; // Objet societe 117 118 /** 119 * Status of the contract 120 * @var int 121 */ 122 public $statut = 0; // 0=Draft, 123 124 public $product; 125 126 /** 127 * @var int Id of user author of the contract 128 */ 129 public $fk_user_author; 130 131 /** 132 * TODO: Which is the correct one? 133 * Author of the contract 134 * @var int 135 */ 136 public $user_author_id; 137 138 /** 139 * @var User Object user that create the contract. Set by the info method. 140 */ 141 public $user_creation; 142 143 /** 144 * @var User Object user that close the contract. Set by the info method. 145 */ 146 public $user_cloture; 147 148 /** 149 * @var integer|string Date of creation 150 */ 151 public $date_creation; 152 153 /** 154 * @var integer|string Date of last modification. Not filled until you call ->info() 155 */ 156 public $date_modification; 157 158 /** 159 * @var integer|string Date of validation 160 */ 161 public $date_validation; 162 163 /** 164 * @var integer|string Date when contract was signed 165 */ 166 public $date_contrat; 167 168 public $commercial_signature_id; 169 public $commercial_suivi_id; 170 171 /** 172 * @deprecated Use fk_project instead 173 * @see $fk_project 174 */ 175 public $fk_projet; 176 177 public $extraparams = array(); 178 179 /** 180 * @var ContratLigne[] Contract lines 181 */ 182 public $lines = array(); 183 184 public $nbofservices; 185 public $nbofserviceswait; 186 public $nbofservicesopened; 187 public $nbofservicesexpired; 188 //public $lower_planned_end_date; 189 //public $higher_planner_end_date; 190 191 /** 192 * Maps ContratLigne IDs to $this->lines indexes 193 * @var int[] 194 */ 195 protected $lines_id_index_mapper = array(); 196 197 198 /** 199 * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password') 200 * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)" 201 * 'label' the translation key. 202 * 'enabled' is a condition when the field must be managed. 203 * 'position' is the sort order of field. 204 * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0). 205 * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing) 206 * 'noteditable' says if field is not editable (1 or 0) 207 * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created. 208 * 'index' if we want an index in database. 209 * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...). 210 * 'searchall' is 1 if we want to search in this field when making a search from the quick search button. 211 * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8). 212 * 'css' is the CSS style to use on field. For example: 'maxwidth200' 213 * 'help' is a string visible as a tooltip on field 214 * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record 215 * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code. 216 * 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel") 217 * 'comment' is not used. You can store here any text of your choice. It is not used by application. 218 * 219 * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor. 220 */ 221 222 // BEGIN MODULEBUILDER PROPERTIES 223 /** 224 * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor. 225 */ 226 public $fields = array( 227 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10), 228 'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'showoncombobox'=>1, 'position'=>15), 229 'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>20), 230 'ref_supplier' =>array('type'=>'varchar(50)', 'label'=>'Ref supplier', 'enabled'=>1, 'visible'=>-1, 'position'=>25), 231 'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>30, 'index'=>1), 232 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>35), 233 'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>40), 234 'date_contrat' =>array('type'=>'datetime', 'label'=>'Date contrat', 'enabled'=>1, 'visible'=>-1, 'position'=>45), 235 'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>70), 236 'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Fk projet', 'enabled'=>1, 'visible'=>-1, 'position'=>75), 237 'fk_commercial_signature' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk commercial signature', 'enabled'=>1, 'visible'=>-1, 'position'=>80), 238 'fk_commercial_suivi' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk commercial suivi', 'enabled'=>1, 'visible'=>-1, 'position'=>85), 239 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>90), 240 'note_private' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>105), 241 'note_public' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>110), 242 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>115), 243 'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>120), 244 'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>125), 245 'ref_customer' =>array('type'=>'varchar(50)', 'label'=>'Ref customer', 'enabled'=>1, 'visible'=>-1, 'position'=>130), 246 'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>135), 247 'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'Last main doc', 'enabled'=>1, 'visible'=>-1, 'position'=>140), 248 'statut' =>array('type'=>'smallint(6)', 'label'=>'Statut', 'enabled'=>1, 'visible'=>-1, 'position'=>500, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Closed')) 249 ); 250 // END MODULEBUILDER PROPERTIES 251 252 const STATUS_DRAFT = 0; 253 const STATUS_VALIDATED = 1; 254 const STATUS_CLOSED = 2; 255 256 257 258 /** 259 * Constructor 260 * 261 * @param DoliDB $db Database handler 262 */ 263 public function __construct($db) 264 { 265 $this->db = $db; 266 } 267 268 /** 269 * Return next contract ref 270 * 271 * @param Societe $soc Thirdparty object 272 * @return string free reference for contract 273 */ 274 public function getNextNumRef($soc) 275 { 276 global $db, $langs, $conf; 277 $langs->load("contracts"); 278 279 if (!empty($conf->global->CONTRACT_ADDON)) { 280 $mybool = false; 281 282 $file = $conf->global->CONTRACT_ADDON.".php"; 283 $classname = $conf->global->CONTRACT_ADDON; 284 285 // Include file with class 286 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); 287 288 foreach ($dirmodels as $reldir) { 289 $dir = dol_buildpath($reldir."core/modules/contract/"); 290 291 // Load file with numbering class (if found) 292 $mybool |= @include_once $dir.$file; 293 } 294 295 if (!$mybool) { 296 dol_print_error('', "Failed to include file ".$file); 297 return ''; 298 } 299 300 $obj = new $classname(); 301 $numref = $obj->getNextValue($soc, $this); 302 303 if ($numref != "") { 304 return $numref; 305 } else { 306 $this->error = $obj->error; 307 dol_print_error($db, get_class($this)."::getNextValue ".$obj->error); 308 return ""; 309 } 310 } else { 311 $langs->load("errors"); 312 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Contract")); 313 return ""; 314 } 315 } 316 317 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 318 /** 319 * Activate a contract line 320 * 321 * @param User $user Objet User who activate contract 322 * @param int $line_id Id of line to activate 323 * @param int $date Opening date 324 * @param int|string $date_end Expected end date 325 * @param string $comment A comment typed by user 326 * @return int <0 if KO, >0 if OK 327 */ 328 public function active_line($user, $line_id, $date, $date_end = '', $comment = '') 329 { 330 // phpcs:enable 331 $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->active_line($user, $date, $date_end, $comment); 332 if ($result < 0) { 333 $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error; 334 $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors; 335 } 336 return $result; 337 } 338 339 340 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 341 /** 342 * Close a contract line 343 * 344 * @param User $user Objet User who close contract 345 * @param int $line_id Id of line to close 346 * @param int $date_end End date 347 * @param string $comment A comment typed by user 348 * @return int <0 if KO, >0 if OK 349 */ 350 public function close_line($user, $line_id, $date_end, $comment = '') 351 { 352 // phpcs:enable 353 $result = $this->lines[$this->lines_id_index_mapper[$line_id]]->close_line($user, $date_end, $comment); 354 if ($result < 0) { 355 $this->error = $this->lines[$this->lines_id_index_mapper[$line_id]]->error; 356 $this->errors = $this->lines[$this->lines_id_index_mapper[$line_id]]->errors; 357 } 358 return $result; 359 } 360 361 362 /** 363 * Open all lines of a contract 364 * 365 * @param User $user Object User making action 366 * @param int|string $date_start Date start (now if empty) 367 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 368 * @param string $comment Comment 369 * @return int <0 if KO, >0 if OK 370 * @see () 371 */ 372 public function activateAll($user, $date_start = '', $notrigger = 0, $comment = '') 373 { 374 if (empty($date_start)) { 375 $date_start = dol_now(); 376 } 377 378 $this->db->begin(); 379 380 $error = 0; 381 382 // Load lines 383 $this->fetch_lines(); 384 385 foreach ($this->lines as $contratline) { 386 // Open lines not already open 387 if ($contratline->statut != ContratLigne::STATUS_OPEN) { 388 $contratline->context = $this->context; 389 390 $result = $contratline->active_line($user, $date_start, -1, $comment); 391 if ($result < 0) { 392 $error++; 393 $this->error = $contratline->error; 394 $this->errors = $contratline->errors; 395 break; 396 } 397 } 398 } 399 400 if (!$error && $this->statut == 0) { 401 $result = $this->validate($user, '', $notrigger); 402 if ($result < 0) { 403 $error++; 404 } 405 } 406 407 if (!$error) { 408 $this->db->commit(); 409 return 1; 410 } else { 411 $this->db->rollback(); 412 return -1; 413 } 414 } 415 416 /** 417 * Close all lines of a contract 418 * 419 * @param User $user Object User making action 420 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 421 * @param string $comment Comment 422 * @return int <0 if KO, >0 if OK 423 * @see activateAll() 424 */ 425 public function closeAll(User $user, $notrigger = 0, $comment = '') 426 { 427 $this->db->begin(); 428 429 // Load lines 430 $this->fetch_lines(); 431 432 $now = dol_now(); 433 434 $error = 0; 435 436 foreach ($this->lines as $contratline) { 437 // Close lines not already closed 438 if ($contratline->statut != ContratLigne::STATUS_CLOSED) { 439 $contratline->date_end_real = $now; 440 $contratline->date_cloture = $now; // For backward compatibility 441 $contratline->fk_user_cloture = $user->id; 442 $contratline->statut = ContratLigne::STATUS_CLOSED; 443 $result = $contratline->close_line($user, $now, $comment, $notrigger); 444 if ($result < 0) { 445 $error++; 446 $this->error = $contratline->error; 447 $this->errors = $contratline->errors; 448 break; 449 } 450 } 451 } 452 453 if (!$error && $this->statut == 0) { 454 $result = $this->validate($user, '', $notrigger); 455 if ($result < 0) { 456 $error++; 457 } 458 } 459 460 if (!$error) { 461 $this->db->commit(); 462 return 1; 463 } else { 464 $this->db->rollback(); 465 return -1; 466 } 467 } 468 469 /** 470 * Validate a contract 471 * 472 * @param User $user Objet User 473 * @param string $force_number Reference to force on contract (not implemented yet) 474 * @param int $notrigger 1=Does not execute triggers, 0= execute triggers 475 * @return int <0 if KO, >0 if OK 476 */ 477 public function validate(User $user, $force_number = '', $notrigger = 0) 478 { 479 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 480 global $langs, $conf; 481 482 $now = dol_now(); 483 484 $error = 0; 485 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number); 486 487 488 $this->db->begin(); 489 490 $this->fetch_thirdparty(); 491 492 // A contract is validated so we can move thirdparty to status customer 493 if (empty($conf->global->CONTRACT_DISABLE_AUTOSET_AS_CLIENT_ON_CONTRACT_VALIDATION)) { 494 $result = $this->thirdparty->set_as_client(); 495 } 496 497 // Define new ref 498 if ($force_number) { 499 $num = $force_number; 500 } elseif (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life 501 $num = $this->getNextNumRef($this->thirdparty); 502 } else { 503 $num = $this->ref; 504 } 505 $this->newref = dol_sanitizeFileName($num); 506 507 if ($num) { 508 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET ref = '".$this->db->escape($num)."', statut = 1"; 509 //$sql.= ", fk_user_valid = ".$user->id.", date_valid = '".$this->db->idate($now)."'"; 510 $sql .= " WHERE rowid = ".$this->id." AND statut = 0"; 511 512 dol_syslog(get_class($this)."::validate", LOG_DEBUG); 513 $resql = $this->db->query($sql); 514 if (!$resql) { 515 dol_print_error($this->db); 516 $error++; 517 $this->error = $this->db->lasterror(); 518 } 519 520 // Trigger calls 521 if (!$error && !$notrigger) { 522 // Call trigger 523 $result = $this->call_trigger('CONTRACT_VALIDATE', $user); 524 if ($result < 0) { 525 $error++; 526 } 527 // End call triggers 528 } 529 530 if (!$error) { 531 $this->oldref = $this->ref; 532 533 // Rename directory if dir was a temporary ref 534 if (preg_match('/^[\(]?PROV/i', $this->ref)) { 535 // Now we rename also files into index 536 $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)."'"; 537 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'contract/".$this->db->escape($this->ref)."' and entity = ".$conf->entity; 538 $resql = $this->db->query($sql); 539 if (!$resql) { 540 $error++; $this->error = $this->db->lasterror(); 541 } 542 543 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments 544 $oldref = dol_sanitizeFileName($this->ref); 545 $newref = dol_sanitizeFileName($num); 546 $dirsource = $conf->contract->dir_output.'/'.$oldref; 547 $dirdest = $conf->contract->dir_output.'/'.$newref; 548 if (!$error && file_exists($dirsource)) { 549 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest); 550 551 if (@rename($dirsource, $dirdest)) { 552 dol_syslog("Rename ok"); 553 // Rename docs starting with $oldref with $newref 554 $listoffiles = dol_dir_list($conf->contract->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/')); 555 foreach ($listoffiles as $fileentry) { 556 $dirsource = $fileentry['name']; 557 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource); 558 $dirsource = $fileentry['path'].'/'.$dirsource; 559 $dirdest = $fileentry['path'].'/'.$dirdest; 560 @rename($dirsource, $dirdest); 561 } 562 } 563 } 564 } 565 } 566 567 // Set new ref and define current statut 568 if (!$error) { 569 $this->ref = $num; 570 $this->statut = 1; 571 $this->brouillon = 0; 572 $this->date_validation = $now; 573 } 574 } else { 575 $error++; 576 } 577 578 if (!$error) { 579 $this->db->commit(); 580 return 1; 581 } else { 582 $this->db->rollback(); 583 return -1; 584 } 585 } 586 587 /** 588 * Unvalidate a contract 589 * 590 * @param User $user Object User 591 * @param int $notrigger 1=Does not execute triggers, 0=execute triggers 592 * @return int <0 if KO, >0 if OK 593 */ 594 public function reopen($user, $notrigger = 0) 595 { 596 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 597 global $langs, $conf; 598 599 $now = dol_now(); 600 601 $error = 0; 602 dol_syslog(get_class($this).'::reopen user='.$user->id); 603 604 $this->db->begin(); 605 606 $this->fetch_thirdparty(); 607 608 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET statut = 0"; 609 //$sql.= ", fk_user_valid = null, date_valid = null"; 610 $sql .= " WHERE rowid = ".$this->id." AND statut = 1"; 611 612 dol_syslog(get_class($this)."::validate", LOG_DEBUG); 613 $resql = $this->db->query($sql); 614 if (!$resql) { 615 dol_print_error($this->db); 616 $error++; 617 $this->error = $this->db->lasterror(); 618 } 619 620 // Trigger calls 621 if (!$error && !$notrigger) { 622 // Call trigger 623 $result = $this->call_trigger('CONTRACT_REOPEN', $user); 624 if ($result < 0) { 625 $error++; 626 } 627 // End call triggers 628 } 629 630 // Set new ref and define current status 631 if (!$error) { 632 $this->statut = 0; 633 $this->brouillon = 1; 634 $this->date_validation = $now; 635 } 636 637 if (!$error) { 638 $this->db->commit(); 639 return 1; 640 } else { 641 $this->db->rollback(); 642 return -1; 643 } 644 } 645 646 /** 647 * Load a contract from database 648 * 649 * @param int $id Id of contract to load 650 * @param string $ref Ref 651 * @param string $ref_customer Customer ref 652 * @param string $ref_supplier Supplier ref 653 * @return int <0 if KO, 0 if not found or if two records found for same ref, Id of contract if OK 654 */ 655 public function fetch($id, $ref = '', $ref_customer = '', $ref_supplier = '') 656 { 657 $sql = "SELECT rowid, statut, ref, fk_soc,"; 658 $sql .= " ref_supplier, ref_customer,"; 659 $sql .= " ref_ext,"; 660 $sql .= " entity,"; 661 $sql .= " date_contrat as datecontrat,"; 662 $sql .= " fk_user_author,"; 663 $sql .= " fk_projet as fk_project,"; 664 $sql .= " fk_commercial_signature, fk_commercial_suivi,"; 665 $sql .= " note_private, note_public, model_pdf, extraparams"; 666 $sql .= " FROM ".MAIN_DB_PREFIX."contrat"; 667 if (!$id) { 668 $sql .= " WHERE entity IN (".getEntity('contract').")"; 669 } else { 670 $sql .= " WHERE rowid=".(int) $id; 671 } 672 if ($ref_customer) { 673 $sql .= " AND ref_customer = '".$this->db->escape($ref_customer)."'"; 674 } 675 if ($ref_supplier) { 676 $sql .= " AND ref_supplier = '".$this->db->escape($ref_supplier)."'"; 677 } 678 if ($ref) { 679 $sql .= " AND ref='".$this->db->escape($ref)."'"; 680 } 681 682 dol_syslog(get_class($this)."::fetch", LOG_DEBUG); 683 $resql = $this->db->query($sql); 684 if ($resql) { 685 $num = $this->db->num_rows($resql); 686 if ($num > 1) { 687 $this->error = 'Fetch found several records.'; 688 dol_syslog($this->error, LOG_ERR); 689 $result = -2; 690 } elseif ($num) { // $num = 1 691 $obj = $this->db->fetch_object($resql); 692 if ($obj) { 693 $this->id = $obj->rowid; 694 $this->ref = (!isset($obj->ref) || !$obj->ref) ? $obj->rowid : $obj->ref; 695 $this->ref_customer = $obj->ref_customer; 696 $this->ref_supplier = $obj->ref_supplier; 697 $this->ref_ext = $obj->ref_ext; 698 $this->entity = $obj->entity; 699 $this->statut = $obj->statut; 700 701 $this->date_contrat = $this->db->jdate($obj->datecontrat); 702 $this->date_creation = $this->db->jdate($obj->datecontrat); 703 704 $this->user_author_id = $obj->fk_user_author; 705 706 $this->commercial_signature_id = $obj->fk_commercial_signature; 707 $this->commercial_suivi_id = $obj->fk_commercial_suivi; 708 709 $this->note_private = $obj->note_private; 710 $this->note_public = $obj->note_public; 711 $this->model_pdf = $obj->model_pdf; 712 $this->modelpdf = $obj->model_pdf; // deprecated 713 714 $this->fk_projet = $obj->fk_project; // deprecated 715 $this->fk_project = $obj->fk_project; 716 717 $this->socid = $obj->fk_soc; 718 $this->fk_soc = $obj->fk_soc; 719 720 $this->extraparams = (array) json_decode($obj->extraparams, true); 721 722 $this->db->free($resql); 723 724 // Retrieve all extrafields 725 // fetch optionals attributes and labels 726 $this->fetch_optionals(); 727 728 // Lines 729 $result = $this->fetch_lines(); 730 if ($result < 0) { 731 $this->error = $this->db->lasterror(); 732 return -3; 733 } 734 735 return $this->id; 736 } 737 } else { 738 dol_syslog(get_class($this)."::fetch Contract not found"); 739 $this->error = "Contract not found"; 740 return 0; 741 } 742 } else { 743 dol_syslog(get_class($this)."::fetch Error searching contract"); 744 $this->error = $this->db->error(); 745 return -1; 746 } 747 } 748 749 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 750 /** 751 * Load lines array into this->lines. 752 * This set also nbofserviceswait, nbofservicesopened, nbofservicesexpired and nbofservicesclosed 753 * 754 * @param int $only_product Return only physical products 755 * @param int $loadalsotranslation Return translation for products 756 * 757 * @return ContratLigne[] Return array of contract lines 758 */ 759 public function fetch_lines($only_product = 0, $loadalsotranslation = 0) 760 { 761 // phpcs:enable 762 global $langs, $conf, $extrafields; 763 764 $this->nbofservices = 0; 765 $this->nbofserviceswait = 0; 766 $this->nbofservicesopened = 0; 767 $this->nbofservicesexpired = 0; 768 $this->nbofservicesclosed = 0; 769 770 $total_ttc = 0; 771 $total_vat = 0; 772 $total_ht = 0; 773 774 $now = dol_now(); 775 776 if (!is_object($extrafields)) { 777 require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php'; 778 $extrafields = new ExtraFields($this->db); 779 } 780 781 $line = new ContratLigne($this->db); 782 $extrafields->fetch_name_optionals_label($line->table_element, true); 783 784 $this->lines = array(); 785 $pos = 0; 786 787 // Selects contract lines related to a product 788 $sql = "SELECT p.label as product_label, p.description as product_desc, p.ref as product_ref, p.fk_product_type as product_type,"; 789 $sql .= " d.rowid, d.fk_contrat, d.statut, d.description, d.price_ht, 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.subprice, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht,"; 790 $sql .= " d.total_ht,"; 791 $sql .= " d.total_tva,"; 792 $sql .= " d.total_localtax1,"; 793 $sql .= " d.total_localtax2,"; 794 $sql .= " d.total_ttc,"; 795 $sql .= " d.info_bits, d.fk_product,"; 796 $sql .= " d.date_ouverture_prevue, d.date_ouverture,"; 797 $sql .= " d.date_fin_validite, d.date_cloture,"; 798 $sql .= " d.fk_user_author,"; 799 $sql .= " d.fk_user_ouverture,"; 800 $sql .= " d.fk_user_cloture,"; 801 $sql .= " d.fk_unit,"; 802 $sql .= " d.product_type as type"; 803 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as d LEFT JOIN ".MAIN_DB_PREFIX."product as p ON d.fk_product = p.rowid"; 804 $sql .= " WHERE d.fk_contrat = ".((int) $this->id); 805 $sql .= " ORDER by d.rowid ASC"; 806 807 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG); 808 $result = $this->db->query($sql); 809 if ($result) { 810 $num = $this->db->num_rows($result); 811 $i = 0; 812 813 while ($i < $num) { 814 $objp = $this->db->fetch_object($result); 815 816 $line = new ContratLigne($this->db); 817 $line->id = $objp->rowid; 818 $line->ref = $objp->rowid; 819 $line->fk_contrat = $objp->fk_contrat; 820 $line->desc = $objp->description; // Description line 821 $line->qty = $objp->qty; 822 $line->vat_src_code = $objp->vat_src_code; 823 $line->tva_tx = $objp->tva_tx; 824 $line->localtax1_tx = $objp->localtax1_tx; 825 $line->localtax2_tx = $objp->localtax2_tx; 826 $line->localtax1_type = $objp->localtax1_type; 827 $line->localtax2_type = $objp->localtax2_type; 828 $line->subprice = $objp->subprice; 829 $line->statut = $objp->statut; 830 $line->remise_percent = $objp->remise_percent; 831 $line->price_ht = $objp->price_ht; 832 $line->price = $objp->price_ht; // For backward compatibility 833 $line->total_ht = $objp->total_ht; 834 $line->total_tva = $objp->total_tva; 835 $line->total_localtax1 = $objp->total_localtax1; 836 $line->total_localtax2 = $objp->total_localtax2; 837 $line->total_ttc = $objp->total_ttc; 838 $line->fk_product = (($objp->fk_product > 0) ? $objp->fk_product : 0); 839 $line->info_bits = $objp->info_bits; 840 $line->type = $objp->type; 841 842 $line->fk_fournprice = $objp->fk_fournprice; 843 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht); 844 $line->pa_ht = $marginInfos[0]; 845 846 $line->fk_user_author = $objp->fk_user_author; 847 $line->fk_user_ouverture = $objp->fk_user_ouverture; 848 $line->fk_user_cloture = $objp->fk_user_cloture; 849 $line->fk_unit = $objp->fk_unit; 850 851 $line->ref = $objp->product_ref; // deprecated 852 $line->product_ref = $objp->product_ref; // Product Ref 853 $line->product_type = $objp->product_type; // Product Type 854 $line->product_desc = $objp->product_desc; // Product Description 855 $line->product_label = $objp->product_label; // Product Label 856 857 $line->description = $objp->description; 858 859 $line->date_start = $this->db->jdate($objp->date_ouverture_prevue); 860 $line->date_start_real = $this->db->jdate($objp->date_ouverture); 861 $line->date_end = $this->db->jdate($objp->date_fin_validite); 862 $line->date_end_real = $this->db->jdate($objp->date_cloture); 863 // For backward compatibility 864 $line->date_ouverture_prevue = $this->db->jdate($objp->date_ouverture_prevue); 865 $line->date_ouverture = $this->db->jdate($objp->date_ouverture); 866 $line->date_fin_validite = $this->db->jdate($objp->date_fin_validite); 867 $line->date_cloture = $this->db->jdate($objp->date_cloture); 868 $line->date_debut_prevue = $this->db->jdate($objp->date_ouverture_prevue); 869 $line->date_debut_reel = $this->db->jdate($objp->date_ouverture); 870 $line->date_fin_prevue = $this->db->jdate($objp->date_fin_validite); 871 $line->date_fin_reel = $this->db->jdate($objp->date_cloture); 872 873 // Retrieve all extrafields for contract 874 // fetch optionals attributes and labels 875 $line->fetch_optionals(); 876 877 // multilangs 878 if (!empty($conf->global->MAIN_MULTILANGS) && !empty($objp->fk_product) && !empty($loadalsotranslation)) { 879 $line = new Product($this->db); 880 $line->fetch($objp->fk_product); 881 $line->getMultiLangs(); 882 } 883 884 $this->lines[$pos] = $line; 885 $this->lines_id_index_mapper[$line->id] = $pos; 886 887 //dol_syslog("1 ".$line->desc); 888 //dol_syslog("2 ".$line->product_desc); 889 890 if ($line->statut == ContratLigne::STATUS_INITIAL) { 891 $this->nbofserviceswait++; 892 } 893 if ($line->statut == ContratLigne::STATUS_OPEN && (empty($line->date_fin_prevue) || $line->date_fin_prevue >= $now)) { 894 $this->nbofservicesopened++; 895 } 896 if ($line->statut == ContratLigne::STATUS_OPEN && (!empty($line->date_fin_prevue) && $line->date_fin_prevue < $now)) { 897 $this->nbofservicesexpired++; 898 } 899 if ($line->statut == ContratLigne::STATUS_CLOSED) { 900 $this->nbofservicesclosed++; 901 } 902 903 $total_ttc += $objp->total_ttc; // TODO Not saved into database 904 $total_vat += $objp->total_tva; 905 $total_ht += $objp->total_ht; 906 907 $i++; 908 $pos++; 909 } 910 $this->db->free($result); 911 } else { 912 dol_syslog(get_class($this)."::Fetch Error when reading lines of contracts linked to products"); 913 return -3; 914 } 915 916 $this->nbofservices = count($this->lines); 917 $this->total_ttc = price2num($total_ttc); // TODO For the moment value is false as value is not stored in database for line linked to products 918 $this->total_tva = price2num($total_vat); // TODO For the moment value is false as value is not stored in database for line linked to products 919 $this->total_ht = price2num($total_ht); // TODO For the moment value is false as value is not stored in database for line linked to products 920 921 return $this->lines; 922 } 923 924 /** 925 * Create a contract into database 926 * 927 * @param User $user User that create 928 * @return int <0 if KO, id of contract if OK 929 */ 930 public function create($user) 931 { 932 global $conf, $langs, $mysoc; 933 934 // Check parameters 935 $paramsok = 1; 936 if ($this->commercial_signature_id <= 0) { 937 $langs->load("commercial"); 938 $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeSignature")); 939 $paramsok = 0; 940 } 941 if ($this->commercial_suivi_id <= 0) { 942 $langs->load("commercial"); 943 $this->error .= ($this->error ? "<br>" : ''); 944 $this->error .= $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("SalesRepresentativeFollowUp")); 945 $paramsok = 0; 946 } 947 if (!$paramsok) { 948 return -1; 949 } 950 951 952 $this->db->begin(); 953 954 $now = dol_now(); 955 956 // Insert contract 957 $sql = "INSERT INTO ".MAIN_DB_PREFIX."contrat (datec, fk_soc, fk_user_author, date_contrat,"; 958 $sql .= " fk_commercial_signature, fk_commercial_suivi, fk_projet,"; 959 $sql .= " ref, entity, note_private, note_public, ref_customer, ref_supplier, ref_ext)"; 960 $sql .= " VALUES ('".$this->db->idate($now)."',".$this->socid.",".$user->id; 961 $sql .= ", ".(dol_strlen($this->date_contrat) != 0 ? "'".$this->db->idate($this->date_contrat)."'" : "NULL"); 962 $sql .= ",".($this->commercial_signature_id > 0 ? $this->commercial_signature_id : "NULL"); 963 $sql .= ",".($this->commercial_suivi_id > 0 ? $this->commercial_suivi_id : "NULL"); 964 $sql .= ",".($this->fk_project > 0 ? $this->fk_project : "NULL"); 965 $sql .= ", ".(dol_strlen($this->ref) <= 0 ? "null" : "'".$this->db->escape($this->ref)."'"); 966 $sql .= ", ".$conf->entity; 967 $sql .= ", ".(!empty($this->note_private) ? ("'".$this->db->escape($this->note_private)."'") : "NULL"); 968 $sql .= ", ".(!empty($this->note_public) ? ("'".$this->db->escape($this->note_public)."'") : "NULL"); 969 $sql .= ", ".(!empty($this->ref_customer) ? ("'".$this->db->escape($this->ref_customer)."'") : "NULL"); 970 $sql .= ", ".(!empty($this->ref_supplier) ? ("'".$this->db->escape($this->ref_supplier)."'") : "NULL"); 971 $sql .= ", ".(!empty($this->ref_ext) ? ("'".$this->db->escape($this->ref_ext)."'") : "NULL"); 972 $sql .= ")"; 973 $resql = $this->db->query($sql); 974 975 if ($resql) { 976 $error = 0; 977 978 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."contrat"); 979 980 // Load object modContract 981 $module = (!empty($conf->global->CONTRACT_ADDON) ? $conf->global->CONTRACT_ADDON : 'mod_contract_serpis'); 982 if (substr($module, 0, 13) == 'mod_contract_' && substr($module, -3) == 'php') { 983 $module = substr($module, 0, dol_strlen($module) - 4); 984 } 985 $result = dol_include_once('/core/modules/contract/'.$module.'.php'); 986 if ($result > 0) { 987 $modCodeContract = new $module(); 988 989 if (!empty($modCodeContract->code_auto)) { 990 // Force the ref to a draft value if numbering module is an automatic numbering 991 $sql = 'UPDATE '.MAIN_DB_PREFIX."contrat SET ref='(PROV".$this->id.")' WHERE rowid=".((int) $this->id); 992 if ($this->db->query($sql)) { 993 if ($this->id) { 994 $this->ref = "(PROV".$this->id.")"; 995 } 996 } 997 } 998 } 999 1000 if (!$error) { 1001 $result = $this->insertExtraFields(); 1002 if ($result < 0) { 1003 $error++; 1004 } 1005 } 1006 1007 // Insert business contacts ('SALESREPSIGN','contrat') 1008 if (!$error) { 1009 $result = $this->add_contact($this->commercial_signature_id, 'SALESREPSIGN', 'internal'); 1010 if ($result < 0) { 1011 $error++; 1012 } 1013 } 1014 1015 // Insert business contacts ('SALESREPFOLL','contrat') 1016 if (!$error) { 1017 $result = $this->add_contact($this->commercial_suivi_id, 'SALESREPFOLL', 'internal'); 1018 if ($result < 0) { 1019 $error++; 1020 } 1021 } 1022 1023 if (!$error) { 1024 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects 1025 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds 1026 } 1027 1028 // Add object linked 1029 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) { 1030 foreach ($this->linked_objects as $origin => $tmp_origin_id) { 1031 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, ...)) 1032 foreach ($tmp_origin_id as $origin_id) { 1033 $ret = $this->add_object_linked($origin, $origin_id); 1034 if (!$ret) { 1035 $this->error = $this->db->lasterror(); 1036 $error++; 1037 } 1038 } 1039 } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1)) 1040 { 1041 $origin_id = $tmp_origin_id; 1042 $ret = $this->add_object_linked($origin, $origin_id); 1043 if (!$ret) { 1044 $this->error = $this->db->lasterror(); 1045 $error++; 1046 } 1047 } 1048 } 1049 } 1050 1051 if (!$error && $this->id && !empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object 1052 $originforcontact = $this->origin; 1053 $originidforcontact = $this->origin_id; 1054 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order 1055 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php'; 1056 $exp = new Expedition($this->db); 1057 $exp->fetch($this->origin_id); 1058 $exp->fetchObjectLinked(); 1059 if (count($exp->linkedObjectsIds['commande']) > 0) { 1060 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) { 1061 $originforcontact = 'commande'; 1062 $originidforcontact = $value->id; 1063 break; // We take first one 1064 } 1065 } 1066 } 1067 1068 $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"; 1069 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'"; 1070 1071 $resqlcontact = $this->db->query($sqlcontact); 1072 if ($resqlcontact) { 1073 while ($objcontact = $this->db->fetch_object($resqlcontact)) { 1074 if ($objcontact->source == 'internal' && in_array($objcontact->code, array('SALESREPSIGN', 'SALESREPFOLL'))) { 1075 continue; // ignore this, already forced previously 1076 } 1077 1078 //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n"; 1079 $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 1080 } 1081 } else { 1082 dol_print_error($resqlcontact); 1083 } 1084 } 1085 } 1086 1087 if (!$error) { 1088 // Call trigger 1089 $result = $this->call_trigger('CONTRACT_CREATE', $user); 1090 if ($result < 0) { 1091 $error++; 1092 } 1093 // End call triggers 1094 1095 if (!$error) { 1096 $this->db->commit(); 1097 return $this->id; 1098 } else { 1099 dol_syslog(get_class($this)."::create - 30 - ".$this->error, LOG_ERR); 1100 $this->db->rollback(); 1101 return -3; 1102 } 1103 } else { 1104 $this->error = "Failed to add contract"; 1105 dol_syslog(get_class($this)."::create - 20 - ".$this->error, LOG_ERR); 1106 $this->db->rollback(); 1107 return -2; 1108 } 1109 } else { 1110 $this->error = $langs->trans("UnknownError: ".$this->db->error()." -", LOG_DEBUG); 1111 1112 $this->db->rollback(); 1113 return -1; 1114 } 1115 } 1116 1117 1118 /** 1119 * Delete object 1120 * 1121 * @param User $user User that deletes 1122 * @return int < 0 if KO, > 0 if OK 1123 */ 1124 public function delete($user) 1125 { 1126 global $conf, $langs; 1127 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 1128 1129 $error = 0; 1130 1131 $this->db->begin(); 1132 1133 // Call trigger 1134 $result = $this->call_trigger('CONTRACT_DELETE', $user); 1135 if ($result < 0) { 1136 $error++; 1137 } 1138 // End call triggers 1139 1140 if (!$error) { 1141 // Delete linked contacts 1142 $res = $this->delete_linked_contact(); 1143 if ($res < 0) { 1144 dol_syslog(get_class($this)."::delete error", LOG_ERR); 1145 $error++; 1146 } 1147 } 1148 1149 if (!$error) { 1150 // Delete linked object 1151 $res = $this->deleteObjectLinked(); 1152 if ($res < 0) { 1153 $error++; 1154 } 1155 } 1156 1157 if (!$error) { 1158 // Delete contratdet_log 1159 /* 1160 $sql = "DELETE cdl"; 1161 $sql.= " FROM ".MAIN_DB_PREFIX."contratdet_log as cdl, ".MAIN_DB_PREFIX."contratdet as cd"; 1162 $sql.= " WHERE cdl.fk_contratdet=cd.rowid AND cd.fk_contrat=".((int) $this->id); 1163 */ 1164 $sql = "SELECT cdl.rowid as cdlrowid "; 1165 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet_log as cdl, ".MAIN_DB_PREFIX."contratdet as cd"; 1166 $sql .= " WHERE cdl.fk_contratdet=cd.rowid AND cd.fk_contrat=".((int) $this->id); 1167 1168 dol_syslog(get_class($this)."::delete contratdet_log", LOG_DEBUG); 1169 $resql = $this->db->query($sql); 1170 if (!$resql) { 1171 $this->error = $this->db->error(); 1172 $error++; 1173 } 1174 $numressql = $this->db->num_rows($resql); 1175 if (!$error && $numressql) { 1176 $tab_resql = array(); 1177 for ($i = 0; $i < $numressql; $i++) { 1178 $objresql = $this->db->fetch_object($resql); 1179 $tab_resql[] = $objresql->cdlrowid; 1180 } 1181 $this->db->free($resql); 1182 1183 $sql = "DELETE FROM ".MAIN_DB_PREFIX."contratdet_log "; 1184 $sql .= " WHERE ".MAIN_DB_PREFIX."contratdet_log.rowid IN (".$this->db->sanitize(implode(",", $tab_resql)).")"; 1185 1186 dol_syslog(get_class($this)."::delete contratdet_log", LOG_DEBUG); 1187 $resql = $this->db->query($sql); 1188 if (!$resql) { 1189 $this->error = $this->db->error(); 1190 $error++; 1191 } 1192 } 1193 } 1194 1195 // Delete lines 1196 if (!$error) { 1197 // Delete contratdet extrafields 1198 $main = MAIN_DB_PREFIX.'contratdet'; 1199 $ef = $main."_extrafields"; 1200 $sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_contrat = ".((int) $this->id).")"; 1201 1202 dol_syslog(get_class($this)."::delete contratdet_extrafields", LOG_DEBUG); 1203 $resql = $this->db->query($sql); 1204 if (!$resql) { 1205 $this->error = $this->db->error(); 1206 $error++; 1207 } 1208 } 1209 1210 if (!$error) { 1211 // Delete contratdet 1212 $sql = "DELETE FROM ".MAIN_DB_PREFIX."contratdet"; 1213 $sql .= " WHERE fk_contrat=".((int) $this->id); 1214 1215 dol_syslog(get_class($this)."::delete contratdet", LOG_DEBUG); 1216 $resql = $this->db->query($sql); 1217 if (!$resql) { 1218 $this->error = $this->db->error(); 1219 $error++; 1220 } 1221 } 1222 1223 // Delete llx_ecm_files 1224 if (!$error) { 1225 $sql = 'DELETE FROM '.MAIN_DB_PREFIX."ecm_files WHERE src_object_type = '".$this->db->escape($this->table_element.(empty($this->module) ? '' : '@'.$this->module))."' AND src_object_id = ".((int) $this->id); 1226 $resql = $this->db->query($sql); 1227 if (!$resql) { 1228 $this->error = $this->db->lasterror(); 1229 $this->errors[] = $this->error; 1230 $error++; 1231 } 1232 } 1233 1234 // Delete contract 1235 if (!$error) { 1236 $sql = "DELETE FROM ".MAIN_DB_PREFIX."contrat"; 1237 $sql .= " WHERE rowid=".((int) $this->id); 1238 1239 dol_syslog(get_class($this)."::delete contrat", LOG_DEBUG); 1240 $resql = $this->db->query($sql); 1241 if (!$resql) { 1242 $this->error = $this->db->error(); 1243 $error++; 1244 } 1245 } 1246 1247 // Removed extrafields 1248 if (!$error) { 1249 $result = $this->deleteExtraFields(); 1250 if ($result < 0) { 1251 $error++; 1252 dol_syslog(get_class($this)."::delete error -3 ".$this->error, LOG_ERR); 1253 } 1254 } 1255 1256 if (!$error) { 1257 // We remove directory 1258 $ref = dol_sanitizeFileName($this->ref); 1259 if ($conf->contrat->dir_output) { 1260 $dir = $conf->contrat->multidir_output[$this->entity]."/".$ref; 1261 if (file_exists($dir)) { 1262 $res = @dol_delete_dir_recursive($dir); 1263 if (!$res) { 1264 $this->error = 'ErrorFailToDeleteDir'; 1265 $error++; 1266 } 1267 } 1268 } 1269 } 1270 1271 if (!$error) { 1272 $this->db->commit(); 1273 return 1; 1274 } else { 1275 $this->error = $this->db->lasterror(); 1276 $this->db->rollback(); 1277 return -1; 1278 } 1279 } 1280 1281 /** 1282 * Update object into database 1283 * 1284 * @param User $user User that modifies 1285 * @param int $notrigger 0=launch triggers after, 1=disable triggers 1286 * @return int <0 if KO, >0 if OK 1287 */ 1288 public function update($user, $notrigger = 0) 1289 { 1290 global $conf, $langs; 1291 $error = 0; 1292 1293 // Clean parameters 1294 if (empty($this->fk_commercial_signature) && $this->commercial_signature_id > 0) { 1295 $this->fk_commercial_signature = $this->commercial_signature_id; 1296 } 1297 if (empty($this->fk_commercial_suivi) && $this->commercial_suivi_id > 0) { 1298 $this->fk_commercial_suivi = $this->commercial_suivi_id; 1299 } 1300 if (empty($this->fk_soc) && $this->socid > 0) { 1301 $this->fk_soc = (int) $this->socid; 1302 } 1303 if (empty($this->fk_project) && $this->projet > 0) { 1304 $this->fk_project = (int) $this->projet; 1305 } 1306 1307 if (isset($this->ref)) { 1308 $this->ref = trim($this->ref); 1309 } 1310 if (isset($this->ref_customer)) { 1311 $this->ref_customer = trim($this->ref_customer); 1312 } 1313 if (isset($this->ref_supplier)) { 1314 $this->ref_supplier = trim($this->ref_supplier); 1315 } 1316 if (isset($this->ref_ext)) { 1317 $this->ref_ext = trim($this->ref_ext); 1318 } 1319 if (isset($this->entity)) { 1320 $this->entity = (int) $this->entity; 1321 } 1322 if (isset($this->statut)) { 1323 $this->statut = (int) $this->statut; 1324 } 1325 if (isset($this->fk_soc)) { 1326 $this->fk_soc = (int) $this->fk_soc; 1327 } 1328 if (isset($this->fk_commercial_signature)) { 1329 $this->fk_commercial_signature = trim($this->fk_commercial_signature); 1330 } 1331 if (isset($this->fk_commercial_suivi)) { 1332 $this->fk_commercial_suivi = trim($this->fk_commercial_suivi); 1333 } 1334 if (isset($this->note_private)) { 1335 $this->note_private = trim($this->note_private); 1336 } 1337 if (isset($this->note_public)) { 1338 $this->note_public = trim($this->note_public); 1339 } 1340 if (isset($this->import_key)) { 1341 $this->import_key = trim($this->import_key); 1342 } 1343 //if (isset($this->extraparams)) $this->extraparams=trim($this->extraparams); 1344 1345 // Check parameters 1346 // Put here code to add a control on parameters values 1347 1348 // Update request 1349 $sql = "UPDATE ".MAIN_DB_PREFIX."contrat SET"; 1350 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").","; 1351 $sql .= " ref_customer=".(isset($this->ref_customer) ? "'".$this->db->escape($this->ref_customer)."'" : "null").","; 1352 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").","; 1353 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").","; 1354 $sql .= " entity=".$conf->entity.","; 1355 $sql .= " date_contrat=".(dol_strlen($this->date_contrat) != 0 ? "'".$this->db->idate($this->date_contrat)."'" : 'null').","; 1356 $sql .= " statut=".(isset($this->statut) ? $this->statut : "null").","; 1357 $sql .= " fk_soc=".($this->fk_soc > 0 ? $this->fk_soc : "null").","; 1358 $sql .= " fk_projet=".($this->fk_project > 0 ? $this->fk_project : "null").","; 1359 $sql .= " fk_commercial_signature=".(isset($this->fk_commercial_signature) ? $this->fk_commercial_signature : "null").","; 1360 $sql .= " fk_commercial_suivi=".(isset($this->fk_commercial_suivi) ? $this->fk_commercial_suivi : "null").","; 1361 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").","; 1362 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").","; 1363 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").""; 1364 //$sql.= " extraparams=".(isset($this->extraparams)?"'".$this->db->escape($this->extraparams)."'":"null").""; 1365 $sql .= " WHERE rowid=".((int) $this->id); 1366 1367 $this->db->begin(); 1368 1369 $resql = $this->db->query($sql); 1370 if (!$resql) { 1371 $error++; $this->errors[] = "Error ".$this->db->lasterror(); 1372 } 1373 1374 if (!$error) { 1375 $result = $this->insertExtraFields(); 1376 if ($result < 0) { 1377 $error++; 1378 } 1379 } 1380 1381 if (!$error && !$notrigger) { 1382 // Call triggers 1383 $result = $this->call_trigger('CONTRACT_MODIFY', $user); 1384 if ($result < 0) { 1385 $error++; 1386 } 1387 // End call triggers 1388 } 1389 1390 // Commit or rollback 1391 if ($error) { 1392 foreach ($this->errors as $errmsg) { 1393 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR); 1394 $this->error .= ($this->error ? ', '.$errmsg : $errmsg); 1395 } 1396 $this->db->rollback(); 1397 return -1 * $error; 1398 } else { 1399 $this->db->commit(); 1400 return 1; 1401 } 1402 } 1403 1404 1405 /** 1406 * Ajoute une ligne de contrat en base 1407 * 1408 * @param string $desc Description of line 1409 * @param float $pu_ht Unit price net 1410 * @param int $qty Quantity 1411 * @param float $txtva Vat rate 1412 * @param float $txlocaltax1 Local tax 1 rate 1413 * @param float $txlocaltax2 Local tax 2 rate 1414 * @param int $fk_product Id produit 1415 * @param float $remise_percent Percentage discount of the line 1416 * @param int $date_start Date de debut prevue 1417 * @param int $date_end Date de fin prevue 1418 * @param string $price_base_type HT or TTC 1419 * @param float $pu_ttc Prix unitaire TTC 1420 * @param int $info_bits Bits of type of lines 1421 * @param int $fk_fournprice Fourn price id 1422 * @param int $pa_ht Buying price HT 1423 * @param array $array_options extrafields array 1424 * @param string $fk_unit Code of the unit to use. Null to use the default one 1425 * @param string $rang Position 1426 * @return int <0 if KO, >0 if OK 1427 */ 1428 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 = 0, $fk_unit = null, $rang = 0) 1429 { 1430 global $user, $langs, $conf, $mysoc; 1431 $error = 0; 1432 1433 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"); 1434 1435 // Check parameters 1436 if ($fk_product <= 0 && empty($desc)) { 1437 $this->error = "ErrorDescRequiredForFreeProductLines"; 1438 return -1; 1439 } 1440 1441 if ($this->statut >= 0) { 1442 // Clean parameters 1443 $pu_ht = price2num($pu_ht); 1444 $pu_ttc = price2num($pu_ttc); 1445 $pa_ht = price2num($pa_ht); 1446 1447 // Clean vat code 1448 $reg = array(); 1449 $vat_src_code = ''; 1450 if (preg_match('/\((.*)\)/', $txtva, $reg)) { 1451 $vat_src_code = $reg[1]; 1452 $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate. 1453 } 1454 $txtva = price2num($txtva); 1455 $txlocaltax1 = price2num($txlocaltax1); 1456 $txlocaltax2 = price2num($txlocaltax2); 1457 1458 $remise_percent = price2num($remise_percent); 1459 $qty = price2num($qty); 1460 if (empty($qty)) { 1461 $qty = 1; 1462 } 1463 if (empty($info_bits)) { 1464 $info_bits = 0; 1465 } 1466 if (empty($pu_ht) || !is_numeric($pu_ht)) { 1467 $pu_ht = 0; 1468 } 1469 if (empty($pu_ttc)) { 1470 $pu_ttc = 0; 1471 } 1472 if (empty($txtva) || !is_numeric($txtva)) { 1473 $txtva = 0; 1474 } 1475 if (empty($txlocaltax1) || !is_numeric($txlocaltax1)) { 1476 $txlocaltax1 = 0; 1477 } 1478 if (empty($txlocaltax2) || !is_numeric($txlocaltax2)) { 1479 $txlocaltax2 = 0; 1480 } 1481 1482 if ($price_base_type == 'HT') { 1483 $pu = $pu_ht; 1484 } else { 1485 $pu = $pu_ttc; 1486 } 1487 1488 // Check parameters 1489 if (empty($remise_percent)) { 1490 $remise_percent = 0; 1491 } 1492 1493 if ($date_start && $date_end && $date_start > $date_end) { 1494 $langs->load("errors"); 1495 $this->error = $langs->trans('ErrorStartDateGreaterEnd'); 1496 return -1; 1497 } 1498 1499 $this->db->begin(); 1500 1501 $localtaxes_type = getLocalTaxesFromRate($txtva.($vat_src_code ? ' ('.$vat_src_code.')' : ''), 0, $this->societe, $mysoc); 1502 1503 // Calcul du total TTC et de la TVA pour la ligne a partir de 1504 // qty, pu, remise_percent et txtva 1505 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker 1506 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. 1507 1508 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type); 1509 $total_ht = $tabprice[0]; 1510 $total_tva = $tabprice[1]; 1511 $total_ttc = $tabprice[2]; 1512 $total_localtax1 = $tabprice[9]; 1513 $total_localtax2 = $tabprice[10]; 1514 1515 $localtax1_type = $localtaxes_type[0]; 1516 $localtax2_type = $localtaxes_type[2]; 1517 1518 // TODO A virer 1519 // Anciens indicateurs: $price, $remise (a ne plus utiliser) 1520 $remise = 0; 1521 $price = price2num(round($pu_ht, 2)); 1522 if (dol_strlen($remise_percent) > 0) { 1523 $remise = round(($pu_ht * $remise_percent / 100), 2); 1524 $price = $pu_ht - $remise; 1525 } 1526 1527 if (empty($pa_ht)) { 1528 $pa_ht = 0; 1529 } 1530 1531 1532 // if buy price not defined, define buyprice as configured in margin admin 1533 if ($this->pa_ht == 0) { 1534 if (($result = $this->defineBuyPrice($pu_ht, $remise_percent, $fk_product)) < 0) { 1535 return $result; 1536 } else { 1537 $pa_ht = $result; 1538 } 1539 } 1540 1541 // Insertion dans la base 1542 $sql = "INSERT INTO ".MAIN_DB_PREFIX."contratdet"; 1543 $sql .= " (fk_contrat, label, description, fk_product, qty, tva_tx, vat_src_code,"; 1544 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice,"; 1545 $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc,"; 1546 $sql .= " info_bits,"; 1547 $sql .= " price_ht, remise, fk_product_fournisseur_price, buy_price_ht"; 1548 if ($date_start > 0) { 1549 $sql .= ",date_ouverture_prevue"; 1550 } 1551 if ($date_end > 0) { 1552 $sql .= ",date_fin_validite"; 1553 } 1554 $sql .= ", fk_unit"; 1555 $sql .= ") VALUES ("; 1556 $sql .= $this->id.", '', '".$this->db->escape($desc)."',"; 1557 $sql .= ($fk_product > 0 ? $fk_product : "null").","; 1558 $sql .= " ".((float) $qty).","; 1559 $sql .= " ".((float) $txtva).","; 1560 $sql .= " ".($vat_src_code ? "'".$this->db->escape($vat_src_code)."'" : "null").","; 1561 $sql .= " ".((float) $txlocaltax1).","; 1562 $sql .= " ".((float) $txlocaltax2).","; 1563 $sql .= " '".$this->db->escape($localtax1_type)."',"; 1564 $sql .= " '".$this->db->escape($localtax2_type)."',"; 1565 $sql .= " ".price2num($remise_percent).","; 1566 $sql .= " ".price2num($pu_ht).","; 1567 $sql .= " ".price2num($total_ht).",".price2num($total_tva).",".price2num($total_localtax1).",".price2num($total_localtax2).",".price2num($total_ttc).","; 1568 $sql .= " '".$this->db->escape($info_bits)."',"; 1569 $sql .= " ".price2num($price).",".price2num($remise).","; 1570 if (isset($fk_fournprice)) { 1571 $sql .= ' '.((int) $fk_fournprice).','; 1572 } else { 1573 $sql .= ' null,'; 1574 } 1575 if (isset($pa_ht)) { 1576 $sql .= ' '.price2num($pa_ht); 1577 } else { 1578 $sql .= ' null'; 1579 } 1580 if ($date_start > 0) { 1581 $sql .= ",'".$this->db->idate($date_start)."'"; 1582 } 1583 if ($date_end > 0) { 1584 $sql .= ",'".$this->db->idate($date_end)."'"; 1585 } 1586 $sql .= ", ".($fk_unit ? "'".$this->db->escape($fk_unit)."'" : "null"); 1587 $sql .= ")"; 1588 1589 $resql = $this->db->query($sql); 1590 if ($resql) { 1591 $contractlineid = $this->db->last_insert_id(MAIN_DB_PREFIX."contratdet"); 1592 1593 if (!$error) { 1594 $contractline = new ContratLigne($this->db); 1595 $contractline->array_options = $array_options; 1596 $contractline->id = $contractlineid; 1597 $result = $contractline->insertExtraFields(); 1598 if ($result < 0) { 1599 $this->error[] = $contractline->error; 1600 $error++; 1601 } 1602 } 1603 1604 if (empty($error)) { 1605 // Call trigger 1606 $result = $this->call_trigger('LINECONTRACT_INSERT', $user); 1607 if ($result < 0) { 1608 $error++; 1609 } 1610 // End call triggers 1611 } 1612 1613 if ($error) { 1614 $this->db->rollback(); 1615 return -1; 1616 } else { 1617 $this->db->commit(); 1618 return $contractlineid; 1619 } 1620 } else { 1621 $this->db->rollback(); 1622 $this->error = $this->db->error()." sql=".$sql; 1623 return -1; 1624 } 1625 } else { 1626 dol_syslog(get_class($this)."::addline ErrorTryToAddLineOnValidatedContract", LOG_ERR); 1627 return -2; 1628 } 1629 } 1630 1631 /** 1632 * Mets a jour une ligne de contrat 1633 * 1634 * @param int $rowid Id de la ligne de facture 1635 * @param string $desc Description de la ligne 1636 * @param float $pu Prix unitaire 1637 * @param int $qty Quantite 1638 * @param float $remise_percent Percentage discount of the line 1639 * @param int $date_start Date de debut prevue 1640 * @param int $date_end Date de fin prevue 1641 * @param float $tvatx Taux TVA 1642 * @param float $localtax1tx Local tax 1 rate 1643 * @param float $localtax2tx Local tax 2 rate 1644 * @param int|string $date_debut_reel Date de debut reelle 1645 * @param int|string $date_fin_reel Date de fin reelle 1646 * @param string $price_base_type HT or TTC 1647 * @param int $info_bits Bits of type of lines 1648 * @param int $fk_fournprice Fourn price id 1649 * @param int $pa_ht Buying price HT 1650 * @param array $array_options extrafields array 1651 * @param string $fk_unit Code of the unit to use. Null to use the default one 1652 * @return int < 0 si erreur, > 0 si ok 1653 */ 1654 public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $tvatx, $localtax1tx = 0.0, $localtax2tx = 0.0, $date_debut_reel = '', $date_fin_reel = '', $price_base_type = 'HT', $info_bits = 0, $fk_fournprice = null, $pa_ht = 0, $array_options = 0, $fk_unit = null) 1655 { 1656 global $user, $conf, $langs, $mysoc; 1657 1658 $error = 0; 1659 1660 // Clean parameters 1661 $qty = trim($qty); 1662 $desc = trim($desc); 1663 $desc = trim($desc); 1664 $price = price2num($pu); 1665 $tvatx = price2num($tvatx); 1666 $localtax1tx = price2num($localtax1tx); 1667 $localtax2tx = price2num($localtax2tx); 1668 $pa_ht = price2num($pa_ht); 1669 if (empty($fk_fournprice)) { 1670 $fk_fournprice = 0; 1671 } 1672 1673 $subprice = $price; 1674 $remise = 0; 1675 if (dol_strlen($remise_percent) > 0) { 1676 $remise = round(($pu * $remise_percent / 100), 2); 1677 $price = $pu - $remise; 1678 } else { 1679 $remise_percent = 0; 1680 } 1681 1682 if ($date_start && $date_end && $date_start > $date_end) { 1683 $langs->load("errors"); 1684 $this->error = $langs->trans('ErrorStartDateGreaterEnd'); 1685 return -1; 1686 } 1687 1688 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $date_debut_reel, $date_fin_reel, $tvatx, $localtax1tx, $localtax2tx, $price_base_type, $info_bits"); 1689 1690 $this->db->begin(); 1691 1692 // Calcul du total TTC et de la TVA pour la ligne a partir de 1693 // qty, pu, remise_percent et tvatx 1694 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker 1695 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. 1696 1697 $localtaxes_type = getLocalTaxesFromRate($tvatx, 0, $this->societe, $mysoc); 1698 $tvatx = preg_replace('/\s*\(.*\)/', '', $tvatx); // Remove code into vatrate. 1699 1700 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $tvatx, $localtax1tx, $localtax2tx, 0, $price_base_type, $info_bits, 1, $mysoc, $localtaxes_type); 1701 $total_ht = $tabprice[0]; 1702 $total_tva = $tabprice[1]; 1703 $total_ttc = $tabprice[2]; 1704 $total_localtax1 = $tabprice[9]; 1705 $total_localtax2 = $tabprice[10]; 1706 1707 $localtax1_type = $localtaxes_type[0]; 1708 $localtax2_type = $localtaxes_type[2]; 1709 1710 // TODO A virer 1711 // Anciens indicateurs: $price, $remise (a ne plus utiliser) 1712 $remise = 0; 1713 $price = price2num(round($pu, 2)); 1714 if (dol_strlen($remise_percent) > 0) { 1715 $remise = round(($pu * $remise_percent / 100), 2); 1716 $price = $pu - $remise; 1717 } 1718 1719 if (empty($pa_ht)) { 1720 $pa_ht = 0; 1721 } 1722 1723 // if buy price not defined, define buyprice as configured in margin admin 1724 if ($this->pa_ht == 0) { 1725 if (($result = $this->defineBuyPrice($pu, $remise_percent)) < 0) { 1726 return $result; 1727 } else { 1728 $pa_ht = $result; 1729 } 1730 } 1731 1732 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet set description='".$this->db->escape($desc)."'"; 1733 $sql .= ",price_ht='".price2num($price)."'"; 1734 $sql .= ",subprice='".price2num($subprice)."'"; 1735 $sql .= ",remise='".price2num($remise)."'"; 1736 $sql .= ",remise_percent='".price2num($remise_percent)."'"; 1737 $sql .= ",qty='".$qty."'"; 1738 $sql .= ",tva_tx='".price2num($tvatx)."'"; 1739 $sql .= ",localtax1_tx='".price2num($localtax1tx)."'"; 1740 $sql .= ",localtax2_tx='".price2num($localtax2tx)."'"; 1741 $sql .= ",localtax1_type='".$this->db->escape($localtax1_type)."'"; 1742 $sql .= ",localtax2_type='".$this->db->escape($localtax2_type)."'"; 1743 $sql .= ", total_ht='".price2num($total_ht)."'"; 1744 $sql .= ", total_tva='".price2num($total_tva)."'"; 1745 $sql .= ", total_localtax1='".price2num($total_localtax1)."'"; 1746 $sql .= ", total_localtax2='".price2num($total_localtax2)."'"; 1747 $sql .= ", total_ttc='".price2num($total_ttc)."'"; 1748 $sql .= ", fk_product_fournisseur_price=".($fk_fournprice > 0 ? $fk_fournprice : "null"); 1749 $sql .= ", buy_price_ht='".price2num($pa_ht)."'"; 1750 if ($date_start > 0) { 1751 $sql .= ",date_ouverture_prevue='".$this->db->idate($date_start)."'"; 1752 } else { 1753 $sql .= ",date_ouverture_prevue=null"; 1754 } 1755 if ($date_end > 0) { 1756 $sql .= ",date_fin_validite='".$this->db->idate($date_end)."'"; 1757 } else { 1758 $sql .= ",date_fin_validite=null"; 1759 } 1760 if ($date_debut_reel > 0) { 1761 $sql .= ",date_ouverture='".$this->db->idate($date_debut_reel)."'"; 1762 } else { 1763 $sql .= ",date_ouverture=null"; 1764 } 1765 if ($date_fin_reel > 0) { 1766 $sql .= ",date_cloture='".$this->db->idate($date_fin_reel)."'"; 1767 } else { 1768 $sql .= ",date_cloture=null"; 1769 } 1770 $sql .= ", fk_unit=".($fk_unit ? "'".$this->db->escape($fk_unit)."'" : "null"); 1771 $sql .= " WHERE rowid = ".((int) $rowid); 1772 1773 dol_syslog(get_class($this)."::updateline", LOG_DEBUG); 1774 $result = $this->db->query($sql); 1775 if ($result) { 1776 $result = $this->update_statut($user); 1777 if ($result >= 0) { 1778 if (is_array($array_options) && count($array_options) > 0) { // For avoid conflicts if trigger used 1779 $contractline = new ContratLigne($this->db); 1780 $contractline->fetch($rowid); 1781 $contractline->fetch_optionals(); 1782 1783 // We replace values in $contractline->array_options only for entries defined into $array_options 1784 foreach ($array_options as $key => $value) { 1785 $contractline->array_options[$key] = $array_options[$key]; 1786 } 1787 1788 $result = $contractline->insertExtraFields(); 1789 if ($result < 0) { 1790 $this->error[] = $contractline->error; 1791 $error++; 1792 } 1793 } 1794 1795 if (empty($error)) { 1796 // Call trigger 1797 $result = $this->call_trigger('LINECONTRACT_UPDATE', $user); 1798 if ($result < 0) { 1799 $this->db->rollback(); 1800 return -3; 1801 } 1802 // End call triggers 1803 1804 $this->db->commit(); 1805 return 1; 1806 } 1807 } else { 1808 $this->db->rollback(); 1809 dol_syslog(get_class($this)."::updateline Erreur -2"); 1810 return -2; 1811 } 1812 } else { 1813 $this->db->rollback(); 1814 $this->error = $this->db->error(); 1815 dol_syslog(get_class($this)."::updateline Erreur -1"); 1816 return -1; 1817 } 1818 } 1819 1820 /** 1821 * Delete a contract line 1822 * 1823 * @param int $idline Id of line to delete 1824 * @param User $user User that delete 1825 * @return int >0 if OK, <0 if KO 1826 */ 1827 public function deleteline($idline, User $user) 1828 { 1829 global $conf, $langs; 1830 1831 $error = 0; 1832 1833 if ($this->statut >= 0) { 1834 // Call trigger 1835 $result = $this->call_trigger('LINECONTRACT_DELETE', $user); 1836 if ($result < 0) { 1837 return -1; 1838 } 1839 // End call triggers 1840 1841 $this->db->begin(); 1842 1843 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element_line; 1844 $sql .= " WHERE rowid = ".((int) $idline); 1845 1846 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG); 1847 $resql = $this->db->query($sql); 1848 if (!$resql) { 1849 $this->error = "Error ".$this->db->lasterror(); 1850 $error++; 1851 } 1852 1853 if (!$error) { 1854 // Remove extrafields 1855 $contractline = new ContratLigne($this->db); 1856 $contractline->id = $idline; 1857 $result = $contractline->deleteExtraFields(); 1858 if ($result < 0) { 1859 $error++; 1860 $this->error = "Error ".get_class($this)."::deleteline deleteExtraFields error -4 ".$contractline->error; 1861 } 1862 } 1863 1864 if (empty($error)) { 1865 $this->db->commit(); 1866 return 1; 1867 } else { 1868 dol_syslog(get_class($this)."::deleteline ERROR:".$this->error, LOG_ERR); 1869 $this->db->rollback(); 1870 return -1; 1871 } 1872 } else { 1873 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus'; 1874 return -2; 1875 } 1876 } 1877 1878 1879 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1880 /** 1881 * Update statut of contract according to services 1882 * 1883 * @param User $user Object user 1884 * @return int <0 if KO, >0 if OK 1885 * @deprecated This function will never be used. Status of a contract is status of its lines. 1886 */ 1887 public function update_statut($user) 1888 { 1889 // phpcs:enable 1890 dol_syslog(__METHOD__." is deprecated", LOG_WARNING); 1891 1892 // If draft, we keep it (should not happen) 1893 if ($this->statut == 0) { 1894 return 1; 1895 } 1896 1897 // Load $this->lines array 1898 // $this->fetch_lines(); 1899 1900 // $newstatut=1; 1901 // foreach($this->lines as $key => $contractline) 1902 // { 1903 // // if ($contractline) // Loop on each service 1904 // } 1905 1906 return 1; 1907 } 1908 1909 1910 /** 1911 * Return label of a contract status 1912 * 1913 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto, 7=Same than 6 with fixed length 1914 * @return string Label 1915 */ 1916 public function getLibStatut($mode) 1917 { 1918 return $this->LibStatut($this->statut, $mode); 1919 } 1920 1921 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 1922 /** 1923 * Renvoi label of a given contrat status 1924 * 1925 * @param int $status Id status 1926 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto, 7=Same than 6 with fixed length 1927 * @return string Label 1928 */ 1929 public function LibStatut($status, $mode) 1930 { 1931 // phpcs:enable 1932 global $langs; 1933 1934 if (empty($this->labelStatus) || empty($this->labelStatusShort)) { 1935 global $langs; 1936 $langs->load("contracts"); 1937 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft'); 1938 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated'); 1939 $this->labelStatus[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed'); 1940 $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv('ContractStatusDraft'); 1941 $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv('ContractStatusValidated'); 1942 $this->labelStatusShort[self::STATUS_CLOSED] = $langs->transnoentitiesnoconv('ContractStatusClosed'); 1943 } 1944 1945 $statusType = 'status'.$status; 1946 if ($status == self::STATUS_VALIDATED) { 1947 $statusType = 'status6'; 1948 } 1949 1950 if ($mode == 4 || $mode == 6 || $mode == 7) { 1951 $text = ''; 1952 if ($mode == 4) { 1953 $text = '<span class="hideonsmartphone">'; 1954 $text .= ($this->nbofserviceswait + $this->nbofservicesopened + $this->nbofservicesexpired + $this->nbofservicesclosed); 1955 $text .= ' '.$langs->trans("Services"); 1956 $text .= ': '; 1957 $text .= '</span>'; 1958 } 1959 $text .= ($mode == 7 ? '<span class="nowraponall">' : ''); 1960 $text .= ($mode != 7 || $this->nbofserviceswait > 0) ? ($this->nbofserviceswait.ContratLigne::LibStatut(0, 3, -1, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesopened || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' ' : '') : ''; 1961 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : ''); 1962 $text .= ($mode != 7 || $this->nbofservicesopened > 0) ? ($this->nbofservicesopened.ContratLigne::LibStatut(4, 3, 0, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesexpired || $this->nbofservicesclosed) ? ' ' : '') : ''; 1963 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : ''); 1964 $text .= ($mode != 7 || $this->nbofservicesexpired > 0) ? ($this->nbofservicesexpired.ContratLigne::LibStatut(4, 3, 1, 'class="marginleft2"')).(($mode != 7 || $this->nbofservicesclosed) ? ' ' : '') : ''; 1965 $text .= ($mode == 7 ? '</span><span class="nowraponall">' : ''); 1966 $text .= ($mode != 7 || $this->nbofservicesclosed > 0) ? ($this->nbofservicesclosed.ContratLigne::LibStatut(5, 3, -1, 'class="marginleft2"')) : ''; 1967 $text .= ($mode == 7 ? '</span>' : ''); 1968 return $text; 1969 } else { 1970 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode); 1971 } 1972 } 1973 1974 1975 /** 1976 * Return clicable name (with picto eventually) 1977 * 1978 * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto 1979 * @param int $maxlength Max length of ref 1980 * @param int $notooltip 1=Disable tooltip 1981 * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking 1982 * @return string Chaine avec URL 1983 */ 1984 public function getNomUrl($withpicto = 0, $maxlength = 0, $notooltip = 0, $save_lastsearch_value = -1) 1985 { 1986 global $conf, $langs, $user, $hookmanager; 1987 1988 $result = ''; 1989 1990 $url = DOL_URL_ROOT.'/contrat/card.php?id='.$this->id; 1991 1992 //if ($option !== 'nolink') 1993 //{ 1994 // Add param to save lastsearch_values or not 1995 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); 1996 if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) { 1997 $add_save_lastsearch_values = 1; 1998 } 1999 if ($add_save_lastsearch_values) { 2000 $url .= '&save_lastsearch_values=1'; 2001 } 2002 //} 2003 2004 $label = ''; 2005 2006 if ($user->rights->contrat->lire) { 2007 $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Contract").'</u>'; 2008 /* Status of a contract is status of all services, so disabled 2009 if (isset($this->statut)) { 2010 $label .= ' '.$this->getLibStatut(5); 2011 }*/ 2012 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.($this->ref ? $this->ref : $this->id); 2013 $ref_customer = (!empty($this->ref_customer) ? $this->ref_customer : (empty($this->ref_client) ? '' : $this->ref_client)); 2014 $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$ref_customer; 2015 $label .= '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier; 2016 if (!empty($this->total_ht)) { 2017 $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency); 2018 } 2019 if (!empty($this->total_tva)) { 2020 $label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency); 2021 } 2022 if (!empty($this->total_ttc)) { 2023 $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency); 2024 } 2025 } 2026 2027 $linkclose = ''; 2028 if (empty($notooltip) && $user->rights->contrat->lire) { 2029 if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) { 2030 $label = $langs->trans("ShowOrder"); 2031 $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; 2032 } 2033 $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; 2034 $linkclose .= ' class="classfortooltip"'; 2035 } 2036 2037 $linkstart = '<a href="'.$url.'"'; 2038 $linkstart .= $linkclose.'>'; 2039 $linkend = '</a>'; 2040 2041 $result .= $linkstart; 2042 if ($withpicto) { 2043 $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1); 2044 } 2045 if ($withpicto != 2) { 2046 $result .= ($this->ref ? $this->ref : $this->id); 2047 } 2048 $result .= $linkend; 2049 2050 global $action; 2051 $hookmanager->initHooks(array('contractdao')); 2052 $parameters = array('id'=>$this->id, 'getnomurl'=>$result); 2053 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks 2054 if ($reshook > 0) { 2055 $result = $hookmanager->resPrint; 2056 } else { 2057 $result .= $hookmanager->resPrint; 2058 } 2059 2060 return $result; 2061 } 2062 2063 /** 2064 * Charge les informations d'ordre info dans l'objet contrat 2065 * 2066 * @param int $id id du contrat a charger 2067 * @return void 2068 */ 2069 public function info($id) 2070 { 2071 $sql = "SELECT c.rowid, c.ref, c.datec,"; 2072 $sql .= " c.tms as date_modification,"; 2073 $sql .= " fk_user_author"; 2074 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c"; 2075 $sql .= " WHERE c.rowid = ".((int) $id); 2076 2077 $result = $this->db->query($sql); 2078 if ($result) { 2079 if ($this->db->num_rows($result)) { 2080 $obj = $this->db->fetch_object($result); 2081 2082 $this->id = $obj->rowid; 2083 2084 if ($obj->fk_user_author) { 2085 $cuser = new User($this->db); 2086 $cuser->fetch($obj->fk_user_author); 2087 $this->user_creation = $cuser; 2088 } 2089 2090 $this->ref = (!$obj->ref) ? $obj->rowid : $obj->ref; 2091 $this->date_creation = $this->db->jdate($obj->datec); 2092 $this->date_modification = $this->db->jdate($obj->date_modification); 2093 } 2094 2095 $this->db->free($result); 2096 } else { 2097 dol_print_error($this->db); 2098 } 2099 } 2100 2101 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2102 /** 2103 * Return list of line rowid 2104 * 2105 * @param int $status Status of lines to get 2106 * @return array|int Array of line's rowid or <0 if error 2107 */ 2108 public function array_detail($status = -1) 2109 { 2110 // phpcs:enable 2111 $tab = array(); 2112 2113 $sql = "SELECT cd.rowid"; 2114 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as cd"; 2115 $sql .= " WHERE fk_contrat =".$this->id; 2116 if ($status >= 0) { 2117 $sql .= " AND statut = ".((int) $status); 2118 } 2119 2120 dol_syslog(get_class($this)."::array_detail()", LOG_DEBUG); 2121 $resql = $this->db->query($sql); 2122 if ($resql) { 2123 $num = $this->db->num_rows($resql); 2124 $i = 0; 2125 while ($i < $num) { 2126 $obj = $this->db->fetch_object($resql); 2127 $tab[$i] = $obj->rowid; 2128 $i++; 2129 } 2130 return $tab; 2131 } else { 2132 $this->error = $this->db->error(); 2133 return -1; 2134 } 2135 } 2136 2137 /** 2138 * Return list of other contracts for same company than current contract 2139 * 2140 * @param string $option 'all' or 'others' 2141 * @return array|int Array of contracts id or <0 if error 2142 */ 2143 public function getListOfContracts($option = 'all') 2144 { 2145 $tab = array(); 2146 2147 $sql = "SELECT c.rowid, c.ref"; 2148 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c"; 2149 $sql .= " WHERE fk_soc =".$this->socid; 2150 if ($option == 'others') { 2151 $sql .= " AND c.rowid != ".$this->id; 2152 } 2153 2154 dol_syslog(get_class($this)."::getOtherContracts()", LOG_DEBUG); 2155 $resql = $this->db->query($sql); 2156 if ($resql) { 2157 $num = $this->db->num_rows($resql); 2158 $i = 0; 2159 while ($i < $num) { 2160 $obj = $this->db->fetch_object($resql); 2161 $contrat = new Contrat($this->db); 2162 $contrat->fetch($obj->rowid); 2163 $tab[] = $contrat; 2164 $i++; 2165 } 2166 return $tab; 2167 } else { 2168 $this->error = $this->db->error(); 2169 return -1; 2170 } 2171 } 2172 2173 2174 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2175 /** 2176 * Load indicators for dashboard (this->nbtodo and this->nbtodolate) 2177 * 2178 * @param User $user Objet user 2179 * @param string $mode "inactive" pour services a activer, "expired" pour services expires 2180 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK 2181 */ 2182 public function load_board($user, $mode) 2183 { 2184 // phpcs:enable 2185 global $conf, $langs; 2186 2187 $this->from = " FROM ".MAIN_DB_PREFIX."contrat as c"; 2188 $this->from .= ", ".MAIN_DB_PREFIX."contratdet as cd"; 2189 $this->from .= ", ".MAIN_DB_PREFIX."societe as s"; 2190 if (!$user->rights->societe->client->voir && !$user->socid) { 2191 $this->from .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; 2192 } 2193 2194 if ($mode == 'inactive') { 2195 $sql = "SELECT cd.rowid, cd.date_ouverture_prevue as datefin"; 2196 $sql .= $this->from; 2197 $sql .= " WHERE c.statut = 1"; 2198 $sql .= " AND c.rowid = cd.fk_contrat"; 2199 $sql .= " AND cd.statut = 0"; 2200 } elseif ($mode == 'expired') { 2201 $sql = "SELECT cd.rowid, cd.date_fin_validite as datefin"; 2202 $sql .= $this->from; 2203 $sql .= " WHERE c.statut = 1"; 2204 $sql .= " AND c.rowid = cd.fk_contrat"; 2205 $sql .= " AND cd.statut = 4"; 2206 $sql .= " AND cd.date_fin_validite < '".$this->db->idate(dol_now())."'"; 2207 } elseif ($mode == 'active') { 2208 $sql = "SELECT cd.rowid, cd.date_fin_validite as datefin"; 2209 $sql .= $this->from; 2210 $sql .= " WHERE c.statut = 1"; 2211 $sql .= " AND c.rowid = cd.fk_contrat"; 2212 $sql .= " AND cd.statut = 4"; 2213 //$datetouse = dol_now(); 2214 //$sql.= " AND cd.date_fin_validite < '".$this->db->idate($datetouse)."'"; 2215 } 2216 $sql .= " AND c.fk_soc = s.rowid"; 2217 $sql .= " AND c.entity = ".((int) $conf->entity); 2218 if ($user->socid) { 2219 $sql .= " AND c.fk_soc = ".((int) $user->socid); 2220 } 2221 if (!$user->rights->societe->client->voir && !$user->socid) { 2222 $sql .= " AND c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id); 2223 } 2224 2225 $resql = $this->db->query($sql); 2226 if ($resql) { 2227 $langs->load("contracts"); 2228 $now = dol_now(); 2229 2230 if ($mode == 'inactive') { 2231 $warning_delay = $conf->contrat->services->inactifs->warning_delay; 2232 $label = $langs->trans("BoardNotActivatedServices"); 2233 $labelShort = $langs->trans("BoardNotActivatedServicesShort"); 2234 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&mode=0&sortfield=cd.date_fin_validite&sortorder=asc'; 2235 } elseif ($mode == 'expired') { 2236 $warning_delay = $conf->contrat->services->expires->warning_delay; 2237 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&mode=4&filter=expired&sortfield=cd.date_fin_validite&sortorder=asc'; 2238 $label = $langs->trans("BoardExpiredServices"); 2239 $labelShort = $langs->trans("BoardExpiredServicesShort"); 2240 } else { 2241 $warning_delay = $conf->contrat->services->expires->warning_delay; 2242 $url = DOL_URL_ROOT.'/contrat/services_list.php?mainmenu=commercial&leftmenu=contracts&mode=4&sortfield=cd.date_fin_validite&sortorder=asc'; 2243 //$url.= '&op2day='.$arraydatetouse['mday'].'&op2month='.$arraydatetouse['mon'].'&op2year='.$arraydatetouse['year']; 2244 //if ($warning_delay >= 0) $url.='&filter=expired'; 2245 $label = $langs->trans("BoardRunningServices"); 2246 $labelShort = $langs->trans("BoardRunningServicesShort"); 2247 } 2248 2249 $response = new WorkboardResponse(); 2250 $response->warning_delay = $warning_delay / 60 / 60 / 24; 2251 $response->label = $label; 2252 $response->labelShort = $labelShort; 2253 $response->url = $url; 2254 $response->img = img_object('', "contract"); 2255 2256 while ($obj = $this->db->fetch_object($resql)) { 2257 $response->nbtodo++; 2258 2259 if ($obj->datefin && $this->db->jdate($obj->datefin) < ($now - $warning_delay)) { 2260 $response->nbtodolate++; 2261 } 2262 } 2263 2264 return $response; 2265 } else { 2266 dol_print_error($this->db); 2267 $this->error = $this->db->error(); 2268 return -1; 2269 } 2270 } 2271 2272 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2273 /** 2274 * Charge indicateurs this->nb de tableau de bord 2275 * 2276 * @return int <0 si ko, >0 si ok 2277 */ 2278 public function load_state_board() 2279 { 2280 // phpcs:enable 2281 global $conf, $user; 2282 2283 $this->nb = array(); 2284 $clause = "WHERE"; 2285 2286 $sql = "SELECT count(c.rowid) as nb"; 2287 $sql .= " FROM ".MAIN_DB_PREFIX."contrat as c"; 2288 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid"; 2289 if (!$user->rights->societe->client->voir && !$user->socid) { 2290 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc"; 2291 $sql .= " WHERE sc.fk_user = ".((int) $user->id); 2292 $clause = "AND"; 2293 } 2294 $sql .= " ".$clause." c.entity = ".$conf->entity; 2295 2296 $resql = $this->db->query($sql); 2297 if ($resql) { 2298 while ($obj = $this->db->fetch_object($resql)) { 2299 $this->nb["contracts"] = $obj->nb; 2300 } 2301 $this->db->free($resql); 2302 return 1; 2303 } else { 2304 dol_print_error($this->db); 2305 $this->error = $this->db->error(); 2306 return -1; 2307 } 2308 } 2309 2310 2311 /* gestion des contacts d'un contrat */ 2312 2313 /** 2314 * Return id des contacts clients de facturation 2315 * 2316 * @return array Liste des id contacts facturation 2317 */ 2318 public function getIdBillingContact() 2319 { 2320 return $this->getIdContact('external', 'BILLING'); 2321 } 2322 2323 /** 2324 * Return id des contacts clients de prestation 2325 * 2326 * @return array Liste des id contacts prestation 2327 */ 2328 public function getIdServiceContact() 2329 { 2330 return $this->getIdContact('external', 'SERVICE'); 2331 } 2332 2333 2334 /** 2335 * Initialise an instance with random values. 2336 * Used to build previews or test instances. 2337 * id must be 0 if object instance is a specimen. 2338 * 2339 * @return void 2340 */ 2341 public function initAsSpecimen() 2342 { 2343 global $user, $langs, $conf; 2344 2345 // Load array of products prodids 2346 $num_prods = 0; 2347 $prodids = array(); 2348 $sql = "SELECT rowid"; 2349 $sql .= " FROM ".MAIN_DB_PREFIX."product"; 2350 $sql .= " WHERE entity IN (".getEntity('product').")"; 2351 $sql .= " AND tosell = 1"; 2352 $sql .= $this->db->plimit(100); 2353 2354 $resql = $this->db->query($sql); 2355 if ($resql) { 2356 $num_prods = $this->db->num_rows($resql); 2357 $i = 0; 2358 while ($i < $num_prods) { 2359 $i++; 2360 $row = $this->db->fetch_row($resql); 2361 $prodids[$i] = $row[0]; 2362 } 2363 } 2364 2365 // Initialise parametres 2366 $this->id = 0; 2367 $this->specimen = 1; 2368 2369 $this->ref = 'SPECIMEN'; 2370 $this->ref_customer = 'SPECIMENCUST'; 2371 $this->ref_supplier = 'SPECIMENSUPP'; 2372 $this->socid = 1; 2373 $this->statut = 0; 2374 $this->date_creation = (dol_now() - 3600 * 24 * 7); 2375 $this->date_contrat = dol_now(); 2376 $this->commercial_signature_id = 1; 2377 $this->commercial_suivi_id = 1; 2378 $this->note_private = 'This is a comment (private)'; 2379 $this->note_public = 'This is a comment (public)'; 2380 $this->fk_projet = 0; 2381 // Lines 2382 $nbp = 5; 2383 $xnbp = 0; 2384 while ($xnbp < $nbp) { 2385 $line = new ContratLigne($this->db); 2386 $line->qty = 1; 2387 $line->subprice = 100; 2388 $line->price = 100; 2389 $line->tva_tx = 19.6; 2390 $line->remise_percent = 10; 2391 $line->total_ht = 90; 2392 $line->total_ttc = 107.64; // 90 * 1.196 2393 $line->total_tva = 17.64; 2394 $line->date_start = dol_now() - 500000; 2395 $line->date_start_real = dol_now() - 200000; 2396 $line->date_end = dol_now() + 500000; 2397 $line->date_end_real = dol_now() - 100000; 2398 if ($num_prods > 0) { 2399 $prodid = mt_rand(1, $num_prods); 2400 $line->fk_product = $prodids[$prodid]; 2401 } 2402 $this->lines[$xnbp] = $line; 2403 $xnbp++; 2404 } 2405 } 2406 2407 /** 2408 * Create an array of order lines 2409 * 2410 * @return int >0 if OK, <0 if KO 2411 */ 2412 public function getLinesArray() 2413 { 2414 return $this->fetch_lines(); 2415 } 2416 2417 2418 /** 2419 * Create a document onto disk according to template module. 2420 * 2421 * @param string $modele Force model to use ('' to not force) 2422 * @param Translate $outputlangs Object langs to use for output 2423 * @param int $hidedetails Hide details of lines 2424 * @param int $hidedesc Hide description 2425 * @param int $hideref Hide ref 2426 * @param null|array $moreparams Array to provide more information 2427 * @return int 0 if KO, 1 if OK 2428 */ 2429 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null) 2430 { 2431 global $conf, $langs; 2432 2433 $langs->load("contracts"); 2434 $outputlangs->load("products"); 2435 2436 if (!dol_strlen($modele)) { 2437 $modele = 'strato'; 2438 2439 if (!empty($this->model_pdf)) { 2440 $modele = $this->model_pdf; 2441 } elseif (!empty($this->modelpdf)) { // deprecated 2442 $modele = $this->modelpdf; 2443 } elseif (!empty($conf->global->CONTRACT_ADDON_PDF)) { 2444 $modele = $conf->global->CONTRACT_ADDON_PDF; 2445 } 2446 } 2447 2448 $modelpath = "core/modules/contract/doc/"; 2449 2450 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams); 2451 } 2452 2453 /** 2454 * Function used to replace a thirdparty id with another one. 2455 * 2456 * @param DoliDB $db Database handler 2457 * @param int $origin_id Old thirdparty id 2458 * @param int $dest_id New thirdparty id 2459 * @return bool 2460 */ 2461 public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id) 2462 { 2463 $tables = array( 2464 'contrat' 2465 ); 2466 2467 return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables); 2468 } 2469 2470 /** 2471 * Load an object from its id and create a new one in database 2472 * 2473 * @param User $user User making the clone 2474 * @param int $socid Id of thirdparty 2475 * @param int $notrigger 1=Does not execute triggers, 0= execute triggers 2476 * @return int New id of clone 2477 */ 2478 public function createFromClone(User $user, $socid = 0, $notrigger = 0) 2479 { 2480 global $db, $langs, $conf, $hookmanager, $extrafields; 2481 2482 dol_include_once('/projet/class/project.class.php'); 2483 2484 $error = 0; 2485 2486 $this->fetch($this->id); 2487 2488 // Load dest object 2489 $clonedObj = clone $this; 2490 $clonedObj->socid = $socid; 2491 2492 $this->db->begin(); 2493 2494 $objsoc = new Societe($this->db); 2495 2496 $objsoc->fetch($clonedObj->socid); 2497 2498 // Clean data 2499 $clonedObj->statut = 0; 2500 // Clean extrafields 2501 if (is_array($clonedObj->array_options) && count($clonedObj->array_options) > 0) { 2502 $extrafields->fetch_name_optionals_label($this->table_element); 2503 foreach ($clonedObj->array_options as $key => $option) { 2504 $shortkey = preg_replace('/options_/', '', $key); 2505 //var_dump($shortkey); var_dump($extrafields->attributes[$this->element]['unique'][$shortkey]); 2506 if (!empty($extrafields->attributes[$this->element]['unique'][$shortkey])) { 2507 //var_dump($key); var_dump($clonedObj->array_options[$key]); exit; 2508 unset($clonedObj->array_options[$key]); 2509 } 2510 } 2511 } 2512 2513 if (empty($conf->global->CONTRACT_ADDON) || !is_readable(DOL_DOCUMENT_ROOT."/core/modules/contract/".$conf->global->CONTRACT_ADDON.".php")) { 2514 $this->error = 'ErrorSetupNotComplete'; 2515 dol_syslog($this->error); 2516 return -1; 2517 } 2518 2519 // Set ref 2520 require_once DOL_DOCUMENT_ROOT."/core/modules/contract/".$conf->global->CONTRACT_ADDON.'.php'; 2521 $obj = $conf->global->CONTRACT_ADDON; 2522 $modContract = new $obj(); 2523 $clonedObj->ref = $modContract->getNextValue($objsoc, $clonedObj); 2524 2525 // get extrafields so they will be clone 2526 foreach ($this->lines as $line) { 2527 $line->fetch_optionals($line->id); 2528 } 2529 2530 // Create clone 2531 $clonedObj->context['createfromclone'] = 'createfromclone'; 2532 $result = $clonedObj->create($user); 2533 if ($result < 0) { 2534 $error++; 2535 $this->error = $clonedObj->error; 2536 $this->errors[] = $clonedObj->error; 2537 } else { 2538 // copy external contacts if same company 2539 if ($this->socid == $clonedObj->socid) { 2540 if ($clonedObj->copy_linked_contact($this, 'external') < 0) { 2541 $error++; 2542 } 2543 } 2544 } 2545 2546 if (!$error) { 2547 foreach ($this->lines as $line) { 2548 $result = $clonedObj->addline($line->desc, $line->subprice, $line->qty, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, $line->fk_product, $line->remise_percent, $line->date_ouverture, $line->date_cloture, 'HT', 0, $line->info_bits, $line->fk_fournprice, $line->pa_ht, $line->array_options, $line->fk_unit); 2549 if ($result < 0) { 2550 $error++; 2551 $this->error = $clonedObj->error; 2552 $this->errors[] = $clonedObj->error; 2553 } 2554 } 2555 } 2556 2557 if (!$error) { 2558 // Hook of thirdparty module 2559 if (is_object($hookmanager)) { 2560 $parameters = array( 2561 'objFrom' => $this, 2562 'clonedObj' => $clonedObj 2563 ); 2564 $action = ''; 2565 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $clonedObj, $action); // Note that $action and $object may have been modified by some hooks 2566 if ($reshook < 0) { 2567 $error++; 2568 } 2569 } 2570 } 2571 2572 unset($clonedObj->context['createfromclone']); 2573 2574 // End 2575 if (!$error) { 2576 $this->db->commit(); 2577 return $clonedObj->id; 2578 } else { 2579 $this->db->rollback(); 2580 return -1; 2581 } 2582 } 2583} 2584 2585 2586/** 2587 * Class to manage lines of contracts 2588 */ 2589class ContratLigne extends CommonObjectLine 2590{ 2591 /** 2592 * @var string ID to identify managed object 2593 */ 2594 public $element = 'contratdet'; 2595 2596 /** 2597 * @var string Name of table without prefix where object is stored 2598 */ 2599 public $table_element = 'contratdet'; 2600 2601 /** 2602 * @var int ID 2603 */ 2604 public $id; 2605 2606 /** 2607 * @var string Ref 2608 */ 2609 public $ref; 2610 2611 public $tms; 2612 2613 /** 2614 * @var int ID 2615 */ 2616 public $fk_contrat; 2617 2618 /** 2619 * @var int ID 2620 */ 2621 public $fk_product; 2622 2623 public $statut; // 0 inactive, 4 active, 5 closed 2624 public $type; // 0 for product, 1 for service 2625 2626 /** 2627 * @var string 2628 * @deprecated 2629 */ 2630 public $label; 2631 2632 /** 2633 * @var string 2634 * @deprecated 2635 */ 2636 public $libelle; 2637 2638 /** 2639 * @var string description 2640 */ 2641 public $description; 2642 2643 public $product_type; // 0 for product, 1 for service 2644 public $product_ref; 2645 public $product_label; 2646 2647 public $date_commande; 2648 2649 public $date_start; // date start planned 2650 public $date_start_real; // date start real 2651 public $date_end; // date end planned 2652 public $date_end_real; // date end real 2653 // For backward compatibility 2654 /** 2655 * @deprecated Use date_start 2656 */ 2657 public $date_ouverture_prevue; // date start planned 2658 /** 2659 * @deprecated Use date_start_real 2660 */ 2661 public $date_ouverture; // date start real 2662 /** 2663 * @deprecated Use date_end 2664 */ 2665 public $date_fin_validite; // date end planned 2666 /** 2667 * @deprecated Use date_end_real 2668 */ 2669 public $date_cloture; // date end real 2670 2671 public $tva_tx; 2672 public $localtax1_tx; 2673 public $localtax2_tx; 2674 public $localtax1_type; // Local tax 1 type 2675 public $localtax2_type; // Local tax 2 type 2676 public $qty; 2677 public $remise_percent; 2678 public $remise; 2679 2680 /** 2681 * @var int ID 2682 */ 2683 public $fk_remise_except; 2684 2685 public $subprice; // Unit price HT 2686 2687 /** 2688 * @var float 2689 * @deprecated Use $price_ht instead 2690 * @see $price_ht 2691 */ 2692 public $price; 2693 2694 public $price_ht; 2695 2696 public $total_ht; 2697 public $total_tva; 2698 public $total_localtax1; 2699 public $total_localtax2; 2700 public $total_ttc; 2701 2702 /** 2703 * @var int ID 2704 */ 2705 public $fk_fournprice; 2706 2707 public $pa_ht; 2708 2709 public $info_bits; 2710 2711 /** 2712 * @var int ID 2713 */ 2714 public $fk_user_author; 2715 2716 /** 2717 * @var int ID 2718 */ 2719 public $fk_user_ouverture; 2720 2721 /** 2722 * @var int ID 2723 */ 2724 public $fk_user_cloture; 2725 2726 public $commentaire; 2727 2728 const STATUS_INITIAL = 0; 2729 const STATUS_OPEN = 4; 2730 const STATUS_CLOSED = 5; 2731 2732 2733 2734 /** 2735 * Constructor 2736 * 2737 * @param DoliDb $db Database handler 2738 */ 2739 public function __construct($db) 2740 { 2741 $this->db = $db; 2742 } 2743 2744 2745 /** 2746 * Return label of this contract line status 2747 * 2748 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 2749 * @return string Label of status 2750 */ 2751 public function getLibStatut($mode) 2752 { 2753 return $this->LibStatut($this->statut, $mode, ((!empty($this->date_fin_validite)) ? ($this->date_fin_validite < dol_now() ? 1 : 0) : -1)); 2754 } 2755 2756 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 2757 /** 2758 * Return label of a contract line status 2759 * 2760 * @param int $status Id status 2761 * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto 2762 * @param int $expired 0=Not expired, 1=Expired, -1=Both or unknown 2763 * @param string $moreatt More attribute 2764 * @return string Label of status 2765 */ 2766 public static function LibStatut($status, $mode, $expired = -1, $moreatt = '') 2767 { 2768 // phpcs:enable 2769 global $langs; 2770 $langs->load("contracts"); 2771 2772 if ($status == self::STATUS_INITIAL) { 2773 $labelStatus = $langs->trans("ServiceStatusInitial"); 2774 $labelStatusShort = $langs->trans("ServiceStatusInitial"); 2775 } elseif ($status == self::STATUS_OPEN && $expired == -1) { 2776 $labelStatus = $langs->trans("ServiceStatusRunning"); 2777 $labelStatusShort = $langs->trans("ServiceStatusRunning"); 2778 } elseif ($status == self::STATUS_OPEN && $expired == 0) { 2779 $labelStatus = $langs->trans("ServiceStatusNotLate"); 2780 $labelStatusShort = $langs->trans("ServiceStatusNotLateShort"); 2781 } elseif ($status == self::STATUS_OPEN && $expired == 1) { 2782 $labelStatus = $langs->trans("ServiceStatusLate"); 2783 $labelStatusShort = $langs->trans("ServiceStatusLateShort"); 2784 } elseif ($status == self::STATUS_CLOSED) { 2785 $labelStatus = $langs->trans("ServiceStatusClosed"); 2786 $labelStatusShort = $langs->trans("ServiceStatusClosed"); 2787 } 2788 2789 $statusType = 'status'.$status; 2790 if ($status == self::STATUS_OPEN && $expired == 1) { 2791 $statusType = 'status1'; 2792 } 2793 if ($status == self::STATUS_CLOSED) { 2794 $statusType = 'status6'; 2795 } 2796 2797 $params = array(); $reg = array(); 2798 if (preg_match('/class="(.*)"/', $moreatt, $reg)) { 2799 $params = array('badgeParams'=>array('css' => $reg[1])); 2800 } 2801 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $params); 2802 } 2803 2804 /** 2805 * Return clicable name (with picto eventually) 2806 * 2807 * @param int $withpicto 0=No picto, 1=Include picto into link, 2=Only picto 2808 * @param int $maxlength Max length 2809 * @return string Chaine avec URL 2810 */ 2811 public function getNomUrl($withpicto = 0, $maxlength = 0) 2812 { 2813 global $langs; 2814 2815 $result = ''; 2816 $label = $langs->trans("ShowContractOfService").': '.$this->label; 2817 if (empty($label)) { 2818 $label = $this->description; 2819 } 2820 2821 $link = '<a href="'.DOL_URL_ROOT.'/contrat/card.php?id='.$this->fk_contrat.'" title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip">'; 2822 $linkend = '</a>'; 2823 2824 $picto = 'service'; 2825 if ($this->type == 0) { 2826 $picto = 'product'; 2827 } 2828 2829 if ($withpicto) { 2830 $result .= ($link.img_object($label, $picto, 'class="classfortooltip"').$linkend); 2831 } 2832 if ($withpicto && $withpicto != 2) { 2833 $result .= ' '; 2834 } 2835 if ($withpicto != 2) { 2836 $result .= $link.($this->product_ref ? $this->product_ref.' ' : '').($this->label ? $this->label : $this->description).$linkend; 2837 } 2838 return $result; 2839 } 2840 2841 /** 2842 * Load object in memory from database 2843 * 2844 * @param int $id Id object 2845 * @param string $ref Ref of contract line 2846 * @return int <0 if KO, >0 if OK 2847 */ 2848 public function fetch($id, $ref = '') 2849 { 2850 // Check parameters 2851 if (empty($id) && empty($ref)) { 2852 return -1; 2853 } 2854 2855 $sql = "SELECT"; 2856 $sql .= " t.rowid,"; 2857 $sql .= " t.tms,"; 2858 $sql .= " t.fk_contrat,"; 2859 $sql .= " t.fk_product,"; 2860 $sql .= " t.statut,"; 2861 $sql .= " t.label,"; // This field is not used. Only label of product 2862 $sql .= " p.ref as product_ref,"; 2863 $sql .= " p.label as product_label,"; 2864 $sql .= " p.description as product_desc,"; 2865 $sql .= " p.fk_product_type as product_type,"; 2866 $sql .= " t.description,"; 2867 $sql .= " t.date_commande,"; 2868 $sql .= " t.date_ouverture_prevue as date_ouverture_prevue,"; 2869 $sql .= " t.date_ouverture as date_ouverture,"; 2870 $sql .= " t.date_fin_validite as date_fin_validite,"; 2871 $sql .= " t.date_cloture as date_cloture,"; 2872 $sql .= " t.tva_tx,"; 2873 $sql .= " t.vat_src_code,"; 2874 $sql .= " t.localtax1_tx,"; 2875 $sql .= " t.localtax2_tx,"; 2876 $sql .= " t.localtax1_type,"; 2877 $sql .= " t.localtax2_type,"; 2878 $sql .= " t.qty,"; 2879 $sql .= " t.remise_percent,"; 2880 $sql .= " t.remise,"; 2881 $sql .= " t.fk_remise_except,"; 2882 $sql .= " t.subprice,"; 2883 $sql .= " t.price_ht,"; 2884 $sql .= " t.total_ht,"; 2885 $sql .= " t.total_tva,"; 2886 $sql .= " t.total_localtax1,"; 2887 $sql .= " t.total_localtax2,"; 2888 $sql .= " t.total_ttc,"; 2889 $sql .= " t.fk_product_fournisseur_price as fk_fournprice,"; 2890 $sql .= " t.buy_price_ht as pa_ht,"; 2891 $sql .= " t.info_bits,"; 2892 $sql .= " t.fk_user_author,"; 2893 $sql .= " t.fk_user_ouverture,"; 2894 $sql .= " t.fk_user_cloture,"; 2895 $sql .= " t.commentaire,"; 2896 $sql .= " t.fk_unit"; 2897 $sql .= " FROM ".MAIN_DB_PREFIX."contratdet as t LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = t.fk_product"; 2898 if ($id) { 2899 $sql .= " WHERE t.rowid = ".((int) $id); 2900 } 2901 if ($ref) { 2902 $sql .= " WHERE t.rowid = '".$this->db->escape($ref)."'"; 2903 } 2904 2905 dol_syslog(get_class($this)."::fetch", LOG_DEBUG); 2906 $resql = $this->db->query($sql); 2907 if ($resql) { 2908 if ($this->db->num_rows($resql)) { 2909 $obj = $this->db->fetch_object($resql); 2910 2911 $this->id = $obj->rowid; 2912 $this->ref = $obj->rowid; 2913 2914 $this->tms = $this->db->jdate($obj->tms); 2915 $this->fk_contrat = $obj->fk_contrat; 2916 $this->fk_product = $obj->fk_product; 2917 $this->statut = $obj->statut; 2918 $this->product_ref = $obj->product_ref; 2919 $this->product_label = $obj->product_label; 2920 $this->product_description = $obj->product_description; 2921 $this->product_type = $obj->product_type; 2922 $this->label = $obj->label; // deprecated. We do not use this field. Only ref and label of product, and description of contract line 2923 $this->description = $obj->description; 2924 $this->date_commande = $this->db->jdate($obj->date_commande); 2925 2926 $this->date_start = $this->db->jdate($obj->date_ouverture_prevue); 2927 $this->date_start_real = $this->db->jdate($obj->date_ouverture); 2928 $this->date_end = $this->db->jdate($obj->date_fin_validite); 2929 $this->date_end_real = $this->db->jdate($obj->date_cloture); 2930 // For backward compatibility 2931 $this->date_ouverture_prevue = $this->db->jdate($obj->date_ouverture_prevue); 2932 $this->date_ouverture = $this->db->jdate($obj->date_ouverture); 2933 $this->date_fin_validite = $this->db->jdate($obj->date_fin_validite); 2934 $this->date_cloture = $this->db->jdate($obj->date_cloture); 2935 2936 $this->tva_tx = $obj->tva_tx; 2937 $this->vat_src_code = $obj->vat_src_code; 2938 $this->localtax1_tx = $obj->localtax1_tx; 2939 $this->localtax2_tx = $obj->localtax2_tx; 2940 $this->localtax1_type = $obj->localtax1_type; 2941 $this->localtax2_type = $obj->localtax2_type; 2942 $this->qty = $obj->qty; 2943 $this->remise_percent = $obj->remise_percent; 2944 $this->remise = $obj->remise; 2945 $this->fk_remise_except = $obj->fk_remise_except; 2946 $this->subprice = $obj->subprice; 2947 $this->price_ht = $obj->price_ht; 2948 $this->total_ht = $obj->total_ht; 2949 $this->total_tva = $obj->total_tva; 2950 $this->total_localtax1 = $obj->total_localtax1; 2951 $this->total_localtax2 = $obj->total_localtax2; 2952 $this->total_ttc = $obj->total_ttc; 2953 $this->info_bits = $obj->info_bits; 2954 $this->fk_user_author = $obj->fk_user_author; 2955 $this->fk_user_ouverture = $obj->fk_user_ouverture; 2956 $this->fk_user_cloture = $obj->fk_user_cloture; 2957 $this->commentaire = $obj->commentaire; 2958 $this->fk_fournprice = $obj->fk_fournprice; 2959 2960 $marginInfos = getMarginInfos($obj->subprice, $obj->remise_percent, $obj->tva_tx, $obj->localtax1_tx, $obj->localtax2_tx, $this->fk_fournprice, $obj->pa_ht); 2961 $this->pa_ht = $marginInfos[0]; 2962 $this->fk_unit = $obj->fk_unit; 2963 2964 $this->fetch_optionals(); 2965 } 2966 2967 $this->db->free($resql); 2968 2969 return 1; 2970 } else { 2971 $this->error = "Error ".$this->db->lasterror(); 2972 return -1; 2973 } 2974 } 2975 2976 2977 /** 2978 * Update database for contract line 2979 * 2980 * @param User $user User that modify 2981 * @param int $notrigger 0=no, 1=yes (no update trigger) 2982 * @return int <0 if KO, >0 if OK 2983 */ 2984 public function update($user, $notrigger = 0) 2985 { 2986 global $conf, $langs, $mysoc; 2987 2988 $error = 0; 2989 2990 // Clean parameters 2991 $this->fk_contrat = (int) $this->fk_contrat; 2992 $this->fk_product = (int) $this->fk_product; 2993 $this->statut = (int) $this->statut; 2994 $this->label = trim($this->label); 2995 $this->description = trim($this->description); 2996 $this->vat_src_code = trim($this->vat_src_code); 2997 $this->tva_tx = trim($this->tva_tx); 2998 $this->localtax1_tx = trim($this->localtax1_tx); 2999 $this->localtax2_tx = trim($this->localtax2_tx); 3000 $this->qty = trim($this->qty); 3001 $this->remise_percent = trim($this->remise_percent); 3002 $this->remise = trim($this->remise); 3003 $this->fk_remise_except = (int) $this->fk_remise_except; 3004 $this->subprice = price2num($this->subprice); 3005 $this->price_ht = price2num($this->price_ht); 3006 $this->total_ht = trim($this->total_ht); 3007 $this->total_tva = trim($this->total_tva); 3008 $this->total_localtax1 = trim($this->total_localtax1); 3009 $this->total_localtax2 = trim($this->total_localtax2); 3010 $this->total_ttc = trim($this->total_ttc); 3011 $this->info_bits = trim($this->info_bits); 3012 $this->fk_user_author = (int) $this->fk_user_author; 3013 $this->fk_user_ouverture = (int) $this->fk_user_ouverture; 3014 $this->fk_user_cloture = (int) $this->fk_user_cloture; 3015 $this->commentaire = trim($this->commentaire); 3016 //if (empty($this->subprice)) $this->subprice = 0; 3017 if (empty($this->price_ht)) { 3018 $this->price_ht = 0; 3019 } 3020 if (empty($this->total_ht)) { 3021 $this->total_ht = 0; 3022 } 3023 if (empty($this->total_tva)) { 3024 $this->total_tva = 0; 3025 } 3026 if (empty($this->total_ttc)) { 3027 $this->total_ttc = 0; 3028 } 3029 if (empty($this->localtax1_tx)) { 3030 $this->localtax1_tx = 0; 3031 } 3032 if (empty($this->localtax2_tx)) { 3033 $this->localtax2_tx = 0; 3034 } 3035 if (empty($this->remise_percent)) { 3036 $this->remise_percent = 0; 3037 } 3038 // For backward compatibility 3039 if (empty($this->date_start)) { 3040 $this->date_start = $this->date_ouverture_prevue; 3041 } 3042 if (empty($this->date_start_real)) { 3043 $this->date_start = $this->date_ouverture; 3044 } 3045 if (empty($this->date_end)) { 3046 $this->date_start = $this->date_fin_validite; 3047 } 3048 if (empty($this->date_end_real)) { 3049 $this->date_start = $this->date_cloture; 3050 } 3051 3052 3053 // Check parameters 3054 // Put here code to add control on parameters values 3055 3056 // Calcul du total TTC et de la TVA pour la ligne a partir de 3057 // qty, pu, remise_percent et txtva 3058 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker 3059 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva. 3060 $localtaxes_type = getLocalTaxesFromRate($this->txtva, 0, $this->societe, $mysoc); 3061 3062 $tabprice = calcul_price_total($this->qty, $this->price_ht, $this->remise_percent, $this->tva_tx, $this->localtax1_tx, $this->localtax2_tx, 0, 'HT', 0, 1, $mysoc, $localtaxes_type); 3063 $this->total_ht = $tabprice[0]; 3064 $this->total_tva = $tabprice[1]; 3065 $this->total_ttc = $tabprice[2]; 3066 $this->total_localtax1 = $tabprice[9]; 3067 $this->total_localtax2 = $tabprice[10]; 3068 3069 if (empty($this->pa_ht)) { 3070 $this->pa_ht = 0; 3071 } 3072 3073 // if buy price not defined, define buyprice as configured in margin admin 3074 if ($this->pa_ht == 0) { 3075 if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) { 3076 return $result; 3077 } else { 3078 $this->pa_ht = $result; 3079 } 3080 } 3081 3082 3083 $this->db->begin(); 3084 3085 $this->oldcopy = new ContratLigne($this->db); 3086 $this->oldcopy->fetch($this->id); 3087 3088 // Update request 3089 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET"; 3090 $sql .= " fk_contrat=".((int) $this->fk_contrat).","; 3091 $sql .= " fk_product=".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : 'null').","; 3092 $sql .= " statut=".((int) $this->statut).","; 3093 $sql .= " label='".$this->db->escape($this->label)."',"; 3094 $sql .= " description='".$this->db->escape($this->description)."',"; 3095 $sql .= " date_commande=".($this->date_commande != '' ? "'".$this->db->idate($this->date_commande)."'" : "null").","; 3096 $sql .= " date_ouverture_prevue=".($this->date_ouverture_prevue != '' ? "'".$this->db->idate($this->date_ouverture_prevue)."'" : "null").","; 3097 $sql .= " date_ouverture=".($this->date_ouverture != '' ? "'".$this->db->idate($this->date_ouverture)."'" : "null").","; 3098 $sql .= " date_fin_validite=".($this->date_fin_validite != '' ? "'".$this->db->idate($this->date_fin_validite)."'" : "null").","; 3099 $sql .= " date_cloture=".($this->date_cloture != '' ? "'".$this->db->idate($this->date_cloture)."'" : "null").","; 3100 $sql .= " vat_src_code='".$this->db->escape($this->vat_src_code)."',"; 3101 $sql .= " tva_tx=".price2num($this->tva_tx).","; 3102 $sql .= " localtax1_tx=".price2num($this->localtax1_tx).","; 3103 $sql .= " localtax2_tx=".price2num($this->localtax2_tx).","; 3104 $sql .= " qty=".price2num($this->qty).","; 3105 $sql .= " remise_percent=".price2num($this->remise_percent).","; 3106 $sql .= " remise=".($this->remise ?price2num($this->remise) : "null").","; 3107 $sql .= " fk_remise_except=".($this->fk_remise_except > 0 ? $this->fk_remise_except : "null").","; 3108 $sql .= " subprice=".($this->subprice != '' ? $this->subprice : "null").","; 3109 $sql .= " price_ht=".($this->price_ht != '' ? $this->price_ht : "null").","; 3110 $sql .= " total_ht=".$this->total_ht.","; 3111 $sql .= " total_tva=".$this->total_tva.","; 3112 $sql .= " total_localtax1=".$this->total_localtax1.","; 3113 $sql .= " total_localtax2=".$this->total_localtax2.","; 3114 $sql .= " total_ttc=".$this->total_ttc.","; 3115 $sql .= " fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? $this->fk_fournprice : "NULL").","; 3116 $sql .= " buy_price_ht='".price2num($this->pa_ht)."',"; 3117 $sql .= " info_bits='".$this->db->escape($this->info_bits)."',"; 3118 $sql .= " fk_user_author=".($this->fk_user_author >= 0 ? $this->fk_user_author : "NULL").","; 3119 $sql .= " fk_user_ouverture=".($this->fk_user_ouverture > 0 ? $this->fk_user_ouverture : "NULL").","; 3120 $sql .= " fk_user_cloture=".($this->fk_user_cloture > 0 ? $this->fk_user_cloture : "NULL").","; 3121 $sql .= " commentaire='".$this->db->escape($this->commentaire)."',"; 3122 $sql .= " fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit); 3123 $sql .= " WHERE rowid=".((int) $this->id); 3124 3125 dol_syslog(get_class($this)."::update", LOG_DEBUG); 3126 $resql = $this->db->query($sql); 3127 if (!$resql) { 3128 $this->error = "Error ".$this->db->lasterror(); 3129 $error++; 3130 } 3131 3132 if (!$error) { // For avoid conflicts if trigger used 3133 $result = $this->insertExtraFields(); 3134 if ($result < 0) { 3135 $error++; 3136 } 3137 } 3138 3139 // If we change a planned date (start or end), sync dates for all services 3140 if (!$error && !empty($conf->global->CONTRACT_SYNC_PLANNED_DATE_OF_SERVICES)) { 3141 if ($this->date_ouverture_prevue != $this->oldcopy->date_ouverture_prevue) { 3142 $sql = 'UPDATE '.MAIN_DB_PREFIX.'contratdet SET'; 3143 $sql .= " date_ouverture_prevue = ".($this->date_ouverture_prevue != '' ? "'".$this->db->idate($this->date_ouverture_prevue)."'" : "null"); 3144 $sql .= " WHERE fk_contrat = ".((int) $this->fk_contrat); 3145 3146 $resql = $this->db->query($sql); 3147 if (!$resql) { 3148 $error++; 3149 $this->error = "Error ".$this->db->lasterror(); 3150 } 3151 } 3152 if ($this->date_fin_validite != $this->oldcopy->date_fin_validite) { 3153 $sql = 'UPDATE '.MAIN_DB_PREFIX.'contratdet SET'; 3154 $sql .= " date_fin_validite = ".($this->date_fin_validite != '' ? "'".$this->db->idate($this->date_fin_validite)."'" : "null"); 3155 $sql .= " WHERE fk_contrat = ".((int) $this->fk_contrat); 3156 3157 $resql = $this->db->query($sql); 3158 if (!$resql) { 3159 $error++; 3160 $this->error = "Error ".$this->db->lasterror(); 3161 } 3162 } 3163 } 3164 3165 if (!$error && !$notrigger) { 3166 // Call trigger 3167 $result = $this->call_trigger('LINECONTRACT_UPDATE', $user); 3168 if ($result < 0) { 3169 $error++; 3170 $this->db->rollback(); 3171 } 3172 // End call triggers 3173 } 3174 3175 if (!$error) { 3176 $this->db->commit(); 3177 return 1; 3178 } else { 3179 $this->db->rollback(); 3180 $this->errors[] = $this->error; 3181 return -1; 3182 } 3183 } 3184 3185 3186 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 3187 /** 3188 * Mise a jour en base des champs total_xxx de ligne 3189 * Used by migration process 3190 * 3191 * @return int <0 if KO, >0 if OK 3192 */ 3193 public function update_total() 3194 { 3195 // phpcs:enable 3196 $this->db->begin(); 3197 3198 // Mise a jour ligne en base 3199 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET"; 3200 $sql .= " total_ht=".price2num($this->total_ht, 'MT').""; 3201 $sql .= ",total_tva=".price2num($this->total_tva, 'MT').""; 3202 $sql .= ",total_localtax1=".price2num($this->total_localtax1, 'MT').""; 3203 $sql .= ",total_localtax2=".price2num($this->total_localtax2, 'MT').""; 3204 $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT').""; 3205 $sql .= " WHERE rowid = ".$this->id; 3206 3207 dol_syslog(get_class($this)."::update_total", LOG_DEBUG); 3208 3209 $resql = $this->db->query($sql); 3210 if ($resql) { 3211 $this->db->commit(); 3212 return 1; 3213 } else { 3214 $this->error = $this->db->error(); 3215 $this->db->rollback(); 3216 return -2; 3217 } 3218 } 3219 3220 3221 /** 3222 * Inserts a contrat line into database 3223 * 3224 * @param int $notrigger Set to 1 if you don't want triggers to be fired 3225 * @return int <0 if KO, >0 if OK 3226 */ 3227 public function insert($notrigger = 0) 3228 { 3229 global $conf, $user; 3230 3231 $error = 0; 3232 3233 // Insertion dans la base 3234 $sql = "INSERT INTO ".MAIN_DB_PREFIX."contratdet"; 3235 $sql .= " (fk_contrat, label, description, fk_product, qty, vat_src_code, tva_tx,"; 3236 $sql .= " localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice,"; 3237 $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc,"; 3238 $sql .= " info_bits,"; 3239 $sql .= " price_ht, remise, fk_product_fournisseur_price, buy_price_ht"; 3240 if ($this->date_ouverture_prevue > 0) { 3241 $sql .= ",date_ouverture_prevue"; 3242 } 3243 if ($this->date_fin_validite > 0) { 3244 $sql .= ",date_fin_validite"; 3245 } 3246 $sql .= ") VALUES ($this->fk_contrat, '', '".$this->db->escape($this->description)."',"; 3247 $sql .= ($this->fk_product > 0 ? $this->fk_product : "null").","; 3248 $sql .= " '".$this->db->escape($this->qty)."',"; 3249 $sql .= " '".$this->db->escape($this->vat_src_code)."',"; 3250 $sql .= " '".$this->db->escape($this->tva_tx)."',"; 3251 $sql .= " '".$this->db->escape($this->localtax1_tx)."',"; 3252 $sql .= " '".$this->db->escape($this->localtax2_tx)."',"; 3253 $sql .= " '".$this->db->escape($this->localtax1_type)."',"; 3254 $sql .= " '".$this->db->escape($this->localtax2_type)."',"; 3255 $sql .= " ".price2num($this->remise_percent).",".price2num($this->subprice).","; 3256 $sql .= " ".price2num($this->total_ht).",".price2num($this->total_tva).",".price2num($this->total_localtax1).",".price2num($this->total_localtax2).",".price2num($this->total_ttc).","; 3257 $sql .= " '".$this->db->escape($this->info_bits)."',"; 3258 $sql .= " ".price2num($this->price_ht).",".price2num($this->remise).","; 3259 if ($this->fk_fournprice > 0) { 3260 $sql .= ' '.$this->fk_fournprice.','; 3261 } else { 3262 $sql .= ' null,'; 3263 } 3264 if ($this->pa_ht > 0) { 3265 $sql .= ' '.price2num($this->pa_ht); 3266 } else { 3267 $sql .= ' null'; 3268 } 3269 if ($this->date_ouverture > 0) { 3270 $sql .= ",'".$this->db->idate($this->date_ouverture)."'"; 3271 } 3272 if ($this->date_cloture > 0) { 3273 $sql .= ",'".$this->db->idate($this->date_cloture)."'"; 3274 } 3275 $sql .= ")"; 3276 3277 dol_syslog(get_class($this)."::insert", LOG_DEBUG); 3278 3279 $resql = $this->db->query($sql); 3280 if ($resql) { 3281 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'contratdet'); 3282 3283 // Insert of extrafields 3284 if (!$error) { 3285 $result = $this->insertExtraFields(); 3286 if ($result < 0) { 3287 $this->db->rollback(); 3288 return -1; 3289 } 3290 } 3291 3292 if (!$notrigger) { 3293 // Call trigger 3294 $result = $this->call_trigger('LINECONTRACT_INSERT', $user); 3295 if ($result < 0) { 3296 $this->db->rollback(); 3297 return -1; 3298 } 3299 // End call triggers 3300 } 3301 3302 $this->db->commit(); 3303 return 1; 3304 } else { 3305 $this->db->rollback(); 3306 $this->error = $this->db->error()." sql=".$sql; 3307 return -1; 3308 } 3309 } 3310 3311 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 3312 /** 3313 * Activate a contract line 3314 * 3315 * @param User $user Objet User who activate contract 3316 * @param int $date Date activation 3317 * @param int|string $date_end Date planned end. Use '-1' to keep it unchanged. 3318 * @param string $comment A comment typed by user 3319 * @return int <0 if KO, >0 if OK 3320 */ 3321 public function active_line($user, $date, $date_end = '', $comment = '') 3322 { 3323 // phpcs:enable 3324 global $langs, $conf; 3325 3326 $error = 0; 3327 3328 $this->db->begin(); 3329 3330 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET statut = ".ContratLigne::STATUS_OPEN.","; 3331 $sql .= " date_ouverture = ".(dol_strlen($date) != 0 ? "'".$this->db->idate($date)."'" : "null").","; 3332 if ($date_end >= 0) { 3333 $sql .= " date_fin_validite = ".(dol_strlen($date_end) != 0 ? "'".$this->db->idate($date_end)."'" : "null").","; 3334 } 3335 $sql .= " fk_user_ouverture = ".$user->id.","; 3336 $sql .= " date_cloture = null,"; 3337 $sql .= " commentaire = '".$this->db->escape($comment)."'"; 3338 $sql .= " WHERE rowid = ".$this->id." AND (statut = ".ContratLigne::STATUS_INITIAL." OR statut = ".ContratLigne::STATUS_CLOSED.")"; 3339 3340 dol_syslog(get_class($this)."::active_line", LOG_DEBUG); 3341 $resql = $this->db->query($sql); 3342 if ($resql) { 3343 // Call trigger 3344 $result = $this->call_trigger('LINECONTRACT_ACTIVATE', $user); 3345 if ($result < 0) { 3346 $error++; 3347 } 3348 // End call triggers 3349 3350 if (!$error) { 3351 $this->statut = ContratLigne::STATUS_OPEN; 3352 $this->date_ouverture = $date; 3353 $this->date_fin_validite = $date_end; 3354 $this->fk_user_ouverture = $user->id; 3355 $this->date_cloture = null; 3356 $this->commentaire = $comment; 3357 3358 $this->db->commit(); 3359 return 1; 3360 } else { 3361 $this->db->rollback(); 3362 return -1; 3363 } 3364 } else { 3365 $this->error = $this->db->lasterror(); 3366 $this->db->rollback(); 3367 return -1; 3368 } 3369 } 3370 3371 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps 3372 /** 3373 * Close a contract line 3374 * 3375 * @param User $user Objet User who close contract 3376 * @param int $date_end Date end 3377 * @param string $comment A comment typed by user 3378 * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers 3379 * @return int <0 if KO, >0 if OK 3380 */ 3381 public function close_line($user, $date_end, $comment = '', $notrigger = 0) 3382 { 3383 // phpcs:enable 3384 global $langs, $conf; 3385 3386 // Update object 3387 $this->date_cloture = $date_end; 3388 $this->fk_user_cloture = $user->id; 3389 $this->commentaire = $comment; 3390 3391 $error = 0; 3392 3393 // statut actif : 4 3394 3395 $this->db->begin(); 3396 3397 $sql = "UPDATE ".MAIN_DB_PREFIX."contratdet SET statut = ".((int) ContratLigne::STATUS_CLOSED).","; 3398 $sql .= " date_cloture = '".$this->db->idate($date_end)."',"; 3399 $sql .= " fk_user_cloture = ".$user->id.","; 3400 $sql .= " commentaire = '".$this->db->escape($comment)."'"; 3401 $sql .= " WHERE rowid = ".$this->id." AND statut = ".((int) ContratLigne::STATUS_OPEN); 3402 3403 $resql = $this->db->query($sql); 3404 if ($resql) { 3405 if (!$notrigger) { 3406 // Call trigger 3407 $result = $this->call_trigger('LINECONTRACT_CLOSE', $user); 3408 if ($result < 0) { 3409 $error++; 3410 $this->db->rollback(); 3411 return -1; 3412 } 3413 // End call triggers 3414 } 3415 3416 $this->db->commit(); 3417 return 1; 3418 } else { 3419 $this->error = $this->db->lasterror(); 3420 $this->db->rollback(); 3421 return -1; 3422 } 3423 } 3424} 3425