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