Passed
Branch develop (356b3a)
by
unknown
98:06
created

Propal::updateline()   F

Complexity

Conditions 21
Paths 800

Size

Total Lines 161
Code Lines 110

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 110
c 0
b 0
f 0
nop 24
dl 0
loc 161
rs 0.2222
nc 800

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2002-2004  Rodolphe Quiedeville    <[email protected]>
3
 * Copyright (C) 2004       Eric Seigne             <[email protected]>
4
 * Copyright (C) 2004-2011  Laurent Destailleur     <[email protected]>
5
 * Copyright (C) 2005       Marc Barilley           <[email protected]>
6
 * Copyright (C) 2005-2013  Regis Houssin           <[email protected]>
7
 * Copyright (C) 2006       Andre Cianfarani        <[email protected]>
8
 * Copyright (C) 2008       Raphael Bertrand        <[email protected]>
9
 * Copyright (C) 2010-2020  Juanjo Menent           <[email protected]>
10
 * Copyright (C) 2010-2022  Philippe Grand          <[email protected]>
11
 * Copyright (C) 2012-2014  Christophe Battarel     <[email protected]>
12
 * Copyright (C) 2012       Cedric Salvador         <[email protected]>
13
 * Copyright (C) 2013       Florian Henry           <[email protected]>
14
 * Copyright (C) 2014-2015  Marcos García           <[email protected]>
15
 * Copyright (C) 2018       Nicolas ZABOURI         <[email protected]>
16
 * Copyright (C) 2018-2021  Frédéric France         <[email protected]>
17
 * Copyright (C) 2018       Ferran Marcet           <[email protected]>
18
 * Copyright (C) 2022       ATM Consulting          <[email protected]>
19
 * Copyright (C) 2022       OpenDSI                 <[email protected]>
20
 * Copyright (C) 2022      	Gauthier VERDOL     	<[email protected]>
21
 *
22
 * This program is free software; you can redistribute it and/or modify
23
 * it under the terms of the GNU General Public License as published by
24
 * the Free Software Foundation; either version 3 of the License, or
25
 * (at your option) any later version.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30
 * GNU General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU General Public License
33
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
34
 */
35
36
/**
37
 *	\file       htdocs/comm/propal/class/propal.class.php
38
 *	\brief      File of class to manage proposals
39
 */
40
41
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
42
require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
43
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
44
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
45
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
46
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
47
require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
48
49
/**
50
 *	Class to manage proposals
51
 */
52
class Propal extends CommonObject
53
{
54
	use CommonIncoterm;
55
56
	/**
57
	 * @var string code
58
	 */
59
	public $code = "";
60
61
	/**
62
	 * @var string ID to identify managed object
63
	 */
64
	public $element = 'propal';
65
66
	/**
67
	 * @var string Name of table without prefix where object is stored
68
	 */
69
	public $table_element = 'propal';
70
71
	/**
72
	 * @var int    Name of subtable line
73
	 */
74
	public $table_element_line = 'propaldet';
75
76
	/**
77
	 * @var string Fieldname with ID of parent key if this field has a parent
78
	 */
79
	public $fk_element = 'fk_propal';
80
81
	/**
82
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
83
	 */
84
	public $picto = 'propal';
85
86
	/**
87
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
88
	 * @var int
89
	 */
90
	public $ismultientitymanaged = 1;
91
92
	/**
93
	 * 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
94
	 * @var integer
95
	 */
96
	public $restrictiononfksoc = 1;
97
98
	/**
99
	 * {@inheritdoc}
100
	 */
101
	protected $table_ref_field = 'ref';
102
103
	/**
104
	 * ID of the client
105
	 * @var int
106
	 */
107
	public $socid;
108
109
	/**
110
	 * ID of the contact
111
	 * @var int
112
	 */
113
	public $contactid;
114
	public $author;
115
116
	/**
117
	 * Ref from thirdparty
118
	 * @var string
119
	 */
120
	public $ref_client;
121
122
	/**
123
	 * Status of the quote
124
	 * @var int
125
	 * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED
126
	 */
127
	public $statut;
128
129
	/**
130
	 * Status of the quote
131
	 * @var int
132
	 * @see Propal::STATUS_DRAFT, Propal::STATUS_VALIDATED, Propal::STATUS_SIGNED, Propal::STATUS_NOTSIGNED, Propal::STATUS_BILLED
133
	 */
134
	public $status;
135
136
	/**
137
	 * @deprecated
138
	 * @see $date_creation
139
	 */
140
	public $datec;
141
142
	/**
143
	 * @var integer|string $date_creation;
144
	 */
145
	public $date_creation;
146
147
	/**
148
	 * @deprecated
149
	 * @see $date_validation
150
	 */
151
	public $datev;
152
153
	/**
154
	 * @var integer|string $date_validation;
155
	 */
156
	public $date_validation;
157
158
	/**
159
	 * @var integer|string $date_signature;
160
	 */
161
	public $date_signature;
162
163
	/**
164
	 * @var User $user_signature
165
	 */
166
	public $user_signature;
167
168
	/**
169
	 * @var integer|string date of the quote;
170
	 */
171
	public $date;
172
173
	/**
174
	 * @deprecated
175
	 * @see $date
176
	 */
177
	public $datep;
178
179
	/**
180
	 * @var int	Date expected for delivery
181
	 * @deprecated
182
	 */
183
	public $date_livraison; // deprecated; Use delivery_date instead.
184
185
	/**
186
	 * @var integer|string 	$delivery_date;
187
	 */
188
	public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
189
190
191
	public $fin_validite;
192
193
	public $user_author_id;
194
	public $user_valid_id;
195
	public $user_close_id;
196
197
	/**
198
	 * @deprecated
199
	 * @see $total_ht
200
	 */
201
	public $price;
202
	/**
203
	 * @deprecated
204
	 * @see $total_tva
205
	 */
206
	public $tva;
207
	/**
208
	 * @deprecated
209
	 * @see $total_ttc
210
	 */
211
	public $total;
212
213
	public $cond_reglement_code;
214
	public $deposit_percent;
215
	public $mode_reglement_code;
216
	public $remise_percent;
217
218
	/**
219
	 * @deprecated
220
	 */
221
	public $remise;
222
	/**
223
	 * @deprecated
224
	 */
225
	public $remise_absolue;
226
227
	/**
228
	 * @var int ID
229
	 * @deprecated
230
	 */
231
	public $fk_address;
232
233
	public $address_type;
234
	public $address;
235
236
	public $availability_id;
237
	public $availability_code;
238
239
	public $duree_validite;
240
241
	public $demand_reason_id;
242
	public $demand_reason_code;
243
244
	public $warehouse_id;
245
246
	public $extraparams = array();
247
248
	/**
249
	 * @var PropaleLigne[]
250
	 */
251
	public $lines = array();
252
	public $line;
253
254
	public $labelStatus = array();
255
	public $labelStatusShort = array();
256
257
	// Multicurrency
258
	/**
259
	 * @var int ID
260
	 */
261
	public $fk_multicurrency;
262
263
	public $multicurrency_code;
264
	public $multicurrency_tx;
265
	public $multicurrency_total_ht;
266
	public $multicurrency_total_tva;
267
	public $multicurrency_total_ttc;
268
269
270
	/**
271
	 *  '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')
272
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
273
	 *  'label' the translation key.
274
	 *  'enabled' is a condition when the field must be managed.
275
	 *  'position' is the sort order of field.
276
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
277
	 *  '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)
278
	 *  'noteditable' says if field is not editable (1 or 0)
279
	 *  '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.
280
	 *  'index' if we want an index in database.
281
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
282
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
283
	 *  '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).
284
	 *  'css' is the CSS style to use on field. For example: 'maxwidth200'
285
	 *  'help' is a string visible as a tooltip on field
286
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
287
	 *  '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.
288
	 *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
289
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
290
	 *
291
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
292
	 */
293
294
	// BEGIN MODULEBUILDER PROPERTIES
295
	/**
296
	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
297
	 */
298
	public $fields = array(
299
		'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
300
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
301
		'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
302
		'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
303
		'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
304
		'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>-1, 'position'=>23),
305
		'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Fk projet', 'enabled'=>"isModEnabled('project')", 'visible'=>-1, 'position'=>24),
306
		'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
307
		'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
308
		'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
309
		'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
310
		'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
311
		'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
312
		'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
313
		'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
314
		'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
315
		'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
316
		'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
317
		'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
318
		//'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
319
		//'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
320
		'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
321
		'total_tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
322
		'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
323
		'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
324
		'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
325
		'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'$conf->banque->enabled', 'visible'=>-1, 'position'=>150),
326
		'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
327
		'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
328
		'deposit_percent' =>array('type'=>'varchar(63)', 'label'=>'DepositPercent', 'enabled'=>1, 'visible'=>-1, 'position'=>161),
329
		'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
330
		'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>170),
331
		'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>175),
332
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
333
		'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
334
		'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
335
		'fk_warehouse' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Fk warehouse', 'enabled'=>'$conf->stock->enabled', 'visible'=>-1, 'position'=>191),
336
		'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
337
		'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
338
		'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
339
		'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
340
		'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
341
		'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
342
		'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
343
		'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
344
		'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
345
		'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
346
		'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
347
		'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
348
		'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
349
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
350
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
351
	);
352
	// END MODULEBUILDER PROPERTIES
353
354
	/**
355
	 * Draft status
356
	 */
357
	const STATUS_DRAFT = 0;
358
	/**
359
	 * Validated status
360
	 */
361
	const STATUS_VALIDATED = 1;
362
	/**
363
	 * Signed quote
364
	 */
365
	const STATUS_SIGNED = 2;
366
	/**
367
	 * Not signed quote
368
	 */
369
	const STATUS_NOTSIGNED = 3;
370
	/**
371
	 * Billed or processed quote
372
	 */
373
	const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
374
375
376
	/**
377
	 *	Constructor
378
	 *
379
	 *	@param      DoliDB	$db         Database handler
380
	 *	@param      int		$socid		Id third party
381
	 *	@param      int		$propalid   Id proposal
382
	 */
383
	public function __construct($db, $socid = 0, $propalid = 0)
384
	{
385
		global $conf, $langs;
386
387
		$this->db = $db;
388
389
		$this->socid = $socid;
390
		$this->id = $propalid;
391
392
		$this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
393
	}
394
395
396
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
397
	/**
398
	 *  Add line into array ->lines
399
	 *  $this->thirdparty should be loaded
400
	 *
401
	 * 	@param  int		$idproduct       	Product Id to add
402
	 * 	@param  int		$qty             	Quantity
403
	 * 	@param  int		$remise_percent  	Discount effected on Product
404
	 *  @return	int							<0 if KO, >0 if OK
405
	 *
406
	 *	TODO	Replace calls to this function by generation objet Ligne
407
	 */
408
	public function add_product($idproduct, $qty, $remise_percent = 0)
409
	{
410
		// phpcs:enable
411
		global $conf, $mysoc;
412
413
		if (!$qty) {
414
			$qty = 1;
415
		}
416
417
		dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
418
		if ($idproduct > 0) {
419
			$prod = new Product($this->db);
420
			$prod->fetch($idproduct);
421
422
			$productdesc = $prod->description;
423
424
			$tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
425
			$tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
426
			if (empty($tva_tx)) {
427
				$tva_npr = 0;
428
			}
429
			$vat_src_code = ''; // May be defined into tva_tx
430
431
			$localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
432
			$localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
433
434
			// multiprices
435
			if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
436
				$price = $prod->multiprices[$this->thirdparty->price_level];
437
			} else {
438
				$price = $prod->price;
439
			}
440
441
			$line = new PropaleLigne($this->db);
442
443
			$line->fk_product = $idproduct;
444
			$line->desc = $productdesc;
445
			$line->qty = $qty;
446
			$line->subprice = $price;
447
			$line->remise_percent = $remise_percent;
448
			$line->vat_src_code = $vat_src_code;
449
			$line->tva_tx = $tva_tx;
450
			$line->fk_unit = $prod->fk_unit;
451
			if ($tva_npr) {
452
				$line->info_bits = 1;
453
			}
454
455
			$this->lines[] = $line;
456
		}
457
458
		return 1;
459
	}
460
461
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
462
	/**
463
	 *	Adding line of fixed discount in the proposal in DB
464
	 *
465
	 *	@param     int		$idremise			Id of fixed discount
466
	 *  @return    int          				>0 if OK, <0 if KO
467
	 */
468
	public function insert_discount($idremise)
469
	{
470
		// phpcs:enable
471
		global $langs;
472
473
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
474
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
475
476
		$this->db->begin();
477
478
		$remise = new DiscountAbsolute($this->db);
479
		$result = $remise->fetch($idremise);
480
481
		if ($result > 0) {
482
			if ($remise->fk_facture) {	// Protection against multiple submission
483
				$this->error = $langs->trans("ErrorDiscountAlreadyUsed");
484
				$this->db->rollback();
485
				return -5;
486
			}
487
488
			$line = new PropaleLigne($this->db);
489
490
			$this->line->context = $this->context;
491
492
			$line->fk_propal = $this->id;
493
			$line->fk_remise_except = $remise->id;
494
			$line->desc = $remise->description; // Description ligne
495
			$line->vat_src_code = $remise->vat_src_code;
496
			$line->tva_tx = $remise->tva_tx;
497
			$line->subprice = -$remise->amount_ht;
498
			$line->fk_product = 0; // Id produit predefined
499
			$line->qty = 1;
500
			$line->remise_percent = 0;
501
			$line->rang = -1;
502
			$line->info_bits = 2;
503
504
			// TODO deprecated
505
			$line->price = -$remise->amount_ht;
506
507
			$line->total_ht  = -$remise->amount_ht;
508
			$line->total_tva = -$remise->amount_tva;
509
			$line->total_ttc = -$remise->amount_ttc;
510
511
			$result = $line->insert();
512
			if ($result > 0) {
513
				$result = $this->update_price(1);
514
				if ($result > 0) {
515
					$this->db->commit();
516
					return 1;
517
				} else {
518
					$this->db->rollback();
519
					return -1;
520
				}
521
			} else {
522
				$this->error = $line->error;
523
				$this->errors = $line->errors;
524
				$this->db->rollback();
525
				return -2;
526
			}
527
		} else {
528
			$this->db->rollback();
529
			return -2;
530
		}
531
	}
532
533
	/**
534
	 *    	Add a proposal line into database (linked to product/service or not)
535
	 *      The parameters are already supposed to be appropriate and with final values to the call
536
	 *      of this method. Also, for the VAT rate, it must have already been defined
537
	 *      by whose calling the method get_default_tva (societe_vendeuse, societe_acheteuse, '' product)
538
	 *      and desc must already have the right value (it's up to the caller to manage multilanguage)
539
	 *
540
	 * 		@param    	string		$desc				Description of line
541
	 * 		@param    	float		$pu_ht				Unit price
542
	 * 		@param    	float		$qty             	Quantity
543
	 * 		@param    	float		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
544
	 * 		@param		float		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
545
	 *  	@param		float		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
546
	 *		@param    	int			$fk_product      	Product/Service ID predefined
547
	 * 		@param    	float		$remise_percent  	Pourcentage de remise de la ligne
548
	 * 		@param    	string		$price_base_type	HT or TTC
549
	 * 		@param    	float		$pu_ttc             Prix unitaire TTC
550
	 * 		@param    	int			$info_bits			Bits for type of lines
551
	 *      @param      int			$type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
552
	 *      @param      int			$rang               Position of line
553
	 *      @param		int			$special_code		Special code (also used by externals modules!)
554
	 *      @param		int			$fk_parent_line		Id of parent line
555
	 *      @param		int			$fk_fournprice		Id supplier price
556
	 *      @param		int			$pa_ht				Buying price without tax
557
	 *      @param		string		$label				???
558
	 *		@param      int			$date_start       	Start date of the line
559
	 *		@param      int			$date_end         	End date of the line
560
	 *      @param		array		$array_options		extrafields array
561
	 * 		@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
562
	 *      @param		string		$origin				Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
563
	 *      @param		int			$origin_id			Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
564
	 * 		@param		double		$pu_ht_devise		Unit price in currency
565
	 * 		@param		int    		$fk_remise_except	Id discount if line is from a discount
566
	 *  	@param		int			$noupdateafterinsertline	No update after insert of line
567
	 *    	@return    	int         	    			>0 if OK, <0 if KO
568
	 *    	@see       	add_product()
569
	 */
570
	public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
571
	{
572
		global $mysoc, $conf, $langs;
573
574
		dol_syslog(get_class($this)."::addline propalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type, fk_remise_except=".$fk_remise_except);
575
576
		if ($this->statut == self::STATUS_DRAFT) {
577
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
578
579
			// Clean parameters
580
			if (empty($remise_percent)) {
581
				$remise_percent = 0;
582
			}
583
			if (empty($qty)) {
584
				$qty = 0;
585
			}
586
			if (empty($info_bits)) {
587
				$info_bits = 0;
588
			}
589
			if (empty($rang)) {
590
				$rang = 0;
591
			}
592
			if (empty($fk_parent_line) || $fk_parent_line < 0) {
593
				$fk_parent_line = 0;
594
			}
595
596
			$remise_percent = price2num($remise_percent);
597
			$qty = price2num($qty);
598
			$pu_ht = price2num($pu_ht);
599
			$pu_ht_devise = price2num($pu_ht_devise);
600
			$pu_ttc = price2num($pu_ttc);
601
			if (!preg_match('/\((.*)\)/', $txtva)) {
602
				$txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
603
			}
604
			$txlocaltax1 = price2num($txlocaltax1);
605
			$txlocaltax2 = price2num($txlocaltax2);
606
			$pa_ht = price2num($pa_ht);
607
			if ($price_base_type == 'HT') {
608
				$pu = $pu_ht;
609
			} else {
610
				$pu = $pu_ttc;
611
			}
612
613
			// Check parameters
614
			if ($type < 0) {
615
				return -1;
616
			}
617
618
			if ($date_start && $date_end && $date_start > $date_end) {
619
				$langs->load("errors");
620
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
621
				return -1;
622
			}
623
624
			$this->db->begin();
625
626
			$product_type = $type;
627
			if (!empty($fk_product) && $fk_product > 0) {
628
				$product = new Product($this->db);
629
				$result = $product->fetch($fk_product);
630
				$product_type = $product->type;
631
632
				if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
633
					$langs->load("errors");
634
					$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
635
					$this->db->rollback();
636
					return -3;
637
				}
638
			}
639
640
			// Calcul du total TTC et de la TVA pour la ligne a partir de
641
			// qty, pu, remise_percent et txtva
642
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
643
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
644
645
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
646
647
			// Clean vat code
648
			$reg = array();
649
			$vat_src_code = '';
650
			$reg = array();
651
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
652
				$vat_src_code = $reg[1];
653
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
654
			}
