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

Commande::getTooltipContentArray()   B

Complexity

Conditions 11

Size

Total Lines 35
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 21
nop 1
dl 0
loc 35
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

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:

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