Completed
Branch develop (5d2154)
by
unknown
33:07
created

Commande::cancel()   C

Complexity

Conditions 12
Paths 37

Size

Total Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nc 37
nop 1
dl 0
loc 72
rs 6.1842
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* Copyright (C) 2003-2006 Rodolphe Quiedeville <[email protected]>
3
 * Copyright (C) 2004-2012 Laurent Destailleur  <[email protected]>
4
 * Copyright (C) 2005-2014 Regis Houssin        <[email protected]>
5
 * Copyright (C) 2006      Andre Cianfarani     <[email protected]>
6
 * Copyright (C) 2010-2016 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-2018 Ferran Marcet        <[email protected]>
14
 *
15
 * This program is free software; you can redistribute it and/or modify
16
 * it under the terms of the GNU General Public License as published by
17
 * the Free Software Foundation; either version 3 of the License, or
18
 * (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU General Public License
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27
 */
28
29
/**
30
 *  \file       htdocs/commande/class/commande.class.php
31
 *  \ingroup    commande
32
 *  \brief      Fichier des classes de commandes
33
 */
34
include_once DOL_DOCUMENT_ROOT .'/core/class/commonorder.class.php';
35
require_once DOL_DOCUMENT_ROOT .'/core/class/commonobjectline.class.php';
36
require_once DOL_DOCUMENT_ROOT .'/product/class/product.class.php';
37
require_once DOL_DOCUMENT_ROOT .'/margin/lib/margins.lib.php';
38
require_once DOL_DOCUMENT_ROOT .'/multicurrency/class/multicurrency.class.php';
39
40
/**
41
 *  Class to manage customers orders
42
 */
43
class Commande extends CommonOrder
44
{
45
	/**
46
	 * @var string ID to identify managed object
47
	 */
48
	public $element='commande';
49
50
	/**
51
	 * @var string Name of table without prefix where object is stored
52
	 */
53
	public $table_element='commande';
54
55
	/**
56
	 * @var int    Name of subtable line
57
	 */
58
	public $table_element_line = 'commandedet';
59
60
	public $class_element_line = 'OrderLine';
61
62
	/**
63
	 * @var int Field with ID of parent key if this field has a parent
64
	 */
65
	public $fk_element = 'fk_commande';
66
67
	/**
68
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
69
	 */
70
	public $picto = 'order';
71
72
	/**
73
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
74
	 * @var int
75
	 */
76
	public $ismultientitymanaged = 1;
77
78
	/**
79
	 * 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
80
	 * @var integer
81
	 */
82
	public $restrictiononfksoc = 1;
83
84
	/**
85
	 * {@inheritdoc}
86
	 */
87
	protected $table_ref_field = 'ref';
88
89
	/**
90
	 * Client ID
91
	 * @var int
92
	 */
93
	public $socid;
94
95
	public $ref_client;
96
	public $ref_int;
97
	public $contactid;
98
99
	/**
100
	 * Status of the order
101
	 * @var int
102
	 */
103
	public $statut;
104
105
	/**
106
	 * Billed
107
	 * @var int
108
	 */
109
	public $billed;		// billed or not
110
111
    /**
112
     * @var int Draft Status of the order
113
     */
114
    public $brouillon;
115
    public $cond_reglement_code;
116
117
	/**
118
     * @var int ID
119
     */
120
	public $fk_account;
121
122
	/**
123
	 * It holds the label of the payment mode. Use it in case translation cannot be found.
124
	 * @var string
125
	 */
126
	public $mode_reglement;
127
128
	/**
129
	 * Payment mode id
130
	 * @var int
131
	 */
132
	public $mode_reglement_id;
133
134
	/**
135
	 * Payment mode code
136
	 * @var string
137
	 */
138
	public $mode_reglement_code;
139
140
	/**
141
	 * Availability delivery time id
142
	 * @var int
143
	 */
144
	public $availability_id;
145
146
	/**
147
	 * Availability delivery time code
148
	 * @var string
149
	 */
150
	public $availability_code;
151
152
	/**
153
	 * Label of availability delivery time. Use it in case translation cannot be found.
154
	 * @var string
155
	 */
156
	public $availability;
157
158
	public $demand_reason_id;   // Source reason. Why we receive order (after a phone campaign, ...)
159
	public $demand_reason_code;
160
	public $date;				// Date commande
161
162
	/**
163
	 * @deprecated
164
	 * @see date
165
	 */
166
	public $date_commande;
167
168
	public $date_livraison;	    // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
169
170
	/**
171
     * @var int ID
172
     */
173
	public $fk_remise_except;
174
175
	public $remise_percent;
176
	public $remise_absolue;
177
	public $info_bits;
178
	public $rang;
179
	public $special_code;
180
	public $source;			    // Order mode. How we received order (by phone, by email, ...)
181
	public $extraparams=array();
182
183
	public $linked_objects=array();
184
185
	public $user_author_id;
186
	public $user_valid;
187
188
	/**
189
	 * @var OrderLine[]
190
	 */
191
	public $lines = array();
192
193
	// Multicurrency
194
	/**
195
     * @var int ID
196
     */
197
	public $fk_multicurrency;
198
199
	public $multicurrency_code;
200
	public $multicurrency_tx;
201
	public $multicurrency_total_ht;
202
	public $multicurrency_total_tva;
203
	public $multicurrency_total_ttc;
204
205
	public $oldcopy;
206
207
	//! key of module source when order generated from a dedicated module ('cashdesk', 'takepos', ...)
208
	public $module_source;
209
	//! key of pos source ('0', '1', ...)
210
	public $pos_source;
211
212
	/**
213
	 * ERR Not enough stock
214
	 */
215
	const STOCK_NOT_ENOUGH_FOR_ORDER = -3;
216
217
	/**
218
	 * Canceled status
219
	 */
220
	const STATUS_CANCELED = -1;
221
	/**
222
	 * Draft status
223
	 */
224
	const STATUS_DRAFT = 0;
225
	/**
226
	 * Validated status
227
	 */
228
	const STATUS_VALIDATED = 1;
229
	/**
230
	 * Shipment on process
231
	 */
232
	const STATUS_SHIPMENTONPROCESS = 2;
233
	const STATUS_ACCEPTED = 2;				// For backward compatibility. Use key STATUS_SHIPMENTONPROCESS instead.
234
235
	/**
236
	 * Closed (Sent, billed or not)
237
	 */
238
	const STATUS_CLOSED = 3;
239
240
241
	/**
242
	 *	Constructor
243
	 *
244
	 *  @param		DoliDB		$db      Database handler
245
	 */
246
	public function __construct($db)
247
	{
248
		$this->db = $db;
249
250
		$this->remise = 0;
251
		$this->remise_percent = 0;
252
253
		$this->products = array();
254
	}
255
256
	/**
257
	 *  Returns the reference to the following non used Order depending on the active numbering module
258
	 *  defined into COMMANDE_ADDON
259
	 *
260
	 *  @param	Societe		$soc  	Object thirdparty
261
	 *  @return string      		Order free reference
262
	 */
263
	public function getNextNumRef($soc)
264
	{
265
		global $langs, $conf;
266
		$langs->load("order");
267
268
		if (! empty($conf->global->COMMANDE_ADDON))
269
		{
270
			$mybool=false;
271
272
			$file = $conf->global->COMMANDE_ADDON.".php";
273
			$classname = $conf->global->COMMANDE_ADDON;
274
275
			// Include file with class
276
			$dirmodels=array_merge(array('/'), (array) $conf->modules_parts['models']);
277
			foreach ($dirmodels as $reldir)
278
			{
279
				$dir = dol_buildpath($reldir."core/modules/commande/");
280
281
				// Load file with numbering class (if found)
282
				$mybool|=@include_once $dir.$file;
283
			}
284
285
            if ($mybool === false)
286
            {
287
                dol_print_error('', "Failed to include file ".$file);
288
                return '';
289
            }
290
291
			$obj = new $classname();
292
			$numref = $obj->getNextValue($soc, $this);
293
294
			if ($numref != "")
295
			{
296
				return $numref;
297
			}
298
			else
299
			{
300
				$this->error=$obj->error;
301
				//dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
302
				return "";
303
			}
304
		}
305
		else
306
		{
307
			print $langs->trans("Error")." ".$langs->trans("Error_COMMANDE_ADDON_NotDefined");
308
			return "";
309
		}
310
	}
311
312
313
	/**
314
	 *	Validate order
315
	 *
316
	 *	@param		User	$user     		User making status change
317
	 *	@param		int		$idwarehouse	Id of warehouse to use for stock decrease
318
	 *  @param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
319
	 *	@return  	int						<=0 if OK, 0=Nothing done, >0 if KO
320
	 */
321
	public function valid($user, $idwarehouse = 0, $notrigger = 0)
322
	{
323
		global $conf,$langs;
324
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
325
326
		$error=0;
327
328
		// Protection
329
		if ($this->statut == self::STATUS_VALIDATED)
330
		{
331
			dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
332
			return 0;
333
		}
334
335
		if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->creer))
336
			|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->order_advance->validate))))
337
		{
338
			$this->error='NotEnoughPermissions';
339
			dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
340
			return -1;
341
		}
342
343
		$now=dol_now();
344
345
		$this->db->begin();
346
347
		// Definition du nom de module de numerotation de commande
348
		$soc = new Societe($this->db);
349
		$soc->fetch($this->socid);
350
351
		// Class of company linked to order
352
		$result=$soc->set_as_client();
353
354
		// Define new ref
355
		if (! $error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
356
		{
357
			$num = $this->getNextNumRef($soc);
358
		}
359
		else
360
		{
361
			$num = $this->ref;
362
		}
363
		$this->newref = $num;
364
365
		// Validate
366
		$sql = "UPDATE ".MAIN_DB_PREFIX."commande";
367
		$sql.= " SET ref = '".$num."',";
368
		$sql.= " fk_statut = ".self::STATUS_VALIDATED.",";
369
		$sql.= " date_valid='".$this->db->idate($now)."',";
370
		$sql.= " fk_user_valid = ".$user->id;
371
		$sql.= " WHERE rowid = ".$this->id;
372
373
		dol_syslog(get_class($this)."::valid()", LOG_DEBUG);
374
		$resql=$this->db->query($sql);
375
		if (! $resql)
376
		{
377
			dol_print_error($this->db);
378
			$this->error=$this->db->lasterror();
379
			$error++;
380
		}
381
382
		if (! $error)
383
		{
384
			// If stock is incremented on validate order, we must increment it
385
			if ($result >= 0 && ! empty($conf->stock->enabled) && $conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER == 1)
386
			{
387
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
388
				$langs->load("agenda");
389
390
				// Loop on each line
391
				$cpt=count($this->lines);
392
				for ($i = 0; $i < $cpt; $i++)
393
				{
394
					if ($this->lines[$i]->fk_product > 0)
395
					{
396
						$mouvP = new MouvementStock($this->db);
397
						$mouvP->origin = &$this;
398
						// We decrement stock of product (and sub-products)
399
						$result=$mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
400
						if ($result < 0)
401
						{
402
							$error++;
403
							$this->error=$mouvP->error;
404
						}
405
					}
406
					if ($error) break;
407
				}
408
			}
409
		}
410
411
		if (! $error && ! $notrigger)
412
		{
413
			// Call trigger
414
			$result=$this->call_trigger('ORDER_VALIDATE', $user);
415
			if ($result < 0) $error++;
416
			// End call triggers
417
		}
418
419
		if (! $error)
420
		{
421
			$this->oldref = $this->ref;
422
423
			// Rename directory if dir was a temporary ref
424
			if (preg_match('/^[\(]?PROV/i', $this->ref))
425
			{
426
				// On renomme repertoire ($this->ref = ancienne ref, $num = nouvelle ref)
427
				// in order not to lose the attachments
428
				$oldref = dol_sanitizeFileName($this->ref);
429
				$newref = dol_sanitizeFileName($num);
430
				$dirsource = $conf->commande->dir_output.'/'.$oldref;
431
				$dirdest = $conf->commande->dir_output.'/'.$newref;
432
				if (file_exists($dirsource))
433
				{
434
					dol_syslog(get_class($this)."::valid() rename dir ".$dirsource." into ".$dirdest);
435
436
					if (@rename($dirsource, $dirdest))
437
					{
438
						dol_syslog("Rename ok");
439
						// Rename docs starting with $oldref with $newref
440
						$listoffiles=dol_dir_list($conf->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
441
						foreach($listoffiles as $fileentry)
442
						{
443
							$dirsource=$fileentry['name'];
444
							$dirdest=preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
445
							$dirsource=$fileentry['path'].'/'.$dirsource;
446
							$dirdest=$fileentry['path'].'/'.$dirdest;
447
							@rename($dirsource, $dirdest);
448
						}
449
					}
450
				}
451
			}
452
		}
453
454
		// Set new ref and current status
455
		if (! $error)
456
		{
457
			$this->ref = $num;
458
			$this->statut = self::STATUS_VALIDATED;
459
            $this->brouillon = 0;
460
		}
461
462
		if (! $error)
463
		{
464
			$this->db->commit();
465
			return 1;
466
		}
467
		else
468
		{
469
			$this->db->rollback();
470
			return -1;
471
		}
472
	}
473
474
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
475
	/**
476
	 *	Set draft status
477
	 *
478
	 *	@param	User	$user			Object user that modify
479
	 *	@param	int		$idwarehouse	Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
480
	 *	@return	int						<0 if KO, >0 if OK
481
	 */
482
    public function setDraft($user, $idwarehouse = -1)
483
    {
484
        //phpcs:enable
485
		global $conf,$langs;
486
487
		$error=0;
488
489
		// Protection
490
		if ($this->statut <= self::STATUS_DRAFT)
491
		{
492
			return 0;
493
		}
494
495
		if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->creer))
496
			|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->order_advance->validate))))
497
		{
498
			$this->error='Permission denied';
499
			return -1;
500
		}
501
502
		$this->db->begin();
503
504
		$sql = "UPDATE ".MAIN_DB_PREFIX."commande";
505
		$sql.= " SET fk_statut = ".self::STATUS_DRAFT;
506
		$sql.= " WHERE rowid = ".$this->id;
507
508
		dol_syslog(get_class($this)."::set_draft", LOG_DEBUG);
509
		if ($this->db->query($sql))
510
		{
511
			// If stock is decremented on validate order, we must reincrement it
512
			if (! empty($conf->stock->enabled) && $conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER == 1)
513
			{
514
				$result = 0;
515
516
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
517
				$langs->load("agenda");
518
519
				$num=count($this->lines);
520
				for ($i = 0; $i < $num; $i++)
521
				{
522
					if ($this->lines[$i]->fk_product > 0)
523
					{
524
						$mouvP = new MouvementStock($this->db);
525
						$mouvP->origin = &$this;
526
						// We increment stock of product (and sub-products)
527
						$result=$mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
528
						if ($result < 0) { $error++; $this->error=$mouvP->error; break; }
529
					}
530
				}
531
			}
532
533
			if (!$error) {
534
				// Call trigger
535
				$result=$this->call_trigger('ORDER_UNVALIDATE', $user);
536
				if ($result < 0) $error++;
537
			}
538
539
			if (!$error) {
540
				$this->statut=self::STATUS_DRAFT;
541
				$this->db->commit();
542
				return 1;
543
			}else {
544
				$this->db->rollback();
545
				return -1;
546
			}
547
		}
548
		else
549
		{
550
			$this->error=$this->db->error();
551
			$this->db->rollback();
552
			return -1;
553
		}
554
    }
555
556
557
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
558
	/**
559
	 *	Tag the order as validated (opened)
560
	 *	Function used when order is reopend after being closed.
561
	 *
562
	 *	@param      User	$user       Object user that change status
563
	 *	@return     int         		<0 if KO, 0 if nothing is done, >0 if OK
564
	 */
565
	public function set_reopen($user)
566
	{
567
        // phpcs:enable
568
		$error=0;
569
570
		if ($this->statut != self::STATUS_CANCELED && $this->statut != self::STATUS_CLOSED)
571
		{
572
			dol_syslog(get_class($this)."::set_reopen order has not status closed", LOG_WARNING);
573
			return 0;
574
		}
575
576
		$this->db->begin();
577
578
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
579
		$sql.= ' SET fk_statut='.self::STATUS_VALIDATED.', facture=0';
580
		$sql.= ' WHERE rowid = '.$this->id;
581
582
		dol_syslog(get_class($this)."::set_reopen", LOG_DEBUG);
583
		$resql = $this->db->query($sql);
584
		if ($resql)
585
		{
586
			// Call trigger
587
			$result=$this->call_trigger('ORDER_REOPEN', $user);
588
			if ($result < 0) $error++;
589
			// End call triggers
590
		}
591
		else
592
		{
593
			$error++;
594
			$this->error=$this->db->lasterror();
595
			dol_print_error($this->db);
596
		}
597
598
		if (! $error)
599
		{
600
			$this->statut = self::STATUS_VALIDATED;
601
			$this->billed = 0;
602
603
			$this->db->commit();
604
			return 1;
605
		}
606
		else
607
		{
608
			foreach($this->errors as $errmsg)
609
			{
610
				dol_syslog(get_class($this)."::set_reopen ".$errmsg, LOG_ERR);
611
				$this->error.=($this->error?', '.$errmsg:$errmsg);
612
			}
613
			$this->db->rollback();
614
			return -1*$error;
615
		}
616
	}
617
618
	/**
619
	 *  Close order
620
	 *
621
	 * 	@param      User	$user       Objet user that close
622
	 *  @param		int		$notrigger	1=Does not execute triggers, 0=Execute triggers
623
	 *	@return		int					<0 if KO, >0 if OK
624
	 */
625
	public function cloture($user, $notrigger = 0)
626
	{
627
		global $conf;
628
629
		$error=0;
630
631
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->creer))
632
			|| (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->commande->order_advance->validate)))
633
		{
634
			$this->db->begin();
635
636
			$now=dol_now();
637
638
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
639
			$sql.= ' SET fk_statut = '.self::STATUS_CLOSED.',';
640
			$sql.= ' fk_user_cloture = '.$user->id.',';
641
			$sql.= " date_cloture = '".$this->db->idate($now)."'";
642
			$sql.= ' WHERE rowid = '.$this->id.' AND fk_statut > '.self::STATUS_DRAFT;
643
644
			if ($this->db->query($sql))
645
			{
646
				if (! $notrigger)
647
				{
648
					// Call trigger
649
					$result=$this->call_trigger('ORDER_CLOSE', $user);
650
					if ($result < 0) $error++;
651
					// End call triggers
652
				}
653
654
				if (! $error)
655
				{
656
					$this->statut=self::STATUS_CLOSED;
657
658
					$this->db->commit();
659
					return 1;
660
				}
661
				else
662
				{
663
					$this->db->rollback();
664
					return -1;
665
				}
666
			}
667
			else
668
			{
669
				$this->error=$this->db->lasterror();
670
671
				$this->db->rollback();
672
				return -1;
673
			}
674
		}