655
656
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
657
658
			$total_ht  = $tabprice[0];
659
			$total_tva = $tabprice[1];
660
			$total_ttc = $tabprice[2];
661
			$total_localtax1 = $tabprice[9];
662
			$total_localtax2 = $tabprice[10];
663
			$pu_ht  = $tabprice[3];
664
			$pu_tva = $tabprice[4];
665
			$pu_ttc = $tabprice[5];
666
667
			// MultiCurrency
668
			$multicurrency_total_ht  = $tabprice[16];
669
			$multicurrency_total_tva = $tabprice[17];
670
			$multicurrency_total_ttc = $tabprice[18];
671
			$pu_ht_devise = $tabprice[19];
672
673
			// Rang to use
674
			$ranktouse = $rang;
675
			if ($ranktouse == -1) {
676
				$rangmax = $this->line_max($fk_parent_line);
677
				$ranktouse = $rangmax + 1;
678
			}
679
680
			// TODO A virer
681
			// Anciens indicateurs: $price, $remise (a ne plus utiliser)
682
			$price = $pu;
683
			$remise = 0;
684
			if ($remise_percent > 0) {
685
				$remise = round(($pu * $remise_percent / 100), 2);
686
				$price = $pu - $remise;
687
			}
688
689
			// Insert line
690
			$this->line = new PropaleLigne($this->db);
691
692
			$this->line->context = $this->context;
693
694
			$this->line->fk_propal = $this->id;
695
			$this->line->label = $label;
696
			$this->line->desc = $desc;
697
			$this->line->qty = $qty;
698
699
			$this->line->vat_src_code = $vat_src_code;
700
			$this->line->tva_tx = $txtva;
701
			$this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
702
			$this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
703
			$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
704
			$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
705
			$this->line->fk_product = $fk_product;
706
			$this->line->product_type = $type;
707
			$this->line->fk_remise_except = $fk_remise_except;
708
			$this->line->remise_percent = $remise_percent;
709
			$this->line->subprice = $pu_ht;
710
			$this->line->rang = $ranktouse;
711
			$this->line->info_bits = $info_bits;
712
			$this->line->total_ht = $total_ht;
713
			$this->line->total_tva = $total_tva;
714
			$this->line->total_localtax1 = $total_localtax1;
715
			$this->line->total_localtax2 = $total_localtax2;
716
			$this->line->total_ttc = $total_ttc;
717
			$this->line->special_code = $special_code;
718
			$this->line->fk_parent_line = $fk_parent_line;
719
			$this->line->fk_unit = $fk_unit;
720
721
			$this->line->date_start = $date_start;
722
			$this->line->date_end = $date_end;
723
724
			$this->line->fk_fournprice = $fk_fournprice;
725
			$this->line->pa_ht = $pa_ht;
726
727
			$this->line->origin_id = $origin_id;
728
			$this->line->origin = $origin;
729
730
			// Multicurrency
731
			$this->line->fk_multicurrency = $this->fk_multicurrency;
732
			$this->line->multicurrency_code = $this->multicurrency_code;
733
			$this->line->multicurrency_subprice		= $pu_ht_devise;
734
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
735
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
736
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
737
738
			// Mise en option de la ligne
739
			if (empty($qty) && empty($special_code)) {
740
				$this->line->special_code = 3;
741
			}
742
743
			// TODO deprecated
744
			$this->line->price = $price;
745
746
			if (is_array($array_options) && count($array_options) > 0) {
747
				$this->line->array_options = $array_options;
748
			}
749
750
			$result = $this->line->insert();
751
			if ($result > 0) {
752
				// Reorder if child line
753
				if (!empty($fk_parent_line)) {
754
					$this->line_order(true, 'DESC');
755
				} elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
756
					$linecount = count($this->lines);
757
					for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
758
						$this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
759
					}
760
				}
761
762
				// Mise a jour informations denormalisees au niveau de la propale meme
763
				if (empty($noupdateafterinsertline)) {
764
					$result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
765
				}
766
767
				if ($result > 0) {
768
					$this->db->commit();
769
					return $this->line->id;
770
				} else {
771
					$this->error = $this->db->error();
772
					$this->db->rollback();
773
					return -1;
774
				}
775
			} else {
776
				$this->error = $this->line->error;
777
				$this->errors = $this->line->errors;
778
				$this->db->rollback();
779
				return -2;
780
			}
781
		} else {
782
			dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
783
			return -3;
784
		}
785
	}
786
787
788
	/**
789
	 *  Update a proposal line
790
	 *
791
	 *  @param      int			$rowid           	Id of line
792
	 *  @param      float		$pu		     	  	Unit price (HT or TTC depending on price_base_type)
793
	 *  @param      float		$qty            	Quantity
794
	 *  @param      float		$remise_percent  	Discount on line
795
	 *  @param      float		$txtva	          	VAT Rate (Can be '1.23' or '1.23 (ABC)')
796
	 * 	@param	  	float		$txlocaltax1		Local tax 1 rate
797
	 *  @param	  	float		$txlocaltax2		Local tax 2 rate
798
	 *  @param      string		$desc            	Description
799
	 *	@param	  	string		$price_base_type	HT or TTC
800
	 *	@param      int			$info_bits        	Miscellaneous informations
801
	 *	@param		int			$special_code		Special code (also used by externals modules!)
802
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
803
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
804
	 *  @param		int			$fk_fournprice		Id of origin supplier price
805
	 *  @param		int			$pa_ht				Price (without tax) of product when it was bought
806
	 *  @param		string		$label				???
807
	 *  @param		int			$type				0/1=Product/service
808
	 *	@param      int			$date_start       	Start date of the line
809
	 *	@param      int			$date_end         	End date of the line
810
	 *  @param		array		$array_options		extrafields array
811
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
812
	 * 	@param		double		$pu_ht_devise		Unit price in currency
813
	 * 	@param		int			$notrigger			disable line update trigger
814
	 * @param       integer $rang   line rank
815
	 *  @return     int     		        		0 if OK, <0 if KO
816
	 */
817
	public function updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $desc = '', $price_base_type = 'HT', $info_bits = 0, $special_code = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $type = 0, $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
818
	{
819
		global $mysoc, $langs;
820
821
		dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
822
        txtva=$txtva, desc=$desc, price_base_type=$price_base_type, info_bits=$info_bits, special_code=$special_code, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, type=$type, date_start=$date_start, date_end=$date_end");
823
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
824
825
		// Clean parameters
826
		$remise_percent = price2num($remise_percent);
827
		$qty = price2num($qty);
828
		$pu = price2num($pu);
829
		$pu_ht_devise = price2num($pu_ht_devise);
830
		if (!preg_match('/\((.*)\)/', $txtva)) {
831
			$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
832
		}
833
		$txlocaltax1 = price2num($txlocaltax1);
834
		$txlocaltax2 = price2num($txlocaltax2);
835
		$pa_ht = price2num($pa_ht);
836
		if (empty($qty) && empty($special_code)) {
837
			$special_code = 3; // Set option tag
838
		}
839
		if (!empty($qty) && $special_code == 3) {
840
			$special_code = 0; // Remove option tag
841
		}
842
		if (empty($type)) {
843
			$type = 0;
844
		}
845
846
		if ($date_start && $date_end && $date_start > $date_end) {
847
			$langs->load("errors");
848
			$this->error = $langs->trans('ErrorStartDateGreaterEnd');
849
			return -1;
850
		}
851
852
		if ($this->statut == self::STATUS_DRAFT) {
853
			$this->db->begin();
854
855
			// Calcul du total TTC et de la TVA pour la ligne a partir de
856
			// qty, pu, remise_percent et txtva
857
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
858
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
859
860
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
861
862
			// Clean vat code
863
			$reg = array();
864
			$vat_src_code = '';
865
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
866
				$vat_src_code = $reg[1];
867
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
868
			}
869
870
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
871
			$total_ht  = $tabprice[0];
872
			$total_tva = $tabprice[1];
873
			$total_ttc = $tabprice[2];
874
			$total_localtax1 = $tabprice[9];
875
			$total_localtax2 = $tabprice[10];
876
			$pu_ht  = $tabprice[3];
877
			$pu_tva = $tabprice[4];
878
			$pu_ttc = $tabprice[5];
879
880
			// MultiCurrency
881
			$multicurrency_total_ht  = $tabprice[16];
882
			$multicurrency_total_tva = $tabprice[17];
883
			$multicurrency_total_ttc = $tabprice[18];
884
			$pu_ht_devise = $tabprice[19];
885
886
			// Anciens indicateurs: $price, $remise (a ne plus utiliser)
887
			$price = $pu;
888
			$remise = 0;
889
			if ($remise_percent > 0) {
890
				$remise = round(($pu * $remise_percent / 100), 2);
891
				$price = $pu - $remise;
892
			}
893
894
			//Fetch current line from the database and then clone the object and set it in $oldline property
895
			$line = new PropaleLigne($this->db);
896
			$line->fetch($rowid);
897
898
			$staticline = clone $line;
899
900
			$line->oldline = $staticline;
901
			$this->line = $line;
902
			$this->line->context = $this->context;
903
			$this->line->rang = $rang;
904
905
			// Reorder if fk_parent_line change
906
			if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
907
				$rangmax = $this->line_max($fk_parent_line);
908
				$this->line->rang = $rangmax + 1;
909
			}
910
911
			$this->line->id = $rowid;
912
			$this->line->label = $label;
913
			$this->line->desc = $desc;
914
			$this->line->qty = $qty;
915
			$this->line->product_type		= $type;
916
			$this->line->vat_src_code		= $vat_src_code;
917
			$this->line->tva_tx = $txtva;
918
			$this->line->localtax1_tx		= $txlocaltax1;
919
			$this->line->localtax2_tx		= $txlocaltax2;
920
			$this->line->localtax1_type		= $localtaxes_type[0];
921
			$this->line->localtax2_type		= $localtaxes_type[2];
922
			$this->line->remise_percent		= $remise_percent;
923
			$this->line->subprice			= $pu_ht;
924
			$this->line->info_bits			= $info_bits;
925
926
			$this->line->total_ht			= $total_ht;
927
			$this->line->total_tva			= $total_tva;
928
			$this->line->total_localtax1	= $total_localtax1;
929
			$this->line->total_localtax2	= $total_localtax2;
930
			$this->line->total_ttc			= $total_ttc;
931
			$this->line->special_code = $special_code;
932
			$this->line->fk_parent_line		= $fk_parent_line;
933
			$this->line->skip_update_total = $skip_update_total;
934
			$this->line->fk_unit = $fk_unit;
935
936
			$this->line->fk_fournprice = $fk_fournprice;
937
			$this->line->pa_ht = $pa_ht;
938
939
			$this->line->date_start = $date_start;
940
			$this->line->date_end = $date_end;
941
942
			if (is_array($array_options) && count($array_options) > 0) {
943
				// We replace values in this->line->array_options only for entries defined into $array_options
944
				foreach ($array_options as $key => $value) {
945
					$this->line->array_options[$key] = $array_options[$key];
946
				}
947
			}
948
949
			// Multicurrency
950
			$this->line->multicurrency_subprice		= $pu_ht_devise;
951
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
952
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
953
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
954
955
			$result = $this->line->update($notrigger);
956
			if ($result > 0) {
957
				// Reorder if child line
958
				if (!empty($fk_parent_line)) {
959
					$this->line_order(true, 'DESC');
960
				}
961
962
				$this->update_price(1);
963
964
				$this->fk_propal = $this->id;
965
				$this->rowid = $rowid;
966
967
				$this->db->commit();
968
				return $result;
969
			} else {
970
				$this->error = $this->line->error;
971
				$this->errors = $this->line->errors;
972
				$this->db->rollback();
973
				return -1;
974
			}
975
		} else {
976
			dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
977
			return -2;
978
		}
979
	}
980
981
982
	/**
983
	 *  Delete detail line
984
	 *
985
	 *  @param		int		$lineid			Id of line to delete
986
	 *  @param		int		$id				Id of object (for a check)
987
	 *  @return     int         			>0 if OK, <0 if KO
988
	 */
989
	public function deleteline($lineid, $id = 0)
990
	{
991
		global $user;
992
993
		if ($this->statut == self::STATUS_DRAFT) {
994
			$this->db->begin();
995
996
			$line = new PropaleLigne($this->db);
997
998
			$line->context = $this->context;
999
1000
			// Load data
1001
			$line->fetch($lineid);
1002
1003
			if ($id > 0 && $line->fk_propal != $id) {
1004
				$this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1005
				return -1;
1006
			}
1007
1008
			// Memorize previous line for triggers
1009
			$staticline = clone $line;
1010
			$line->oldline = $staticline;
1011
1012
			if ($line->delete($user) > 0) {
1013
				$this->update_price(1);
1014
1015
				$this->db->commit();
1016
				return 1;
1017
			} else {
1018
				$this->error = $line->error;
1019
				$this->errors = $line->errors;
1020
				$this->db->rollback();
1021
				return -1;
1022
			}
1023
		} else {
1024
			$this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1025
			return -2;
1026
		}
1027
	}
1028
1029
1030
	/**
1031
	 *  Create commercial proposal into database
1032
	 * 	this->ref can be set or empty. If empty, we will use "(PROVid)"
1033
	 *
1034
	 * 	@param		User	$user		User that create
1035
	 * 	@param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
1036
	 *  @return     int     			<0 if KO, >=0 if OK
1037
	 */
1038
	public function create($user, $notrigger = 0)
1039
	{
1040
		global $conf, $hookmanager, $mysoc;
1041
		$error = 0;
1042
1043
		$now = dol_now();
1044
1045
		// Clean parameters
1046
		if (empty($this->date)) {
1047
			$this->date = $this->datep;
1048
		}
1049
		$this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1050
		if (empty($this->availability_id)) {
1051
			$this->availability_id = 0;
1052
		}
1053
		if (empty($this->demand_reason_id)) {
1054
			$this->demand_reason_id = 0;
1055
		}
1056
1057
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1058
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1059
			list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1060
		} else {
1061
			$this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1062
		}
1063
		if (empty($this->fk_multicurrency)) {
1064
			$this->multicurrency_code = $conf->currency;
1065
			$this->fk_multicurrency = 0;
1066
			$this->multicurrency_tx = 1;
1067
		}
1068
1069
		// Set tmp vars
1070
		$delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1071
1072
		dol_syslog(get_class($this)."::create");
1073
1074
		// Check parameters
1075
		$result = $this->fetch_thirdparty();
1076
		if ($result < 0) {
1077
			$this->error = "Failed to fetch company";
1078
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1079
			return -3;
1080
		}
1081
1082
		// Check parameters
1083
		if (!empty($this->ref)) {	// We check that ref is not already used
1084
			$result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1085
			if ($result > 0) {
1086
				$this->error = 'ErrorRefAlreadyExists';
1087
				dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1088
				$this->db->rollback();
1089
				return -1;
1090
			}
1091
		}
1092
1093
		if (empty($this->date)) {
1094
			$this->error = "Date of proposal is required";
1095
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1096
			return -4;
1097
		}
1098
1099
1100
		$this->db->begin();
1101
1102
		// Insert into database
1103
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1104
		$sql .= "fk_soc";
1105
		$sql .= ", price";
1106
		$sql .= ", remise";
1107
		$sql .= ", remise_percent";
1108
		$sql .= ", remise_absolue";
1109
		$sql .= ", total_tva";
1110
		$sql .= ", total_ttc";
1111
		$sql .= ", datep";
1112
		$sql .= ", datec";
1113
		$sql .= ", ref";
1114
		$sql .= ", fk_user_author";
1115
		$sql .= ", note_private";
1116
		$sql .= ", note_public";
1117
		$sql .= ", model_pdf";
1118
		$sql .= ", fin_validite";
1119
		$sql .= ", fk_cond_reglement";
1120
		$sql .= ", deposit_percent";
1121
		$sql .= ", fk_mode_reglement";
1122
		$sql .= ", fk_account";
1123
		$sql .= ", ref_client";
1124
		$sql .= ", ref_ext";
1125
		$sql .= ", date_livraison";
1126
		$sql .= ", fk_shipping_method";
1127
		$sql .= ", fk_warehouse";
1128
		$sql .= ", fk_availability";
1129
		$sql .= ", fk_input_reason";
1130
		$sql .= ", fk_projet";
1131
		$sql .= ", fk_incoterms";
1132
		$sql .= ", location_incoterms";
1133
		$sql .= ", entity";
1134
		$sql .= ", fk_multicurrency";
1135
		$sql .= ", multicurrency_code";
1136
		$sql .= ", multicurrency_tx";
1137
		$sql .= ") ";
1138
		$sql .= " VALUES (";
1139
		$sql .= $this->socid;
1140
		$sql .= ", 0";
1141
		$sql .= ", ".((float) $this->remise);												// deprecated
1142
		$sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL');
1143
		$sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL');	// deprecated
1144
		$sql .= ", 0";
1145
		$sql .= ", 0";
1146
		$sql .= ", '".$this->db->idate($this->date)."'";
1147
		$sql .= ", '".$this->db->idate($now)."'";
1148
		$sql .= ", '(PROV)'";
1149
		$sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1150
		$sql .= ", '".$this->db->escape($this->note_private)."'";
1151
		$sql .= ", '".$this->db->escape($this->note_public)."'";
1152
		$sql .= ", '".$this->db->escape($this->model_pdf)."'";
1153
		$sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1154
		$sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1155
		$sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1156
		$sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1157
		$sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1158
		$sql .= ", '".$this->db->escape($this->ref_client)."'";
1159
		$sql .= ", '".$this->db->escape($this->ref_ext)."'";
1160
		$sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1161
		$sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1162
		$sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1163
		$sql .= ", ".$this->availability_id;
1164
		$sql .= ", ".$this->demand_reason_id;
1165
		$sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1166
		$sql .= ", ".(int) $this->fk_incoterms;
