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