675
		return 0;
676
	}
677
678
	/**
679
	 * 	Cancel an order
680
	 * 	If stock is decremented on order validation, we must reincrement it
681
	 *
682
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
683
	 *	@return	int						<0 if KO, >0 if OK
684
	 */
685
	public function cancel($idwarehouse = -1)
686
	{
687
		global $conf,$user,$langs;
688
689
		$error=0;
690
691
		$this->db->begin();
692
693
		$sql = "UPDATE ".MAIN_DB_PREFIX."commande";
694
		$sql.= " SET fk_statut = ".self::STATUS_CANCELED;
695
		$sql.= " WHERE rowid = ".$this->id;
696
		$sql.= " AND fk_statut = ".self::STATUS_VALIDATED;
697
698
		dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
699
		if ($this->db->query($sql))
700
		{
701
			// If stock is decremented on validate order, we must reincrement it
702
			if (! empty($conf->stock->enabled) && $conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER == 1)
703
			{
704
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
705
				$langs->load("agenda");
706
707
				$num=count($this->lines);
708
				for ($i = 0; $i < $num; $i++)
709
				{
710
					if ($this->lines[$i]->fk_product > 0)
711
					{
712
						$mouvP = new MouvementStock($this->db);
713
						// We increment stock of product (and sub-products)
714
						$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
715
						if ($result < 0)
716
						{
717
							$error++;
718
							$this->error=$mouvP->error;
719
							break;
720
						}
721
					}
722
				}
723
			}
724
725
			if (! $error)
726
			{
727
				// Call trigger
728
				$result=$this->call_trigger('ORDER_CANCEL', $user);
729
				if ($result < 0) $error++;
730
				// End call triggers
731
			}
732
733
			if (! $error)
734
			{
735
				$this->statut=self::STATUS_CANCELED;
736
				$this->db->commit();
737
				return 1;
738
			}
739
			else
740
			{
741
				foreach($this->errors as $errmsg)
742
				{
743
					dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
744
					$this->error.=($this->error?', '.$errmsg:$errmsg);
745
				}
746
				$this->db->rollback();
747
				return -1*$error;
748
			}
749
		}
750
		else
751
		{
752
			$this->error=$this->db->error();
753
			$this->db->rollback();
754
			return -1;
755
		}
756
	}
757
758
	/**
759
	 *	Create order
760
	 *	Note that this->ref can be set or empty. If empty, we will use "(PROV)"
761
	 *
762
	 *	@param		User	$user 		Objet user that make creation
763
	 *	@param		int	    $notrigger	Disable all triggers
764
	 *	@return 	int			        <0 if KO, >0 if OK
765
	 */
766
	public function create($user, $notrigger = 0)
767
	{
768
		global $conf,$langs;
769
		$error=0;
770
771
		// Clean parameters
772
		$this->brouillon = 1;		// set command as draft
773
774
		// $date_commande is deprecated
775
		$date = ($this->date_commande ? $this->date_commande : $this->date);
776
777
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
778
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency,$this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
779
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
780
		if (empty($this->fk_multicurrency))
781
		{
782
			$this->multicurrency_code = $conf->currency;
783
			$this->fk_multicurrency = 0;
784
			$this->multicurrency_tx = 1;
785
		}
786
787
		dol_syslog(get_class($this)."::create user=".$user->id);
788
789
		// Check parameters
790
		if (! empty($this->ref))	// We check that ref is not already used
791
		{
792
			$result=self::isExistingObject($this->element, 0, $this->ref);	// Check ref is not yet used
793
			if ($result > 0)
794
			{
795
				$this->error='ErrorRefAlreadyExists';
796
				dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
797
				$this->db->rollback();
798
				return -1;
799
			}
800
		}
801
802
		$soc = new Societe($this->db);
803
		$result=$soc->fetch($this->socid);
804
		if ($result < 0)
805
		{
806
			$this->error="Failed to fetch company";
807
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
808
			return -2;
809
		}
810
		if (! empty($conf->global->COMMANDE_REQUIRE_SOURCE) && $this->source < 0)
811
		{
812
			$this->error=$langs->trans("ErrorFieldRequired", $langs->trans("Source"));
813
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
814
			return -1;
815
		}
816
817
		$now=dol_now();
818
819
		$this->db->begin();
820
821
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."commande (";
822
		$sql.= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client, ref_int";
823
		$sql.= ", model_pdf, fk_cond_reglement, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
824
		$sql.= ", fk_shipping_method";
825
		$sql.= ", fk_warehouse";
826
		$sql.= ", remise_absolue, remise_percent";
827
		$sql.= ", fk_incoterms, location_incoterms";
828
		$sql.= ", entity, module_source, pos_source";
829
		$sql.= ", fk_multicurrency";
830
		$sql.= ", multicurrency_code";
831
		$sql.= ", multicurrency_tx";
832
		$sql.= ")";
833
		$sql.= " VALUES ('(PROV)', ".$this->socid.", '".$this->db->idate($now)."', ".$user->id;
834
		$sql.= ", ".($this->fk_project>0?$this->fk_project:"null");
835
		$sql.= ", '".$this->db->idate($date)."'";
836
		$sql.= ", ".($this->source>=0 && $this->source != '' ?$this->db->escape($this->source):'null');
837
		$sql.= ", '".$this->db->escape($this->note_private)."'";
838
		$sql.= ", '".$this->db->escape($this->note_public)."'";
839
		$sql.= ", ".($this->ref_ext?"'".$this->db->escape($this->ref_ext)."'":"null");
840
		$sql.= ", ".($this->ref_client?"'".$this->db->escape($this->ref_client)."'":"null");
841
		$sql.= ", ".($this->ref_int?"'".$this->db->escape($this->ref_int)."'":"null");
842
		$sql.= ", '".$this->db->escape($this->modelpdf)."'";
843
		$sql.= ", ".($this->cond_reglement_id>0?$this->cond_reglement_id:"null");
844
		$sql.= ", ".($this->mode_reglement_id>0?$this->mode_reglement_id:"null");
845
		$sql.= ", ".($this->fk_account>0?$this->fk_account:'NULL');
846
		$sql.= ", ".($this->availability_id>0?$this->availability_id:"null");
847
		$sql.= ", ".($this->demand_reason_id>0?$this->demand_reason_id:"null");
848
		$sql.= ", ".($this->date_livraison?"'".$this->db->idate($this->date_livraison)."'":"null");
849
		$sql.= ", ".($this->fk_delivery_address>0?$this->fk_delivery_address:'NULL');
850
		$sql.= ", ".($this->shipping_method_id>0?$this->shipping_method_id:'NULL');
851
		$sql.= ", ".($this->warehouse_id>0?$this->warehouse_id:'NULL');
852
		$sql.= ", ".($this->remise_absolue>0?$this->db->escape($this->remise_absolue):'NULL');
853
		$sql.= ", ".($this->remise_percent>0?$this->db->escape($this->remise_percent):0);
854
		$sql.= ", ".(int) $this->fk_incoterms;
855
		$sql.= ", '".$this->db->escape($this->location_incoterms)."'";
856
		$sql.= ", ".$conf->entity;
857
        $sql.= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
858
		$sql.= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
859
		$sql.= ", ".(int) $this->fk_multicurrency;
860
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
861
		$sql.= ", ".(double) $this->multicurrency_tx;
862
		$sql.= ")";
863
864
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
865
		$resql=$this->db->query($sql);
866
		if ($resql)
867
		{
868
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'commande');
869
870
			if ($this->id)
871
			{
872
				$fk_parent_line=0;
873
				$num=count($this->lines);
874
875
				/*
876
				 *  Insert products details into db
877
				 */
878
				for ($i=0;$i<$num;$i++)
879
				{
880
					$line = $this->lines[$i];
881
882
					// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
883
					//if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
884
					if (! is_object($line)) $line = (object) $line;
885
886
					// Reset fk_parent_line for no child products and special product
887
					if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
888
						$fk_parent_line = 0;
889
					}
890
891
					// Complete vat rate with code
892
					$vatrate = $line->tva_tx;
893
					if ($line->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$line->vat_src_code.')';
894
895
                    $result = $this->addline(
896
						$line->desc,
897
						$line->subprice,
898
						$line->qty,
899
						$vatrate,
900
						$line->localtax1_tx,
901
						$line->localtax2_tx,
902
						$line->fk_product,
903
						$line->remise_percent,
904
						$line->info_bits,
905
						$line->fk_remise_except,
906
						'HT',
907
						0,
908
						$line->date_start,
909
						$line->date_end,
910
						$line->product_type,
911
						$line->rang,
912
						$line->special_code,
913
						$fk_parent_line,
914
						$line->fk_fournprice,
915
						$line->pa_ht,
916
						$line->label,
917
						$line->array_options,
918
						$line->fk_unit,
919
						$this->element,
920
						$line->id
921
					);
922
					if ($result < 0)
923
					{
924
						if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER)
925
						{
926
							$this->error=$this->db->lasterror();
927
							dol_print_error($this->db);
928
						}
929
						$this->db->rollback();
930
						return -1;
931
					}
932
					// Defined the new fk_parent_line
933
					if ($result > 0 && $line->product_type == 9) {
934
						$fk_parent_line = $result;
935
					}
936
				}
937
938
				// update ref
939
				$initialref='(PROV'.$this->id.')';
940
				if (! empty($this->ref)) $initialref=$this->ref;
941
942
				$sql = 'UPDATE '.MAIN_DB_PREFIX."commande SET ref='".$this->db->escape($initialref)."' WHERE rowid=".$this->id;
943
				if ($this->db->query($sql))
944
				{
945
					if ($this->id)
946
					{
947
						$this->ref = $initialref;
948
949
						if (! empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
950
						{
951
							$this->linked_objects = $this->linkedObjectsIds;	// TODO Replace linked_objects with linkedObjectsIds
952
						}
953
954
						// Add object linked
955
						if (! $error && $this->id && is_array($this->linked_objects) && ! empty($this->linked_objects))
956
						{
957
							foreach($this->linked_objects as $origin => $tmp_origin_id)
958
							{
959
								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, ...))
960
								{
961
									foreach($tmp_origin_id as $origin_id)
962
									{
963
										$ret = $this->add_object_linked($origin, $origin_id);
964
										if (! $ret)
965
										{
966
											$this->error=$this->db->lasterror();
967
											$error++;
968
										}
969
									}
970
								}
971
								else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
972
								{
973
									$origin_id = $tmp_origin_id;
974
									$ret = $this->add_object_linked($origin, $origin_id);
975
									if (! $ret)
976
									{
977
										$this->error=$this->db->lasterror();
978
										$error++;
979
									}
980
								}
981
							}
982
						}
983
984
						if (! $error && $this->id && ! empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && ! empty($this->origin) && ! empty($this->origin_id))   // Get contact from origin object
985
						{
986
							$originforcontact = $this->origin;
987
							$originidforcontact = $this->origin_id;
988
							if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
989
							{
990
								require_once DOL_DOCUMENT_ROOT . '/expedition/class/expedition.class.php';
991
								$exp = new Expedition($this->db);
992
								$exp->fetch($this->origin_id);
993
								$exp->fetchObjectLinked();
994
								if (count($exp->linkedObjectsIds['commande']) > 0)
995
								{
996
									foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
997
									{
998
										$originforcontact = 'commande';
999
										if (is_object($value)) $originidforcontact = $value->id;
1000
										else $originidforcontact = $value;
1001
										break; // We take first one
1002
									}
1003
								}
1004
							}
1005
1006
							$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";
1007
							$sqlcontact.= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
1008
1009
							$resqlcontact = $this->db->query($sqlcontact);
1010
							if ($resqlcontact)
1011
							{
1012
								while($objcontact = $this->db->fetch_object($resqlcontact))
1013
								{
1014
									//print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1015
									$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
1016
								}
1017
							}
1018
							else dol_print_error($resqlcontact);
1019
						}
1020
					}
1021
1022
					if (! $error)
1023
					{
1024
						$result=$this->insertExtraFields();
1025
						if ($result < 0) $error++;
1026
					}
1027
1028
					if (! $error && ! $notrigger)
1029
					{
1030
						// Call trigger
1031
						$result=$this->call_trigger('ORDER_CREATE', $user);
1032
						if ($result < 0) $error++;
1033
						// End call triggers
1034
					}
1035
1036
					if (! $error)
1037
					{
1038
						$this->db->commit();
1039
						return $this->id;
1040
					}
1041
					else
1042
					{
1043
						$this->db->rollback();
1044
						return -1*$error;
1045
					}
1046
				}
1047
				else
1048
				{
1049
					$this->error=$this->db->lasterror();
1050
					$this->db->rollback();
1051
					return -1;
1052
				}
1053
			}
1054
		}
1055
		else
1056
		{
1057
			dol_print_error($this->db);
1058
			$this->db->rollback();
1059
			return -1;
1060
		}
1061
	}
1062
1063
1064
	/**
1065
	 *	Load an object from its id and create a new one in database
1066
	 *
1067
	 *	@param		int			$socid			Id of thirdparty
1068
	 *	@return		int							New id of clone
1069
	 */
1070
	public function createFromClone($socid = 0)
1071
	{
1072
		global $user,$hookmanager;
1073
1074
		$error=0;
1075
1076
		$this->db->begin();
1077
1078
		// get lines so they will be clone
1079
		foreach($this->lines as $line)
1080
			$line->fetch_optionals();
1081
1082
			// Load source object
1083
			$objFrom = clone $this;
1084
1085
			// Change socid if needed
1086
			if (! empty($socid) && $socid != $this->socid)
1087
			{
1088
				$objsoc = new Societe($this->db);
1089
1090
				if ($objsoc->fetch($socid)>0)
1091
				{
1092
					$this->socid 				= $objsoc->id;
1093
					$this->cond_reglement_id	= (! empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1094
					$this->mode_reglement_id	= (! empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1095
					$this->fk_project			= 0;
1096
					$this->fk_delivery_address	= 0;
1097
				}
1098
1099
				// TODO Change product price if multi-prices
1100
			}
1101
1102
			$this->id=0;
1103
			$this->ref = '';
1104
			$this->statut=self::STATUS_DRAFT;
1105
1106
			// Clear fields
1107
			$this->user_author_id     = $user->id;
1108
			$this->user_valid         = '';
1109
			$this->date				  = dol_now();
1110
			$this->date_commande	  = dol_now();
1111
			$this->date_creation      = '';
1112
			$this->date_validation    = '';
1113
			$this->ref_client         = '';
1114
1115
			// Create clone
1116
			$this->context['createfromclone'] = 'createfromclone';
1117
			$result=$this->create($user);
1118
			if ($result < 0) $error++;
1119
1120
			if (! $error)
1121
			{
1122
				// Hook of thirdparty module
1123
				if (is_object($hookmanager))
1124
				{
1125
					$parameters=array('objFrom'=>$objFrom);
1126
					$action='';
1127
					$reshook=$hookmanager->executeHooks('createFrom', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
1128
					if ($reshook < 0) $error++;
1129
				}
1130
			}
1131
1132
			unset($this->context['createfromclone']);
1133
1134
			// End
1135
			if (! $error)
1136
			{
1137
				$this->db->commit();
1138
				return $this->id;
1139
			}
1140
			else
1141
			{
1142
				$this->db->rollback();
1143
				return -1;
1144
			}
1145
	}
1146
1147
1148
	/**
1149
	 *  Load an object from a proposal and create a new order into database
1150
	 *
1151
	 *  @param      Object			$object 	        Object source
1152
	 *  @param		User			$user				User making creation
1153
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1154
	 */
1155
	public function createFromProposal($object, User $user)
1156
	{
1157
		global $conf, $hookmanager;
1158
1159
		dol_include_once('/core/class/extrafields.class.php');
1160
1161
		$error=0;
1162
1163
1164
		$this->date_commande = dol_now();
1165
		$this->source = 0;
1166
1167
		$num=count($object->lines);
1168
		for ($i = 0; $i < $num; $i++)
1169
		{
1170
			$line = new OrderLine($this->db);
1171
1172
			$line->libelle           = $object->lines[$i]->libelle;
1173
			$line->label             = $object->lines[$i]->label;
1174
			$line->desc              = $object->lines[$i]->desc;
1175
			$line->price             = $object->lines[$i]->price;
1176
			$line->subprice          = $object->lines[$i]->subprice;
1177
			$line->vat_src_code      = $object->lines[$i]->vat_src_code;
1178
			$line->tva_tx            = $object->lines[$i]->tva_tx;
1179
			$line->localtax1_tx      = $object->lines[$i]->localtax1_tx;
1180
			$line->localtax2_tx      = $object->lines[$i]->localtax2_tx;
1181
			$line->qty               = $object->lines[$i]->qty;
1182
			$line->fk_remise_except  = $object->lines[$i]->fk_remise_except;
1183
			$line->remise_percent    = $object->lines[$i]->remise_percent;
1184
			$line->fk_product        = $object->lines[$i]->fk_product;
1185
			$line->info_bits         = $object->lines[$i]->info_bits;
1186
			$line->product_type      = $object->lines[$i]->product_type;
1187
			$line->rang              = $object->lines[$i]->rang;
1188
			$line->special_code      = $object->lines[$i]->special_code;
1189
			$line->fk_parent_line    = $object->lines[$i]->fk_parent_line;
1190
			$line->fk_unit			 = $object->lines[$i]->fk_unit;
1191
1192
			$line->date_start      	= $object->lines[$i]->date_start;
1193
			$line->date_end    		= $object->lines[$i]->date_end;
1194
1195
			$line->fk_fournprice	= $object->lines[$i]->fk_fournprice;
1196
			$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);
1197
			$line->pa_ht			= $marginInfos[0];
1198
			$line->marge_tx			= $marginInfos[1];
1199
			$line->marque_tx		= $marginInfos[2];
1200
1201
			// get extrafields from original line
1202
			$object->lines[$i]->fetch_optionals();
1203
			foreach($object->lines[$i]->array_options as $options_key => $value)
1204
				$line->array_options[$options_key] = $value;
1205
1206
				$this->lines[$i] = $line;
1207
		}
1208
1209
		$this->socid                = $object->socid;
1210
		$this->fk_project           = $object->fk_project;
1211
		$this->cond_reglement_id    = $object->cond_reglement_id;
1212
		$this->mode_reglement_id    = $object->mode_reglement_id;
1213
		$this->fk_account           = $object->fk_account;
1214
		$this->availability_id      = $object->availability_id;
1215
		$this->demand_reason_id     = $object->demand_reason_id;
1216
		$this->date_livraison       = $object->date_livraison;
1217
		$this->shipping_method_id   = $object->shipping_method_id;
1218
		$this->warehouse_id         = $object->warehouse_id;
1219
		$this->fk_delivery_address  = $object->fk_delivery_address;
1220
		$this->contact_id           = $object->contactid;
1221
		$this->ref_client           = $object->ref_client;
1222
1223
		if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN))
1224
		{
1225
            $this->note_private         = $object->note_private;
1226
            $this->note_public          = $object->note_public;
1227
		}
1228
1229
		$this->origin				= $object->element;
1230
		$this->origin_id			= $object->id;
1231
1232
		// get extrafields from original line
1233
		$object->fetch_optionals($object->id);
1234
1235
		$e = new ExtraFields($this->db);
1236
		$element_extrafields = $e->fetch_name_optionals_label($this->element);
1237
1238
		foreach($object->array_options as $options_key => $value) {
1239
			if(array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)){
1240
				$this->array_options[$options_key] = $value;
1241
			}
1242
		}