1167
		$sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1168
		$sql .= ", ".setEntity($this);
1169
		$sql .= ", ".(int) $this->fk_multicurrency;
1170
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1171
		$sql .= ", ".(double) $this->multicurrency_tx;
1172
		$sql .= ")";
1173
1174
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
1175
		$resql = $this->db->query($sql);
1176
		if ($resql) {
1177
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1178
1179
			if ($this->id) {
1180
				$this->ref = '(PROV'.$this->id.')';
1181
				$sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1182
1183
				dol_syslog(get_class($this)."::create", LOG_DEBUG);
1184
				$resql = $this->db->query($sql);
1185
				if (!$resql) {
1186
					$error++;
1187
				}
1188
1189
				if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {	// To use new linkedObjectsIds instead of old linked_objects
1190
					$this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1191
				}
1192
1193
				// Add object linked
1194
				if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1195
					foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1196
						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, ...))
1197
							foreach ($tmp_origin_id as $origin_id) {
1198
								$ret = $this->add_object_linked($origin, $origin_id);
1199
								if (!$ret) {
1200
									$this->error = $this->db->lasterror();
1201
									$error++;
1202
								}
1203
							}
1204
						} else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1205
						{
1206
							$origin_id = $tmp_origin_id;
1207
							$ret = $this->add_object_linked($origin, $origin_id);
1208
							if (!$ret) {
1209
								$this->error = $this->db->lasterror();
1210
								$error++;
1211
							}
1212
						}
1213
					}
1214
				}
1215
1216
				/*
1217
				 *  Insertion du detail des produits dans la base
1218
				 *  Insert products detail in database
1219
				 */
1220
				if (!$error) {
1221
					$fk_parent_line = 0;
1222
					$num = count($this->lines);
1223
1224
					for ($i = 0; $i < $num; $i++) {
1225
						if (!is_object($this->lines[$i])) {	// If this->lines is not array of objects, coming from REST API
1226
							// Convert into object this->lines[$i].
1227
							$line = (object) $this->lines[$i];
1228
						} else {
1229
							$line = $this->lines[$i];
1230
						}
1231
						// Reset fk_parent_line for line that are not child lines or special product
1232
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1233
							$fk_parent_line = 0;
1234
						}
1235
						// Complete vat rate with code
1236
						$vatrate = $line->tva_tx;
1237
						if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) {
1238
							$vatrate .= ' ('.$line->vat_src_code.')';
1239
						}
1240
1241
						if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1242
							$originid = $line->origin_id;
1243
							$origintype = $line->origin;
1244
						} else {
1245
							$originid = $line->id;
1246
							$origintype = $this->element;
1247
						}
1248
1249
						$result = $this->addline(
1250
							$line->desc,
1251
							$line->subprice,
1252
							$line->qty,
1253
							$vatrate,
1254
							$line->localtax1_tx,
1255
							$line->localtax2_tx,
1256
							$line->fk_product,
1257
							$line->remise_percent,
1258
							'HT',
1259
							0,
1260
							$line->info_bits,
1261
							$line->product_type,
1262
							$line->rang,
1263
							$line->special_code,
1264
							$fk_parent_line,
1265
							$line->fk_fournprice,
1266
							$line->pa_ht,
1267
							$line->label,
1268
							$line->date_start,
1269
							$line->date_end,
1270
							$line->array_options,
1271
							$line->fk_unit,
1272
							$origintype,
1273
							$originid,
1274
							0,
1275
							0,
1276
							1
1277
						);
1278
1279
						if ($result < 0) {
1280
							$error++;
1281
							$this->error = $this->db->error;
1282
							dol_print_error($this->db);
1283
							break;
1284
						}
1285
						// Defined the new fk_parent_line
1286
						if ($result > 0 && $line->product_type == 9) {
1287
							$fk_parent_line = $result;
1288
						}
1289
					}
1290
				}
1291
1292
				// Set delivery address
1293
				/*if (! $error && $this->fk_delivery_address)
1294
				{
1295
					$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1296
					$sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1297
					$sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1298
					$sql.= " AND entity = ".setEntity($this);
1299
1300
					$result=$this->db->query($sql);
1301
				}*/
1302
1303
				if (!$error) {
1304
					// Mise a jour infos denormalisees
1305
					$resql = $this->update_price(1, 'auto', 0, $mysoc);
1306
					if ($resql) {
1307
						$action = 'update';
1308
1309
						// Actions on extra fields
1310
						if (!$error) {
1311
							$result = $this->insertExtraFields();
1312
							if ($result < 0) {
1313
								$error++;
1314
							}
1315
						}
1316
1317
						if (!$error && !$notrigger) {
1318
							// Call trigger
1319
							$result = $this->call_trigger('PROPAL_CREATE', $user);
1320
							if ($result < 0) {
1321
								$error++;
1322
							}
1323
							// End call triggers
1324
						}
1325
					} else {
1326
						$this->error = $this->db->lasterror();
1327
						$error++;
1328
					}
1329
				}
1330
			} else {
1331
				$this->error = $this->db->lasterror();
1332
				$error++;
1333
			}
1334
1335
			if (!$error) {
1336
				$this->db->commit();
1337
				dol_syslog(get_class($this)."::create done id=".$this->id);
1338
				return $this->id;
1339
			} else {
1340
				$this->db->rollback();
1341
				return -2;
1342
			}
1343
		} else {
1344
			$this->error = $this->db->lasterror();
1345
			$this->db->rollback();
1346
			return -1;
1347
		}
1348
	}
1349
1350
	/**
1351
	 *		Load an object from its id and create a new one in database
1352
	 *
1353
	 *      @param	    User	$user		    User making the clone
1354
	 *		@param		int		$socid			Id of thirdparty
1355
	 *		@param		int		$forceentity	Entity id to force
1356
	 *		@param		bool	$update_prices	[=false] Update prices if true
1357
	 * 	 	@return		int						New id of clone
1358
	 */
1359
	public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false)
1360
	{
1361
		global $conf, $hookmanager, $mysoc;
1362
1363
		dol_include_once('/projet/class/project.class.php');
1364
1365
		$error = 0;
1366
		$now = dol_now();
1367
1368
		dol_syslog(__METHOD__, LOG_DEBUG);
1369
1370
		$object = new self($this->db);
1371
1372
		$this->db->begin();
1373
1374
		// Load source object
1375
		$object->fetch($this->id);
1376
1377
		$objsoc = new Societe($this->db);
1378
1379
		// Change socid if needed
1380
		if (!empty($socid) && $socid != $object->socid) {
1381
			if ($objsoc->fetch($socid) > 0) {
1382
				$object->socid = $objsoc->id;
1383
				$object->cond_reglement_id	= (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1384
				$object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1385
				$object->mode_reglement_id	= (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1386
				$object->fk_delivery_address = '';
1387
1388
				/*if (isModEnabled('project'))
1389
				{
1390
					$project = new Project($db);
1391
					if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1392
						if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1393
						else $clonedObj->fk_project = '';
1394
					} else {
1395
						$clonedObj->fk_project = '';
1396
					}
1397
				}*/
1398
				$object->fk_project = ''; // A cloned proposal is set by default to no project.
1399
			}
1400
1401
			// reset ref_client
1402
			$object->ref_client = '';
1403
1404
			// TODO Change product price if multi-prices
1405
		} else {
1406
			$objsoc->fetch($object->socid);
1407
		}
1408
1409
		// update prices
1410
		if ($update_prices === true) {
1411
			if ($objsoc->id > 0 && !empty($object->lines)) {
1412
				if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1413
					// If price per customer
1414
					require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1415
				}
1416
1417
				foreach ($object->lines as $line) {
1418
					if ($line->fk_product > 0) {
1419
						$prod = new Product($this->db);
1420
						$res = $prod->fetch($line->fk_product);
1421
						if ($res > 0) {
1422
							$pu_ht = $prod->price;
1423
							$tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1424
							$remise_percent = $objsoc->remise_percent;
1425
1426
							if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
1427
								$pu_ht = $prod->multiprices[$objsoc->price_level];
1428
								if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) {  // using this option is a bug. kept for backward compatibility
1429
									if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1430
										$tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1431
									}
1432
								}
1433
							} elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1434
								$prodcustprice = new Productcustomerprice($this->db);
1435
								$filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1436
								$result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1437
								if ($result) {
1438
									// If there is some prices specific to the customer
1439
									if (count($prodcustprice->lines) > 0) {
1440
										$pu_ht = price($prodcustprice->lines[0]->price);
1441
										$tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1442
										if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\(.*\)/', $tva_tx)) {
1443
											$tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1444
										}
1445
									}
1446
								}
1447
							}
1448
1449
							$line->subprice = $pu_ht;
1450
							$line->tva_tx = $tva_tx;
1451
							$line->remise_percent = $remise_percent;
1452
						}
1453
					}
1454
				}
1455
			}
1456
		}
1457
1458
		$object->id = 0;
1459
		$object->ref = '';
1460
		$object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1461
		$object->statut = self::STATUS_DRAFT;
1462
1463
		// Clear fields
1464
		$object->user_author = $user->id;
1465
		$object->user_valid = 0;
1466
		$object->date = $now;
1467
		$object->datep = $now; // deprecated
1468
		$object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1469
		if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
1470
			$object->ref_client = '';
1471
		}
1472
		if ($conf->global->MAIN_DONT_KEEP_NOTE_ON_CLONING == 1) {
1473
			$object->note_private = '';
1474
			$object->note_public = '';
1475
		}
1476
		// Create clone
1477
		$object->context['createfromclone'] = 'createfromclone';
1478
		$result = $object->create($user);
1479
		if ($result < 0) {
1480
			$this->error = $object->error;
1481
			$this->errors = array_merge($this->errors, $object->errors);
1482
			$error++;
1483
		}
1484
1485
		if (!$error) {
1486
			// copy internal contacts
1487
			if ($object->copy_linked_contact($this, 'internal') < 0) {
1488
				$error++;
1489
			}
1490
		}
1491
1492
		if (!$error) {
1493
			// copy external contacts if same company
1494
			if ($this->socid == $object->socid) {
1495
				if ($object->copy_linked_contact($this, 'external') < 0) {
1496
					$error++;
1497
				}
1498
			}
1499
		}
1500
1501
		if (!$error) {
1502
			// Hook of thirdparty module
1503
			if (is_object($hookmanager)) {
1504
				$parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1505
				$action = '';
1506
				$reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1507
				if ($reshook < 0) {
1508
					$this->errors += $hookmanager->errors;
1509
					$this->error = $hookmanager->error;
1510
					$error++;
1511
				}
1512
			}
1513
		}
1514
1515
		unset($object->context['createfromclone']);
1516
1517
		// End
1518
		if (!$error) {
1519
			$this->db->commit();
1520
			return $object->id;
1521
		} else {
1522
			$this->db->rollback();
1523
			return -1;
1524
		}
1525
	}
1526
1527
	/**
1528
	 *	Load a proposal from database. Get also lines.
1529
	 *
1530
	 *	@param      int			$rowid			Id of object to load
1531
	 *	@param		string		$ref			Ref of proposal
1532
	 *	@param		string		$ref_ext		Ref ext of proposal
1533
	 *	@param		int			$forceentity	Entity id to force when searching on ref or ref_ext
1534
	 *	@return     int         				>0 if OK, <0 if KO
1535
	 */
1536
	public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1537
	{
1538
		$sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1539
		$sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1540
		$sql .= ", p.datec";
1541
		$sql .= ", p.date_signature as dates";
1542
		$sql .= ", p.date_valid as datev";
1543
		$sql .= ", p.datep as dp";
1544
		$sql .= ", p.fin_validite as dfv";
1545
		$sql .= ", p.date_livraison as delivery_date";
1546
		$sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1547
		$sql .= ", p.note_private, p.note_public";
1548
		$sql .= ", p.fk_projet as fk_project, p.fk_statut";
1549
		$sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1550
		$sql .= ", p.fk_delivery_address";
1551
		$sql .= ", p.fk_availability";
1552
		$sql .= ", p.fk_input_reason";
1553
		$sql .= ", p.fk_cond_reglement";
1554
		$sql .= ", p.fk_mode_reglement";
1555
		$sql .= ', p.fk_account';
1556
		$sql .= ", p.fk_shipping_method";
1557
		$sql .= ", p.fk_warehouse";
1558
		$sql .= ", p.fk_incoterms, p.location_incoterms";
1559
		$sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1560
		$sql .= ", p.tms as date_modification";
1561
		$sql .= ", i.libelle as label_incoterms";
1562
		$sql .= ", c.label as statut_label";
1563
		$sql .= ", ca.code as availability_code, ca.label as availability";
1564
		$sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1565
		$sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1566
		$sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1567
		$sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1568
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1569
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1570
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON p.fk_cond_reglement = cr.rowid AND cr.entity IN ('.getEntity('c_payment_term').')';
1571
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1572
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1573
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1574
1575
		if (!empty($ref)) {
1576
			if (!empty($forceentity)) {
1577
				$sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1578
			} else {
1579
				$sql .= " WHERE p.entity IN (".getEntity('propal').")";
1580
			}
1581
			$sql .= " AND p.ref='".$this->db->escape($ref)."'";
1582
		} else {
1583
			// Dont't use entity if you use rowid
1584
			$sql .= " WHERE p.rowid = ".((int) $rowid);
1585
		}
1586
1587
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1588
		$resql = $this->db->query($sql);
1589
		if ($resql) {
1590
			if ($this->db->num_rows($resql)) {
1591
				$obj = $this->db->fetch_object($resql);
1592
1593
				$this->id                   = $obj->rowid;
1594
				$this->entity               = $obj->entity;
1595
1596
				$this->ref                  = $obj->ref;
1597
				$this->ref_client           = $obj->ref_client;
1598
				$this->ref_ext           = $obj->ref_ext;
1599
				$this->remise               = $obj->remise;
1600
				$this->remise_percent       = $obj->remise_percent;
1601
				$this->remise_absolue       = $obj->remise_absolue;
1602
				$this->total                = $obj->total_ttc; // TODO deprecated
1603
				$this->total_ttc            = $obj->total_ttc;
1604
				$this->total_ht             = $obj->total_ht;
1605
				$this->total_tva            = $obj->total_tva;
1606
				$this->total_localtax1		= $obj->localtax1;
1607
				$this->total_localtax2		= $obj->localtax2;
1608
1609
				$this->socid = $obj->fk_soc;
1610
				$this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1611
1612
				$this->fk_project = $obj->fk_project;
1613
				$this->project = null; // Clear if another value was already set by fetch_projet
1614
1615
				$this->model_pdf            = $obj->model_pdf;
1616
				$this->modelpdf             = $obj->model_pdf; // deprecated
1617
				$this->last_main_doc = $obj->last_main_doc;
1618
				$this->note                 = $obj->note_private; // TODO deprecated
1619
				$this->note_private         = $obj->note_private;
1620
				$this->note_public          = $obj->note_public;
1621
1622
				$this->status               = (int) $obj->fk_statut;
1623
				$this->statut               = $this->status; // deprecated
1624
				$this->statut_libelle       = $obj->statut_label;
1625
1626
				$this->datec                = $this->db->jdate($obj->datec); // TODO deprecated
1627
				$this->datev                = $this->db->jdate($obj->datev); // TODO deprecated
1628
				$this->date_creation = $this->db->jdate($obj->datec); //Creation date
1629
				$this->date_validation = $this->db->jdate($obj->datev); //Validation date
1630
				$this->date_modification = $this->db->jdate($obj->date_modification); // tms
1631
				$this->date_signature = $this->db->jdate($obj->dates); // Signature date
1632
				$this->date                 = $this->db->jdate($obj->dp); // Proposal date
1633
				$this->datep                = $this->db->jdate($obj->dp); // deprecated
1634
				$this->fin_validite         = $this->db->jdate($obj->dfv);
1635
				$this->date_livraison       = $this->db->jdate($obj->delivery_date); // deprecated
1636
				$this->delivery_date        = $this->db->jdate($obj->delivery_date);
1637
				$this->shipping_method_id   = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1638
				$this->warehouse_id         = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1639
				$this->availability_id      = $obj->fk_availability;
1640
				$this->availability_code    = $obj->availability_code;
1641
				$this->availability         = $obj->availability;
1642
				$this->demand_reason_id     = $obj->fk_input_reason;
1643
				$this->demand_reason_code   = $obj->demand_reason_code;
1644
				$this->demand_reason        = $obj->demand_reason;
1645
				$this->fk_address = $obj->fk_delivery_address;
1646
1647
				$this->mode_reglement_id    = $obj->fk_mode_reglement;
1648
				$this->mode_reglement_code  = $obj->mode_reglement_code;
1649
				$this->mode_reglement       = $obj->mode_reglement;
1650
				$this->fk_account           = ($obj->fk_account > 0) ? $obj->fk_account : null;
1651
				$this->cond_reglement_id    = $obj->fk_cond_reglement;
1652
				$this->cond_reglement_code  = $obj->cond_reglement_code;
1653
				$this->cond_reglement       = $obj->cond_reglement;
1654
				$this->cond_reglement_doc   = $obj->cond_reglement_libelle_doc;
1655
				$this->deposit_percent      = $obj->deposit_percent;
1656
1657
				$this->extraparams = (array) json_decode($obj->extraparams, true);
1658
1659
				$this->user_author_id = $obj->fk_user_author;
1660
				$this->user_valid_id = $obj->fk_user_valid;
1661
				$this->user_close_id = $obj->fk_user_cloture;
1662
1663
				//Incoterms
1664
				$this->fk_incoterms = $obj->fk_incoterms;
1665
				$this->location_incoterms = $obj->location_incoterms;
1666
				$this->label_incoterms = $obj->label_incoterms;
1667
1668
				// Multicurrency
1669
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1670
				$this->multicurrency_code = $obj->multicurrency_code;
1671
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1672
				$this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1673
				$this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1674
				$this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1675
1676
				if ($obj->fk_statut == self::STATUS_DRAFT) {
1677
					$this->brouillon = 1;
1678
				}
1679
1680
				// Retrieve all extrafield
1681
				// fetch optionals attributes and labels
1682
				$this->fetch_optionals();
1683
1684
				$this->db->free($resql);
1685
1686
				$this->lines = array();
1687
1688
				// Lines
1689
				$result = $this->fetch_lines();
1690
				if ($result < 0) {
1691
					return -3;
1692
				}
1693
1694
				return 1;
1695
			}
1696
1697
			$this->error = "Record Not Found";
1698
			return 0;
1699
		} else {
1700
			$this->error = $this->db->lasterror();
1701
			return -1;
1702
		}
1703
	}
