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 .= ': &nbsp; &nbsp; ';
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) ? ' &nbsp; ' : '') : '';
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) ? ' &nbsp; ' : '') : '';
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) ? ' &nbsp; ' : '') : '';
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.='&amp;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