1243
		// Possibility to add external linked objects with hooks
1244
		$this->linked_objects[$this->origin] = $this->origin_id;
1245
		if (is_array($object->other_linked_objects) && ! empty($object->other_linked_objects))
1246
		{
1247
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1248
		}
1249
1250
		$ret = $this->create($user);
1251
1252
		if ($ret > 0)
1253
		{
1254
			// Actions hooked (by external module)
1255
			$hookmanager->initHooks(array('orderdao'));
1256
1257
			$parameters=array('objFrom'=>$object);
1258
			$action='';
1259
			$reshook=$hookmanager->executeHooks('createFrom', $parameters, $this, $action);    // Note that $action and $object may have been modified by some hooks
1260
			if ($reshook < 0) $error++;
1261
1262
			if (! $error)
1263
			{
1264
				// Ne pas passer par la commande provisoire
1265
				if ($conf->global->COMMANDE_VALID_AFTER_CLOSE_PROPAL == 1)
1266
				{
1267
					$this->fetch($ret);
1268
					$this->valid($user);
1269
				}
1270
				return $ret;
1271
			}
1272
			else return -1;
1273
		}
1274
		else return -1;
1275
	}
1276
1277
1278
	/**
1279
	 *	Add an order line into database (linked to product/service or not)
1280
	 *
1281
	 *	@param      string			$desc            	Description of line
1282
	 *	@param      float			$pu_ht    	        Unit price (without tax)
1283
	 *	@param      float			$qty             	Quantite
1284
	 * 	@param    	float			$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
1285
	 * 	@param		float			$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
1286
	 * 	@param		float			$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
1287
	 *	@param      int				$fk_product      	Id of product
1288
	 *	@param      float			$remise_percent  	Percentage discount of the line
1289
	 *	@param      int				$info_bits			Bits de type de lignes
1290
	 *	@param      int				$fk_remise_except	Id remise
1291
	 *	@param      string			$price_base_type	HT or TTC
1292
	 *	@param      float			$pu_ttc    		    Prix unitaire TTC
1293
	 *	@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)
1294
	 *	@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)
1295
	 *	@param      int				$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
1296
	 *	@param      int				$rang             	Position of line
1297
	 *	@param		int				$special_code		Special code (also used by externals modules!)
1298
	 *	@param		int				$fk_parent_line		Parent line
1299
	 *  @param		int				$fk_fournprice		Id supplier price
1300
	 *  @param		int				$pa_ht				Buying price (without tax)
1301
	 *  @param		string			$label				Label
1302
	 *  @param		array			$array_options		extrafields array. Example array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
1303
	 * 	@param 		string			$fk_unit 			Code of the unit to use. Null to use the default one
1304
	 * 	@param		string		    $origin				'order', ...
1305
	 *  @param		int			    $origin_id			Id of origin object
1306
	 * 	@param		double			$pu_ht_devise		Unit price in currency
1307
	 *	@return     int             					>0 if OK, <0 if KO
1308
	 *
1309
	 *	@see        add_product
1310
	 *
1311
	 *	Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
1312
	 *	de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
1313
	 *	par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
1314
	 *	et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
1315
	 */
1316
	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)
1317
	{
1318
		global $mysoc, $conf, $langs, $user;
1319
1320
		$logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1321
		$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";
1322
		$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";
1323
		dol_syslog(get_class($this).$logtext, LOG_DEBUG);
1324
1325
		if ($this->statut == self::STATUS_DRAFT)
1326
		{
1327
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1328
1329
			// Clean parameters
1330
			if (empty($remise_percent)) $remise_percent=0;
1331
			if (empty($qty)) $qty=0;
1332
			if (empty($info_bits)) $info_bits=0;
1333
			if (empty($rang)) $rang=0;
1334
			if (empty($txtva)) $txtva=0;
1335
			if (empty($txlocaltax1)) $txlocaltax1=0;
1336
			if (empty($txlocaltax2)) $txlocaltax2=0;
1337
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line=0;
1338
			if (empty($this->fk_multicurrency)) $this->fk_multicurrency=0;
1339
1340
			$remise_percent=price2num($remise_percent);
1341
			$qty=price2num($qty);
1342
			$pu_ht=price2num($pu_ht);
1343
			$pu_ht_devise=price2num($pu_ht_devise);
1344
			$pu_ttc=price2num($pu_ttc);
1345
			$pa_ht=price2num($pa_ht);
1346
			if (!preg_match('/\((.*)\)/', $txtva)) {
1347
				$txtva = price2num($txtva);               // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1348
			}
1349
			$txlocaltax1 = price2num($txlocaltax1);
1350
			$txlocaltax2 = price2num($txlocaltax2);
1351
			if ($price_base_type=='HT')
1352
			{
1353
				$pu=$pu_ht;
1354
			}
1355
			else
1356
			{
1357
				$pu=$pu_ttc;
1358
			}
1359
			$label=trim($label);
1360
			$desc=trim($desc);
1361
1362
			// Check parameters
1363
			if ($type < 0) return -1;
1364
1365
            $this->db->begin();
1366
1367
			$product_type=$type;
1368
			if (!empty($fk_product))
1369
			{
1370
				$product=new Product($this->db);
1371
				$result=$product->fetch($fk_product);
1372
				$product_type=$product->type;
1373
1374
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_ORDER) && $product_type == 0 && $product->stock_reel < $qty)
1375
				{
1376
					$langs->load("errors");
1377
					$this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1378
					dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
1379
					$this->db->rollback();
1380
					return self::STOCK_NOT_ENOUGH_FOR_ORDER;
1381
				}
1382
			}
1383
			// Calcul du total TTC et de la TVA pour la ligne a partir de
1384
			// qty, pu, remise_percent et txtva
1385
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1386
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1387
1388
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1389
1390
			// Clean vat code
1391
			$vat_src_code='';
1392
			if (preg_match('/\((.*)\)/', $txtva, $reg))
1393
			{
1394
				$vat_src_code = $reg[1];
1395
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
1396
			}
1397
1398
			$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);
1399
1400
			/*var_dump($txlocaltax1);
1401
			 var_dump($txlocaltax2);
1402
			 var_dump($localtaxes_type);
1403
			 var_dump($tabprice);
1404
			 var_dump($tabprice[9]);
1405
			 var_dump($tabprice[10]);
1406
			 exit;*/
1407
1408
			$total_ht  = $tabprice[0];
1409
			$total_tva = $tabprice[1];
1410
			$total_ttc = $tabprice[2];
1411
			$total_localtax1 = $tabprice[9];
1412
			$total_localtax2 = $tabprice[10];
1413
			$pu_ht = $tabprice[3];
1414
1415
			// MultiCurrency
1416
			$multicurrency_total_ht  = $tabprice[16];
1417
			$multicurrency_total_tva = $tabprice[17];
1418
			$multicurrency_total_ttc = $tabprice[18];
1419
			$pu_ht_devise = $tabprice[19];
1420
1421
			// Rang to use
1422
			$rangtouse = $rang;
1423
			if ($rangtouse == -1)
1424
			{
1425
				$rangmax = $this->line_max($fk_parent_line);
1426
				$rangtouse = $rangmax + 1;
1427
			}
1428
1429
			// TODO A virer
1430
			// Anciens indicateurs: $price, $remise (a ne plus utiliser)
1431
			$price = $pu;
1432
			$remise = 0;
1433
			if ($remise_percent > 0)
1434
			{
1435
				$remise = round(($pu * $remise_percent / 100), 2);
1436
				$price = $pu - $remise;
1437
			}
1438
1439
			// Insert line
1440
			$this->line=new OrderLine($this->db);
1441
1442
			$this->line->context = $this->context;
1443
1444
			$this->line->fk_commande=$this->id;
1445
			$this->line->label=$label;
1446
			$this->line->desc=$desc;
1447
			$this->line->qty=$qty;
1448
1449
			$this->line->vat_src_code=$vat_src_code;
1450
			$this->line->tva_tx=$txtva;
1451
			$this->line->localtax1_tx=($total_localtax1?$localtaxes_type[1]:0);
1452
			$this->line->localtax2_tx=($total_localtax2?$localtaxes_type[3]:0);
1453
			$this->line->localtax1_type=$localtaxes_type[0];
1454
			$this->line->localtax2_type=$localtaxes_type[2];
1455
			$this->line->fk_product=$fk_product;
1456
			$this->line->product_type=$product_type;
1457
			$this->line->fk_remise_except=$fk_remise_except;
1458
			$this->line->remise_percent=$remise_percent;
1459
			$this->line->subprice=$pu_ht;
1460
			$this->line->rang=$rangtouse;
1461
			$this->line->info_bits=$info_bits;
1462
			$this->line->total_ht=$total_ht;
1463
			$this->line->total_tva=$total_tva;
1464
			$this->line->total_localtax1=$total_localtax1;
1465
			$this->line->total_localtax2=$total_localtax2;
1466
			$this->line->total_ttc=$total_ttc;
1467
			$this->line->special_code=$special_code;
1468
			$this->line->origin=$origin;
1469
			$this->line->origin_id=$origin_id;
1470
			$this->line->fk_parent_line=$fk_parent_line;
1471
			$this->line->fk_unit=$fk_unit;
1472
1473
			$this->line->date_start=$date_start;
1474
			$this->line->date_end=$date_end;
1475
1476
			$this->line->fk_fournprice = $fk_fournprice;
1477
			$this->line->pa_ht = $pa_ht;
1478
1479
			// Multicurrency
1480
			$this->line->fk_multicurrency			= $this->fk_multicurrency;
1481
			$this->line->multicurrency_code			= $this->multicurrency_code;
1482
			$this->line->multicurrency_subprice		= $pu_ht_devise;
1483
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
1484
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
1485
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
1486
1487
			// TODO Ne plus utiliser
1488
			$this->line->price=$price;
1489
			$this->line->remise=$remise;
1490
1491
			if (is_array($array_options) && count($array_options)>0) {
1492
				$this->line->array_options=$array_options;
1493
			}
1494
1495
			$result=$this->line->insert($user);
1496
			if ($result > 0)
1497
			{
1498
				// Reorder if child line
1499
				if (! empty($fk_parent_line)) $this->line_order(true, 'DESC');
1500
1501
				// Mise a jour informations denormalisees au niveau de la commande meme
1502
				$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.
1503
				if ($result > 0)
1504
				{
1505
					$this->db->commit();
1506
					return $this->line->rowid;
1507
				}
1508
				else
1509
				{
1510
					$this->db->rollback();
1511
					return -1;
1512
				}
1513
			}
1514
			else
1515
			{
1516
				$this->error=$this->line->error;
1517
				dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
1518
				$this->db->rollback();
1519
				return -2;
1520
			}
1521
		}
1522
		else
1523
		{
1524
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1525
			return -3;
1526
		}
1527
	}
1528
1529
1530
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1531
	/**
1532
	 *	Add line into array
1533
	 *	$this->client must be loaded
1534
	 *
1535
	 *	@param  int     $idproduct          Product Id
1536
	 *	@param  float   $qty                Quantity
1537
	 *	@param  float   $remise_percent     Product discount relative
1538
	 * 	@param  int     $date_start         Start date of the line
1539
	 * 	@param  int     $date_end           End date of the line
1540
	 * 	@return void
1541
	 *
1542
	 *	TODO	Remplacer les appels a cette fonction par generation objet Ligne
1543
	 *			insere dans tableau $this->products
1544
	 */
1545
	public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1546
	{
1547
        // phpcs:enable
1548
		global $conf, $mysoc;
1549
1550
		if (! $qty) $qty = 1;
1551
1552
		if ($idproduct > 0)
1553
		{
1554
			$prod=new Product($this->db);
1555
			$prod->fetch($idproduct);
1556
1557
			$tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1558
			$tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1559
			if (empty($tva_tx)) $tva_npr=0;
1560
			$vat_src_code = '';     // May be defined into tva_tx
1561
1562
			$localtax1_tx=get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1563
			$localtax2_tx=get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1564
1565
			// multiprix
1566
			if($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
1567
				$price = $prod->multiprices[$this->thirdparty->price_level];
1568
			} else {
1569
				$price = $prod->price;
1570
			}
1571
1572
			$line=new OrderLine($this->db);
1573
1574
			$line->context = $this->context;
1575
1576
			$line->fk_product=$idproduct;
1577
			$line->desc=$prod->description;
1578
			$line->qty=$qty;
1579
			$line->subprice=$price;
1580
			$line->remise_percent=$remise_percent;
1581
			$line->vat_src_code=$vat_src_code;
1582
			$line->tva_tx=$tva_tx;
1583
			$line->localtax1_tx=$localtax1_tx;
1584
			$line->localtax2_tx=$localtax2_tx;
1585
			$line->ref=$prod->ref;
1586
			$line->libelle=$prod->label;
1587
			$line->product_desc=$prod->description;
1588
			$line->fk_unit=$prod->fk_unit;
1589
1590
			// Save the start and end date of the line in the object
1591
			if ($date_start) { $line->date_start = $date_start; }
1592
			if ($date_end)   { $line->date_end = $date_end; }
1593
1594
			$this->lines[] = $line;
1595
1596
			/** POUR AJOUTER AUTOMATIQUEMENT LES SOUSPRODUITS a LA COMMANDE
1597
			 if (! empty($conf->global->PRODUIT_SOUSPRODUITS))
1598
			 {
1599
			 $prod = new Product($this->db);
1600
			 $prod->fetch($idproduct);
1601
			 $prod -> get_sousproduits_arbo();
1602
			 $prods_arbo = $prod->get_arbo_each_prod();
1603
			 if(count($prods_arbo) > 0)
1604
			 {
1605
			 foreach($prods_arbo as $key => $value)
1606
			 {
1607
			 // print "id : ".$value[1].' :qty: '.$value[0].'<br>';
1608
			 if(! in_array($value[1],$this->products))
1609
			 $this->add_product($value[1], $value[0]);
1610
1611
			 }
1612
			 }
1613
1614
			 }
1615
			 **/
1616
		}
1617
	}
1618
1619
1620
	/**
1621
	 *	Get object and lines from database
1622
	 *
1623
	 *	@param      int			$id       		Id of object to load
1624
	 * 	@param		string		$ref			Ref of object
1625
	 * 	@param		string		$ref_ext		External reference of object
1626
	 * 	@param		string		$ref_int		Internal reference of other object
1627
	 *	@return     int         				>0 if OK, <0 if KO, 0 if not found
1628
	 */
1629
	public function fetch($id, $ref = '', $ref_ext = '', $ref_int = '')
1630
	{
1631
1632
		// Check parameters
1633
		if (empty($id) && empty($ref) && empty($ref_ext) && empty($ref_int)) return -1;
1634
1635
		$sql = 'SELECT c.rowid, c.entity, c.date_creation, c.ref, c.fk_soc, c.fk_user_author, c.fk_user_valid, c.fk_statut';
1636
		$sql.= ', c.amount_ht, c.total_ht, c.total_ttc, c.tva as total_tva, c.localtax1 as total_localtax1, c.localtax2 as total_localtax2, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_availability, c.fk_input_reason';
1637
		$sql.= ', c.fk_account';
1638
		$sql.= ', c.date_commande, c.date_valid, c.tms';
1639
		$sql.= ', c.date_livraison';
1640
		$sql.= ', c.fk_shipping_method';
1641
		$sql.= ', c.fk_warehouse';
1642
		$sql.= ', c.fk_projet as fk_project, c.remise_percent, c.remise, c.remise_absolue, c.source, c.facture as billed';
1643
		$sql.= ', c.note_private, c.note_public, c.ref_client, c.ref_ext, c.ref_int, c.model_pdf, c.last_main_doc, c.fk_delivery_address, c.extraparams';
1644
		$sql.= ', c.fk_incoterms, c.location_incoterms';
1645
		$sql.= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
1646
		$sql.= ", c.module_source, c.pos_source";
1647
        $sql.= ", i.libelle as libelle_incoterms";
1648
		$sql.= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1649
		$sql.= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
1650
		$sql.= ', ca.code as availability_code, ca.label as availability_label';
1651
		$sql.= ', dr.code as demand_reason_code';
1652
		$sql.= ' FROM '.MAIN_DB_PREFIX.'commande as c';
1653
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
1654
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON c.fk_mode_reglement = p.id';
1655
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON c.fk_availability = ca.rowid';
1656
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON c.fk_input_reason = ca.rowid';
1657
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
1658
1659
		if ($id) $sql.= " WHERE c.rowid=".$id;
1660
		else $sql.= " WHERE c.entity IN (".getEntity('commande').")"; // Dont't use entity if you use rowid
1661
1662
		if ($ref)     $sql.= " AND c.ref='".$this->db->escape($ref)."'";
1663
		if ($ref_ext) $sql.= " AND c.ref_ext='".$this->db->escape($ref_ext)."'";
1664
		if ($ref_int) $sql.= " AND c.ref_int='".$this->db->escape($ref_int)."'";
1665
1666
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1667
		$result = $this->db->query($sql);
1668
		if ($result)
1669
		{
1670
			$obj = $this->db->fetch_object($result);
1671
			if ($obj)
1672
			{
1673
				$this->id					= $obj->rowid;
1674
				$this->entity				= $obj->entity;
1675
1676
				$this->ref					= $obj->ref;
1677
				$this->ref_client			= $obj->ref_client;
1678
				$this->ref_customer			= $obj->ref_client;
1679
				$this->ref_ext				= $obj->ref_ext;
1680
				$this->ref_int				= $obj->ref_int;
1681
				$this->socid				= $obj->fk_soc;
1682
				$this->statut				= $obj->fk_statut;
1683
				$this->user_author_id		= $obj->fk_user_author;
1684
				$this->user_valid           = $obj->fk_user_valid;
1685
				$this->total_ht				= $obj->total_ht;
1686
				$this->total_tva			= $obj->total_tva;
1687
				$this->total_localtax1		= $obj->total_localtax1;
1688
				$this->total_localtax2		= $obj->total_localtax2;
1689
				$this->total_ttc			= $obj->total_ttc;
1690
				$this->date					= $this->db->jdate($obj->date_commande);
1691
				$this->date_commande		= $this->db->jdate($obj->date_commande);
1692
				$this->date_creation		= $this->db->jdate($obj->date_creation);
1693
				$this->date_validation		= $this->db->jdate($obj->date_valid);
1694
				$this->date_modification	= $this->db->jdate($obj->tms);
1695
				$this->remise				= $obj->remise;
1696
				$this->remise_percent		= $obj->remise_percent;
1697
				$this->remise_absolue		= $obj->remise_absolue;
1698
				$this->source				= $obj->source;
1699
				$this->billed				= $obj->billed;
1700
				$this->note					= $obj->note_private;	// deprecated
1701
				$this->note_private			= $obj->note_private;
1702
				$this->note_public			= $obj->note_public;
1703
				$this->fk_project			= $obj->fk_project;
1704
				$this->modelpdf				= $obj->model_pdf;
1705
				$this->last_main_doc		= $obj->last_main_doc;
1706
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
1707
				$this->mode_reglement_code	= $obj->mode_reglement_code;
1708
				$this->mode_reglement		= $obj->mode_reglement_libelle;
1709
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
1710
				$this->cond_reglement_code	= $obj->cond_reglement_code;
1711
				$this->cond_reglement		= $obj->cond_reglement_libelle;
1712
				$this->cond_reglement_doc	= $obj->cond_reglement_libelle_doc;
1713
				$this->fk_account           = $obj->fk_account;
1714
				$this->availability_id		= $obj->fk_availability;
1715
				$this->availability_code	= $obj->availability_code;
1716
				$this->availability	    	= $obj->availability_label;
1717
				$this->demand_reason_id		= $obj->fk_input_reason;
1718
				$this->demand_reason_code	= $obj->demand_reason_code;
1719
				$this->date_livraison		= $this->db->jdate($obj->date_livraison);
1720
				$this->shipping_method_id   = ($obj->fk_shipping_method>0)?$obj->fk_shipping_method:null;
1721
				$this->warehouse_id         = ($obj->fk_warehouse>0)?$obj->fk_warehouse:null;
1722
				$this->fk_delivery_address	= $obj->fk_delivery_address;
1723
				$this->module_source        = $obj->module_source;
1724
				$this->pos_source           = $obj->pos_source;
1725
1726
				//Incoterms
1727
				$this->fk_incoterms         = $obj->fk_incoterms;
1728
				$this->location_incoterms   = $obj->location_incoterms;
1729
				$this->libelle_incoterms    = $obj->libelle_incoterms;
1730
1731
				// Multicurrency
1732
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1733
				$this->multicurrency_code 		= $obj->multicurrency_code;
1734
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1735
				$this->multicurrency_total_ht 	= $obj->multicurrency_total_ht;
1736
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
1737
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
1738
1739
				$this->extraparams			= (array) json_decode($obj->extraparams, true);
1740
1741
				$this->lines				= array();
1742
1743
				if ($this->statut == self::STATUS_DRAFT) $this->brouillon = 1;
1744
1745
				// Retreive all extrafield
1746
				// fetch optionals attributes and labels
1747
				$this->fetch_optionals();
1748
1749
				$this->db->free($result);
1750
1751
				/*
1752
				 * Lines
1753
				 */
1754
				$result=$this->fetch_lines();
1755
				if ($result < 0)
1756
				{
1757
					return -3;
1758
				}
1759
				return 1;
1760
			}
1761
			else
1762
			{
1763
				$this->error='Order with id '.$id.' not found sql='.$sql;
1764
				return 0;
1765
			}
1766
		}
1767
		else
1768
		{
1769
			$this->error=$this->db->error();
1770
			return -1;
1771
		}
1772
	}