1704
1705
	/**
1706
	 *      Update database
1707
	 *
1708
	 *      @param      User	$user        	User that modify
1709
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1710
	 *      @return     int      			   	<0 if KO, >0 if OK
1711
	 */
1712
	public function update(User $user, $notrigger = 0)
1713
	{
1714
		global $conf;
1715
1716
		$error = 0;
1717
1718
		// Clean parameters
1719
		if (isset($this->ref)) {
1720
			$this->ref = trim($this->ref);
1721
		}
1722
		if (isset($this->ref_client)) {
1723
			$this->ref_client = trim($this->ref_client);
1724
		}
1725
		if (isset($this->note) || isset($this->note_private)) {
1726
			$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1727
		}
1728
		if (isset($this->note_public)) {
1729
			$this->note_public = trim($this->note_public);
1730
		}
1731
		if (isset($this->model_pdf)) {
1732
			$this->model_pdf = trim($this->model_pdf);
1733
		}
1734
		if (isset($this->import_key)) {
1735
			$this->import_key = trim($this->import_key);
1736
		}
1737
		if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1738
			$this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1739
		}
1740
1741
		// Check parameters
1742
		// Put here code to add control on parameters values
1743
1744
		// Update request
1745
		$sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1746
		$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1747
		$sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1748
		$sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1749
		$sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1750
		$sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1751
		if (!empty($this->fin_validite)) {
1752
			$sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1753
		}
1754
		$sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1755
		$sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1756
		$sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1757
		$sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1758
		$sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1759
		$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1760
		$sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1761
		$sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1762
		$sql .= " fk_user_valid=".(isset($this->user_valid) ? $this->user_valid : "null").",";
1763
		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1764
		$sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1765
		$sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1766
		$sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1767
		$sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1768
		$sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1769
		$sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1770
		$sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1771
		$sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1772
		$sql .= " WHERE rowid=".((int) $this->id);
1773
1774
		$this->db->begin();
1775
1776
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1777
		$resql = $this->db->query($sql);
1778
		if (!$resql) {
1779
			$error++;
1780
			$this->errors[] = "Error ".$this->db->lasterror();
1781
		}
1782
1783
		if (!$error) {
1784
			$result = $this->insertExtraFields();
1785
			if ($result < 0) {
1786
				$error++;
1787
			}
1788
		}
1789
1790
		if (!$error && !$notrigger) {
1791
			// Call trigger
1792
			$result = $this->call_trigger('PROPAL_MODIFY', $user);
1793
			if ($result < 0) {
1794
				$error++;
1795
			}
1796
			// End call triggers
1797
		}
1798
1799
		// Commit or rollback
1800
		if ($error) {
1801
			foreach ($this->errors as $errmsg) {
1802
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1803
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1804
			}
1805
			$this->db->rollback();
1806
			return -1 * $error;
1807
		} else {
1808
			$this->db->commit();
1809
			return 1;
1810
		}
1811
	}
1812
1813
1814
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1815
	/**
1816
	 * Load array lines
1817
	 *
1818
	 *	@param		int			$only_product			Return only physical products
1819
	 *	@param		int			$loadalsotranslation	Return translation for products
1820
	 *	@param		string		$filters				Filter on other fields
1821
	 *
1822
	 *	@return		int									<0 if KO, >0 if OK
1823
	 */
1824
	public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $filters = '')
1825
	{
1826
		// phpcs:enable
1827
		global $langs, $conf;
1828
1829
		$this->lines = array();
1830
1831
		$sql = 'SELECT d.rowid, d.fk_propal, d.fk_parent_line, d.label as custom_label, d.description, d.price, d.vat_src_code, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.localtax1_type, d.localtax2_type, d.qty, d.fk_remise_except, d.remise_percent, d.subprice, d.fk_product,';
1832
		$sql .= ' d.info_bits, d.total_ht, d.total_tva, d.total_localtax1, d.total_localtax2, d.total_ttc, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht, d.special_code, d.rang, d.product_type,';
1833
		$sql .= ' d.fk_unit,';
1834
		$sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_tobatch, p.barcode as product_barcode,';
1835
		$sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1836
		$sql .= ' d.date_start, d.date_end,';
1837
		$sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1838
		$sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1839
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1840
		$sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1841
		if ($only_product) {
1842
			$sql .= ' AND p.fk_product_type = 0';
1843
		}
1844
		if ($filters) {
1845
			$sql .= $filters;
1846
		}
1847
		$sql .= ' ORDER by d.rang';
1848
1849
		dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1850
		$result = $this->db->query($sql);
1851
		if ($result) {
1852
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1853
1854
			$num = $this->db->num_rows($result);
1855
1856
			$i = 0;
1857
			while ($i < $num) {
1858
				$objp                   = $this->db->fetch_object($result);
1859
1860
				$line                   = new PropaleLigne($this->db);
1861
1862
				$line->rowid = $objp->rowid; //Deprecated
1863
				$line->id = $objp->rowid;
1864
				$line->fk_propal = $objp->fk_propal;
1865
				$line->fk_parent_line = $objp->fk_parent_line;
1866
				$line->product_type     = $objp->product_type;
1867
				$line->label            = $objp->custom_label;
1868
				$line->desc             = $objp->description; // Description ligne
1869
				$line->description      = $objp->description; // Description ligne
1870
				$line->qty              = $objp->qty;
1871
				$line->vat_src_code     = $objp->vat_src_code;
1872
				$line->tva_tx           = $objp->tva_tx;
1873
				$line->localtax1_tx		= $objp->localtax1_tx;
1874
				$line->localtax2_tx		= $objp->localtax2_tx;
1875
				$line->localtax1_type	= $objp->localtax1_type;
1876
				$line->localtax2_type	= $objp->localtax2_type;
1877
				$line->subprice         = $objp->subprice;
1878
				$line->fk_remise_except = $objp->fk_remise_except;
1879
				$line->remise_percent   = $objp->remise_percent;
1880
				$line->price            = $objp->price; // TODO deprecated
1881
1882
				$line->info_bits        = $objp->info_bits;
1883
				$line->total_ht         = $objp->total_ht;
1884
				$line->total_tva        = $objp->total_tva;
1885
				$line->total_localtax1	= $objp->total_localtax1;
1886
				$line->total_localtax2	= $objp->total_localtax2;
1887
				$line->total_ttc        = $objp->total_ttc;
1888
				$line->fk_fournprice = $objp->fk_fournprice;
1889
				$marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1890
				$line->pa_ht = $marginInfos[0];
1891
				$line->marge_tx			= $marginInfos[1];
1892
				$line->marque_tx		= $marginInfos[2];
1893
				$line->special_code     = $objp->special_code;
1894
				$line->rang             = $objp->rang;
1895
1896
				$line->fk_product       = $objp->fk_product;
1897
1898
				$line->ref = $objp->product_ref; // deprecated
1899
				$line->libelle = $objp->product_label; // deprecated
1900
1901
				$line->product_ref = $objp->product_ref;
1902
				$line->product_label = $objp->product_label;
1903
				$line->product_desc     = $objp->product_desc; // Description produit
1904
				$line->product_tobatch  = $objp->product_tobatch;
1905
				$line->product_barcode  = $objp->product_barcode;
1906
1907
				$line->fk_product_type  = $objp->fk_product_type; // deprecated
1908
				$line->fk_unit          = $objp->fk_unit;
1909
				$line->weight = $objp->weight;
1910
				$line->weight_units = $objp->weight_units;
1911
				$line->volume = $objp->volume;
1912
				$line->volume_units = $objp->volume_units;
1913
1914
				$line->date_start = $this->db->jdate($objp->date_start);
1915
				$line->date_end = $this->db->jdate($objp->date_end);
1916
1917
				// Multicurrency
1918
				$line->fk_multicurrency = $objp->fk_multicurrency;
1919
				$line->multicurrency_code = $objp->multicurrency_code;
1920
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1921
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1922
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1923
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1924
1925
				$line->fetch_optionals();
1926
1927
				// multilangs
1928
				if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1929
					$tmpproduct = new Product($this->db);
1930
					$tmpproduct->fetch($objp->fk_product);
1931
					$tmpproduct->getMultiLangs();
1932
1933
					$line->multilangs = $tmpproduct->multilangs;
1934
				}
1935
1936
				$this->lines[$i] = $line;
1937
1938
				$i++;
1939
			}
1940
1941
			$this->db->free($result);
1942
1943
			return $num;
1944
		} else {
1945
			$this->error = $this->db->lasterror();
1946
			return -3;
1947
		}
1948
	}
1949
1950
	/**
1951
	 *  Set status to validated
1952
	 *
1953
	 *  @param	User	$user       Object user that validate
1954
	 *  @param	int		$notrigger	1=Does not execute triggers, 0=execute triggers
1955
	 *  @return int         		<0 if KO, 0=Nothing done, >=0 if OK
1956
	 */
1957
	public function valid($user, $notrigger = 0)
1958
	{
1959
		global $conf;
1960
1961
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1962
1963
		$error = 0;
1964
1965
		// Protection
1966
		if ($this->statut == self::STATUS_VALIDATED) {
1967
			dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1968
			return 0;
1969
		}
1970
1971
		if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
1972
		|| (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
1973
			$this->error = 'ErrorPermissionDenied';
1974
			dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1975
			return -1;
1976
		}
1977
1978
		$now = dol_now();
1979
1980
		$this->db->begin();
1981
1982
		// Numbering module definition
1983
		$soc = new Societe($this->db);
1984
		$soc->fetch($this->socid);
1985
1986
		// Define new ref
1987
		if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1988
			$num = $this->getNextNumRef($soc);
1989
		} else {
1990
			$num = $this->ref;
1991
		}
1992
		$this->newref = dol_sanitizeFileName($num);
1993
1994
		$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1995
		$sql .= " SET ref = '".$this->db->escape($num)."',";
1996
		$sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
1997
		$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
1998
1999
		dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2000
		$resql = $this->db->query($sql);
2001
		if (!$resql) {
2002
			dol_print_error($this->db);
2003
			$error++;
2004
		}
2005
2006
		// Trigger calls
2007
		if (!$error && !$notrigger) {
2008
			// Call trigger
2009
			$result = $this->call_trigger('PROPAL_VALIDATE', $user);
2010
			if ($result < 0) {
2011
				$error++;
2012
			}
2013
			// End call triggers
2014
		}
2015
2016
		if (!$error) {
2017
			$this->oldref = $this->ref;
2018
2019
			// Rename directory if dir was a temporary ref
2020
			if (preg_match('/^[\(]?PROV/i', $this->ref)) {
2021
				// Now we rename also files into index
2022
				$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'propale/".$this->db->escape($this->newref)."'";
2023
				$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2024
				$resql = $this->db->query($sql);
2025
				if (!$resql) {
2026
					$error++;
2027
					$this->error = $this->db->lasterror();
2028
				}
2029
2030
				// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2031
				$oldref = dol_sanitizeFileName($this->ref);
2032
				$newref = dol_sanitizeFileName($num);
2033
				$dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2034
				$dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2035
				if (!$error && file_exists($dirsource)) {
2036
					dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2037
					if (@rename($dirsource, $dirdest)) {
2038
						dol_syslog("Rename ok");
2039
						// Rename docs starting with $oldref with $newref
2040
						$listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2041
						foreach ($listoffiles as $fileentry) {
2042
							$dirsource = $fileentry['name'];
2043
							$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2044
							$dirsource = $fileentry['path'].'/'.$dirsource;
2045
							$dirdest = $fileentry['path'].'/'.$dirdest;
2046
							@rename($dirsource, $dirdest);
2047
						}
2048
					}
2049
				}
2050
			}
2051
2052
			$this->ref = $num;
2053
			$this->brouillon = 0;
2054
			$this->statut = self::STATUS_VALIDATED;
2055
			$this->user_valid_id = $user->id;
2056
			$this->datev = $now;
2057
2058
			$this->db->commit();
2059
			return 1;
2060
		} else {
2061
			$this->db->rollback();
2062
			return -1;
2063
		}
2064
	}
2065
2066
2067
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2068
	/**
2069
	 *  Define proposal date
2070
	 *
2071
	 *  @param  User		$user      	Object user that modify
2072
	 *  @param  int			$date		Date
2073
	 *  @param  int			$notrigger	1=Does not execute triggers, 0= execute triggers
2074
	 *  @return	int         			<0 if KO, >0 if OK
2075
	 */
2076
	public function set_date($user, $date, $notrigger = 0)
2077
	{
2078
		// phpcs:enable
2079
		if (empty($date)) {
2080
			$this->error = 'ErrorBadParameter';
2081
			dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2082
			return -1;
2083
		}
2084
2085
		if (!empty($user->rights->propal->creer)) {
2086
			$error = 0;
2087
2088
			$this->db->begin();
2089
2090
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2091
			$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2092
2093
			dol_syslog(__METHOD__, LOG_DEBUG);
2094
			$resql = $this->db->query($sql);
2095
			if (!$resql) {
2096
				$this->errors[] = $this->db->error();
2097
				$error++;
2098
			}
2099
2100
			if (!$error) {
2101
				$this->oldcopy = clone $this;
2102
				$this->date = $date;
2103
				$this->datep = $date; // deprecated
2104
			}
2105
2106
			if (!$notrigger && empty($error)) {
2107
				// Call trigger
2108
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2109
				if ($result < 0) {
2110
					$error++;
2111
				}
2112
				// End call triggers
2113
			}
2114
2115
			if (!$error) {
2116
				$this->db->commit();
2117
				return 1;
2118
			} else {
2119
				foreach ($this->errors as $errmsg) {
2120
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2121
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2122
				}
2123
				$this->db->rollback();
2124
				return -1 * $error;
2125
			}
2126
		}
2127
2128
		return -1;
2129
	}
2130
2131
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2132
	/**
2133
	 *	Define end validity date
2134
	 *
2135
	 *	@param		User	$user        		Object user that modify
2136
	 *	@param      int		$date_end_validity	End of validity date
2137
	 *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2138
	 *	@return     int         				<0 if KO, >0 if OK
2139
	 */
2140
	public function set_echeance($user, $date_end_validity, $notrigger = 0)
2141
	{
2142
		// phpcs:enable
2143
		if (!empty($user->rights->propal->creer)) {
2144
			$error = 0;
2145
2146
			$this->db->begin();
2147
2148
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2149
			$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2150
2151
			dol_syslog(__METHOD__, LOG_DEBUG);
2152
			$resql = $this->db->query($sql);
2153
			if (!$resql) {
2154
				$this->errors[] = $this->db->error();
2155
				$error++;
2156
			}
2157
2158
2159
			if (!$error) {
2160
				$this->oldcopy = clone $this;
2161
				$this->fin_validite = $date_end_validity;
2162
			}
2163
2164
			if (!$notrigger && empty($error)) {
2165
				// Call trigger
2166
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2167
				if ($result < 0) {
2168
					$error++;
2169
				}
2170
				// End call triggers
2171
			}
2172
2173
			if (!$error) {
2174
				$this->db->commit();
2175
				return 1;
2176
			} else {
2177
				foreach ($this->errors as $errmsg) {
2178
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2179
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2180
				}
2181
				$this->db->rollback();
2182
				return -1 * $error;
2183
			}
2184
		}
2185
2186
		return -1;
2187
	}
2188
2189
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2190
	/**
2191
	 *	Set delivery date
2192
	 *
2193
	 *	@param      User 	$user        		Object user that modify
2194
	 *	@param      int		$delivery_date		Delivery date
2195
	 *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2196
	 *	@return     int         				<0 if ko, >0 if ok
2197
	 *	@deprecated Use  setDeliveryDate
2198
	 */
2199
	public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2200
	{
2201
		// phpcs:enable
2202
		return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2203
	}
2204
2205
	/**
2206
	 *	Set delivery date
2207
	 *
2208
	 *	@param      User 	$user        		Object user that modify
2209
	 *	@param      int		$delivery_date     Delivery date
2210
	 *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2211
	 *	@return     int         				<0 if ko, >0 if ok
2212
	 */
2213
	public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2214
	{
2215
		if (!empty($user->rights->propal->creer)) {
2216
			$error = 0;
2217
2218
			$this->db->begin();
2219
2220
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2221
			$sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2222
			$sql .= " WHERE rowid = ".((int) $this->id);
2223
2224
			dol_syslog(__METHOD__, LOG_DEBUG);
2225
			$resql = $this->db->query($sql);
2226
			if (!$resql) {
2227
				$this->errors[] = $this->db->error();
2228
				$error++;
2229
			}
2230
2231
			if (!$error) {
2232
				$this->oldcopy = clone $this;
2233
				$this->date_livraison = $delivery_date;
2234
				$this->delivery_date = $delivery_date;
2235
			}
2236
2237
			if (!$notrigger && empty($error)) {
2238
				// Call trigger
2239
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2240
				if ($result < 0) {
2241
					$error++;
2242
				}
2243
				// End call triggers
2244
			}
2245
2246
			if (!$error) {
2247
				$this->db->commit();
2248
				return 1;
2249
			} else {
2250
				foreach ($this->errors as $errmsg) {
2251
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2252
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2253
				}
2254
				$this->db->rollback();
2255
				return -1 * $error;
2256
			}
2257
		}
2258
2259
		return -1;
2260
	}
2261
2262
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2263
	/**
2264
	 *  Set delivery
2265
	 *
2266
	 *  @param		User	$user		  	Object user that modify
2267
	 *  @param      int		$id				Availability id
2268
	 *  @param  	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2269
	 *  @return     int           			<0 if KO, >0 if OK
2270
	 */
2271
	public function set_availability($user, $id, $notrigger = 0)
