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

Commande::updateline()   F

Complexity

Conditions 32
Paths > 20000

Size

Total Lines 202
Code Lines 135

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
eloc 135
nc 397569
nop 25
dl 0
loc 202
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 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