1773
1774
1775
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1776
	/**
1777
	 *	Adding line of fixed discount in the order in DB
1778
	 *
1779
	 *	@param     int	$idremise			Id de la remise fixe
1780
	 *	@return    int          			>0 si ok, <0 si ko
1781
	 */
1782
	public function insert_discount($idremise)
1783
	{
1784
        // phpcs:enable
1785
		global $langs;
1786
1787
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1788
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1789
1790
		$this->db->begin();
1791
1792
		$remise=new DiscountAbsolute($this->db);
1793
		$result=$remise->fetch($idremise);
1794
1795
		if ($result > 0)
1796
		{
1797
			if ($remise->fk_facture)	// Protection against multiple submission
1798
			{
1799
				$this->error=$langs->trans("ErrorDiscountAlreadyUsed");
1800
				$this->db->rollback();
1801
				return -5;
1802
			}
1803
1804
			$line = new OrderLine($this->db);
1805
1806
			$line->fk_commande=$this->id;
1807
			$line->fk_remise_except=$remise->id;
1808
			$line->desc=$remise->description;   	// Description ligne
1809
			$line->vat_src_code=$remise->vat_src_code;
1810
			$line->tva_tx=$remise->tva_tx;
0 ignored issues
show
Documentation Bug introduced by
It seems like $remise->tva_tx can also be of type integer or string. However, the property $tva_tx is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1811
			$line->subprice=-$remise->amount_ht;
1812
			$line->price=-$remise->amount_ht;
1813
			$line->fk_product=0;					// Id produit predefini
1814
			$line->qty=1;
1815
			$line->remise=0;
1816
			$line->remise_percent=0;
1817
			$line->rang=-1;
1818
			$line->info_bits=2;
1819
1820
			$line->total_ht  = -$remise->amount_ht;
1821
			$line->total_tva = -$remise->amount_tva;
1822
			$line->total_ttc = -$remise->amount_ttc;
1823
1824
			$result=$line->insert();
1825
			if ($result > 0)
1826
			{
1827
				$result=$this->update_price(1);
1828
				if ($result > 0)
1829
				{
1830
					$this->db->commit();
1831
					return 1;
1832
				}
1833
				else
1834
				{
1835
					$this->db->rollback();
1836
					return -1;
1837
				}
1838
			}
1839
			else
1840
			{
1841
				$this->error=$line->error;
1842
				$this->db->rollback();
1843
				return -2;
1844
			}
1845
		}
1846
		else
1847
		{
1848
			$this->db->rollback();
1849
			return -2;
1850
		}
1851
	}
1852
1853
1854
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1855
	/**
1856
	 *	Load array lines
1857
	 *
1858
	 *	@param		int		$only_product	Return only physical products
1859
	 *	@return		int						<0 if KO, >0 if OK
1860
	 */
1861
	public function fetch_lines($only_product = 0)
1862
	{
1863
        // phpcs:enable
1864
		$this->lines=array();
1865
1866
		$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,';
1867
		$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,';
1868
		$sql.= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
1869
		$sql.= ' l.fk_unit,';
1870
		$sql.= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
1871
		$sql.= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_tobatch,';
1872
		$sql.= ' p.weight, p.weight_units, p.volume, p.volume_units';
1873
		$sql.= ' FROM '.MAIN_DB_PREFIX.'commandedet as l';
1874
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = l.fk_product)';
1875
		$sql.= ' WHERE l.fk_commande = '.$this->id;
1876
		if ($only_product) $sql .= ' AND p.fk_product_type = 0';
1877
		$sql .= ' ORDER BY l.rang, l.rowid';
1878
1879
		dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1880
		$result = $this->db->query($sql);
1881
		if ($result)
1882
		{
1883
			$num = $this->db->num_rows($result);
1884
1885
			$i = 0;
1886
			while ($i < $num)
1887
			{
1888
				$objp = $this->db->fetch_object($result);
1889
1890
				$line = new OrderLine($this->db);
1891
1892
				$line->rowid            = $objp->rowid;
1893
				$line->id               = $objp->rowid;
1894
				$line->fk_commande      = $objp->fk_commande;
1895
				$line->commande_id      = $objp->fk_commande;
1896
				$line->label            = $objp->custom_label;
1897
				$line->desc             = $objp->description;
1898
				$line->description      = $objp->description;		// Description line
1899
				$line->product_type     = $objp->product_type;
1900
				$line->qty              = $objp->qty;
1901
1902
				$line->vat_src_code     = $objp->vat_src_code;
1903
				$line->tva_tx           = $objp->tva_tx;
1904
				$line->localtax1_tx     = $objp->localtax1_tx;
1905
				$line->localtax2_tx     = $objp->localtax2_tx;
1906
				$line->localtax1_type	= $objp->localtax1_type;
1907
				$line->localtax2_type	= $objp->localtax2_type;
1908
				$line->total_ht         = $objp->total_ht;
1909
				$line->total_ttc        = $objp->total_ttc;
1910
				$line->total_tva        = $objp->total_tva;
1911
				$line->total_localtax1  = $objp->total_localtax1;
1912
				$line->total_localtax2  = $objp->total_localtax2;
1913
				$line->subprice         = $objp->subprice;
1914
				$line->fk_remise_except = $objp->fk_remise_except;
1915
				$line->remise_percent   = $objp->remise_percent;
1916
				$line->price            = $objp->price;
1917
				$line->fk_product       = $objp->fk_product;
1918
				$line->fk_fournprice 	= $objp->fk_fournprice;
1919
				$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1920
				$line->pa_ht 			= $marginInfos[0];
1921
				$line->marge_tx			= $marginInfos[1];
1922
				$line->marque_tx		= $marginInfos[2];
1923
				$line->rang             = $objp->rang;
1924
				$line->info_bits        = $objp->info_bits;
1925
				$line->special_code		= $objp->special_code;
1926
				$line->fk_parent_line	= $objp->fk_parent_line;
1927
1928
				$line->ref				= $objp->product_ref;
1929
				$line->product_ref		= $objp->product_ref;
1930
				$line->libelle			= $objp->product_label;
1931
				$line->product_label	= $objp->product_label;
1932
				$line->product_desc     = $objp->product_desc;
1933
				$line->product_tobatch  = $objp->product_tobatch;
1934
				$line->fk_product_type  = $objp->fk_product_type;	// Produit ou service
1935
				$line->fk_unit          = $objp->fk_unit;
1936
1937
				$line->weight           = $objp->weight;
1938
				$line->weight_units     = $objp->weight_units;
1939
				$line->volume           = $objp->volume;
1940
				$line->volume_units     = $objp->volume_units;
1941
1942
				$line->date_start       = $this->db->jdate($objp->date_start);
1943
				$line->date_end         = $this->db->jdate($objp->date_end);
1944
1945
				// Multicurrency
1946
				$line->fk_multicurrency 		= $objp->fk_multicurrency;
1947
				$line->multicurrency_code 		= $objp->multicurrency_code;
1948
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1949
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1950
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1951
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1952
1953
				$line->fetch_optionals();
1954
1955
                $this->lines[$i] = $line;
1956
1957
				$i++;
1958
			}
1959
1960
			$this->db->free($result);
1961
1962
			return 1;
1963
		}
1964
		else
1965
		{
1966
			$this->error=$this->db->error();
1967
			return -3;
1968
		}
1969
	}
1970
1971
1972
	/**
1973
	 *	Return number of line with type product.
1974
	 *
1975
	 *	@return		int		<0 if KO, Nbr of product lines if OK
1976
	 */
1977
	public function getNbOfProductsLines()
1978
	{
1979
		$nb=0;
1980
		foreach($this->lines as $line)
1981
		{
1982
			if ($line->product_type == 0) $nb++;
1983
		}
1984
		return $nb;
1985
	}
1986
1987
	/**
1988
	 *	Return number of line with type service.
1989
	 *
1990
	 *	@return		int		<0 if KO, Nbr of service lines if OK
1991
	 */
1992
	public function getNbOfServicesLines()
1993
	{
1994
		$nb=0;
1995
		foreach($this->lines as $line)
1996
		{
1997
			if ($line->product_type == 1) $nb++;
1998
		}
1999
		return $nb;
2000
	}
2001
2002
	/**
2003
	 *	Count number of shipments for this order
2004
	 *
2005
	 * 	@return     int                			<0 if KO, Nb of shipment found if OK
2006
	 */
2007
	public function getNbOfShipments()
2008
	{
2009
		$nb = 0;
2010
2011
		$sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2012
		$sql.= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2013
		$sql.= ' '.MAIN_DB_PREFIX.'commandedet as cd';
2014
		$sql.= ' WHERE';
2015
		$sql.= ' ed.fk_origin_line = cd.rowid';
2016
		$sql.= ' AND cd.fk_commande =' .$this->id;
2017
		//print $sql;
2018
2019
		dol_syslog(get_class($this)."::getNbOfShipments", LOG_DEBUG);
2020
		$resql = $this->db->query($sql);
2021
		if ($resql)
2022
		{
2023
			$obj = $this->db->fetch_object($resql);
2024
			if ($obj) $nb = $obj->nb;
2025
2026
			$this->db->free($resql);
2027
			return $nb;
2028
		}
2029
		else
2030
		{
2031
			$this->error=$this->db->lasterror();
2032
			return -1;
2033
		}
2034
	}
2035
2036
	/**
2037
	 *	Load array this->expeditions of lines of shipments with nb of products sent for each order line
2038
	 *  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
2039
	 *
2040
	 *	@param      int		$filtre_statut      Filter on shipment status
2041
	 * 	@return     int                			<0 if KO, Nb of lines found if OK
2042
	 */
2043
	public function loadExpeditions($filtre_statut = -1)
2044
	{
2045
		$this->expeditions = array();
2046
2047
		$sql = 'SELECT cd.rowid, cd.fk_product,';
2048
		$sql.= ' sum(ed.qty) as qty';
2049
		$sql.= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2050
		if ($filtre_statut >= 0) $sql.= ' '.MAIN_DB_PREFIX.'expedition as e,';
2051
		$sql.= ' '.MAIN_DB_PREFIX.'commandedet as cd';
2052
		$sql.= ' WHERE';
2053
		if ($filtre_statut >= 0) $sql.= ' ed.fk_expedition = e.rowid AND';
2054
		$sql.= ' ed.fk_origin_line = cd.rowid';
2055
		$sql.= ' AND cd.fk_commande =' .$this->id;
2056
		if ($this->fk_product > 0) $sql.= ' AND cd.fk_product = '.$this->fk_product;
2057
		if ($filtre_statut >= 0) $sql.=' AND e.fk_statut >= '.$filtre_statut;
2058
		$sql.= ' GROUP BY cd.rowid, cd.fk_product';
2059
		//print $sql;
2060
2061
		dol_syslog(get_class($this)."::loadExpeditions", LOG_DEBUG);
2062
		$resql = $this->db->query($sql);
2063
		if ($resql)
2064
		{
2065
			$num = $this->db->num_rows($resql);
2066
			$i = 0;
2067
			while ($i < $num)
2068
			{
2069
				$obj = $this->db->fetch_object($resql);
2070
				$this->expeditions[$obj->rowid] = $obj->qty;
2071
				$i++;
2072
			}
2073
			$this->db->free($resql);
2074
			return $num;
2075
		}
2076
		else
2077
		{
2078
			$this->error=$this->db->lasterror();
2079
			return -1;
2080
		}
2081
	}
2082
2083
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2084
	/**
2085
	 * Returns a array with expeditions lines number
2086
	 *
2087
	 * @return	int		Nb of shipments
2088
	 *
2089
	 * TODO deprecate, move to Shipping class
2090
	 */
2091
	public function nb_expedition()
2092
	{
2093
        // phpcs:enable
2094
		$sql = 'SELECT count(*)';
2095
		$sql.= ' FROM '.MAIN_DB_PREFIX.'expedition as e';
2096
		$sql.= ', '.MAIN_DB_PREFIX.'element_element as el';
2097
		$sql.= ' WHERE el.fk_source = '.$this->id;
2098
		$sql.= " AND el.fk_target = e.rowid";
2099
		$sql.= " AND el.targettype = 'shipping'";
2100
2101
		$resql = $this->db->query($sql);
2102
		if ($resql)
2103
		{
2104
			$row = $this->db->fetch_row($resql);
2105
			return $row[0];
2106
		}
2107
		else dol_print_error($this->db);
2108
	}
2109
2110
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2111
	/**
2112
	 *	Return a array with the pending stock by product
2113
	 *
2114
	 *	@param      int		$filtre_statut      Filtre sur statut
2115
	 *	@return     int                 		0 si OK, <0 si KO
2116
	 *
2117
	 *	TODO		FONCTION NON FINIE A FINIR
2118
	 */
2119
	public function stock_array($filtre_statut = self::STATUS_CANCELED)
2120
	{
2121
        // phpcs:enable
2122
		$this->stocks = array();
2123
2124
		// Tableau des id de produit de la commande
2125
		$array_of_product=array();
2126
2127
		// Recherche total en stock pour chaque produit
2128
		// TODO $array_of_product est défini vide juste au dessus !!
2129
		if (count($array_of_product))
2130
		{
2131
			$sql = "SELECT fk_product, sum(ps.reel) as total";
2132
			$sql.= " FROM ".MAIN_DB_PREFIX."product_stock as ps";
2133
			$sql.= " WHERE ps.fk_product IN (".join(',', $array_of_product).")";
2134
			$sql.= ' GROUP BY fk_product ';
2135
			$resql = $this->db->query($sql);
2136
			if ($resql)
2137
			{
2138
				$num = $this->db->num_rows($resql);
2139
				$i = 0;
2140
				while ($i < $num)
2141
				{
2142
					$obj = $this->db->fetch_object($resql);
2143
					$this->stocks[$obj->fk_product] = $obj->total;
2144
					$i++;
2145
				}
2146
				$this->db->free($resql);
2147
			}
2148
		}
2149
		return 0;
2150
	}
2151
2152
	/**
2153
	 *  Delete an order line
2154
	 *
2155
	 *	@param      User	$user		User object
2156
	 *  @param      int		$lineid		Id of line to delete
2157
	 *  @return     int        		 	>0 if OK, 0 if nothing to do, <0 if KO
2158
	 */
2159
	public function deleteline($user = null, $lineid = 0)
2160
	{
2161
		if ($this->statut == self::STATUS_DRAFT)
2162
		{
2163
			$this->db->begin();
2164
2165
			$sql = "SELECT fk_product, qty";
2166
			$sql.= " FROM ".MAIN_DB_PREFIX."commandedet";
2167
			$sql.= " WHERE rowid = ".$lineid;
2168
2169
			$result = $this->db->query($sql);
2170
			if ($result)
2171
			{
2172
				$obj = $this->db->fetch_object($result);
2173
2174
				if ($obj)
2175
				{
2176
					$product = new Product($this->db);
2177
					$product->id = $obj->fk_product;
2178
2179
					// Delete line
2180
					$line = new OrderLine($this->db);
2181
2182
					// For triggers
2183
					$line->fetch($lineid);
2184
2185
					if ($line->delete($user) > 0)
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 2159 can be null; however, OrderLine::delete() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
2186
					{
2187
						$result=$this->update_price(1);
2188
2189
						if ($result > 0)
2190
						{
2191
							$this->db->commit();
2192
							return 1;
2193
						}
2194
						else
2195
						{
2196
							$this->db->rollback();
2197
							$this->error=$this->db->lasterror();
2198
							return -1;
2199
						}
2200
					}
2201
					else
2202
					{
2203
						$this->db->rollback();
2204
						$this->error=$line->error;
2205
						return -1;
2206
					}
2207
				}
2208
				else
2209
				{
2210
					$this->db->rollback();
2211
					return 0;
2212
				}
2213
			}
2214
			else
2215
			{
2216
				$this->db->rollback();
2217
				$this->error=$this->db->lasterror();
2218
				return -1;
2219
			}
2220
		}
2221
		else
2222
		{
2223
			$this->error='ErrorDeleteLineNotAllowedByObjectStatus';
2224
			return -1;
2225
		}
2226
	}