2272
	{
2273
		// phpcs:enable
2274
		if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2275
			$error = 0;
2276
2277
			$this->db->begin();
2278
2279
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2280
			$sql .= " SET fk_availability = ".((int) $id);
2281
			$sql .= " WHERE rowid = ".((int) $this->id);
2282
2283
			dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2284
			$resql = $this->db->query($sql);
2285
			if (!$resql) {
2286
				$this->errors[] = $this->db->error();
2287
				$error++;
2288
			}
2289
2290
			if (!$error) {
2291
				$this->oldcopy = clone $this;
2292
				$this->fk_availability = $id;
2293
				$this->availability_id = $id;
2294
			}
2295
2296
			if (!$notrigger && empty($error)) {
2297
				// Call trigger
2298
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2299
				if ($result < 0) {
2300
					$error++;
2301
				}
2302
				// End call triggers
2303
			}
2304
2305
			if (!$error) {
2306
				$this->db->commit();
2307
				return 1;
2308
			} else {
2309
				foreach ($this->errors as $errmsg) {
2310
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2311
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2312
				}
2313
				$this->db->rollback();
2314
				return -1 * $error;
2315
			}
2316
		} else {
2317
			$error_str = 'Propal status do not meet requirement '.$this->statut;
2318
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
2319
			$this->error = $error_str;
2320
			$this->errors[] = $this->error;
2321
			return -2;
2322
		}
2323
	}
2324
2325
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2326
	/**
2327
	 *  Set source of demand
2328
	 *
2329
	 *  @param		User	$user		Object user that modify
2330
	 *  @param      int		$id			Input reason id
2331
	 *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2332
	 *  @return     int           		<0 if KO, >0 if OK
2333
	 */
2334
	public function set_demand_reason($user, $id, $notrigger = 0)
2335
	{
2336
		// phpcs:enable
2337
		if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2338
			$error = 0;
2339
2340
			$this->db->begin();
2341
2342
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2343
			$sql .= " SET fk_input_reason = ".((int) $id);
2344
			$sql .= " WHERE rowid = ".((int) $this->id);
2345
2346
			dol_syslog(__METHOD__, LOG_DEBUG);
2347
			$resql = $this->db->query($sql);
2348
			if (!$resql) {
2349
				$this->errors[] = $this->db->error();
2350
				$error++;
2351
			}
2352
2353
2354
			if (!$error) {
2355
				$this->oldcopy = clone $this;
2356
				$this->fk_input_reason = $id;
2357
				$this->demand_reason_id = $id;
2358
			}
2359
2360
2361
			if (!$notrigger && empty($error)) {
2362
				// Call trigger
2363
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2364
				if ($result < 0) {
2365
					$error++;
2366
				}
2367
				// End call triggers
2368
			}
2369
2370
			if (!$error) {
2371
				$this->db->commit();
2372
				return 1;
2373
			} else {
2374
				foreach ($this->errors as $errmsg) {
2375
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2376
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2377
				}
2378
				$this->db->rollback();
2379
				return -1 * $error;
2380
			}
2381
		} else {
2382
			$error_str = 'Propal status do not meet requirement '.$this->statut;
2383
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
2384
			$this->error = $error_str;
2385
			$this->errors[] = $this->error;
2386
			return -2;
2387
		}
2388
	}
2389
2390
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2391
	/**
2392
	 * Set customer reference number
2393
	 *
2394
	 *  @param      User	$user			Object user that modify
2395
	 *  @param      string	$ref_client		Customer reference
2396
	 *  @param  	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2397
	 *  @return     int						<0 if ko, >0 if ok
2398
	 */
2399
	public function set_ref_client($user, $ref_client, $notrigger = 0)
2400
	{
2401
		// phpcs:enable
2402
		if (!empty($user->rights->propal->creer)) {
2403
			$error = 0;
2404
2405
			$this->db->begin();
2406
2407
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2408
			$sql .= " WHERE rowid = ".((int) $this->id);
2409
2410
			dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2411
			$resql = $this->db->query($sql);
2412
			if (!$resql) {
2413
				$this->errors[] = $this->db->error();
2414
				$error++;
2415
			}
2416
2417
			if (!$error) {
2418
				$this->oldcopy = clone $this;
2419
				$this->ref_client = $ref_client;
2420
			}
2421
2422
			if (!$notrigger && empty($error)) {
2423
				// Call trigger
2424
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2425
				if ($result < 0) {
2426
					$error++;
2427
				}
2428
				// End call triggers
2429
			}
2430
2431
			if (!$error) {
2432
				$this->db->commit();
2433
				return 1;
2434
			} else {
2435
				foreach ($this->errors as $errmsg) {
2436
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2437
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2438
				}
2439
				$this->db->rollback();
2440
				return -1 * $error;
2441
			}
2442
		}
2443
2444
		return -1;
2445
	}
2446
2447
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2448
	/**
2449
	 *	Set an overall discount on the proposal
2450
	 *
2451
	 *	@param      User	$user       Object user that modify
2452
	 *	@param      double	$remise     Amount discount
2453
	 *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2454
	 *	@return     int         		<0 if ko, >0 if ok
2455
	 */
2456
	public function set_remise_percent($user, $remise, $notrigger = 0)
2457
	{
2458
		// phpcs:enable
2459
		$remise = trim($remise) ?trim($remise) : 0;
2460
2461
		if (!empty($user->rights->propal->creer)) {
2462
			$remise = price2num($remise, 2);
2463
2464
			$error = 0;
2465
2466
			$this->db->begin();
2467
2468
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
2469
			$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2470
2471
			dol_syslog(__METHOD__, LOG_DEBUG);
2472
			$resql = $this->db->query($sql);
2473
			if (!$resql) {
2474
				$this->errors[] = $this->db->error();
2475
				$error++;
2476
			}
2477
2478
			if (!$error) {
2479
				$this->oldcopy = clone $this;
2480
				$this->remise_percent = $remise;
2481
				$this->update_price(1);
2482
			}
2483
2484
			if (!$notrigger && empty($error)) {
2485
				// Call trigger
2486
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2487
				if ($result < 0) {
2488
					$error++;
2489
				}
2490
				// End call triggers
2491
			}
2492
2493
			if (!$error) {
2494
				$this->db->commit();
2495
				return 1;
2496
			} else {
2497
				foreach ($this->errors as $errmsg) {
2498
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2499
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2500
				}
2501
				$this->db->rollback();
2502
				return -1 * $error;
2503
			}
2504
		}
2505
2506
		return -1;
2507
	}
2508
2509
2510
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2511
	/**
2512
	 *	Set an absolute overall discount on the proposal
2513
	 *
2514
	 *	@param      User	$user       Object user that modify
2515
	 *	@param      double	$remise     Amount discount
2516
	 *  @param  	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2517
	 *	@return     int         		<0 if ko, >0 if ok
2518
	 */
2519
	public function set_remise_absolue($user, $remise, $notrigger = 0)
2520
	{
2521
		// phpcs:enable
2522
		if (empty($remise)) {
2523
			$remise = 0;
2524
		}
2525
		$remise = price2num($remise);
2526
2527
		if (!empty($user->rights->propal->creer)) {
2528
			$error = 0;
2529
2530
			$this->db->begin();
2531
2532
			$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2533
			$sql .= " SET remise_absolue = ".((float) $remise);
2534
			$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2535
2536
			dol_syslog(__METHOD__, LOG_DEBUG);
2537
			$resql = $this->db->query($sql);
2538
			if (!$resql) {
2539
				$this->errors[] = $this->db->error();
2540
				$error++;
2541
			}
2542
2543
			if (!$error) {
2544
				$this->oldcopy = clone $this;
2545
				$this->update_price(1);
2546
			}
2547
2548
			if (!$notrigger && empty($error)) {
2549
				// Call trigger
2550
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
2551
				if ($result < 0) {
2552
					$error++;
2553
				}
2554
				// End call triggers
2555
			}
2556
2557
			if (!$error) {
2558
				$this->db->commit();
2559
				return 1;
2560
			} else {
2561
				foreach ($this->errors as $errmsg) {
2562
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2563
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2564
				}
2565
				$this->db->rollback();
2566
				return -1 * $error;
2567
			}
2568
		}
2569
2570
		return -1;
2571
	}
2572
2573
2574
2575
	/**
2576
	 *	Reopen the commercial proposal
2577
	 *
2578
	 *	@param      User	$user		Object user that close
2579
	 *	@param      int		$status		Status
2580
	 *	@param      string	$note		Comment
2581
	 *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2582
	 *	@return     int         		<0 if KO, >0 if OK
2583
	 */
2584
	public function reopen($user, $status, $note = '', $notrigger = 0)
2585
	{
2586
		$error = 0;
2587
2588
		$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2589
		$sql .= " SET fk_statut = ".((int) $status).",";
2590
		if (!empty($note)) {
2591
			$sql .= " note_private = '".$this->db->escape($note)."',";
2592
		}
2593
		$sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2594
		$sql .= " WHERE rowid = ".((int) $this->id);
2595
2596
		$this->db->begin();
2597
2598
		dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2599
		$resql = $this->db->query($sql);
2600
		if (!$resql) {
2601
			$error++;
2602
			$this->errors[] = "Error ".$this->db->lasterror();
2603
		}
2604
		if (!$error) {
2605
			if (!$notrigger) {
2606
				// Call trigger
2607
				$result = $this->call_trigger('PROPAL_REOPEN', $user);
2608
				if ($result < 0) {
2609
					$error++;
2610
				}
2611
				// End call triggers
2612
			}
2613
		}
2614
2615
		// Commit or rollback
2616
		if ($error) {
2617
			if (!empty($this->errors)) {
2618
				foreach ($this->errors as $errmsg) {
2619
					dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2620
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2621
				}
2622
			}
2623
			$this->db->rollback();
2624
			return -1 * $error;
2625
		} else {
2626
			$this->statut = $status;
2627
			$this->status = $status;
2628
2629
			$this->db->commit();
2630
			return 1;
2631
		}
2632
	}
2633
2634
	/**
2635
	 *	Close/set the commercial proposal to status signed or refused (fill also date signature)
2636
	 *
2637
	 *	@param      User	$user		Object user that close
2638
	 *	@param      int		$status		Status (self::STATUS_BILLED or self::STATUS_REFUSED)
2639
	 *	@param      string	$note		Complete private note with this note
2640
	 *  @param		int		$notrigger	1=Does not execute triggers, 0=Execute triggers
2641
	 *	@return     int         		<0 if KO, >0 if OK
2642
	 */
2643
	public function closeProposal($user, $status, $note = '', $notrigger = 0)
2644
	{
2645
		global $langs,$conf;
2646
2647
		$error = 0;
2648
		$now = dol_now();
2649
2650
		$this->db->begin();
2651
2652
		$newprivatenote = dol_concatdesc($this->note_private, $note);
2653
2654
		if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
2655
			$date_signature = $now;
2656
			$fk_user_signature = $user->id;
2657
		} else {
2658
			$this->info($this->id);
2659
			if (!isset($this->date_signature) || $this->date_signature == '') {
2660
				$date_signature = $now;
2661
				$fk_user_signature = $user->id;
2662
			} else {
2663
				$date_signature = $this->date_signature;
2664
				$fk_user_signature = $this->user_signature->id;
2665
			}
2666
		}
2667
2668
		$sql  = "UPDATE ".MAIN_DB_PREFIX."propal";
2669
		$sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."', date_signature='".$this->db->idate($date_signature)."', fk_user_signature=".$fk_user_signature;
2670
		$sql .= " WHERE rowid = ".((int) $this->id);
2671
2672
		$resql = $this->db->query($sql);
2673
		if ($resql) {
2674
			// Status self::STATUS_REFUSED by default
2675
			$modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2676
			$trigger_name = 'PROPAL_CLOSE_REFUSED';		// used later in call_trigger()
2677
2678
			if ($status == self::STATUS_SIGNED) {	// Status self::STATUS_SIGNED
2679
				$trigger_name = 'PROPAL_CLOSE_SIGNED';	// used later in call_trigger()
2680
				$modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2681
2682
				// The connected company is classified as a client
2683
				$soc=new Societe($this->db);
2684
				$soc->id = $this->socid;
2685
				$result = $soc->set_as_client();
2686
2687
				if ($result < 0) {
2688
					$this->error=$this->db->lasterror();
2689
					$this->db->rollback();
2690
					return -2;
2691
				}
2692
			}
2693
2694
			if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2695
				// Define output language
2696
				$outputlangs = $langs;
2697
				if (getDolGlobalInt('MAIN_MULTILANGS')) {
2698
					$outputlangs = new Translate("", $conf);
2699
					$newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2700
					$outputlangs->setDefaultLang($newlang);
2701
				}
2702
2703
				// PDF
2704
				$hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2705
				$hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2706
				$hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2707
2708
				//$ret=$object->fetch($id);    // Reload to get new records
2709
				$this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2710
			}
2711
2712
			if (!$error) {
2713
				$this->oldcopy= clone $this;
2714
				$this->statut = $status;
2715
				$this->status = $status;
2716
				$this->date_signature = $date_signature;
2717
				$this->note_private = $newprivatenote;
2718
			}
2719
2720
			if (!$notrigger && empty($error)) {
2721
				// Call trigger
2722
				$result=$this->call_trigger($trigger_name, $user);
2723
				if ($result < 0) {
2724
					$error++;
2725
				}
2726
				// End call triggers
2727
			}
2728
2729
			if (!$error ) {
2730
				$this->db->commit();
2731
				return 1;
2732
			} else {
2733
				$this->statut = $this->oldcopy->statut;
2734
				$this->status = $this->oldcopy->statut;
2735
				$this->date_signature = $this->oldcopy->date_signature;
2736
				$this->note_private = $this->oldcopy->note_private;
2737
2738
				$this->db->rollback();
2739
				return -1;
2740
			}
2741
		} else {
2742
			$this->error = $this->db->lasterror();
2743
			$this->db->rollback();
2744
			return -1;
2745
		}
2746
	}
2747
2748
	/**
2749
	 *	Classify the proposal to status Billed
2750
	 *
2751
	 *	@param  	User	$user    	Object user
2752
	 *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2753
	 *	@param      string	$note		Complete private note with this note
2754
	 *	@return     int     			<0 if KO, 0 = nothing done, >0 if OK
2755
	 */
2756
	public function classifyBilled(User $user, $notrigger = 0, $note = '')
2757
	{
2758
		global $conf, $langs;
2759
2760
		$error = 0;
2761
2762
		$now = dol_now();
2763
		$num = 0;
2764
2765
		$triggerName = 'PROPAL_CLASSIFY_BILLED';
2766
2767
		$this->db->begin();
2768
2769
		$newprivatenote = dol_concatdesc($this->note_private, $note);
2770
2771
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2772
		$sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2773
		$sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2774
2775
		dol_syslog(__METHOD__, LOG_DEBUG);
2776
		$resql = $this->db->query($sql);
2777
		if (!$resql) {
2778
			$this->errors[] = $this->db->error();
2779
			$error++;
2780
		} else {
2781
			$num = $this->db->affected_rows($resql);
2782
		}
2783
2784
		if (!$error) {
2785
			$modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2786
2787
			if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2788
				// Define output language
2789
				$outputlangs = $langs;
2790
				if (getDolGlobalInt('MAIN_MULTILANGS')) {
2791
					$outputlangs = new Translate("", $conf);
2792
					$newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2793
					$outputlangs->setDefaultLang($newlang);
2794
				}
2795
2796
				// PDF
2797
				$hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2798
				$hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2799
				$hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2800
2801
				//$ret=$object->fetch($id);    // Reload to get new records
2802
				$this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2803
			}
2804
2805
			$this->oldcopy = clone $this;
2806
			$this->statut = self::STATUS_BILLED;
2807
			$this->date_cloture = $now;
2808
			$this->note_private = $newprivatenote;
2809
		}
2810
2811
		if (!$notrigger && empty($error)) {
2812
			// Call trigger
2813
			$result = $this->call_trigger($triggerName, $user);
2814
			if ($result < 0) {
2815
				$error++;
2816
			}
2817
			// End call triggers
2818
		}
2819
2820
		if (!$error) {
2821
			$this->db->commit();
2822
			return $num;
2823
		} else {
2824
			foreach ($this->errors as $errmsg) {
2825
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2826
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2827
			}
2828
			$this->db->rollback();
2829
			return -1 * $error;
2830
		}
2831
	}
2832
2833
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2834
	/**
2835
	 *	Set draft status
2836
	 *
2837
	 *	@param		User	$user		Object user that modify
2838
	 *  @param		int		$notrigger	1=Does not execute triggers, 0= execute triggers
2839
	 *	@return		int					<0 if KO, >0 if OK
2840
	 */
2841
	public function setDraft($user, $notrigger = 0)
2842
	{
2843
		// phpcs:enable
2844
		$error = 0;
2845
2846
		// Protection
2847
		if ($this->statut <= self::STATUS_DRAFT) {
2848
			return 0;
2849
		}
2850
2851
		dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2852
2853
		$this->db->begin();
2854
2855
		$sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2856
		$sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2857
		$sql .= ",  online_sign_ip = NULL , online_sign_name = NULL";
2858
		$sql .= " WHERE rowid = ".((int) $this->id);
2859
2860
		$resql = $this->db->query($sql);
2861
		if (!$resql) {
2862
			$this->errors[] = $this->db->error();
2863
			$error++;
2864
		}
2865
2866
		if (!$error) {
2867
			$this->oldcopy = clone $this;
2868
		}
2869
2870
		if (!$notrigger && empty($error)) {
2871
			// Call trigger
2872
			$result = $this->call_trigger('PROPAL_MODIFY', $user);
2873
			if ($result < 0) {
2874
				$error++;
2875
			}
2876
			// End call triggers
2877
		}
2878
2879
		if (!$error) {
2880
			$this->statut = self::STATUS_DRAFT;
2881
			$this->brouillon = 1;
2882
2883
			$this->db->commit();
2884
			return 1;
2885
		} else {
2886
			foreach ($this->errors as $errmsg) {
2887
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2888
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2889
			}
2890
			$this->db->rollback();
2891
			return -1 * $error;
2892
		}
2893
	}
2894
2895
2896
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2897
	/**
2898
	 *    Return list of proposal (eventually filtered on user) into an array
2899
	 *
2900
	 *    @param	int		$shortlist			0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
2901
	 *    @param	int		$draft				0=not draft, 1=draft
2902
	 *    @param	int		$notcurrentuser		0=all user, 1=not current user
2903
	 *    @param    int		$socid				Id third pary
2904
	 *    @param    int		$limit				For pagination
2905
	 *    @param    int		$offset				For pagination
2906
	 *    @param    string	$sortfield			Sort criteria
2907
	 *    @param    string	$sortorder			Sort order
2908
	 *    @return	array|int		       		-1 if KO, array with result if OK
2909
	 */
