Passed
Branch develop (4336b1)
by
unknown
85:39
created

Commande::nb_expedition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 13
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 17
rs 9.8333
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