2227
2228
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2229
	/**
2230
	 * 	Applique une remise relative
2231
	 *
2232
	 * 	@param     	User		$user		User qui positionne la remise
2233
	 * 	@param     	float		$remise		Discount (percent)
2234
	 * 	@param     	int			$notrigger	1=Does not execute triggers, 0= execute triggers
2235
	 *	@return		int 					<0 if KO, >0 if OK
2236
	 */
2237
	public function set_remise($user, $remise, $notrigger = 0)
2238
	{
2239
        // phpcs:enable
2240
		$remise=trim($remise)?trim($remise):0;
2241
2242
		if ($user->rights->commande->creer)
2243
		{
2244
			$error=0;
2245
2246
			$this->db->begin();
2247
2248
			$remise=price2num($remise);
2249
2250
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2251
			$sql.= ' SET remise_percent = '.$remise;
2252
			$sql.= ' WHERE rowid = '.$this->id.' AND fk_statut = '.self::STATUS_DRAFT.' ;';
2253
2254
			dol_syslog(__METHOD__, LOG_DEBUG);
2255
			$resql=$this->db->query($sql);
2256
			if (!$resql)
2257
			{
2258
				$this->errors[]=$this->db->error();
2259
				$error++;
2260
			}
2261
2262
			if (! $error)
2263
			{
2264
				$this->oldcopy= clone $this;
2265
				$this->remise_percent = $remise;
2266
				$this->update_price(1);
2267
			}
2268
2269
			if (! $notrigger && empty($error))
2270
			{
2271
				// Call trigger
2272
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2273
				if ($result < 0) $error++;
2274
				// End call triggers
2275
			}
2276
2277
			if (! $error)
2278
			{
2279
				$this->db->commit();
2280
				return 1;
2281
			}
2282
			else
2283
			{
2284
				foreach($this->errors as $errmsg)
2285
				{
2286
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2287
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2288
				}
2289
				$this->db->rollback();
2290
				return -1*$error;
2291
			}
2292
		}
2293
	}
2294
2295
2296
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2297
	/**
2298
	 * 		Applique une remise absolue
2299
	 *
2300
	 * 		@param     	User		$user 		User qui positionne la remise
2301
	 * 		@param     	float		$remise		Discount
2302
	 * 		@param     	int			$notrigger	1=Does not execute triggers, 0= execute triggers
2303
	 *		@return		int 					<0 if KO, >0 if OK
2304
	 */
2305
	public function set_remise_absolue($user, $remise, $notrigger = 0)
2306
	{
2307
        // phpcs:enable
2308
		$remise=trim($remise)?trim($remise):0;
2309
2310
		if ($user->rights->commande->creer)
2311
		{
2312
			$error=0;
2313
2314
			$this->db->begin();
2315
2316
			$remise=price2num($remise);
2317
2318
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2319
			$sql.= ' SET remise_absolue = '.$remise;
2320
			$sql.= ' WHERE rowid = '.$this->id.' AND fk_statut = '.self::STATUS_DRAFT.' ;';
2321
2322
			dol_syslog(__METHOD__, LOG_DEBUG);
2323
			$resql=$this->db->query($sql);
2324
			if (!$resql)
2325
			{
2326
				$this->errors[]=$this->db->error();
2327
				$error++;
2328
			}
2329
2330
			if (! $error)
2331
			{
2332
				$this->oldcopy= clone $this;
2333
				$this->remise_absolue = $remise;
2334
				$this->update_price(1);
2335
			}
2336
2337
			if (! $notrigger && empty($error))
2338
			{
2339
				// Call trigger
2340
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2341
				if ($result < 0) $error++;
2342
				// End call triggers
2343
			}
2344
2345
			if (! $error)
2346
			{
2347
				$this->db->commit();
2348
				return 1;
2349
			}
2350
			else
2351
			{
2352
				foreach($this->errors as $errmsg)
2353
				{
2354
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2355
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2356
				}
2357
				$this->db->rollback();
2358
				return -1*$error;
2359
			}
2360
		}
2361
	}
2362
2363
2364
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2365
	/**
2366
	 *	Set the order date
2367
	 *
2368
	 *	@param      User	$user       Object user making change
2369
	 *	@param      int		$date		Date
2370
	 * 	@param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2371
	 *	@return     int         		<0 if KO, >0 if OK
2372
	 */
2373
	public function set_date($user, $date, $notrigger = 0)
2374
	{
2375
        // phpcs:enable
2376
		if ($user->rights->commande->creer)
2377
		{
2378
			$error=0;
2379
2380
			$this->db->begin();
2381
2382
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande";
2383
			$sql.= " SET date_commande = ".($date ? "'".$this->db->idate($date)."'" : 'null');
2384
			$sql.= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
2385
2386
			dol_syslog(__METHOD__, LOG_DEBUG);
2387
			$resql=$this->db->query($sql);
2388
			if (!$resql)
2389
			{
2390
				$this->errors[]=$this->db->error();
2391
				$error++;
2392
			}
2393
2394
			if (! $error)
2395
			{
2396
				$this->oldcopy= clone $this;
2397
				$this->date = $date;
2398
			}
2399
2400
			if (! $notrigger && empty($error))
2401
			{
2402
				// Call trigger
2403
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2404
				if ($result < 0) $error++;
2405
				// End call triggers
2406
			}
2407
2408
			if (! $error)
2409
			{
2410
				$this->db->commit();
2411
				return 1;
2412
			}
2413
			else
2414
			{
2415
				foreach($this->errors as $errmsg)
2416
				{
2417
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2418
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2419
				}
2420
				$this->db->rollback();
2421
				return -1*$error;
2422
			}
2423
		}
2424
		else
2425
		{
2426
			return -2;
2427
		}
2428
	}
2429
2430
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2431
	/**
2432
	 *	Set the planned delivery date
2433
	 *
2434
	 *	@param      User	$user        		Objet utilisateur qui modifie
2435
	 *	@param      int		$date_livraison     Date de livraison
2436
	 *  @param     	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2437
	 *	@return     int         				<0 si ko, >0 si ok
2438
	 */
2439
	public function set_date_livraison($user, $date_livraison, $notrigger = 0)
2440
	{
2441
        // phpcs:enable
2442
		if ($user->rights->commande->creer)
2443
		{
2444
			$error=0;
2445
2446
			$this->db->begin();
2447
2448
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande";
2449
			$sql.= " SET date_livraison = ".($date_livraison ? "'".$this->db->idate($date_livraison)."'" : 'null');
2450
			$sql.= " WHERE rowid = ".$this->id;
2451
2452
			dol_syslog(__METHOD__, LOG_DEBUG);
2453
			$resql=$this->db->query($sql);
2454
			if (!$resql)
2455
			{
2456
				$this->errors[]=$this->db->error();
2457
				$error++;
2458
			}
2459
2460
			if (! $error)
2461
			{
2462
				$this->oldcopy= clone $this;
2463
				$this->date_livraison = $date_livraison;
2464
			}
2465
2466
			if (! $notrigger && empty($error))
2467
			{
2468
				// Call trigger
2469
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2470
				if ($result < 0) $error++;
2471
				// End call triggers
2472
			}
2473
2474
			if (! $error)
2475
			{
2476
				$this->db->commit();
2477
				return 1;
2478
			}
2479
			else
2480
			{
2481
				foreach($this->errors as $errmsg)
2482
				{
2483
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2484
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2485
				}
2486
				$this->db->rollback();
2487
				return -1*$error;
2488
			}
2489
		}
2490
		else
2491
		{
2492
			return -2;
2493
		}
2494
	}
2495
2496
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2497
	/**
2498
	 *  Return list of orders (eventuelly filtered on a user) into an array
2499
	 *
2500
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
2501
	 *  @param      int		$draft      	0=not draft, 1=draft
2502
	 *  @param      User	$excluser      	Objet user to exclude
2503
	 *  @param    	int		$socid			Id third pary
2504
	 *  @param    	int		$limit			For pagination
2505
	 *  @param    	int		$offset			For pagination
2506
	 *  @param    	string	$sortfield		Sort criteria
2507
	 *  @param    	string	$sortorder		Sort order
2508
	 *  @return     int             		-1 if KO, array with result if OK
2509
	 */
2510
	public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2511
	{
2512
        // phpcs:enable
2513
		global $user;
2514
2515
		$ga = array();
2516
2517
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
2518
		$sql.= " c.rowid as cid, c.ref";
2519
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", sc.fk_soc, sc.fk_user";
2520
		$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."commande as c";
2521
		if (! $user->rights->societe->client->voir && ! $socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2522
		$sql.= " WHERE c.entity IN (".getEntity('commande').")";
2523
		$sql.= " AND c.fk_soc = s.rowid";
2524
		if (! $user->rights->societe->client->voir && ! $socid) //restriction
2525
		{
2526
			$sql.= " AND s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id;
2527
		}
2528
		if ($socid) $sql.= " AND s.rowid = ".$socid;
2529
		if ($draft) $sql.= " AND c.fk_statut = ".self::STATUS_DRAFT;
2530
		if (is_object($excluser)) $sql.= " AND c.fk_user_author <> ".$excluser->id;
2531
		$sql.= $this->db->order($sortfield, $sortorder);
2532
		$sql.= $this->db->plimit($limit, $offset);
2533
2534
		$result=$this->db->query($sql);
2535
		if ($result)
2536
		{
2537
			$numc = $this->db->num_rows($result);
2538
			if ($numc)
2539
			{
2540
				$i = 0;
2541
				while ($i < $numc)
2542
				{
2543
					$obj = $this->db->fetch_object($result);
2544
2545
					if ($shortlist == 1)
2546
					{
2547
						$ga[$obj->cid] = $obj->ref;
2548
					}
2549
					elseif ($shortlist == 2)
2550
					{
2551
						$ga[$obj->cid] = $obj->ref.' ('.$obj->name.')';
2552
					}
2553
					else
2554
					{
2555
						$ga[$i]['id']	= $obj->cid;
2556
						$ga[$i]['ref'] 	= $obj->ref;
2557
						$ga[$i]['name'] = $obj->name;
2558
					}
2559
					$i++;
2560
				}
2561
			}
2562
			return $ga;
2563
		}
2564
		else
2565
		{
2566
			dol_print_error($this->db);
2567
			return -1;
2568
		}
2569
	}
2570
2571
	/**
2572
	 *	Update delivery delay
2573
	 *
2574
	 *	@param      int		$availability_id	Id du nouveau mode
2575
	 *  @param     	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2576
	 *	@return     int         				>0 if OK, <0 if KO
2577
	 */
2578
	public function availability($availability_id, $notrigger = 0)
2579
	{
2580
		global $user;
2581
2582
		dol_syslog('Commande::availability('.$availability_id.')');
2583
		if ($this->statut >= self::STATUS_DRAFT)
2584
		{
2585
			$error=0;
2586
2587
			$this->db->begin();
2588
2589
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2590
			$sql .= ' SET fk_availability = '.$availability_id;
2591
			$sql .= ' WHERE rowid='.$this->id;
2592
2593
			dol_syslog(__METHOD__, LOG_DEBUG);
2594
			$resql=$this->db->query($sql);
2595
			if (!$resql)
2596
			{
2597
				$this->errors[]=$this->db->error();
2598
				$error++;
2599
			}
2600
2601
			if (! $error)
2602
			{
2603
				$this->oldcopy= clone $this;
2604
				$this->availability_id = $availability_id;
2605
			}
2606
2607
			if (! $notrigger && empty($error))
2608
			{
2609
				// Call trigger
2610
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2611
				if ($result < 0) $error++;
2612
				// End call triggers
2613
			}
2614
2615
			if (! $error)
2616
			{
2617
				$this->db->commit();
2618
				return 1;
2619
			}
2620
			else
2621
			{
2622
				foreach($this->errors as $errmsg)
2623
				{
2624
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2625
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2626
				}
2627
				$this->db->rollback();
2628
				return -1*$error;
2629
			}
2630
		}
2631
		else
2632
		{
2633
			$error_str='Command status do not meet requirement '.$this->statut;
2634
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
2635
			$this->error=$error_str;
2636
			$this->errors[]= $this->error;
2637
			return -2;
2638
		}
2639
	}
2640
2641
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2642
	/**
2643
	 *	Update order demand_reason
2644
	 *
2645
	 *  @param      int		$demand_reason_id	Id of new demand
2646
	 *  @param     	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2647
	 *  @return     int        			 		>0 if ok, <0 if ko
2648
	 */
2649
	public function demand_reason($demand_reason_id, $notrigger = 0)
2650
	{
2651
        // phpcs:enable
2652
		global $user;
2653
2654
		dol_syslog('Commande::demand_reason('.$demand_reason_id.')');
2655
		if ($this->statut >= self::STATUS_DRAFT)
2656
		{
2657
			$error=0;
2658
2659
			$this->db->begin();
2660
2661
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande';
2662
			$sql .= ' SET fk_input_reason = '.$demand_reason_id;
2663
			$sql .= ' WHERE rowid='.$this->id;
2664
2665
			dol_syslog(__METHOD__, LOG_DEBUG);
2666
			$resql=$this->db->query($sql);
2667
			if (!$resql)
2668
			{
2669
				$this->errors[]=$this->db->error();
2670
				$error++;
2671
			}
2672
2673
			if (! $error)
2674
			{
2675
				$this->oldcopy= clone $this;
2676
				$this->demand_reason_id = $demand_reason_id;
2677
			}
2678
2679
			if (! $notrigger && empty($error))
2680
			{
2681
				// Call trigger
2682
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2683
				if ($result < 0) $error++;
2684
				// End call triggers
2685
			}
2686
2687
			if (! $error)
2688
			{
2689
				$this->db->commit();
2690
				return 1;
2691
			}
2692
			else
2693
			{
2694
				foreach($this->errors as $errmsg)
2695
				{
2696
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2697
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2698
				}
2699
				$this->db->rollback();
2700
				return -1*$error;
2701
			}
2702
		}
2703
		else
2704
		{
2705
			$error_str='order status do not meet requirement '.$this->statut;
2706
			dol_syslog(__METHOD__.$error_str, LOG_ERR);
2707
			$this->error=$error_str;
2708
			$this->errors[]= $this->error;
2709
			return -2;
2710
		}
2711
	}
2712
2713
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2714
	/**
2715
	 *	Set customer ref
2716
	 *
2717
	 *	@param      User	$user           User that make change
2718
	 *	@param      string	$ref_client     Customer ref
2719
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2720
	 *	@return     int             		<0 if KO, >0 if OK
2721
	 */
2722
	public function set_ref_client($user, $ref_client, $notrigger = 0)
2723
	{
2724
        // phpcs:enable
2725
		if ($user->rights->commande->creer)
2726
		{
2727
			$error=0;
2728
2729
			$this->db->begin();
2730
2731
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande SET';
2732
			$sql.= ' ref_client = '.(empty($ref_client) ? 'NULL' : '\''.$this->db->escape($ref_client).'\'');
2733
			$sql.= ' WHERE rowid = '.$this->id;
2734
2735
			dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2736
			$resql=$this->db->query($sql);
2737
			if (!$resql)
2738
			{
2739
				$this->errors[]=$this->db->error();
2740
				$error++;
2741
			}
2742
2743
			if (! $error)
2744
			{
2745
				$this->oldcopy= clone $this;
2746
				$this->ref_client = $ref_client;
2747
			}
2748
2749
			if (! $notrigger && empty($error))
2750
			{
2751
				// Call trigger
2752
				$result=$this->call_trigger('ORDER_MODIFY', $user);
2753
				if ($result < 0) $error++;
2754
				// End call triggers
2755
			}
2756
			if (! $error)
2757
			{
2758
				$this->db->commit();
2759
				return 1;
2760
			}
2761
			else
2762
			{
2763
				foreach($this->errors as $errmsg)
2764
				{
2765
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2766
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2767
				}
2768
				$this->db->rollback();
2769
				return -1*$error;
2770
			}
2771
		}
2772
		else
2773
		{
2774
			return -1;
2775
		}
2776
	}
2777
2778
	/**
2779
	 * Classify the order as invoiced
2780
	 *
2781
	 * @param	User    $user       Object user making the change
2782
	 * @param	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2783
	 * @return	int                 <0 if KO, >0 if OK
2784
	 */
2785
	public function classifyBilled(User $user, $notrigger = 0)
2786
	{
2787
		$error = 0;
2788
2789
		$this->db->begin();
2790
2791
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande SET facture = 1';
2792
		$sql.= ' WHERE rowid = '.$this->id.' AND fk_statut > '.self::STATUS_DRAFT;
2793
2794
		dol_syslog(get_class($this)."::classifyBilled", LOG_DEBUG);
2795
		if ($this->db->query($sql))
2796
		{
2797
2798
			if (! $error)
2799
			{
2800
				$this->oldcopy= clone $this;
2801
				$this->billed=1;
2802
			}
2803
2804
			if (! $notrigger && empty($error))
2805
			{
2806
				// Call trigger
2807
				$result=$this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
2808
				if ($result < 0) $error++;
2809
				// End call triggers
2810
			}
2811
2812
			if (! $error)
2813
			{
2814
				$this->db->commit();
2815
				return 1;
2816
			}
2817
			else
2818
			{
2819
				foreach($this->errors as $errmsg)
2820
				{
2821
					dol_syslog(get_class($this)."::classifyBilled ".$errmsg, LOG_ERR);
2822
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2823
				}
2824
				$this->db->rollback();
2825
				return -1*$error;
2826
			}
2827
		}
2828
		else
2829
		{
2830
			$this->error=$this->db->error();
2831
			$this->db->rollback();
2832
			return -1;
2833
		}
2834
	}
2835
2836
	/**
2837
	 * Classify the order as not invoiced
2838
	 *
2839
	 * @return     int     <0 if ko, >0 if ok
2840
	 */
2841
	public function classifyUnBilled()
2842
	{
2843
		global $conf, $user, $langs;
2844
		$error = 0;
2845
2846
		$this->db->begin();
2847
2848
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande SET facture = 0';
2849
		$sql.= ' WHERE rowid = '.$this->id.' AND fk_statut > '.self::STATUS_DRAFT;
2850
2851
		dol_syslog(get_class($this)."::classifyUnBilled", LOG_DEBUG);
2852
		if ($this->db->query($sql))
2853
		{
2854
			if (! $error)
2855
			{
2856
				$this->oldcopy= clone $this;
2857
				$this->billed=1;
2858
			}
2859
2860
			// Call trigger
2861
			$result=$this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
2862
			if ($result < 0) $error++;
2863
			// End call triggers
2864
2865
			if (! $error)
2866
			{
2867
				$this->billed=0;
2868
2869
				$this->db->commit();
2870
				return 1;
2871
			}
2872
			else
2873
			{
2874
				foreach($this->errors as $errmsg)
2875
				{
2876
					dol_syslog(get_class($this)."::classifyUnBilled ".$errmsg, LOG_ERR);
2877
					$this->error.=($this->error?', '.$errmsg:$errmsg);
2878
				}
2879
				$this->db->rollback();
2880
				return -1*$error;
2881
			}
2882
		}
2883
		else
2884
		{
2885
			$this->error=$this->db->error();
2886
			$this->db->rollback();
2887
			return -1;
2888
		}
2889
	}
2890
2891
2892
	/**
2893
	 *  Update a line in database
2894
	 *
2895
	 *  @param    	int				$rowid            	Id of line to update
2896
	 *  @param    	string			$desc             	Description of line
2897
	 *  @param    	float			$pu               	Unit price
2898
	 *  @param    	float			$qty              	Quantity
2899
	 *  @param    	float			$remise_percent   	Percent of discount
2900
	 *  @param    	float			$txtva           	Taux TVA
2901
	 * 	@param		float			$txlocaltax1		Local tax 1 rate
2902
	 *  @param		float			$txlocaltax2		Local tax 2 rate
2903
	 *  @param    	string			$price_base_type	HT or TTC
2904
	 *  @param    	int				$info_bits        	Miscellaneous informations on line
2905
	 *  @param    	int				$date_start        	Start date of the line
2906
	 *  @param    	int				$date_end          	End date of the line
2907
	 * 	@param		int				$type				Type of line (0=product, 1=service)
2908
	 * 	@param		int				$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
2909
	 * 	@param		int				$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
2910
	 *  @param		int				$fk_fournprice		Id of origin supplier price
2911
	 *  @param		int				$pa_ht				Price (without tax) of product when it was bought
2912
	 *  @param		string			$label				Label
2913
	 *  @param		int				$special_code		Special code (also used by externals modules!)
2914
	 *  @param		array			$array_options		extrafields array
2915
	 * 	@param 		string			$fk_unit 			Code of the unit to use. Null to use the default one
2916
	 *  @param		double			$pu_ht_devise		Amount in currency
2917
	 * 	@param		int				$notrigger			disable line update trigger
2918
	 *  @return   	int              					< 0 if KO, > 0 if OK
2919
	 */
2920
	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)