2910
	public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2911
	{
2912
		// phpcs:enable
2913
		global $user;
2914
2915
		$ga = array();
2916
2917
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
2918
		$sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2919
		$sql .= " p.datep as dp, p.fin_validite as datelimite";
2920
		if (empty($user->rights->societe->client->voir) && !$socid) {
2921
			$sql .= ", sc.fk_soc, sc.fk_user";
2922
		}
2923
		$sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2924
		if (empty($user->rights->societe->client->voir) && !$socid) {
2925
			$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2926
		}
2927
		$sql .= " WHERE p.entity IN (".getEntity('propal').")";
2928
		$sql .= " AND p.fk_soc = s.rowid";
2929
		$sql .= " AND p.fk_statut = c.id";
2930
		if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2931
			$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2932
		}
2933
		if ($socid) {
2934
			$sql .= " AND s.rowid = ".((int) $socid);
2935
		}
2936
		if ($draft) {
2937
			$sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2938
		}
2939
		if ($notcurrentuser > 0) {
2940
			$sql .= " AND p.fk_user_author <> ".((int) $user->id);
2941
		}
2942
		$sql .= $this->db->order($sortfield, $sortorder);
2943
		$sql .= $this->db->plimit($limit, $offset);
2944
2945
		$result = $this->db->query($sql);
2946
		if ($result) {
2947
			$num = $this->db->num_rows($result);
2948
			if ($num) {
2949
				$i = 0;
2950
				while ($i < $num) {
2951
					$obj = $this->db->fetch_object($result);
2952
2953
					if ($shortlist == 1) {
2954
						$ga[$obj->propalid] = $obj->ref;
2955
					} elseif ($shortlist == 2) {
2956
						$ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2957
					} else {
2958
						$ga[$i]['id'] = $obj->propalid;
2959
						$ga[$i]['ref'] 	= $obj->ref;
2960
						$ga[$i]['name'] = $obj->name;
2961
					}
2962
2963
					$i++;
2964
				}
2965
			}
2966
			return $ga;
2967
		} else {
2968
			dol_print_error($this->db);
2969
			return -1;
2970
		}
2971
	}
2972
2973
	/**
2974
	 *  Returns an array with the numbers of related invoices
2975
	 *
2976
	 *	@return	array		Array of invoices
2977
	 */
2978
	public function getInvoiceArrayList()
2979
	{
2980
		return $this->InvoiceArrayList($this->id);
2981
	}
2982
2983
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2984
	/**
2985
	 *  Returns an array with id and ref of related invoices
2986
	 *
2987
	 *	@param		int			$id			Id propal
2988
	 *	@return		array|int				Array of invoices id
2989
	 */
2990
	public function InvoiceArrayList($id)
2991
	{
2992
		// phpcs:enable
2993
		$ga = array();
2994
		$linkedInvoices = array();
2995
2996
		$this->fetchObjectLinked($id, $this->element);
2997
		foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2998
			// Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2999
			// On parcourt donc une liste d'objets en tant qu'objet unique
3000
			foreach ($objectid as $key => $object) {
3001
				// Cas des factures liees directement
3002
				if ($objecttype == 'facture') {
3003
					$linkedInvoices[] = $object;
3004
				} else {
3005
					// Cas des factures liees par un autre objet (ex: commande)
3006
					$this->fetchObjectLinked($object, $objecttype);
3007
					foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3008
						foreach ($subobjectid as $subkey => $subobject) {
3009
							if ($subobjecttype == 'facture') {
3010
								$linkedInvoices[] = $subobject;
3011
							}
3012
						}
3013
					}
3014
				}
3015
			}
3016
		}
3017
3018
		if (count($linkedInvoices) > 0) {
3019
			$sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3020
			$sql .= " FROM ".MAIN_DB_PREFIX."facture";
3021
			$sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3022
3023
			dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3024
			$resql = $this->db->query($sql);
3025
3026
			if ($resql) {
3027
				$tab_sqlobj = array();
3028
				$nump = $this->db->num_rows($resql);
3029
				for ($i = 0; $i < $nump; $i++) {
3030
					$sqlobj = $this->db->fetch_object($resql);
3031
					$tab_sqlobj[] = $sqlobj;
3032
				}
3033
				$this->db->free($resql);
3034
3035
				$nump = count($tab_sqlobj);
3036
3037
				if ($nump) {
3038
					$i = 0;
3039
					while ($i < $nump) {
3040
						$obj = array_shift($tab_sqlobj);
3041
3042
						$ga[$i] = $obj;
3043
3044
						$i++;
3045
					}
3046
				}
3047
				return $ga;
3048
			} else {
3049
				return -1;
3050
			}
3051
		} else {
3052
			return $ga;
3053
		}
3054
	}
3055
3056
	/**
3057
	 *	Delete proposal
3058
	 *
3059
	 *	@param	User	$user        	Object user that delete
3060
	 *	@param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
3061
	 *	@return	int						>0 if OK, <=0 if KO
3062
	 */
3063
	public function delete($user, $notrigger = 0)
3064
	{
3065
		global $conf;
3066
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3067
3068
		$error = 0;
3069
3070
		$this->db->begin();
3071
3072
		if (!$notrigger) {
3073
			// Call trigger
3074
			$result = $this->call_trigger('PROPAL_DELETE', $user);
3075
			if ($result < 0) {
3076
				$error++;
3077
			}
3078
			// End call triggers
3079
		}
3080
3081
		// Delete extrafields of lines and lines
3082
		if (!$error && !empty($this->table_element_line)) {
3083
			$tabletodelete = $this->table_element_line;
3084
			$sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id).")";
3085
			$sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3086
			if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3087
				$error++;
3088
				$this->error = $this->db->lasterror();
3089
				$this->errors[] = $this->error;
3090
				dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3091
			}
3092
		}
3093
3094
		if (!$error) {
3095
			// Delete linked object
3096
			$res = $this->deleteObjectLinked();
3097
			if ($res < 0) {
3098
				$error++;
3099
			}
3100
		}
3101
3102
		if (!$error) {
3103
			// Delete linked contacts
3104
			$res = $this->delete_linked_contact();
3105
			if ($res < 0) {
3106
				$error++;
3107
			}
3108
		}
3109
3110
		// Removed extrafields of object
3111
		if (!$error) {
3112
			$result = $this->deleteExtraFields();
3113
			if ($result < 0) {
3114
				$error++;
3115
				dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3116
			}
3117
		}
3118
3119
		// Delete main record
3120
		if (!$error) {
3121
			$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3122
			$res = $this->db->query($sql);
3123
			if (!$res) {
3124
				$error++;
3125
				$this->error = $this->db->lasterror();
3126
				$this->errors[] = $this->error;
3127
				dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3128
			}
3129
		}
3130
3131
		// Delete record into ECM index and physically
3132
		if (!$error) {
3133
			$res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3134
			if (!$res) {
3135
				$error++;
3136
			}
3137
		}
3138
3139
		if (!$error) {
3140
			// We remove directory
3141
			$ref = dol_sanitizeFileName($this->ref);
3142
			if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3143
				$dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3144
				$file = $dir."/".$ref.".pdf";
3145
				if (file_exists($file)) {
3146
					dol_delete_preview($this);
3147
3148
					if (!dol_delete_file($file, 0, 0, 0, $this)) {
3149
						$this->error = 'ErrorFailToDeleteFile';
3150
						$this->errors[] = $this->error;
3151
						$this->db->rollback();
3152
						return 0;
3153
					}
3154
				}
3155
				if (file_exists($dir)) {
3156
					$res = @dol_delete_dir_recursive($dir);
3157
					if (!$res) {
3158
						$this->error = 'ErrorFailToDeleteDir';
3159
						$this->errors[] = $this->error;
3160
						$this->db->rollback();
3161
						return 0;
3162
					}
3163
				}
3164
			}
3165
		}
3166
3167
		if (!$error) {
3168
			dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3169
			$this->db->commit();
3170
			return 1;
3171
		} else {
3172
			$this->db->rollback();
3173
			return -1;
3174
		}
3175
	}
3176
3177
	/**
3178
	 *  Change the delivery time
3179
	 *
3180
	 *  @param	int	$availability_id	Id of new delivery time
3181
	 * 	@param	int	$notrigger			1=Does not execute triggers, 0= execute triggers
3182
	 *  @return int                  	>0 if OK, <0 if KO
3183
	 *  @deprecated  use set_availability
3184
	 */
3185
	public function availability($availability_id, $notrigger = 0)
3186
	{
3187
		global $user;
3188
3189
		if ($this->statut >= self::STATUS_DRAFT) {
3190
			$error = 0;
3191
3192
			$this->db->begin();
3193
3194
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3195
			$sql .= ' SET fk_availability = '.((int) $availability_id);
3196
			$sql .= ' WHERE rowid='.((int) $this->id);
3197
3198
			dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3199
			$resql = $this->db->query($sql);
3200
			if (!$resql) {
3201
				$this->errors[] = $this->db->error();
3202
				$error++;
3203
			}
3204
3205
			if (!$error) {
3206
				$this->oldcopy = clone $this;
3207
				$this->availability_id = $availability_id;
3208
			}
3209
3210
			if (!$notrigger && empty($error)) {
3211
				// Call trigger
3212
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
3213
				if ($result < 0) {
3214
					$error++;
3215
				}
3216
				// End call triggers
3217
			}
3218
3219
			if (!$error) {
3220
				$this->db->commit();
3221
				return 1;
3222
			} else {
3223
				foreach ($this->errors as $errmsg) {
3224
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3225
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3226
				}
3227
				$this->db->rollback();
3228
				return -1 * $error;
3229
			}
3230
		} else {
3231
			$error_str = 'Propal status do not meet requirement '.$this->statut;
3232
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
3233
			$this->error = $error_str;
3234
			$this->errors[] = $this->error;
3235
			return -2;
3236
		}
3237
	}
3238
3239
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3240
	/**
3241
	 *	Change source demand
3242
	 *
3243
	 *	@param	int $demand_reason_id 	Id of new source demand
3244
	 * 	@param	int	$notrigger			1=Does not execute triggers, 0= execute triggers
3245
	 *	@return int						>0 si ok, <0 si ko
3246
	 *	@deprecated use set_demand_reason
3247
	 */
3248
	public function demand_reason($demand_reason_id, $notrigger = 0)
3249
	{
3250
		// phpcs:enable
3251
		global $user;
3252
3253
		if ($this->statut >= self::STATUS_DRAFT) {
3254
			$error = 0;
3255
3256
			$this->db->begin();
3257
3258
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3259
			$sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3260
			$sql .= ' WHERE rowid='.((int) $this->id);
3261
3262
			dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3263
			$resql = $this->db->query($sql);
3264
			if (!$resql) {
3265
				$this->errors[] = $this->db->error();
3266
				$error++;
3267
			}
3268
3269
			if (!$error) {
3270
				$this->oldcopy = clone $this;
3271
				$this->demand_reason_id = $demand_reason_id;
3272
			}
3273
3274
			if (!$notrigger && empty($error)) {
3275
				// Call trigger
3276
				$result = $this->call_trigger('PROPAL_MODIFY', $user);
3277
				if ($result < 0) {
3278
					$error++;
3279
				}
3280
				// End call triggers
3281
			}
3282
3283
			if (!$error) {
3284
				$this->db->commit();
3285
				return 1;
3286
			} else {
3287
				foreach ($this->errors as $errmsg) {
3288
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3289
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3290
				}
3291
				$this->db->rollback();
3292
				return -1 * $error;
3293
			}
3294
		} else {
3295
			$error_str = 'Propal status do not meet requirement '.$this->statut;
3296
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
3297
			$this->error = $error_str;
3298
			$this->errors[] = $this->error;
3299
			return -2;
3300
		}
3301
	}
3302
3303
3304
	/**
3305
	 *	Object Proposal Information
3306
	 *
3307
	 * 	@param	int		$id		Proposal id
3308
	 *  @return	void
3309
	 */
3310
	public function info($id)
3311
	{
3312
		$sql = "SELECT c.rowid, ";
3313
		$sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3314
		$sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3315
		$sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3316
		$sql .= " WHERE c.rowid = ".((int) $id);
3317
3318
		$result = $this->db->query($sql);
3319
3320
		if ($result) {
3321
			if ($this->db->num_rows($result)) {
3322
				$obj = $this->db->fetch_object($result);
3323
3324
				$this->id                = $obj->rowid;
3325
3326
				$this->date_creation     = $this->db->jdate($obj->datec);
3327
				$this->date_validation   = $this->db->jdate($obj->datev);
3328
				$this->date_signature    = $this->db->jdate($obj->date_signature);
3329
				$this->date_cloture      = $this->db->jdate($obj->date_cloture);
3330
3331
				$cuser = new User($this->db);
3332
				$cuser->fetch($obj->fk_user_author);
3333
				$this->user_creation = $cuser;
3334
3335
				if ($obj->fk_user_valid) {
3336
					$vuser = new User($this->db);
3337
					$vuser->fetch($obj->fk_user_valid);
3338
					$this->user_validation = $vuser;
3339
				}
3340
3341
				if ($obj->fk_user_signature) {
3342
					$user_signature = new User($this->db);
3343
					$user_signature->fetch($obj->fk_user_signature);
3344
					$this->user_signature = $user_signature;
3345
				}
3346
3347
				if ($obj->fk_user_cloture) {
3348
					$cluser = new User($this->db);
3349
					$cluser->fetch($obj->fk_user_cloture);
3350
					$this->user_cloture = $cluser;
3351
				}
3352
			}
3353
			$this->db->free($result);
3354
		} else {
3355
			dol_print_error($this->db);
3356
		}
3357
	}
3358
3359
3360
	/**
3361
	 *    	Return label of status of proposal (draft, validated, ...)
3362
	 *
3363
	 *    	@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
3364
	 *    	@return     string		Label
3365
	 */
3366
	public function getLibStatut($mode = 0)
3367
	{
3368
		return $this->LibStatut($this->statut, $mode);
3369
	}
3370
3371
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3372
	/**
3373
	 *    	Return label of a status (draft, validated, ...)
3374
	 *
3375
	 *    	@param      int			$status		Id status
3376
	 *    	@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
3377
	 *    	@return     string		Label
3378
	 */
3379
	public function LibStatut($status, $mode = 1)
3380
	{
3381
		// phpcs:enable
3382
		global $conf, $hookmanager;
3383
3384
		// Init/load array of translation of status
3385
		if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3386
			global $langs;
3387
			$langs->load("propal");
3388
			$this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3389
			$this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3390
			$this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3391
			$this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3392
			$this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3393
			$this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3394
			$this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3395
			$this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3396
			$this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3397
			$this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3398
		}
3399
3400
		$statusType = '';
3401
		if ($status == self::STATUS_DRAFT) {
3402
			$statusType = 'status0';
3403
		} elseif ($status == self::STATUS_VALIDATED) {
3404
			$statusType = 'status1';
3405
		} elseif ($status == self::STATUS_SIGNED) {
3406
			$statusType = 'status4';
3407
		} elseif ($status == self::STATUS_NOTSIGNED) {
3408
			$statusType = 'status9';
3409
		} elseif ($status == self::STATUS_BILLED) {
3410
			$statusType = 'status6';
3411
		}
3412
3413
3414
		$parameters = array('status' => $status, 'mode' => $mode);
3415
		$reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3416
3417
		if ($reshook > 0) {
3418
			return $hookmanager->resPrint;
3419
		}
3420
3421
		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3422
	}
3423
3424
3425
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3426
	/**
3427
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3428
	 *
3429
	 *      @param          User	$user   Object user
3430
	 *      @param          int		$mode   "opened" for proposal to close, "signed" for proposal to invoice
3431
	 *      @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
3432
	 */
3433
	public function load_board($user, $mode)
3434
	{
3435
		// phpcs:enable
3436
		global $conf, $langs;
3437
3438
		$clause = " WHERE";
3439
3440
		$sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3441
		$sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3442
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
3443
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3444
			$sql .= " WHERE sc.fk_user = ".((int) $user->id);
3445
			$clause = " AND";
3446
		}
3447
		$sql .= $clause." p.entity IN (".getEntity('propal').")";
3448
		if ($mode == 'opened') {
3449
			$sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3450
		}
3451
		if ($mode == 'signed') {
3452
			$sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3453
		}
3454
		if ($user->socid) {
3455
			$sql .= " AND p.fk_soc = ".((int) $user->socid);
3456
		}
3457
3458
		$resql = $this->db->query($sql);
3459
		if ($resql) {
3460
			$langs->load("propal");
3461
			$now = dol_now();
3462
3463
			$delay_warning = 0;
3464
			$status = 0;
3465
			$label = $labelShort = '';
3466
			if ($mode == 'opened') {
3467
				$delay_warning = $conf->propal->cloture->warning_delay;
3468
				$status = self::STATUS_VALIDATED;
3469
				$label = $langs->transnoentitiesnoconv("PropalsToClose");
3470
				$labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3471
			}
3472
			if ($mode == 'signed') {
3473
				$delay_warning = $conf->propal->facturation->warning_delay;
3474
				$status = self::STATUS_SIGNED;
3475
				$label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3476
				$labelShort = $langs->trans("ToBill");
3477
			}
3478
3479
			$response = new WorkboardResponse();
3480
			$response->warning_delay = $delay_warning / 60 / 60 / 24;
3481
			$response->label = $label;
3482
			$response->labelShort = $labelShort;
3483
			$response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3484
			$response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3485
			$response->img = img_object('', "propal");
3486
3487
			// This assignment in condition is not a bug. It allows walking the results.
3488
			while ($obj = $this->db->fetch_object($resql)) {
3489
				$response->nbtodo++;
3490
				$response->total += $obj->total_ht;
3491
3492
				if ($mode == 'opened') {
3493
					$datelimit = $this->db->jdate($obj->datefin);
3494
					if ($datelimit < ($now - $delay_warning)) {
3495
						$response->nbtodolate++;
3496
					}
3497
				}
3498
				// TODO Definir regle des propales a facturer en retard
3499
				// if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3500
			}
3501
3502
			return $response;
3503
		} else {
3504
			$this->error = $this->db->error();
3505
			return -1;
3506
		}
3507
	}