2921
	{
2922
		global $conf, $mysoc, $langs, $user;
2923
2924
		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");
2925
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2926
2927
		if (! empty($this->brouillon))
2928
		{
2929
			$this->db->begin();
2930
2931
			// Clean parameters
2932
			if (empty($qty)) $qty=0;
2933
			if (empty($info_bits)) $info_bits=0;
2934
			if (empty($txtva)) $txtva=0;
2935
			if (empty($txlocaltax1)) $txlocaltax1=0;
2936
			if (empty($txlocaltax2)) $txlocaltax2=0;
2937
			if (empty($remise_percent)) $remise_percent=0;
2938
			if (empty($special_code) || $special_code == 3) $special_code=0;
2939
2940
			$remise_percent=price2num($remise_percent);
2941
			$qty=price2num($qty);
2942
			$pu = price2num($pu);
2943
			$pa_ht=price2num($pa_ht);
2944
			$pu_ht_devise=price2num($pu_ht_devise);
2945
			$txtva=price2num($txtva);
2946
			$txlocaltax1=price2num($txlocaltax1);
2947
			$txlocaltax2=price2num($txlocaltax2);
2948
2949
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2950
			// qty, pu, remise_percent et txtva
2951
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2952
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2953
2954
			$localtaxes_type=getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2955
2956
			// Clean vat code
2957
			$vat_src_code='';
2958
			if (preg_match('/\((.*)\)/', $txtva, $reg))
2959
			{
2960
				$vat_src_code = $reg[1];
2961
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva);    // Remove code into vatrate.
2962
			}
2963
2964
			$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);
2965
2966
			$total_ht  = $tabprice[0];
2967
			$total_tva = $tabprice[1];
2968
			$total_ttc = $tabprice[2];
2969
			$total_localtax1 = $tabprice[9];
2970
			$total_localtax2 = $tabprice[10];
2971
			$pu_ht  = $tabprice[3];
2972
			$pu_tva = $tabprice[4];
2973
			$pu_ttc = $tabprice[5];
2974
2975
			// MultiCurrency
2976
			$multicurrency_total_ht  = $tabprice[16];
2977
			$multicurrency_total_tva = $tabprice[17];
2978
			$multicurrency_total_ttc = $tabprice[18];
2979
			$pu_ht_devise = $tabprice[19];
2980
2981
			// Anciens indicateurs: $price, $subprice (a ne plus utiliser)
2982
			$price = $pu_ht;
2983
			if ($price_base_type == 'TTC')
2984
			{
2985
				$subprice = $pu_ttc;
2986
			}
2987
			else
2988
			{
2989
				$subprice = $pu_ht;
2990
			}
2991
			$remise = 0;
2992
			if ($remise_percent > 0)
2993
			{
2994
				$remise = round(($pu * $remise_percent / 100), 2);
2995
				$price = ($pu - $remise);
2996
			}
2997
2998
			//Fetch current line from the database and then clone the object and set it in $oldline property
2999
			$line = new OrderLine($this->db);
3000
			$line->fetch($rowid);
3001
3002
			if (!empty($line->fk_product))
3003
			{
3004
				$product=new Product($this->db);
3005
				$result=$product->fetch($line->fk_product);
3006
				$product_type=$product->type;
3007
3008
				if (! empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_ORDER) && $product_type == 0 && $product->stock_reel < $qty)
3009
				{
3010
					$langs->load("errors");
3011
					$this->error=$langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
3012
					dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
3013
					$this->db->rollback();
3014
					return self::STOCK_NOT_ENOUGH_FOR_ORDER;
3015
				}
3016
			}
3017
3018
			$staticline = clone $line;
3019
3020
			$line->oldline = $staticline;
3021
			$this->line = $line;
3022
			$this->line->context = $this->context;
3023
3024
			// Reorder if fk_parent_line change
3025
			if (! empty($fk_parent_line) && ! empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
3026
			{
3027
				$rangmax = $this->line_max($fk_parent_line);
3028
				$this->line->rang = $rangmax + 1;
3029
			}
3030
3031
			$this->line->rowid=$rowid;
3032
			$this->line->label=$label;
3033
			$this->line->desc=$desc;
3034
			$this->line->qty=$qty;
3035
3036
			$this->line->vat_src_code	= $vat_src_code;
3037
			$this->line->tva_tx         = $txtva;
3038
			$this->line->localtax1_tx   = $txlocaltax1;
3039
			$this->line->localtax2_tx   = $txlocaltax2;
3040
			$this->line->localtax1_type = $localtaxes_type[0];
3041
			$this->line->localtax2_type = $localtaxes_type[2];
3042
			$this->line->remise_percent = $remise_percent;
3043
			$this->line->subprice       = $subprice;
3044
			$this->line->info_bits      = $info_bits;
3045
			$this->line->special_code   = $special_code;
3046
			$this->line->total_ht       = $total_ht;
3047
			$this->line->total_tva      = $total_tva;
3048
			$this->line->total_localtax1= $total_localtax1;
3049
			$this->line->total_localtax2= $total_localtax2;
3050
			$this->line->total_ttc      = $total_ttc;
3051
			$this->line->date_start     = $date_start;
3052
			$this->line->date_end       = $date_end;
3053
			$this->line->product_type   = $type;
3054
			$this->line->fk_parent_line = $fk_parent_line;
3055
			$this->line->skip_update_total=$skip_update_total;
3056
			$this->line->fk_unit        = $fk_unit;
3057
3058
			$this->line->fk_fournprice = $fk_fournprice;
3059
			$this->line->pa_ht = $pa_ht;
3060
3061
			// Multicurrency
3062
			$this->line->multicurrency_subprice		= $pu_ht_devise;
3063
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
3064
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
3065
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
3066
3067
			// TODO deprecated
3068
			$this->line->price=$price;
3069
			$this->line->remise=$remise;
3070
3071
			if (is_array($array_options) && count($array_options)>0) {
3072
				$this->line->array_options=$array_options;
3073
			}
3074
3075
			$result=$this->line->update($user, $notrigger);
3076
			if ($result > 0)
3077
			{
3078
				// Reorder if child line
3079
				if (! empty($fk_parent_line)) $this->line_order(true, 'DESC');
3080
3081
				// Mise a jour info denormalisees
3082
				$this->update_price(1);
3083
3084
				$this->db->commit();
3085
				return $result;
3086
			}
3087
			else
3088
			{
3089
				$this->error=$this->line->error;
3090
3091
				$this->db->rollback();
3092
				return -1;
3093
			}
3094
		}
3095
		else
3096
		{
3097
			$this->error=get_class($this)."::updateline Order status makes operation forbidden";
3098
			$this->errors=array('OrderStatusMakeOperationForbidden');
3099
			return -2;
3100
		}
3101
	}
3102
3103
	/**
3104
	 *      Update database
3105
	 *
3106
	 *      @param      User	$user        	User that modify
3107
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
3108
	 *      @return     int      			   	<0 if KO, >0 if OK
3109
	 */
3110
	public function update(User $user, $notrigger = 0)
3111
	{
3112
		global $conf;
3113
3114
		$error=0;
3115
3116
		// Clean parameters
3117
		if (isset($this->ref)) $this->ref=trim($this->ref);
3118
		if (isset($this->ref_client)) $this->ref_client=trim($this->ref_client);
3119
		if (isset($this->note) || isset($this->note_private)) $this->note_private=(isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3120
		if (isset($this->note_public)) $this->note_public=trim($this->note_public);
3121
		if (isset($this->modelpdf)) $this->modelpdf=trim($this->modelpdf);
3122
		if (isset($this->import_key)) $this->import_key=trim($this->import_key);
3123
3124
		// Check parameters
3125
		// Put here code to add control on parameters values
3126
3127
		// Update request
3128
		$sql = "UPDATE ".MAIN_DB_PREFIX."commande SET";
3129
3130
		$sql.= " ref=".(isset($this->ref)?"'".$this->db->escape($this->ref)."'":"null").",";
3131
		$sql.= " ref_client=".(isset($this->ref_client)?"'".$this->db->escape($this->ref_client)."'":"null").",";
3132
		$sql.= " ref_ext=".(isset($this->ref_ext)?"'".$this->db->escape($this->ref_ext)."'":"null").",";
3133
		$sql.= " fk_soc=".(isset($this->socid)?$this->socid:"null").",";
3134
		$sql.= " date_commande=".(strval($this->date_commande)!='' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
3135
		$sql.= " date_valid=".(strval($this->date_validation)!='' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
3136
		$sql.= " tva=".(isset($this->total_tva)?$this->total_tva:"null").",";
3137
		$sql.= " localtax1=".(isset($this->total_localtax1)?$this->total_localtax1:"null").",";
3138
		$sql.= " localtax2=".(isset($this->total_localtax2)?$this->total_localtax2:"null").",";
3139
		$sql.= " total_ht=".(isset($this->total_ht)?$this->total_ht:"null").",";
3140
		$sql.= " total_ttc=".(isset($this->total_ttc)?$this->total_ttc:"null").",";
3141
		$sql.= " fk_statut=".(isset($this->statut)?$this->statut:"null").",";
3142
		$sql.= " fk_user_author=".(isset($this->user_author_id)?$this->user_author_id:"null").",";
3143
		$sql.= " fk_user_valid=".(isset($this->user_valid)?$this->user_valid:"null").",";
3144
		$sql.= " fk_projet=".(isset($this->fk_project)?$this->fk_project:"null").",";
3145
		$sql.= " fk_cond_reglement=".(isset($this->cond_reglement_id)?$this->cond_reglement_id:"null").",";
3146
		$sql.= " fk_mode_reglement=".(isset($this->mode_reglement_id)?$this->mode_reglement_id:"null").",";
3147
		$sql.= " fk_account=".($this->fk_account>0?$this->fk_account:"null").",";
3148
		$sql.= " note_private=".(isset($this->note_private)?"'".$this->db->escape($this->note_private)."'":"null").",";
3149
		$sql.= " note_public=".(isset($this->note_public)?"'".$this->db->escape($this->note_public)."'":"null").",";
3150
		$sql.= " model_pdf=".(isset($this->modelpdf)?"'".$this->db->escape($this->modelpdf)."'":"null").",";
3151
		$sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null")."";
3152
3153
		$sql.= " WHERE rowid=".$this->id;
3154
3155
		$this->db->begin();
3156
3157
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
3158
		$resql = $this->db->query($sql);
3159
		if (! $resql) {
3160
			$error++; $this->errors[]="Error ".$this->db->lasterror();
3161
		}
3162
3163
		if (! $error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options)>0)
3164
		{
3165
			$result=$this->insertExtraFields();
3166
			if ($result < 0)
3167
			{
3168
				$error++;
3169
			}
3170
		}
3171
3172
		if (! $error && ! $notrigger)
3173
		{
3174
			// Call trigger
3175
			$result=$this->call_trigger('ORDER_MODIFY', $user);
3176
			if ($result < 0) $error++;
3177
			// End call triggers
3178
		}
3179
3180
		// Commit or rollback
3181
		if ($error)
3182
		{
3183
			foreach($this->errors as $errmsg)
3184
			{
3185
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3186
				$this->error.=($this->error?', '.$errmsg:$errmsg);
3187
			}
3188
			$this->db->rollback();
3189
			return -1*$error;
3190
		}
3191
		else
3192
		{
3193
			$this->db->commit();
3194
			return 1;
3195
		}
3196
	}
3197
3198
	/**
3199
	 *	Delete the customer order
3200
	 *
3201
	 *	@param	User	$user		User object
3202
	 *	@param	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3203
	 * 	@return	int					<=0 if KO, >0 if OK
3204
	 */
3205
	public function delete($user, $notrigger = 0)
3206
	{
3207
		global $conf, $langs;
3208
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3209
3210
		$error = 0;
3211
3212
		dol_syslog(get_class($this) . "::delete ".$this->id, LOG_DEBUG);
3213
3214
		$this->db->begin();
3215
3216
		if (! $error && ! $notrigger)
3217
		{
3218
			// Call trigger
3219
			$result=$this->call_trigger('ORDER_DELETE', $user);
3220
			if ($result < 0) $error++;
3221
			// End call triggers
3222
		}
3223
3224
		if ($this->nb_expedition() != 0)
3225
		{
3226
			$this->errors[] = $langs->trans('SomeShipmentExists');
3227
			$error++;
3228
		}
3229
3230
		if (! $error)
3231
		{
3232
			// Delete order details
3233
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."commandedet WHERE fk_commande = ".$this->id;
3234
			if (! $this->db->query($sql) )
3235
			{
3236
				$error++;
3237
				$this->errors[]=$this->db->lasterror();
3238
			}
3239
		}
3240
3241
		if (! $error)
3242
		{
3243
			// Delete linked object
3244
			$res = $this->deleteObjectLinked();
3245
			if ($res < 0) $error++;
3246
		}
3247
3248
		if (! $error)
3249
		{
3250
			// Delete linked contacts
3251
			$res = $this->delete_linked_contact();
3252
			if ($res < 0) $error++;
3253
		}
3254
3255
		if (! $error)
3256
		{
3257
			// Remove extrafields
3258
			if ((! $error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) // For avoid conflicts if trigger used
3259
			{
3260
				$result=$this->deleteExtraFields();
3261
				if ($result < 0)
3262
				{
3263
					$error++;
3264
					dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
3265
				}
3266
			}
3267
		}
3268
3269
		if (! $error)
3270
		{
3271
			// Delete object
3272
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX."commande WHERE rowid = ".$this->id;
3273
			if (! $this->db->query($sql) )
3274
			{
3275
				$error++;
3276
				$this->errors[]=$this->db->lasterror();
3277
			}
3278
		}
3279
3280
		if (! $error)
3281
		{
3282
			// Remove directory with files
3283
			$comref = dol_sanitizeFileName($this->ref);
3284
			if ($conf->commande->dir_output && !empty($this->ref))
3285
			{
3286
				$dir = $conf->commande->dir_output . "/" . $comref ;
3287
				$file = $conf->commande->dir_output . "/" . $comref . "/" . $comref . ".pdf";
3288
				if (file_exists($file))	// We must delete all files before deleting directory
3289
				{
3290
					dol_delete_preview($this);
3291
3292
					if (! dol_delete_file($file, 0, 0, 0, $this)) // For triggers
3293
					{
3294
						$this->db->rollback();
3295
						return 0;
3296
					}
3297
				}
3298
				if (file_exists($dir))
3299
				{
3300
					if (! dol_delete_dir_recursive($dir))
3301
					{
3302
						$this->error=$langs->trans("ErrorCanNotDeleteDir", $dir);
3303
						$this->db->rollback();
3304
						return 0;
3305
					}
3306
				}
3307
			}
3308
		}
3309
3310
		if (! $error)
3311
		{
3312
			$this->db->commit();
3313
			return 1;
3314
		}
3315
		else
3316
		{
3317
			foreach($this->errors as $errmsg)
3318
			{
3319
				$this->error.=($this->error?', '.$errmsg:$errmsg);
3320
			}
3321
			$this->db->rollback();
3322
			return -1*$error;
3323
		}
3324
	}
3325
3326
3327
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3328
	/**
3329
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3330
	 *
3331
	 *	@param		User	$user   Object user
3332
	 *	@return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
3333
	 */
3334
	public function load_board($user)
3335
	{
3336
        // phpcs:enable
3337
		global $conf, $langs;
3338
3339
		$clause = " WHERE";
3340
3341
		$sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3342
		$sql.= " FROM ".MAIN_DB_PREFIX."commande as c";
3343
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3344
		{
3345
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3346
			$sql.= " WHERE sc.fk_user = " .$user->id;
3347
			$clause = " AND";
3348
		}
3349
		$sql.= $clause." c.entity IN (".getEntity('commande').")";
3350
		//$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3351
		$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
3352
		if ($user->societe_id) $sql.=" AND c.fk_soc = ".$user->societe_id;
3353
3354
		$resql=$this->db->query($sql);
3355
		if ($resql)
3356
		{
3357
			$response = new WorkboardResponse();
3358
			$response->warning_delay=$conf->commande->client->warning_delay/60/60/24;
3359
			$response->label=$langs->trans("OrdersToProcess");
3360
			$response->url=DOL_URL_ROOT.'/commande/list.php?viewstatut=-3&mainmenu=commercial&leftmenu=orders';
3361
			$response->img=img_object('', "order");
3362
3363
			$generic_commande = new Commande($this->db);
3364
3365
			while ($obj=$this->db->fetch_object($resql))
3366
			{
3367
				$response->nbtodo++;
3368
				$response->total+= $obj->total_ht;
3369
3370
				$generic_commande->statut = $obj->fk_statut;
3371
				$generic_commande->date_commande = $this->db->jdate($obj->date_commande);
3372
				$generic_commande->date_livraison = $this->db->jdate($obj->delivery_date);
3373
3374
				if ($generic_commande->hasDelay()) {
3375
					$response->nbtodolate++;
3376
				}
3377
			}
3378
3379
			return $response;
3380
		}
3381
		else
3382
		{
3383
			$this->error=$this->db->error();
3384
			return -1;
3385
		}
3386
	}
3387
3388
	/**
3389
	 *	Return source label of order
3390
	 *
3391
	 *	@return     string      Label
3392
	 */
3393
	public function getLabelSource()
3394
	{
3395
		global $langs;
3396
3397
		$label=$langs->trans('OrderSource'.$this->source);
3398
3399
		if ($label == 'OrderSource') return '';
3400
		return $label;
3401
	}
3402
3403
	/**
3404
	 *	Return status label of Order
3405
	 *
3406
	 *	@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
3407
	 *	@return     string      		Label of status
3408
	 */
3409
	public function getLibStatut($mode)
3410
	{
3411
		return $this->LibStatut($this->statut, $this->billed, $mode);
3412
	}
3413
3414
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3415
	/**
3416
	 *	Return label of status
3417
	 *
3418
	 *	@param		int		$statut      	  Id statut
3419
	 *  @param      int		$billed    		  If invoiced
3420
	 *	@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
3421
	 *  @param      int     $donotshowbilled  Do not show billed status after order status
3422
	 *  @return     string					  Label of status
3423
	 */
3424
	public function LibStatut($statut, $billed, $mode, $donotshowbilled = 0)
3425
	{
3426
        // phpcs:enable
3427
		global $langs, $conf;
3428
3429
		$billedtext = '';
3430
		if (empty($donotshowbilled)) $billedtext .= ($billed?' - '.$langs->trans("Billed"):'');
3431
3432
		if ($statut==self::STATUS_CANCELED){
3433
		    $labelstatut = $langs->trans('StatusOrderCanceled');
3434
		    $labelstatutShort = $langs->trans('StatusOrderCanceledShort');
3435
		    $statusType='status5';
3436
		}
3437
		elseif ($statut==self::STATUS_DRAFT){
3438
		    $labelstatut = $langs->trans('StatusOrderDraft');
3439
		    $labelstatutShort = $langs->trans('StatusOrderDraftShort');
3440
		    $statusType='status0';
3441
		}
3442
		elseif ($statut==self::STATUS_VALIDATED){
3443
		    $labelstatut = $langs->trans('StatusOrderValidated').$billedtext;
3444
		    $labelstatutShort = $langs->trans('StatusOrderValidatedShort').$billedtext;
3445
		    $statusType='status1';
3446
		}
3447
		elseif ($statut==self::STATUS_SHIPMENTONPROCESS){
3448
		    $labelstatut = $langs->trans('StatusOrderSentShort').$billedtext;
3449
		    $labelstatutShort = $langs->trans('StatusOrderSentShort').$billedtext;
3450
		    $statusType='status3';
3451
		}
3452
		elseif ($statut==self::STATUS_CLOSED && (! $billed && empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT))){
3453
		    $labelstatut = $langs->trans('StatusOrderToBill');
3454
		    $labelstatutShort = $langs->trans('StatusOrderToBillShort');
3455
		    $statusType='status4';
3456
		}
3457
		elseif ($statut==self::STATUS_CLOSED && ($billed && empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT))){
3458
		    $labelstatut = $langs->trans('StatusOrderProcessed').$billedtext;
3459
		    $labelstatutShort = $langs->trans('StatusOrderProcessed').$billedtext;
3460
		    $statusType='status6';
3461
		}
3462
		elseif ($statut==self::STATUS_CLOSED && (! empty($conf->global->WORKFLOW_BILL_ON_SHIPMENT))){
3463
		    $labelstatut = $langs->trans('StatusOrderDelivered');
3464
		    $labelstatutShort = $langs->trans('StatusOrderDelivered');
3465
		    $statusType='status6';
3466
		}
3467
		else{
3468
		    $labelstatut = $langs->trans('Unknown');
3469
		    $labelstatutShort = '';
3470
		    $statusType='';
3471
		    $mode = 0;
3472
		}
3473
3474
		return dolGetStatus($labelstatut, $labelstatutShort, '', $statusType, $mode);
3475
	}
3476
3477
3478
	/**
3479
	 *	Return clicable link of object (with eventually picto)
3480
	 *
3481
	 *	@param      int			$withpicto                Add picto into link
3482
	 *	@param      string	    $option                   Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3483
	 *	@param      int			$max          	          Max length to show
3484
	 *	@param      int			$short			          ???
3485
	 *  @param	    int   	    $notooltip		          1=Disable tooltip
3486
	 *  @param      int         $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
3487
	 *	@return     string          			          String with URL
3488
	 */
3489
	public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1)