3508
3509
3510
	/**
3511
	 *  Initialise an instance with random values.
3512
	 *  Used to build previews or test instances.
3513
	 *	id must be 0 if object instance is a specimen.
3514
	 *
3515
	 *  @return	void
3516
	 */
3517
	public function initAsSpecimen()
3518
	{
3519
		global $conf, $langs;
3520
3521
		// Load array of products prodids
3522
		$num_prods = 0;
3523
		$prodids = array();
3524
		$sql = "SELECT rowid";
3525
		$sql .= " FROM ".MAIN_DB_PREFIX."product";
3526
		$sql .= " WHERE entity IN (".getEntity('product').")";
3527
		$sql .= $this->db->plimit(100);
3528
3529
		$resql = $this->db->query($sql);
3530
		if ($resql) {
3531
			$num_prods = $this->db->num_rows($resql);
3532
			$i = 0;
3533
			while ($i < $num_prods) {
3534
				$i++;
3535
				$row = $this->db->fetch_row($resql);
3536
				$prodids[$i] = $row[0];
3537
			}
3538
		}
3539
3540
		// Initialise parametres
3541
		$this->id = 0;
3542
		$this->ref = 'SPECIMEN';
3543
		$this->ref_client = 'NEMICEPS';
3544
		$this->specimen = 1;
3545
		$this->socid = 1;
3546
		$this->date = time();
3547
		$this->fin_validite = $this->date + 3600 * 24 * 30;
3548
		$this->cond_reglement_id   = 1;
3549
		$this->cond_reglement_code = 'RECEP';
3550
		$this->mode_reglement_id   = 7;
3551
		$this->mode_reglement_code = 'CHQ';
3552
		$this->availability_id     = 1;
3553
		$this->availability_code   = 'AV_NOW';
3554
		$this->demand_reason_id    = 1;
3555
		$this->demand_reason_code  = 'SRC_00';
3556
		$this->note_public = 'This is a comment (public)';
3557
		$this->note_private = 'This is a comment (private)';
3558
3559
		$this->multicurrency_tx = 1;
3560
		$this->multicurrency_code = $conf->currency;
3561
3562
		// Lines
3563
		$nbp = 5;
3564
		$xnbp = 0;
3565
		while ($xnbp < $nbp) {
3566
			$line = new PropaleLigne($this->db);
3567
			$line->desc = $langs->trans("Description")." ".$xnbp;
3568
			$line->qty = 1;
3569
			$line->subprice = 100;
3570
			$line->price = 100;
3571
			$line->tva_tx = 20;
3572
			$line->localtax1_tx = 0;
3573
			$line->localtax2_tx = 0;
3574
			if ($xnbp == 2) {
3575
				$line->total_ht = 50;
3576
				$line->total_ttc = 60;
3577
				$line->total_tva = 10;
3578
				$line->remise_percent = 50;
3579
			} else {
3580
				$line->total_ht = 100;
3581
				$line->total_ttc = 120;
3582
				$line->total_tva = 20;
3583
				$line->remise_percent = 00;
3584
			}
3585
3586
			if ($num_prods > 0) {
3587
				$prodid = mt_rand(1, $num_prods);
3588
				$line->fk_product = $prodids[$prodid];
3589
				$line->product_ref = 'SPECIMEN';
3590
			}
3591
3592
			$this->lines[$xnbp] = $line;
3593
3594
			$this->total_ht       += $line->total_ht;
3595
			$this->total_tva      += $line->total_tva;
3596
			$this->total_ttc      += $line->total_ttc;
3597
3598
			$xnbp++;
3599
		}
3600
	}
3601
3602
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3603
	/**
3604
	 *      Charge indicateurs this->nb de tableau de bord
3605
	 *
3606
	 *      @return     int         <0 if ko, >0 if ok
3607
	 */
3608
	public function load_state_board()
3609
	{
3610
		// phpcs:enable
3611
		global $user;
3612
3613
		$this->nb = array();
3614
		$clause = "WHERE";
3615
3616
		$sql = "SELECT count(p.rowid) as nb";
3617
		$sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3618
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3619
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
3620
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3621
			$sql .= " WHERE sc.fk_user = ".((int) $user->id);
3622
			$clause = "AND";
3623
		}
3624
		$sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3625
3626
		$resql = $this->db->query($sql);
3627
		if ($resql) {
3628
			// This assignment in condition is not a bug. It allows walking the results.
3629
			while ($obj = $this->db->fetch_object($resql)) {
3630
				$this->nb["proposals"] = $obj->nb;
3631
			}
3632
			$this->db->free($resql);
3633
			return 1;
3634
		} else {
3635
			dol_print_error($this->db);
3636
			$this->error = $this->db->error();
3637
			return -1;
3638
		}
3639
	}
3640
3641
3642
	/**
3643
	 *  Returns the reference to the following non used Proposal used depending on the active numbering module
3644
	 *  defined into PROPALE_ADDON
3645
	 *
3646
	 *  @param	Societe		$soc  	Object thirdparty
3647
	 *  @return string      		Reference libre pour la propale
3648
	 */
3649
	public function getNextNumRef($soc)
3650
	{
3651
		global $conf, $langs;
3652
		$langs->load("propal");
3653
3654
		$classname = $conf->global->PROPALE_ADDON;
3655
3656
		if (!empty($classname)) {
3657
			$mybool = false;
3658
3659
			$file = $classname.".php";
3660
3661
			// Include file with class
3662
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3663
			foreach ($dirmodels as $reldir) {
3664
				$dir = dol_buildpath($reldir."core/modules/propale/");
3665
3666
				// Load file with numbering class (if found)
3667
				$mybool |= @include_once $dir.$file;
3668
			}
3669
3670
			if (!$mybool) {
3671
				dol_print_error('', "Failed to include file ".$file);
3672
				return '';
3673
			}
3674
3675
			$obj = new $classname();
3676
			$numref = "";
3677
			$numref = $obj->getNextValue($soc, $this);
3678
3679
			if ($numref != "") {
3680
				return $numref;
3681
			} else {
3682
				$this->error = $obj->error;
3683
				//dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3684
				return "";
3685
			}
3686
		} else {
3687
			$langs->load("errors");
3688
			print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3689
			return "";
3690
		}
3691
	}
3692
3693
	/**
3694
	 * getTooltipContentArray
3695
	 * @param array $params params to construct tooltip data
3696
	 * @since v18
3697
	 * @return array
3698
	 */
3699
	public function getTooltipContentArray($params)
3700
	{
3701
		global $conf, $langs, $user;
3702
3703
		$datas = [];
3704
3705
		if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3706
			return ['optimize' => $langs->trans("Proposal")];
3707
		}
3708
		if ($user->hasRight('propal', 'lire')) {
3709
			$datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3710
			if (isset($this->statut)) {
3711
				$datas['status'] = ' '.$this->getLibStatut(5);
3712
			}
3713
			if (!empty($this->ref)) {
3714
				$datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3715
			}
3716
			if (!empty($this->ref_client)) {
3717
				$datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3718
			}
3719
			if (!empty($this->total_ht)) {
3720
				$datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3721
			}
3722
			if (!empty($this->total_tva)) {
3723
				$datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3724
			}
3725
			if (!empty($this->total_ttc)) {
3726
				$datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3727
			}
3728
			if (!empty($this->date)) {
3729
				$datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3730
			}
3731
			if (!empty($this->delivery_date)) {
3732
				$datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3733
			}
3734
		}
3735
3736
		return $datas;
3737
	}
3738
3739
	/**
3740
	 *	Return clicable link of object (with eventually picto)
3741
	 *
3742
	 *	@param      int		$withpicto		          Add picto into link
3743
	 *	@param      string	$option			          Where point the link ('expedition', 'document', ...)
3744
	 *	@param      string	$get_params    	          Parametres added to url
3745
	 *  @param	    int   	$notooltip		          1=Disable tooltip
3746
	 *  @param      int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3747
	 *  @param      int     $addlinktonotes           -1=Disable, 0=Just add label show notes, 1=Add private note (only internal user), 2=Add public note (internal or external user), 3=Add private (internal user) and public note (internal and external user)
3748
	 *	@return     string          		          String with URL
3749
	 */
3750
	public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3751
	{
3752
		global $langs, $conf, $user, $hookmanager;
3753
3754
		if (!empty($conf->dol_no_mouse_hover)) {
3755
			$notooltip = 1; // Force disable tooltips
3756
		}
3757
3758
		$result = '';
3759
		$params = [
3760
			'id' => $this->id,
3761
			'objecttype' => $this->element,
3762
			'option' => $option,
3763
		];
3764
		$classfortooltip = 'classfortooltip';
3765
		$dataparams = '';
3766
		if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3767
			$classfortooltip = 'classforajaxtooltip';
3768
			$dataparams = ' data-params='.json_encode($params);
3769
			// $label = $langs->trans('Loading');
3770
		}
3771
		$label = implode($this->getTooltipContentArray($params));
3772
3773
		$url = '';
3774
		if ($user->rights->propal->lire) {
3775
			if ($option == '') {
3776
				$url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3777
			} elseif ($option == 'compta') {  // deprecated
3778
				$url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3779
			} elseif ($option == 'expedition') {
3780
				$url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3781
			} elseif ($option == 'document') {
3782
				$url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3783
			}
3784
3785
			if ($option != 'nolink') {
3786
				// Add param to save lastsearch_values or not
3787
				$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3788
				if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3789
					$add_save_lastsearch_values = 1;
3790
				}
3791
				if ($add_save_lastsearch_values) {
3792
					$url .= '&save_lastsearch_values=1';
3793
				}
3794
			}
3795
		}
3796
3797
		$linkclose = '';
3798
		if (empty($notooltip) && $user->rights->propal->lire) {
3799
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3800
				$label = $langs->trans("Proposal");
3801
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3802
			}
3803
			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
3804
			$linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3805
		}
3806
3807
		$linkstart = '<a href="'.$url.'"';
3808
		$linkstart .= $linkclose.'>';
3809
		$linkend = '</a>';
3810
3811
		$result .= $linkstart;
3812
		if ($withpicto) {
3813
			$result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'paddingright ' : '').$classfortooltip.'"'), 0, 0, $notooltip ? 0 : 1);
3814
		}
3815
		if ($withpicto != 2) {
3816
			$result .= $this->ref;
3817
		}
3818
		$result .= $linkend;
3819
3820
		if ($addlinktonotes >= 0) {
3821
			$txttoshow = '';
3822
3823
			if ($addlinktonotes == 0) {
3824
				if (!empty($this->note_private) || !empty($this->note_public)) {
3825
					$txttoshow = $langs->trans('ViewPrivateNote');
3826
				}
3827
			} elseif ($addlinktonotes == 1) {
3828
				if (!empty($this->note_private)) {
3829
					$txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3830
				}
3831
			} elseif ($addlinktonotes == 2) {
3832
				if (!empty($this->note_public)) {
3833
					$txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3834
				}
3835
			} elseif ($addlinktonotes == 3) {
3836
				if ($user->socid > 0) {
3837
					if (!empty($this->note_public)) {
3838
						$txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3839
					}
3840
				} else {
3841
					if (!empty($this->note_public)) {
3842
						$txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3843
					}
3844
					if (!empty($this->note_private)) {
3845
						if (!empty($txttoshow)) {
3846
							$txttoshow .= '<br><br>';
3847
						}
3848
						$txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3849
					}
3850
				}
3851
			}
3852
3853
			if ($txttoshow) {
3854
				$result .= ' <span class="note inline-block">';
3855
				$result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3856
				$result .= img_picto('', 'note');
3857
				$result .= '</a>';
3858
				$result .= '</span>';
3859
			}
3860
		}
3861
3862
		global $action;
3863
		$hookmanager->initHooks(array($this->element . 'dao'));
3864
		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3865
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3866
		if ($reshook > 0) {
3867
			$result = $hookmanager->resPrint;
3868
		} else {
3869
			$result .= $hookmanager->resPrint;
3870
		}
3871
		return $result;
3872
	}
3873
3874
	/**
3875
	 * 	Retrieve an array of proposal lines
3876
	 *	@param  string              $filters        Filter on other fields
3877
	 *
3878
	 * 	@return int		>0 if OK, <0 if KO
3879
	 */
3880
	public function getLinesArray($filters = '')
3881
	{
3882
		return $this->fetch_lines(0, 0, $filters);
3883
	}
3884
3885
	/**
3886
	 *  Create a document onto disk according to template module.
3887
	 *
3888
	 * 	@param	    string		$modele			Force model to use ('' to not force)
3889
	 * 	@param		Translate	$outputlangs	Object langs to use for output
3890
	 *  @param      int			$hidedetails    Hide details of lines
3891
	 *  @param      int			$hidedesc       Hide description
3892
	 *  @param      int			$hideref        Hide ref
3893
	 *  @param   	null|array  $moreparams     Array to provide more information
3894
	 * 	@return     int         				0 if KO, 1 if OK
3895
	 */
3896
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3897
	{
3898
		global $conf, $langs;
3899
3900
		$langs->load("propale");
3901
		$outputlangs->load("products");
3902
3903
		if (!dol_strlen($modele)) {
3904
			$modele = 'azur';
3905
3906
			if ($this->model_pdf) {
3907
				$modele = $this->model_pdf;
3908
			} elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3909
				$modele = $conf->global->PROPALE_ADDON_PDF;
3910
			}
3911
		}
3912
3913
		$modelpath = "core/modules/propale/doc/";
3914
3915
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3916
	}
3917
3918
	/**
3919
	 * Function used to replace a thirdparty id with another one.
3920
	 *
3921
	 * @param 	DoliDB 	$dbs 		Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
3922
	 * @param 	int 	$origin_id 	Old thirdparty id
3923
	 * @param 	int 	$dest_id 	New thirdparty id
3924
	 * @return 	bool
3925
	 */
3926
	public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3927
	{
3928
		$tables = array(
3929
			'propal'
3930
		);
3931
3932
		return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3933
	}
3934
3935
	/**
3936
	 * Function used to replace a product id with another one.
3937
	 *
3938
	 * @param DoliDB $db Database handler
3939
	 * @param int $origin_id Old product id
3940
	 * @param int $dest_id New product id
3941
	 * @return bool
3942
	 */
3943
	public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3944
	{
3945
		$tables = array(
3946
			'propaldet'
3947
		);
3948
3949
		return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3950
	}
3951
3952
	/**
3953
	 *	Return clicable link of object (with eventually picto)
3954
	 *
3955
	 *	@param      string	    $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3956
	 *  @param		array		$arraydata				Array of data
3957
	 *  @return		string								HTML Code for Kanban thumb.
3958
	 */
3959
	public function getKanbanView($option = '', $arraydata = null)
3960
	{
3961
		global $langs;
3962
		$return = '<div class="box-flex-item box-flex-grow-zero">';
3963
		$return .= '<div class="info-box info-box-sm">';
3964
		$return .= '<span class="info-box-icon bg-infobox-action">';
3965
		$return .= img_picto('', $this->picto);
3966
		//$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
3967
		$return .= '</span>';
3968
		$return .= '<div class="info-box-content">';
3969
		$return .= '<span class="info-box-ref">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3970
		if (property_exists($this, 'fk_project')) {
3971
			$return .= '<span class="info-box-ref"> | '.$this->fk_project.'</span>';
3972
		}
3973
		if (property_exists($this, 'author')) {
3974
			$return .= '<br><span class="info-box-label">'.$this->author.'</span>';
3975
		}
3976
		if (property_exists($this, 'total_ht')) {
3977
			$return .='<br><span class="" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ht).'</span>';
3978
		}
3979
		if (method_exists($this, 'getLibStatut')) {
3980
			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(5).'</div>';
3981
		}
3982
		$return .= '</div>';
3983
		$return .= '</div>';
3984
		$return .= '</div>';
3985
		return $return;
3986
	}
3987
}
3988
3989
/**
3990
 *	Class to manage commercial proposal lines
3991
 */