3490
	{
3491
		global $conf, $langs, $user;
3492
3493
		if (! empty($conf->dol_no_mouse_hover)) $notooltip=1;   // Force disable tooltips
3494
3495
		$result='';
3496
3497
		if (! empty($conf->expedition->enabled) && ($option == '1' || $option == '2')) $url = DOL_URL_ROOT.'/expedition/shipment.php?id='.$this->id;
3498
		else $url = DOL_URL_ROOT.'/commande/card.php?id='.$this->id;
3499
3500
		if (!$user->rights->commande->lire)
3501
			$option = 'nolink';
3502
3503
		if ($option !== 'nolink')
3504
		{
3505
			// Add param to save lastsearch_values or not
3506
			$add_save_lastsearch_values=($save_lastsearch_value == 1 ? 1 : 0);
3507
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values=1;
3508
			if ($add_save_lastsearch_values) $url.='&save_lastsearch_values=1';
3509
		}
3510
3511
		if ($short) return $url;
3512
3513
		$label = '';
3514
3515
		if ($user->rights->commande->lire) {
3516
			$label = '<u>'.$langs->trans("ShowOrder").'</u>';
3517
			$label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3518
			$label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.($this->ref_customer ? $this->ref_customer : $this->ref_client);
3519
			if (!empty($this->total_ht)) {
3520
				$label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3521
			}
3522
			if (!empty($this->total_tva)) {
3523
				$label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1,	$conf->currency);
3524
			}
3525
			if (!empty($this->total_ttc)) {
3526
				$label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3527
			}
3528
		}
3529
3530
		$linkclose='';
3531
		if (empty($notooltip) && $user->rights->commande->lire)
3532
		{
3533
			if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
3534
			{
3535
				$label=$langs->trans("ShowOrder");
3536
				$linkclose.=' alt="'.dol_escape_htmltag($label, 1).'"';
3537
			}
3538
			$linkclose.= ' title="'.dol_escape_htmltag($label, 1).'"';
3539
			$linkclose.=' class="classfortooltip"';
3540
		}
3541
3542
		$linkstart = '<a href="'.$url.'"';
3543
		$linkstart.=$linkclose.'>';
3544
		$linkend='</a>';
3545
3546
		if ($option === 'nolink') {
3547
			$linkstart = '';
3548
			$linkend = '';
3549
		}
3550
3551
		$result .= $linkstart;
3552
		if ($withpicto) $result.=img_object(($notooltip?'':$label), $this->picto, ($notooltip?(($withpicto != 2) ? 'class="paddingright"' : ''):'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip?0:1);
3553
		if ($withpicto != 2) $result.= $this->ref;
3554
		$result .= $linkend;
3555
3556
		return $result;
3557
	}
3558
3559
3560
	/**
3561
	 *	Charge les informations d'ordre info dans l'objet commande
3562
	 *
3563
	 *	@param  int		$id       Id of order
3564
	 *	@return	void
3565
	 */
3566
	public function info($id)
3567
	{
3568
		$sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
3569
		$sql.= ' date_valid as datev,';
3570
		$sql.= ' date_cloture as datecloture,';
3571
		$sql.= ' fk_user_author, fk_user_valid, fk_user_cloture';
3572
		$sql.= ' FROM '.MAIN_DB_PREFIX.'commande as c';
3573
		$sql.= ' WHERE c.rowid = '.$id;
3574
		$result=$this->db->query($sql);
3575
		if ($result)
3576
		{
3577
			if ($this->db->num_rows($result))
3578
			{
3579
				$obj = $this->db->fetch_object($result);
3580
				$this->id = $obj->rowid;
3581
				if ($obj->fk_user_author)
3582
				{
3583
					$cuser = new User($this->db);
3584
					$cuser->fetch($obj->fk_user_author);
3585
					$this->user_creation   = $cuser;
3586
				}
3587
3588
				if ($obj->fk_user_valid)
3589
				{
3590
					$vuser = new User($this->db);
3591
					$vuser->fetch($obj->fk_user_valid);
3592
					$this->user_validation = $vuser;
3593
				}
3594
3595
				if ($obj->fk_user_cloture)
3596
				{
3597
					$cluser = new User($this->db);
3598
					$cluser->fetch($obj->fk_user_cloture);
3599
					$this->user_cloture   = $cluser;
3600
				}
3601
3602
				$this->date_creation     = $this->db->jdate($obj->datec);
3603
				$this->date_modification = $this->db->jdate($obj->datem);
3604
				$this->date_validation   = $this->db->jdate($obj->datev);
3605
				$this->date_cloture      = $this->db->jdate($obj->datecloture);
3606
			}
3607
3608
			$this->db->free($result);
3609
		}
3610
		else
3611
		{
3612
			dol_print_error($this->db);
3613
		}
3614
	}
3615
3616
3617
	/**
3618
	 *  Initialise an instance with random values.
3619
	 *  Used to build previews or test instances.
3620
	 *	id must be 0 if object instance is a specimen.
3621
	 *
3622
	 *  @return	void
3623
	 */
3624
	public function initAsSpecimen()
3625
	{
3626
		global $langs;
3627
3628
		dol_syslog(get_class($this)."::initAsSpecimen");
3629
3630
		// Load array of products prodids
3631
		$num_prods = 0;
3632
		$prodids = array();
3633
		$sql = "SELECT rowid";
3634
		$sql.= " FROM ".MAIN_DB_PREFIX."product";
3635
		$sql.= " WHERE entity IN (".getEntity('product').")";
3636
		$resql = $this->db->query($sql);
3637
		if ($resql)
3638
		{
3639
			$num_prods = $this->db->num_rows($resql);
3640
			$i = 0;
3641
			while ($i < $num_prods)
3642
			{
3643
				$i++;
3644
				$row = $this->db->fetch_row($resql);
3645
				$prodids[$i] = $row[0];
3646
			}
3647
		}
3648
3649
		// Initialise parametres
3650
		$this->id=0;
3651
		$this->ref = 'SPECIMEN';
3652
		$this->specimen=1;
3653
		$this->socid = 1;
3654
		$this->date = time();
3655
		$this->date_lim_reglement=$this->date+3600*24*30;
3656
		$this->cond_reglement_code = 'RECEP';
3657
		$this->mode_reglement_code = 'CHQ';
3658
		$this->availability_code   = 'DSP';
3659
		$this->demand_reason_code  = 'SRC_00';
3660
		$this->note_public='This is a comment (public)';
3661
		$this->note_private='This is a comment (private)';
3662
		// Lines
3663
		$nbp = 5;
3664
		$xnbp = 0;
3665
		while ($xnbp < $nbp)
3666
		{
3667
			$line=new OrderLine($this->db);
3668
3669
			$line->desc=$langs->trans("Description")." ".$xnbp;
3670
			$line->qty=1;
3671
			$line->subprice=100;
3672
			$line->price=100;
3673
			$line->tva_tx=20;
3674
			if ($xnbp == 2)
3675
			{
3676
				$line->total_ht=50;
3677
				$line->total_ttc=60;
3678
				$line->total_tva=10;
3679
				$line->remise_percent=50;
3680
			}
3681
			else
3682
			{
3683
				$line->total_ht=100;
3684
				$line->total_ttc=120;
3685
				$line->total_tva=20;
3686
				$line->remise_percent=0;
3687
			}
3688
			if ($num_prods > 0)
3689
			{
3690
				$prodid = mt_rand(1, $num_prods);
3691
				$line->fk_product=$prodids[$prodid];
3692
				$line->product_ref='SPECIMEN';
3693
			}
3694
3695
			$this->lines[$xnbp]=$line;
3696
3697
			$this->total_ht       += $line->total_ht;
3698
			$this->total_tva      += $line->total_tva;
3699
			$this->total_ttc      += $line->total_ttc;
3700
3701
			$xnbp++;
3702
		}
3703
	}
3704
3705
3706
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3707
	/**
3708
	 *	Charge indicateurs this->nb de tableau de bord
3709
	 *
3710
	 *	@return     int         <0 si ko, >0 si ok
3711
	 */
3712
	public function load_state_board()
3713
	{
3714
        // phpcs:enable
3715
		global $user;
3716
3717
		$this->nb=array();
3718
		$clause = "WHERE";
3719
3720
		$sql = "SELECT count(co.rowid) as nb";
3721
		$sql.= " FROM ".MAIN_DB_PREFIX."commande as co";
3722
		$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON co.fk_soc = s.rowid";
3723
		if (!$user->rights->societe->client->voir && !$user->societe_id)
3724
		{
3725
			$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3726
			$sql.= " WHERE sc.fk_user = " .$user->id;
3727
			$clause = "AND";
3728
		}
3729
		$sql.= " ".$clause." co.entity IN (".getEntity('commande').")";
3730
3731
		$resql=$this->db->query($sql);
3732
		if ($resql)
3733
		{
3734
			while ($obj=$this->db->fetch_object($resql))
3735
			{
3736
				$this->nb["orders"]=$obj->nb;
3737
			}
3738
			$this->db->free($resql);
3739
			return 1;
3740
		}
3741
		else
3742
		{
3743
			dol_print_error($this->db);
3744
			$this->error=$this->db->error();
3745
			return -1;
3746
		}
3747
	}
3748
3749
	/**
3750
	 * 	Create an array of order lines
3751
	 *
3752
	 * 	@return int		>0 if OK, <0 if KO
3753
	 */
3754
	public function getLinesArray()
3755
	{
3756
		return $this->fetch_lines();
3757
	}
3758
3759
	/**
3760
	 *  Create a document onto disk according to template module.
3761
	 *
3762
	 *  @param	    string		$modele			Force template to use ('' to not force)
3763
	 *  @param		Translate	$outputlangs	objet lang a utiliser pour traduction
3764
	 *  @param      int			$hidedetails    Hide details of lines
3765
	 *  @param      int			$hidedesc       Hide description
3766
	 *  @param      int			$hideref        Hide ref
3767
	 *  @param      null|array  $moreparams     Array to provide more information
3768
	 *  @return     int         				0 if KO, 1 if OK
3769
	 */
3770
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3771
	{
3772
		global $conf,$langs;
3773
3774
		$langs->load("orders");
3775
3776
		if (! dol_strlen($modele)) {
3777
3778
			$modele = 'einstein';
3779
3780
			if ($this->modelpdf) {
3781
				$modele = $this->modelpdf;
3782
			} elseif (! empty($conf->global->COMMANDE_ADDON_PDF)) {
3783
				$modele = $conf->global->COMMANDE_ADDON_PDF;
3784
			}
3785
		}
3786
3787
		$modelpath = "core/modules/commande/doc/";
3788
3789
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3790
	}
3791
3792
3793
	/**
3794
	 * Function used to replace a thirdparty id with another one.
3795
	 *
3796
	 * @param DoliDB $db Database handler
3797
	 * @param int $origin_id Old thirdparty id
3798
	 * @param int $dest_id New thirdparty id
3799
	 * @return bool
3800
	 */
3801
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
3802
	{
3803
		$tables = array(
3804
		'commande'
3805
		);
3806
3807
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3808
	}
3809
3810
	/**
3811
	 * Is the customer order delayed?
3812
	 *
3813
	 * @return bool     true if late, false if not
3814
	 */
3815
	public function hasDelay()
3816
	{
3817
		global $conf;
3818
3819
		if (! ($this->statut > Commande::STATUS_DRAFT && $this->statut < Commande::STATUS_CLOSED)) {
3820
			return false;   // Never late if not inside this status range
3821
		}
3822
3823
		$now = dol_now();
3824
3825
		return max($this->date_commande, $this->date_livraison) < ($now - $conf->commande->client->warning_delay);
3826
	}
3827
3828
	/**
3829
	 * Show the customer delayed info
3830
	 *
3831
	 * @return string       Show delayed information
3832
	 */
3833
	public function showDelay()
3834
	{
3835
		global $conf, $langs;
3836
3837
		if (empty($this->date_livraison)) $text=$langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3838
		else $text=$text=$langs->trans("DeliveryDate").' '.dol_print_date($this->date_livraison, 'day');
3839
		$text.=' '.($conf->commande->client->warning_delay>0?'+':'-').' '.round(abs($conf->commande->client->warning_delay)/3600/24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3840
3841
		return $text;
3842
	}
3843
}
3844
3845
3846
/**
3847
 *  Class to manage order lines
3848
 */
3849
class OrderLine extends CommonOrderLine
3850
{
3851
	/**
3852
	 * @var string ID to identify managed object
3853
	 */
3854
	public $element='commandedet';
3855
3856
	public $table_element='commandedet';
3857
3858
	public $oldline;
3859
3860
	/**
3861
	 * Id of parent order
3862
	 * @var int
3863
	 */
3864
	public $fk_commande;
3865
3866
	/**
3867
	 * Id of parent order
3868
	 * @var int
3869
	 * @deprecated Use fk_commande
3870
	 * @see fk_commande
3871
	 */
3872
	public $commande_id;
3873
3874
	// From llx_commandedet
3875
	public $fk_parent_line;
3876
	public $fk_facture;
3877
3878
	/**
3879
	 * @var string Order lines label
3880
	 */
3881
	public $label;
3882
3883
	public $fk_remise_except;
3884
	public $rang = 0;
3885
	public $fk_fournprice;
3886
3887
	/**
3888
	 * Buy price without taxes
3889
	 * @var float
3890
	 */
3891
	public $pa_ht;
3892
	public $marge_tx;
3893
	public $marque_tx;
3894
3895
	/**
3896
	 * @deprecated
3897
	 * @see remise_percent, fk_remise_except
3898
	 */
3899
	public $remise;
3900
3901
	// Start and end date of the line
3902
	public $date_start;
3903
	public $date_end;
3904
3905
	public $skip_update_total; // Skip update price total for special lines
3906
3907
3908
	/**
3909
	 *      Constructor
3910
	 *
3911
	 *      @param     DoliDB	$db      handler d'acces base de donnee
3912
	 */
3913
    public function __construct($db)
3914
    {
3915
        $this->db= $db;
3916
    }
3917
3918
	/**
3919
	 *  Load line order
3920
	 *
3921
	 *  @param  int		$rowid          Id line order
3922
	 *  @return	int						<0 if KO, >0 if OK
3923
	 */
3924
	public function fetch($rowid)
3925
	{
3926
		$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,';
3927
		$sql.= ' cd.remise, cd.remise_percent, cd.fk_remise_except, cd.subprice,';
3928
		$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,';
3929
		$sql.= ' cd.fk_unit,';
3930
		$sql.= ' cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,';
3931
		$sql.= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc, p.tobatch as product_tobatch,';
3932
		$sql.= ' cd.date_start, cd.date_end';
3933
		$sql.= ' FROM '.MAIN_DB_PREFIX.'commandedet as cd';
3934
		$sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON cd.fk_product = p.rowid';
3935
		$sql.= ' WHERE cd.rowid = '.$rowid;
3936
		$result = $this->db->query($sql);
3937
		if ($result)
3938
		{
3939
			$objp = $this->db->fetch_object($result);
3940
			$this->rowid            = $objp->rowid;
3941
			$this->id				= $objp->rowid;
3942
			$this->fk_commande      = $objp->fk_commande;
3943
			$this->fk_parent_line   = $objp->fk_parent_line;
3944
			$this->label            = $objp->custom_label;
3945
			$this->desc             = $objp->description;
3946
			$this->qty              = $objp->qty;
3947
			$this->price            = $objp->price;
3948
			$this->subprice         = $objp->subprice;
3949
			$this->vat_src_code     = $objp->vat_src_code;
3950
			$this->tva_tx           = $objp->tva_tx;
3951
			$this->localtax1_tx		= $objp->localtax1_tx;
3952
			$this->localtax2_tx		= $objp->localtax2_tx;
3953
			$this->remise           = $objp->remise;
3954
			$this->remise_percent   = $objp->remise_percent;
3955
			$this->fk_remise_except = $objp->fk_remise_except;
3956
			$this->fk_product       = $objp->fk_product;
3957
			$this->product_type     = $objp->product_type;
3958
			$this->info_bits        = $objp->info_bits;
3959
			$this->special_code		= $objp->special_code;
3960
			$this->total_ht         = $objp->total_ht;
3961
			$this->total_tva        = $objp->total_tva;
3962
			$this->total_localtax1  = $objp->total_localtax1;
3963
			$this->total_localtax2  = $objp->total_localtax2;
3964
			$this->total_ttc        = $objp->total_ttc;
3965
			$this->fk_fournprice	= $objp->fk_fournprice;
3966
			$marginInfos			= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
3967
			$this->pa_ht			= $marginInfos[0];
3968
			$this->marge_tx			= $marginInfos[1];
3969
			$this->marque_tx		= $marginInfos[2];
3970
			$this->special_code		= $objp->special_code;
3971
			$this->rang             = $objp->rang;
3972
3973
			$this->ref				= $objp->product_ref;      // deprecated
3974
			$this->product_ref		= $objp->product_ref;
3975
			$this->libelle			= $objp->product_libelle;  // deprecated
3976
			$this->product_label	= $objp->product_libelle;
3977
			$this->product_desc     = $objp->product_desc;
3978
			$this->product_tobatch  = $objp->product_tobatch;
3979
			$this->fk_unit          = $objp->fk_unit;
3980
3981
			$this->date_start       = $this->db->jdate($objp->date_start);
3982
			$this->date_end         = $this->db->jdate($objp->date_end);
3983
3984
			$this->fk_multicurrency			= $objp->fk_multicurrency;
3985
			$this->multicurrency_code		= $objp->multicurrency_code;
3986
			$this->multicurrency_subprice	= $objp->multicurrency_subprice;
3987
			$this->multicurrency_total_ht	= $objp->multicurrency_total_ht;
3988
			$this->multicurrency_total_tva	= $objp->multicurrency_total_tva;
3989
			$this->multicurrency_total_ttc	= $objp->multicurrency_total_ttc;
3990
3991
			$this->db->free($result);
3992
3993
			return 1;
3994
		}
3995
		else
3996
		{
3997
			$this->error = $this->db->lasterror();
3998
			return -1;
3999
		}
4000
	}
4001
4002
	/**
4003
	 * 	Delete line in database
4004
	 *
4005
	 *	@param      User	$user        	User that modify
4006
	 *  @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
4007
	 *	@return	 int  <0 si ko, >0 si ok
4008
	 */
4009
	public function delete(User $user, $notrigger = 0)
4010
	{
4011
		global $conf, $langs;
4012
4013
		$error=0;
4014
4015
		$this->db->begin();
4016
4017
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX."commandedet WHERE rowid=".$this->rowid;
4018
4019
		dol_syslog("OrderLine::delete", LOG_DEBUG);
4020
		$resql=$this->db->query($sql);
4021
		if ($resql)
4022
		{
4023
			// Remove extrafields
4024
			if ((! $error) && (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED))) // For avoid conflicts if trigger used
4025
			{
4026
				$this->id=$this->rowid;
4027
				$result=$this->deleteExtraFields();
4028
				if ($result < 0)
4029
				{
4030
					$error++;
4031
					dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
4032
				}
4033
			}
4034
4035
			if (! $error && ! $notrigger)
4036
			{
4037
				// Call trigger
4038
				$result=$this->call_trigger('LINEORDER_DELETE', $user);
4039
				if ($result < 0) $error++;
4040
				// End call triggers
4041
			}
4042
4043
			if (!$error) {
4044
				$this->db->commit();
4045
				return 1;
4046
			}
4047
4048
			foreach($this->errors as $errmsg)
4049
			{
4050
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
4051
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4052
			}
4053
			$this->db->rollback();
4054
			return -1*$error;
4055
		}
4056
		else
4057
		{
4058
			$this->error=$this->db->lasterror();
4059
			return -1;
4060
		}
4061
	}
4062
4063
	/**
4064
	 *	Insert line into database
4065
	 *
4066
	 *	@param      User	$user        	User that modify
4067
	 *	@param      int		$notrigger		1 = disable triggers
4068
	 *	@return		int						<0 if KO, >0 if OK
4069
	 */
4070
	public function insert($user = null, $notrigger = 0)
4071
	{
4072
		global $langs, $conf;
4073
4074
		$error=0;
4075
4076
		$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'.
4077
4078
		dol_syslog(get_class($this)."::insert rang=".$this->rang);
4079
4080
		// Clean parameters
4081
		if (empty($this->tva_tx)) $this->tva_tx=0;
4082
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4083
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4084
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4085
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4086
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4087
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4088
		if (empty($this->rang)) $this->rang=0;
4089
		if (empty($this->remise)) $this->remise=0;
4090
		if (empty($this->remise_percent)) $this->remise_percent=0;
4091
		if (empty($this->info_bits)) $this->info_bits=0;
4092
		if (empty($this->special_code)) $this->special_code=0;
4093
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4094
		if (empty($this->pa_ht)) $this->pa_ht=0;
4095
4096
		// if buy price not defined, define buyprice as configured in margin admin
4097
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4098
		{
4099
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4100
			{
4101
				return $result;
4102
			}
4103
			else
4104
			{
4105
				$this->pa_ht = $result;
4106
			}
4107
		}
4108
4109
		// Check parameters
4110
		if ($this->product_type < 0) return -1;
4111
4112
		$this->db->begin();
4113
4114
		// Insertion dans base de la ligne
4115
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'commandedet';
4116
		$sql.= ' (fk_commande, fk_parent_line, label, description, qty, ';
4117
		$sql.= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4118
		$sql.= ' fk_product, product_type, remise_percent, subprice, price, remise, fk_remise_except,';
4119
		$sql.= ' special_code, rang, fk_product_fournisseur_price, buy_price_ht,';
4120
		$sql.= ' info_bits, total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, date_start, date_end,';
4121
		$sql.= ' fk_unit';
4122
		$sql.= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
4123
		$sql.= ')';
4124
		$sql.= " VALUES (".$this->fk_commande.",";
4125
		$sql.= " ".($this->fk_parent_line>0?"'".$this->db->escape($this->fk_parent_line)."'":"null").",";
4126
		$sql.= " ".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null").",";
4127
		$sql.= " '".$this->db->escape($this->desc)."',";
4128
		$sql.= " '".price2num($this->qty)."',";
4129
		$sql.= " ".(empty($this->vat_src_code)?"''":"'".$this->db->escape($this->vat_src_code)."'").",";
4130
		$sql.= " '".price2num($this->tva_tx)."',";
4131
		$sql.= " '".price2num($this->localtax1_tx)."',";
4132
		$sql.= " '".price2num($this->localtax2_tx)."',";
4133
		$sql.= " '".$this->db->escape($this->localtax1_type)."',";
4134
		$sql.= " '".$this->db->escape($this->localtax2_type)."',";
4135
		$sql.= ' '.(! empty($this->fk_product)?$this->fk_product:"null").',';
4136
		$sql.= " '".$this->db->escape($this->product_type)."',";
4137
		$sql.= " '".price2num($this->remise_percent)."',";
4138
		$sql.= " ".(price2num($this->subprice)!==''?price2num($this->subprice):"null").",";
4139
		$sql.= " ".($this->price!=''?"'".price2num($this->price)."'":"null").",";
4140
		$sql.= " '".price2num($this->remise)."',";
4141
		$sql.= ' '.(! empty($this->fk_remise_except)?$this->fk_remise_except:"null").',';
4142
		$sql.= ' '.$this->special_code.',';
4143
		$sql.= ' '.$this->rang.',';
4144
		$sql.= ' '.(! empty($this->fk_fournprice)?$this->fk_fournprice:"null").',';
4145
		$sql.= ' '.price2num($this->pa_ht).',';
4146
		$sql.= " '".$this->db->escape($this->info_bits)."',";
4147
		$sql.= " ".price2num($this->total_ht).",";
4148
		$sql.= " ".price2num($this->total_tva).",";
4149
		$sql.= " ".price2num($this->total_localtax1).",";
4150
		$sql.= " ".price2num($this->total_localtax2).",";
4151
		$sql.= " ".price2num($this->total_ttc).",";
4152
		$sql.= " ".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null").',';
4153
		$sql.= " ".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null").',';
4154
		$sql.= ' '.(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4155
		$sql.= ", ".(! empty($this->fk_multicurrency) ? $this->fk_multicurrency : 'NULL');
4156
		$sql.= ", '".$this->db->escape($this->multicurrency_code)."'";
4157
		$sql.= ", ".$this->multicurrency_subprice;
4158
		$sql.= ", ".$this->multicurrency_total_ht;
4159
		$sql.= ", ".$this->multicurrency_total_tva;
4160
		$sql.= ", ".$this->multicurrency_total_ttc;
4161
		$sql.= ')';
4162
4163
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4164
		$resql=$this->db->query($sql);
4165
		if ($resql)
4166
		{
4167
			$this->rowid=$this->db->last_insert_id(MAIN_DB_PREFIX.'commandedet');
4168
4169
			if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4170
			{
4171
				$this->id=$this->rowid;
4172
				$result=$this->insertExtraFields();
4173
				if ($result < 0)
4174
				{
4175
					$error++;
4176
				}
4177
			}
4178
4179
			if (! $error && ! $notrigger)
4180
			{
4181
				// Call trigger
4182
				$result=$this->call_trigger('LINEORDER_INSERT', $user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 4070 can be null; however, CommonObject::call_trigger() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
4183
				if ($result < 0) $error++;
4184
				// End call triggers
4185
			}
4186
4187
			if (!$error) {
4188
				$this->db->commit();
4189
				return 1;
4190
			}
4191
4192
			foreach($this->errors as $errmsg)
4193
			{
4194
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
4195
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4196
			}
4197
			$this->db->rollback();
4198
			return -1*$error;
4199
		}
4200
		else
4201
		{
4202
			$this->error=$this->db->error();
4203
			$this->db->rollback();
4204
			return -2;
4205
		}
4206
	}
4207
4208
	/**
4209
	 *	Update the line object into db
4210
	 *
4211
	 *	@param      User	$user        	User that modify
4212
	 *	@param      int		$notrigger		1 = disable triggers
4213
	 *	@return		int		<0 si ko, >0 si ok
4214
	 */
4215
	public function update(User $user, $notrigger = 0)
4216
	{
4217
		global $conf,$langs;
4218
4219
		$error=0;
4220
4221
		$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'.
4222
4223
		// Clean parameters
4224
		if (empty($this->tva_tx)) $this->tva_tx=0;
4225
		if (empty($this->localtax1_tx)) $this->localtax1_tx=0;
4226
		if (empty($this->localtax2_tx)) $this->localtax2_tx=0;
4227
		if (empty($this->localtax1_type)) $this->localtax1_type=0;
4228
		if (empty($this->localtax2_type)) $this->localtax2_type=0;
4229
		if (empty($this->qty)) $this->qty=0;
4230
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4231
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4232
		if (empty($this->marque_tx)) $this->marque_tx=0;
4233
		if (empty($this->marge_tx)) $this->marge_tx=0;
4234
		if (empty($this->remise)) $this->remise=0;
4235
		if (empty($this->remise_percent)) $this->remise_percent=0;
4236
		if (empty($this->info_bits)) $this->info_bits=0;
4237
		if (empty($this->special_code)) $this->special_code=0;
4238
		if (empty($this->product_type)) $this->product_type=0;
4239
		if (empty($this->fk_parent_line)) $this->fk_parent_line=0;
4240
		if (empty($this->pa_ht)) $this->pa_ht=0;
4241
4242
		// if buy price not defined, define buyprice as configured in margin admin
4243
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4244
		{
4245
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4246
			{
4247
				return $result;
4248
			}
4249
			else
4250
			{
4251
				$this->pa_ht = $result;
4252
			}
4253
		}
4254
4255
		$this->db->begin();
4256
4257
		// Mise a jour ligne en base
4258
		$sql = "UPDATE ".MAIN_DB_PREFIX."commandedet SET";
4259
		$sql.= " description='".$this->db->escape($this->desc)."'";
4260
		$sql.= " , label=".(! empty($this->label)?"'".$this->db->escape($this->label)."'":"null");
4261
		$sql.= " , vat_src_code=".(! empty($this->vat_src_code)?"'".$this->db->escape($this->vat_src_code)."'":"''");
4262
		$sql.= " , tva_tx=".price2num($this->tva_tx);
4263
		$sql.= " , localtax1_tx=".price2num($this->localtax1_tx);
4264
		$sql.= " , localtax2_tx=".price2num($this->localtax2_tx);
4265
		$sql.= " , localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4266
		$sql.= " , localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4267
		$sql.= " , qty=".price2num($this->qty);
4268
		$sql.= " , subprice=".price2num($this->subprice)."";
4269
		$sql.= " , remise_percent=".price2num($this->remise_percent)."";
4270
		$sql.= " , price=".price2num($this->price)."";					// TODO A virer
4271
		$sql.= " , remise=".price2num($this->remise)."";				// TODO A virer
4272
		if (empty($this->skip_update_total))
4273
		{
4274
			$sql.= " , total_ht=".price2num($this->total_ht)."";
4275
			$sql.= " , total_tva=".price2num($this->total_tva)."";
4276
			$sql.= " , total_ttc=".price2num($this->total_ttc)."";
4277
			$sql.= " , total_localtax1=".price2num($this->total_localtax1);
4278
			$sql.= " , total_localtax2=".price2num($this->total_localtax2);
4279
		}
4280
		$sql.= " , fk_product_fournisseur_price=".(! empty($this->fk_fournprice)?$this->fk_fournprice:"null");
4281
		$sql.= " , buy_price_ht='".price2num($this->pa_ht)."'";
4282
		$sql.= " , info_bits=".$this->info_bits;
4283
		$sql.= " , special_code=".$this->special_code;
4284
		$sql.= " , date_start=".(! empty($this->date_start)?"'".$this->db->idate($this->date_start)."'":"null");
4285
		$sql.= " , date_end=".(! empty($this->date_end)?"'".$this->db->idate($this->date_end)."'":"null");
4286
		$sql.= " , product_type=".$this->product_type;
4287
		$sql.= " , fk_parent_line=".(! empty($this->fk_parent_line)?$this->fk_parent_line:"null");
4288
		if (! empty($this->rang)) $sql.= ", rang=".$this->rang;
4289
		$sql.= " , fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4290
4291
		// Multicurrency
4292
		$sql.= " , multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4293
		$sql.= " , multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4294
		$sql.= " , multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4295
		$sql.= " , multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4296
4297
		$sql.= " WHERE rowid = ".$this->rowid;
4298
4299
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
4300
		$resql=$this->db->query($sql);
4301
		if ($resql)
4302
		{
4303
			if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
4304
			{
4305
				$this->id=$this->rowid;
4306
				$result=$this->insertExtraFields();
4307
				if ($result < 0)
4308
				{
4309
					$error++;
4310
				}
4311
			}
4312
4313
			if (! $error && ! $notrigger)
4314
			{
4315
				// Call trigger
4316
				$result=$this->call_trigger('LINEORDER_UPDATE', $user);
4317
				if ($result < 0) $error++;
4318
				// End call triggers
4319
			}
4320
4321
			if (!$error) {
4322
				$this->db->commit();
4323
				return 1;
4324
			}
4325
4326
			foreach($this->errors as $errmsg)
4327
			{
4328
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
4329
				$this->error.=($this->error?', '.$errmsg:$errmsg);
4330
			}
4331
			$this->db->rollback();
4332
			return -1*$error;
4333
		}
4334
		else
4335
		{
4336
			$this->error=$this->db->error();
4337
			$this->db->rollback();
4338
			return -2;
4339
		}
4340
	}
4341
4342
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4343
	/**
4344
	 *	Update DB line fields total_xxx
4345
	 *	Used by migration
4346
	 *
4347
	 *	@return		int		<0 if KO, >0 if OK
4348
	 */
4349
	public function update_total()
4350
	{
4351
        // phpcs:enable
4352
		$this->db->begin();
4353
4354
		// Clean parameters
4355
		if (empty($this->total_localtax1)) $this->total_localtax1=0;
4356
		if (empty($this->total_localtax2)) $this->total_localtax2=0;
4357
4358
		// Mise a jour ligne en base
4359
		$sql = "UPDATE ".MAIN_DB_PREFIX."commandedet SET";
4360
		$sql.= " total_ht='".price2num($this->total_ht)."'";
4361
		$sql.= ",total_tva='".price2num($this->total_tva)."'";
4362
		$sql.= ",total_localtax1='".price2num($this->total_localtax1)."'";
4363
		$sql.= ",total_localtax2='".price2num($this->total_localtax2)."'";
4364
		$sql.= ",total_ttc='".price2num($this->total_ttc)."'";
4365
		$sql.= " WHERE rowid = ".$this->rowid;
4366
4367
		dol_syslog("OrderLine::update_total", LOG_DEBUG);
4368
4369
		$resql=$this->db->query($sql);
4370
		if ($resql)
4371
		{
4372
			$this->db->commit();
4373
			return 1;
4374
		}
4375
		else
4376
		{
4377
			$this->error=$this->db->error();
4378
			$this->db->rollback();
4379
			return -2;
4380
		}
4381
	}
4382
}
4383