3992
class PropaleLigne extends CommonObjectLine
3993
{
3994
	/**
3995
	 * @var string ID to identify managed object
3996
	 */
3997
	public $element = 'propaldet';
3998
3999
	/**
4000
	 * @var string Name of table without prefix where object is stored
4001
	 */
4002
	public $table_element = 'propaldet';
4003
4004
	public $oldline;
4005
4006
	// From llx_propaldet
4007
	public $fk_propal;
4008
	public $fk_parent_line;
4009
	public $desc; // Description ligne
4010
	public $fk_product; // Id produit predefini
4011
	/**
4012
	 * @deprecated
4013
	 * @see $product_type
4014
	 */
4015
	public $fk_product_type;
4016
	/**
4017
	 * Product type.
4018
	 * @var int
4019
	 * @see Product::TYPE_PRODUCT, Product::TYPE_SERVICE
4020
	 */
4021
	public $product_type = Product::TYPE_PRODUCT;
4022
4023
	public $qty;
4024
4025
	public $tva_tx;
4026
	public $vat_src_code;
4027
4028
	public $subprice;
4029
	public $remise_percent;
4030
	public $fk_remise_except;
4031
4032
	public $rang = 0;
4033
4034
	public $fk_fournprice;
4035
	public $pa_ht;
4036
	public $marge_tx;
4037
	public $marque_tx;
4038
4039
	public $special_code; // Tag for special lines (exlusive tags)
4040
	// 1: frais de port
4041
	// 2: ecotaxe
4042
	// 3: option line (when qty = 0)
4043
4044
	public $info_bits = 0; // Some other info:
4045
	// Bit 0: 	0 si TVA normal - 1 si TVA NPR
4046
	// Bit 1:	0 ligne normale - 1 si ligne de remise fixe
4047
4048
	public $total_ht; // Total HT  de la ligne toute quantite et incluant la remise ligne
4049
	public $total_tva; // Total TVA  de la ligne toute quantite et incluant la remise ligne
4050
	public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4051
4052
	/**
4053
	 * @deprecated
4054
	 * @see $remise_percent, $fk_remise_except
4055
	 */
4056
	public $remise;
4057
	/**
4058
	 * @deprecated
4059
	 * @see $subprice
4060
	 */
4061
	public $price;
4062
4063
	// From llx_product
4064
	/**
4065
	 * @deprecated
4066
	 * @see $product_ref
4067
	 */
4068
	public $ref;
4069
	/**
4070
	 * Product reference
4071
	 * @var string
4072
	 */
4073
	public $product_ref;
4074
	/**
4075
	 * @deprecated
4076
	 * @see $product_label
4077
	 */
4078
	public $libelle;
4079
	/**
4080
	 * @deprecated
4081
	 * @see $product_label
4082
	 */
4083
	public $label;
4084
	/**
4085
	 *  Product label
4086
	 * @var string
4087
	 */
4088
	public $product_label;
4089
	/**
4090
	 * Product description
4091
	 * @var string
4092
	 */
4093
	public $product_desc;
4094
4095
	/**
4096
	 * Product use lot
4097
	 * @var string
4098
	 */
4099
	public $product_tobatch;
4100
4101
	/**
4102
	 * Product barcode
4103
	 * @var string
4104
	 */
4105
	public $product_barcode;
4106
4107
	public $localtax1_tx; // Local tax 1
4108
	public $localtax2_tx; // Local tax 2
4109
	public $localtax1_type; // Local tax 1 type
4110
	public $localtax2_type; // Local tax 2 type
4111
	public $total_localtax1; // Line total local tax 1
4112
	public $total_localtax2; // Line total local tax 2
4113
4114
	public $date_start;
4115
	public $date_end;
4116
4117
	public $skip_update_total; // Skip update price total for special lines
4118
4119
	// Multicurrency
4120
	public $fk_multicurrency;
4121
	public $multicurrency_code;
4122
	public $multicurrency_subprice;
4123
	public $multicurrency_total_ht;
4124
	public $multicurrency_total_tva;
4125
	public $multicurrency_total_ttc;
4126
4127
4128
	/**
4129
	 * 	Class line Contructor
4130
	 *
4131
	 * 	@param	DoliDB	$db	Database handler
4132
	 */
4133
	public function __construct($db)
4134
	{
4135
		$this->db = $db;
4136
	}
4137
4138
	/**
4139
	 *	Retrieve the propal line object
4140
	 *
4141
	 *	@param	int		$rowid		Propal line id
4142
	 *	@return	int					<0 if KO, >0 if OK
4143
	 */
4144
	public function fetch($rowid)
4145
	{
4146
		$sql = 'SELECT pd.rowid, pd.fk_propal, pd.fk_parent_line, pd.fk_product, pd.label as custom_label, pd.description, pd.price, pd.qty, pd.vat_src_code, pd.tva_tx,';
4147
		$sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4148
		$sql .= ' pd.info_bits, pd.total_ht, pd.total_tva, pd.total_ttc, pd.fk_product_fournisseur_price as fk_fournprice, pd.buy_price_ht as pa_ht, pd.special_code, pd.rang,';
4149
		$sql .= ' pd.fk_unit,';
4150
		$sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4151
		$sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4152
		$sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4153
		$sql .= ' pd.date_start, pd.date_end, pd.product_type';
4154
		$sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4155
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4156
		$sql .= ' WHERE pd.rowid = '.((int) $rowid);
4157
4158
		$result = $this->db->query($sql);
4159
		if ($result) {
4160
			$objp = $this->db->fetch_object($result);
4161
4162
			if ($objp) {
4163
				$this->id = $objp->rowid;
4164
				$this->rowid			= $objp->rowid; // deprecated
4165
				$this->fk_propal = $objp->fk_propal;
4166
				$this->fk_parent_line = $objp->fk_parent_line;
4167
				$this->label			= $objp->custom_label;
4168
				$this->desc				= $objp->description;
4169
				$this->qty = $objp->qty;
4170
				$this->price			= $objp->price; // deprecated
4171
				$this->subprice = $objp->subprice;
4172
				$this->vat_src_code = $objp->vat_src_code;
4173
				$this->tva_tx			= $objp->tva_tx;
4174
				$this->remise			= $objp->remise; // deprecated
4175
				$this->remise_percent = $objp->remise_percent;
4176
				$this->fk_remise_except = $objp->fk_remise_except;
4177
				$this->fk_product = $objp->fk_product;
4178
				$this->info_bits		= $objp->info_bits;
4179
4180
				$this->total_ht			= $objp->total_ht;
4181
				$this->total_tva		= $objp->total_tva;
4182
				$this->total_ttc		= $objp->total_ttc;
4183
4184
				$this->fk_fournprice = $objp->fk_fournprice;
4185
4186
				$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4187
				$this->pa_ht			= $marginInfos[0];
4188
				$this->marge_tx			= $marginInfos[1];
4189
				$this->marque_tx		= $marginInfos[2];
4190
4191
				$this->special_code		= $objp->special_code;
4192
				$this->product_type		= $objp->product_type;
4193
				$this->rang = $objp->rang;
4194
4195
				$this->ref = $objp->product_ref; // deprecated
4196
				$this->product_ref = $objp->product_ref;
4197
				$this->libelle = $objp->product_label; // deprecated
4198
				$this->product_label	= $objp->product_label;
4199
				$this->product_desc		= $objp->product_desc;
4200
				$this->fk_unit          = $objp->fk_unit;
4201
4202
				$this->date_start       = $this->db->jdate($objp->date_start);
4203
				$this->date_end         = $this->db->jdate($objp->date_end);
4204
4205
				// Multicurrency
4206
				$this->fk_multicurrency = $objp->fk_multicurrency;
4207
				$this->multicurrency_code = $objp->multicurrency_code;
4208
				$this->multicurrency_subprice 	= $objp->multicurrency_subprice;
4209
				$this->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
4210
				$this->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
4211
				$this->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
4212
4213
				$this->fetch_optionals();
4214
4215
				$this->db->free($result);
4216
4217
				return 1;
4218
			} else {
4219
				return 0;
4220
			}
4221
		} else {
4222
			return -1;
4223
		}
4224
	}
4225
4226
	/**
4227
	 *  Insert object line propal in database
4228
	 *
4229
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
4230
	 *	@return		int						<0 if KO, >0 if OK
4231
	 */
4232
	public function insert($notrigger = 0)
4233
	{
4234
		global $conf, $user;
4235
4236
		$error = 0;
4237
4238
		dol_syslog(get_class($this)."::insert rang=".$this->rang);
4239
4240
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4241
4242
		// Clean parameters
4243
		if (empty($this->tva_tx)) {
4244
			$this->tva_tx = 0;
4245
		}
4246
		if (empty($this->localtax1_tx)) {
4247
			$this->localtax1_tx = 0;
4248
		}
4249
		if (empty($this->localtax2_tx)) {
4250
			$this->localtax2_tx = 0;
4251
		}
4252
		if (empty($this->localtax1_type)) {
4253
			$this->localtax1_type = 0;
4254
		}
4255
		if (empty($this->localtax2_type)) {
4256
			$this->localtax2_type = 0;
4257
		}
4258
		if (empty($this->total_localtax1)) {
4259
			$this->total_localtax1 = 0;
4260
		}
4261
		if (empty($this->total_localtax2)) {
4262
			$this->total_localtax2 = 0;
4263
		}
4264
		if (empty($this->rang)) {
4265
			$this->rang = 0;
4266
		}
4267
		if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4268
			$this->remise_percent = 0;
4269
		}
4270
		if (empty($this->info_bits)) {
4271
			$this->info_bits = 0;
4272
		}
4273
		if (empty($this->special_code)) {
4274
			$this->special_code = 0;
4275
		}
4276
		if (empty($this->fk_parent_line)) {
4277
			$this->fk_parent_line = 0;
4278
		}
4279
		if (empty($this->fk_fournprice)) {
4280
			$this->fk_fournprice = 0;
4281
		}
4282
		if (!is_numeric($this->qty)) {
4283
			$this->qty = 0;
4284
		}
4285
		if (empty($this->pa_ht)) {
4286
			$this->pa_ht = 0;
4287
		}
4288
		if (empty($this->multicurrency_subprice)) {
4289
			$this->multicurrency_subprice = 0;
4290
		}
4291
		if (empty($this->multicurrency_total_ht)) {
4292
			$this->multicurrency_total_ht = 0;
4293
		}
4294
		if (empty($this->multicurrency_total_tva)) {
4295
			$this->multicurrency_total_tva = 0;
4296
		}
4297
		if (empty($this->multicurrency_total_ttc)) {
4298
			$this->multicurrency_total_ttc = 0;
4299
		}
4300
4301
		// if buy price not defined, define buyprice as configured in margin admin
4302
		if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4303
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4304
				return $result;
4305
			} else {
4306
				$this->pa_ht = $result;
4307
			}
4308
		}
4309
4310
		// Check parameters
4311
		if ($this->product_type < 0) {
4312
			return -1;
4313
		}
4314
4315
		$this->db->begin();
4316
4317
		// Insert line into database
4318
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4319
		$sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4320
		$sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4321
		$sql .= ' subprice, remise_percent, ';
4322
		$sql .= ' info_bits, ';
4323
		$sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4324
		$sql .= ' fk_unit,';
4325
		$sql .= ' date_start, date_end';
4326
		$sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4327
		$sql .= " VALUES (".$this->fk_propal.",";
4328
		$sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4329
		$sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4330
		$sql .= " '".$this->db->escape($this->desc)."',";
4331
		$sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4332
		$sql .= " '".$this->db->escape($this->product_type)."',";
4333
		$sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4334
		$sql .= " ".price2num($this->qty, 'MS').",";
4335
		$sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4336
		$sql .= " ".price2num($this->tva_tx).",";
4337
		$sql .= " ".price2num($this->localtax1_tx).",";
4338
		$sql .= " ".price2num($this->localtax2_tx).",";
4339
		$sql .= " '".$this->db->escape($this->localtax1_type)."',";
4340
		$sql .= " '".$this->db->escape($this->localtax2_type)."',";
4341
		$sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4342
		$sql .= " ".price2num($this->remise_percent, 3).",";
4343
		$sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4344
		$sql .= " ".price2num($this->total_ht, 'MT').",";
4345
		$sql .= " ".price2num($this->total_tva, 'MT').",";
4346
		$sql .= " ".price2num($this->total_localtax1, 'MT').",";
4347
		$sql .= " ".price2num($this->total_localtax2, 'MT').",";
4348
		$sql .= " ".price2num($this->total_ttc, 'MT').",";
4349
		$sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4350
		$sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4351
		$sql .= ' '.((int) $this->special_code).',';
4352
		$sql .= ' '.((int) $this->rang).',';
4353
		$sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4354
		$sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4355
		$sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4356
		$sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4357
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4358
		$sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4359
		$sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4360
		$sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4361
		$sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4362
		$sql .= ')';
4363
4364
		dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4365
		$resql = $this->db->query($sql);
4366
		if ($resql) {
4367
			$this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4368
4369
			if (!$error) {
4370
				$this->id = $this->rowid;
4371
				$result = $this->insertExtraFields();
4372
				if ($result < 0) {
4373
					$error++;
4374
				}
4375
			}
4376
4377
			if (!$error && !$notrigger) {
4378
				// Call trigger
4379
				$result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4380
				if ($result < 0) {
4381
					$this->db->rollback();
4382
					return -1;
4383
				}
4384
				// End call triggers
4385
			}
4386
4387
			$this->db->commit();
4388
			return 1;
4389
		} else {
4390
			$this->error = $this->db->error()." sql=".$sql;
4391
			$this->db->rollback();
4392
			return -1;
4393
		}
4394
	}
4395
4396
	/**
4397
	 * 	Delete line in database
4398
	 *
4399
	 *  @param	User	$user		Object user
4400
	 *	@param 	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4401
	 *	@return	 int  				<0 if ko, >0 if ok
4402
	 */
4403
	public function delete(User $user, $notrigger = 0)
4404
	{
4405
		global $conf;
4406
4407
		$error = 0;
4408
		$this->db->begin();
4409
4410
		if (!$notrigger) {
4411
			// Call trigger
4412
			$result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4413
			if ($result < 0) {
4414
				$error++;
4415
			}
4416
		}
4417
		// End call triggers
4418
4419
		if (!$error) {
4420
			$sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4421
			dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4422
			if ($this->db->query($sql)) {
4423
				// Remove extrafields
4424
				if (!$error) {
4425
					$this->id = $this->rowid;
4426
					$result = $this->deleteExtraFields();
4427
					if ($result < 0) {
4428
						$error++;
4429
						dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4430
					}
4431
				}
4432
			} else {
4433
				$this->error = $this->db->error() . " sql=" . $sql;
4434
				$error++;
4435
			}
4436
		}
4437
4438
		if ($error) {
4439
			$this->db->rollback();
4440
			return -1;
4441
		} else {
4442
			$this->db->commit();
4443
			return 1;
4444
		}
4445
	}
4446
4447
	/**
4448
	 *	Update propal line object into DB
4449
	 *
4450
	 *	@param 	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4451
	 *	@return	int					<0 if ko, >0 if ok
4452
	 */
4453
	public function update($notrigger = 0)
4454
	{
4455
		global $conf, $user;
4456
4457
		$error = 0;
4458
4459
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4460
4461
		if (empty($this->id) && !empty($this->rowid)) {
4462
			$this->id = $this->rowid;
4463
		}
4464
4465
		// Clean parameters
4466
		if (empty($this->tva_tx)) {
4467
			$this->tva_tx = 0;
4468
		}
4469
		if (empty($this->localtax1_tx)) {
4470
			$this->localtax1_tx = 0;
4471
		}
4472
		if (empty($this->localtax2_tx)) {
4473
			$this->localtax2_tx = 0;
4474
		}
4475
		if (empty($this->total_localtax1)) {
4476
			$this->total_localtax1 = 0;
4477
		}
4478
		if (empty($this->total_localtax2)) {
4479
			$this->total_localtax2 = 0;
4480
		}
4481
		if (empty($this->localtax1_type)) {
4482
			$this->localtax1_type = 0;
4483
		}
4484
		if (empty($this->localtax2_type)) {
4485
			$this->localtax2_type = 0;
4486
		}
4487
		if (empty($this->marque_tx)) {
4488
			$this->marque_tx = 0;
4489
		}
4490
		if (empty($this->marge_tx)) {
4491
			$this->marge_tx = 0;
4492
		}
4493
		if (empty($this->price)) {
4494
			$this->price = 0; // TODO A virer
4495
		}
4496
		if (empty($this->remise_percent)) {
4497
			$this->remise_percent = 0;
4498
		}
4499
		if (empty($this->info_bits)) {
4500
			$this->info_bits = 0;
4501
		}
4502
		if (empty($this->special_code)) {
4503
			$this->special_code = 0;
4504
		}
4505
		if (empty($this->fk_parent_line)) {
4506
			$this->fk_parent_line = 0;
4507
		}
4508
		if (empty($this->fk_fournprice)) {
4509
			$this->fk_fournprice = 0;
4510
		}
4511
		if (empty($this->subprice)) {
4512
			$this->subprice = 0;
4513
		}
4514
		if (empty($this->pa_ht)) {
4515
			$this->pa_ht = 0;
4516
		}
4517
4518
		// if buy price not defined, define buyprice as configured in margin admin
4519
		if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4520
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4521
				return $result;
4522
			} else {
4523
				$this->pa_ht = $result;
4524
			}
4525
		}
4526
4527
		$this->db->begin();
4528
4529
		// Mise a jour ligne en base
4530
		$sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4531
		$sql .= " description='".$this->db->escape($this->desc)."'";
4532
		$sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4533
		$sql .= ", product_type=".$this->product_type;
4534
		$sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4535
		$sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4536
		$sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4537
		$sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4538
		$sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4539
		$sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4540
		$sql .= ", qty='".price2num($this->qty)."'";
4541
		$sql .= ", subprice=".price2num($this->subprice);
4542
		$sql .= ", remise_percent=".price2num($this->remise_percent);
4543
		$sql .= ", price=".(float) price2num($this->price); // TODO A virer
4544
		$sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4545
		$sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4546
		if (empty($this->skip_update_total)) {
4547
			$sql .= ", total_ht=".price2num($this->total_ht);
4548
			$sql .= ", total_tva=".price2num($this->total_tva);
4549
			$sql .= ", total_ttc=".price2num($this->total_ttc);
4550
			$sql .= ", total_localtax1=".price2num($this->total_localtax1);
4551
			$sql .= ", total_localtax2=".price2num($this->total_localtax2);
4552
		}
4553
		$sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4554
		$sql .= ", buy_price_ht=".price2num($this->pa_ht);
4555
		if (strlen($this->special_code)) {
4556
			$sql .= ", special_code=".$this->special_code;
4557
		}
4558
		$sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4559
		if (!empty($this->rang)) {
4560
			$sql .= ", rang=".((int) $this->rang);
4561
		}
4562
		$sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4563
		$sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4564
		$sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4565
4566
		// Multicurrency
4567
		$sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4568
		$sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4569
		$sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4570
		$sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4571
4572
		$sql .= " WHERE rowid = ".((int) $this->id);
4573
4574
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4575
		$resql = $this->db->query($sql);
4576
		if ($resql) {
4577
			if (!$error) {
4578
				$result = $this->insertExtraFields();
4579
				if ($result < 0) {
4580
					$error++;
4581
				}
4582
			}
4583
4584
			if (!$error && !$notrigger) {
4585
				// Call trigger
4586
				$result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4587
				if ($result < 0) {
4588
					$this->db->rollback();
4589
					return -1;
4590
				}
4591
				// End call triggers
4592
			}
4593
4594
			$this->db->commit();
4595
			return 1;
4596
		} else {
4597
			$this->error = $this->db->error();
4598
			$this->db->rollback();
4599
			return -2;
4600
		}
4601
	}
4602
4603
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4604
	/**
4605
	 *	Update DB line fields total_xxx
4606
	 *	Used by migration
4607
	 *
4608
	 *	@return		int		<0 if KO, >0 if OK
4609
	 */
4610
	public function update_total()
4611
	{
4612
		// phpcs:enable
4613
		$this->db->begin();
4614
4615
		// Mise a jour ligne en base
4616
		$sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4617
		$sql .= " total_ht=".price2num($this->total_ht, 'MT');
4618
		$sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4619
		$sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4620
		$sql .= " WHERE rowid = ".((int) $this->rowid);
4621
4622
		dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4623
4624
		$resql = $this->db->query($sql);
4625
		if ($resql) {
4626
			$this->db->commit();
4627
			return 1;
4628
		} else {
4629
			$this->error = $this->db->error();
4630
			$this->db->rollback();
4631
			return -2;
4632
		}
4633
	}
4634
}
4635