dolibarr 22.0.5
commande.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
6 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2011 Jean Heimburger <jean@tiaris.info>
8 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
9 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
10 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
11 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
12 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
13 * Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
14 * Copyright (C) 2021-2025 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
16 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
17 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
39require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
40require_once DOL_DOCUMENT_ROOT.'/commande/class/orderline.class.php';
41require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
42require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
43require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
44require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
45require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
46
47
51class Commande extends CommonOrder
52{
53 use CommonSubtotal;
54
58 public $element = 'commande';
59
63 public $table_element = 'commande';
64
68 public $table_element_line = 'commandedet';
69
73 public $class_element_line = 'OrderLine';
74
78 public $fk_element = 'fk_commande';
79
83 public $picto = 'order';
84
89 public $restrictiononfksoc = 1;
90
94 protected $table_ref_field = 'ref';
95
99 public $socid;
100
104 public $ref_client;
105
109 public $ref_customer;
110
114 public $contactid;
115
121 public $statut;
122
127 public $status;
128
132 public $billed;
133
137 public $date_lim_reglement;
141 public $cond_reglement_code;
142
146 public $cond_reglement_doc;
147
153 public $deposit_percent;
154
158 public $fk_account;
159
163 public $mode_reglement;
164
168 public $mode_reglement_id;
169
173 public $mode_reglement_code;
174
179 public $availability_id;
180
185 public $availability_code;
186
191 public $availability;
192
196 public $demand_reason_id;
197
201 public $demand_reason_code;
202
206 public $date;
207
213 public $date_commande;
214
218 public $delivery_date;
219
223 public $fk_remise_except;
224
229 public $remise_percent;
230
234 public $source;
235
240 public $signed_status = 0;
241
245 public $warehouse_id;
246
250 public $extraparams = array();
251
252 public $linked_objects = array();
253
257 public $user_author_id;
258
262 public $line;
263
267 public $lines = array();
268
269
273 public $module_source;
277 public $pos_source;
278
282 public $expeditions;
283
287 public $online_payment_url;
288
289
290
316 // BEGIN MODULEBUILDER PROPERTIES
320 public $fields = array(
321 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
322 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
323 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 25),
324 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 26),
325 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 28),
326 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 20),
327 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 25),
328 'date_commande' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => 1, 'position' => 60, 'csslist' => 'nowraponall'),
329 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 62, 'csslist' => 'nowraponall'),
330 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 65, 'csslist' => 'nowraponall'),
331 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 85),
332 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 90),
333 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => -1, 'position' => 95),
334 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
335 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
336 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
337 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
338 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
339 'signed_status' => array('type' => 'smallint(6)', 'label' => 'SignedStatus', 'enabled' => 1, 'visible' => -1, 'position' => 146, 'arrayofkeyval' => array(0 => 'NoSignature', 1 => 'SignedSender', 2 => 'SignedReceiver', 9 => 'SignedAll')),
340 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 150),
341 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 155),
342 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 160),
343 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 170),
344 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 175),
345 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 180),
346 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 181),
347 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 185),
348 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 190, 'csslist' => 'nowraponall'),
349 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 195),
350 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'DefaultWarehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 200, 'nodepth' => 1),
351 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 205),
352 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 210),
353 //'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
354 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 225),
355 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 230),
356 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 235),
357 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
358 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245),
359 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
360 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
361 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 260, 'isameasure' => 1),
362 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 265, 'isameasure' => 1),
363 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 270),
364 'module_source' => array('type' => 'varchar(32)', 'label' => 'POSModule', 'enabled' => 1, 'visible' => -1, 'position' => 275),
365 'pos_source' => array('type' => 'varchar(32)', 'label' => 'POSTerminal', 'enabled' => 1, 'visible' => -1, 'position' => 280),
366 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 300),
367 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 302),
368 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 304, 'csslist' => 'nowraponall'),
369 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 306),
370 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 400),
371 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'position' => 500),
372 );
373 // END MODULEBUILDER PROPERTIES
374
379
383 const STATUS_CANCELED = -1;
387 const STATUS_DRAFT = 0;
395 const STATUS_SHIPMENTONPROCESS = 2; // We set this status when a shipment is validated
396
402
406 const STATUS_CLOSED = 3;
407
408 /*
409 * No signature
410 */
411 const STATUS_NO_SIGNATURE = 0;
412
413 /*
414 * Signed by sender
415 */
416 const STATUS_SIGNED_SENDER = 1;
417
418 /*
419 * Signed by receiver
420 */
421 const STATUS_SIGNED_RECEIVER = 2;
422
423 /*
424 * Signed by all
425 */
426 const STATUS_SIGNED_ALL = 9; // To handle future kind of signature (ex: tripartite contract)
427
428
434 public function __construct($db)
435 {
436 $this->db = $db;
437
438 $this->ismultientitymanaged = 1;
439 $this->isextrafieldmanaged = 1;
440
441 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
442 }
443
451 public function getNextNumRef($soc)
452 {
453 global $langs, $conf;
454 $langs->load("order");
455
456 if (getDolGlobalString('COMMANDE_ADDON')) {
457 $mybool = false;
458
459 $file = getDolGlobalString('COMMANDE_ADDON') . ".php";
460 $classname = getDolGlobalString('COMMANDE_ADDON');
461
462 // Include file with class
463 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
464 foreach ($dirmodels as $reldir) {
465 $dir = dol_buildpath($reldir."core/modules/commande/");
466
467 // Load file with numbering class (if found)
468 $mybool = ((bool) @include_once $dir.$file) || $mybool;
469 }
470
471 if (!$mybool) {
472 dol_print_error(null, "Failed to include file ".$file);
473 return '';
474 }
475
476 $obj = new $classname();
478 '@phan-var-force ModeleNumRefCommandes $obj';
479
480 $numref = $obj->getNextValue($soc, $this);
481
482 if ($numref != "") {
483 return $numref;
484 } else {
485 $this->error = $obj->error;
486 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
487 return "";
488 }
489 } else {
490 print $langs->trans("Error")." ".$langs->trans("Error_COMMANDE_ADDON_NotDefined");
491 return "";
492 }
493 }
494
495
504 public function valid($user, $idwarehouse = 0, $notrigger = 0)
505 {
506 global $conf, $langs;
507
508 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
509
510 $error = 0;
511
512 // Protection
513 if ($this->status == self::STATUS_VALIDATED) {
514 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
515 return 0;
516 }
517
518 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
519 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
520 $this->error = 'NotEnoughPermissions';
521 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
522 return -1;
523 }
524
525 $now = dol_now();
526
527 $this->db->begin();
528
529 // Definition du nom de module de numerotation de commande
530 $soc = new Societe($this->db);
531 $soc->fetch($this->socid);
532
533 // Class of company linked to order
534 $result = $soc->setAsCustomer();
535
536 // Define new ref
537 if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
538 $num = $this->getNextNumRef($soc);
539 } else {
540 $num = $this->ref;
541 }
542 $this->newref = dol_sanitizeFileName($num);
543
544 // Validate
545 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
546 $sql .= " SET ref = '".$this->db->escape($num)."',";
547 $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
548 $sql .= " date_valid = '".$this->db->idate($now)."',";
549 $sql .= " fk_user_valid = ".($user->id > 0 ? (int) $user->id : "null").",";
550 $sql .= " fk_user_modif = ".((int) $user->id);
551 $sql .= " WHERE rowid = ".((int) $this->id);
552
553 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
554 $resql = $this->db->query($sql);
555 if (!$resql) {
556 dol_print_error($this->db);
557 $this->error = $this->db->lasterror();
558 $error++;
559 }
560
561 if (!$error) {
562 // If stock is incremented on validate order, we must increment it
563 if ($result >= 0 && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
564 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
565 $langs->load("agenda");
566
567 // Loop on each line
568 $cpt = count($this->lines);
569 for ($i = 0; $i < $cpt; $i++) {
570 if ($this->lines[$i]->fk_product > 0) {
571 $mouvP = new MouvementStock($this->db);
572 $mouvP->origin = &$this;
573 $mouvP->setOrigin($this->element, $this->id);
574 // We decrement stock of product (and sub-products)
575 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
576 if ($result < 0) {
577 $error++;
578 $this->error = $mouvP->error;
579 }
580 }
581 if ($error) {
582 break;
583 }
584 }
585 }
586 }
587
588 if (!$error && !$notrigger) {
589 // Call trigger
590 $result = $this->call_trigger('ORDER_VALIDATE', $user);
591 if ($result < 0) {
592 $error++;
593 }
594 // End call triggers
595 }
596
597 if (!$error) {
598 $this->oldref = $this->ref;
599
600 // Rename directory if dir was a temporary ref
601 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
602 // Now we rename also files into index
603 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'commande/".$this->db->escape($this->newref)."'";
604 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
605 $resql = $this->db->query($sql);
606 if (!$resql) {
607 $error++;
608 $this->error = $this->db->lasterror();
609 }
610 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'commande/".$this->db->escape($this->newref)."'";
611 $sql .= " WHERE filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
612 $resql = $this->db->query($sql);
613 if (!$resql) {
614 $error++;
615 $this->error = $this->db->lasterror();
616 }
617
618 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
619 $oldref = dol_sanitizeFileName($this->ref);
620 $newref = dol_sanitizeFileName($num);
621 $dirsource = $conf->commande->multidir_output[$this->entity].'/'.$oldref;
622 $dirdest = $conf->commande->multidir_output[$this->entity].'/'.$newref;
623 if (!$error && file_exists($dirsource)) {
624 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
625
626 if (@rename($dirsource, $dirdest)) {
627 dol_syslog("Rename ok");
628 // Rename docs starting with $oldref with $newref
629 $listoffiles = dol_dir_list($conf->commande->multidir_output[$this->entity].'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
630 foreach ($listoffiles as $fileentry) {
631 $dirsource = $fileentry['name'];
632 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
633 $dirsource = $fileentry['path'].'/'.$dirsource;
634 $dirdest = $fileentry['path'].'/'.$dirdest;
635 @rename($dirsource, $dirdest);
636 }
637 }
638 }
639 }
640 }
641
642 // Set new ref and current status
643 if (!$error) {
644 $this->ref = $num;
645 $this->statut = self::STATUS_VALIDATED; // deprecated
647 }
648
649 if (!$error) {
650 $this->db->commit();
651 return 1;
652 } else {
653 $this->db->rollback();
654 return -1;
655 }
656 }
657
658 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
666 public function setDraft($user, $idwarehouse = -1)
667 {
668 //phpcs:enable
669 global $langs;
670
671 $error = 0;
672
673 // Protection
674 if ($this->status <= self::STATUS_DRAFT && !getDolGlobalInt('ORDER_REOPEN_TO_DRAFT')) {
675 return 0;
676 }
677
678 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
679 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
680 $this->error = 'Permission denied';
681 return -1;
682 }
683
684 dol_syslog(__METHOD__, LOG_DEBUG);
685
686 $this->db->begin();
687
688 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
689 $sql .= " SET fk_statut = ".((int) self::STATUS_DRAFT).",";
690 $sql .= " fk_user_modif = ".((int) $user->id);
691 $sql .= " WHERE rowid = ".((int) $this->id);
692
693 if ($this->db->query($sql)) {
694 if (!$error) {
695 $this->oldcopy = clone $this;
696 }
697
698 // If stock is decremented on validate order, we must reincrement it
699 if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
700 $result = 0;
701
702 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
703 $langs->load("agenda");
704
705 $num = count($this->lines);
706 for ($i = 0; $i < $num; $i++) {
707 if ($this->lines[$i]->fk_product > 0) {
708 $mouvP = new MouvementStock($this->db);
709 $mouvP->origin = &$this;
710 $mouvP->setOrigin($this->element, $this->id);
711 // We increment stock of product (and sub-products)
712 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
713 if ($result < 0) {
714 $error++;
715 $this->error = $mouvP->error;
716 break;
717 }
718 }
719 }
720 }
721
722 if (!$error) {
723 // Call trigger
724 $result = $this->call_trigger('ORDER_UNVALIDATE', $user);
725 if ($result < 0) {
726 $error++;
727 }
728 }
729
730 if (!$error) {
731 $this->statut = self::STATUS_DRAFT; // deprecated
732 $this->status = self::STATUS_DRAFT;
733 $this->db->commit();
734 return 1;
735 } else {
736 $this->db->rollback();
737 return -1;
738 }
739 } else {
740 $this->error = $this->db->error();
741 $this->db->rollback();
742 return -1;
743 }
744 }
745
746
747 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
755 public function set_reopen($user)
756 {
757 // phpcs:enable
758 $error = 0;
759
760 if ($this->status != self::STATUS_CANCELED && $this->status != self::STATUS_CLOSED) {
761 dol_syslog(get_class($this)."::set_reopen order has not status closed", LOG_WARNING);
762 return 0;
763 }
764
765 $this->db->begin();
766
767 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
768 $sql .= ' SET fk_statut='.self::STATUS_VALIDATED.', facture=0,';
769 $sql .= " fk_user_modif = ".((int) $user->id);
770 $sql .= " WHERE rowid = ".((int) $this->id);
771
772 dol_syslog(get_class($this)."::set_reopen", LOG_DEBUG);
773 $resql = $this->db->query($sql);
774 if ($resql) {
775 // Call trigger
776 $result = $this->call_trigger('ORDER_REOPEN', $user);
777 if ($result < 0) {
778 $error++;
779 }
780 // End call triggers
781 } else {
782 $error++;
783 $this->error = $this->db->lasterror();
784 dol_print_error($this->db);
785 }
786
787 if (!$error) {
788 $this->statut = self::STATUS_VALIDATED; // deprecated
790 $this->billed = 0;
791
792 $this->db->commit();
793 return 1;
794 } else {
795 foreach ($this->errors as $errmsg) {
796 dol_syslog(get_class($this)."::set_reopen ".$errmsg, LOG_ERR);
797 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
798 }
799 $this->db->rollback();
800 return -1 * $error;
801 }
802 }
803
811 public function cloture($user, $notrigger = 0)
812 {
813 $error = 0;
814
815 $usercanclose = ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
816 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
817
818 if ($usercanclose) {
819 if ($this->status == self::STATUS_CLOSED) {
820 return 0;
821 }
822 $this->db->begin();
823
824 $now = dol_now();
825
826 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
827 $sql .= ' SET fk_statut = '.self::STATUS_CLOSED.',';
828 $sql .= ' fk_user_cloture = '.((int) $user->id).',';
829 $sql .= " date_cloture = '".$this->db->idate($now)."',";
830 $sql .= " fk_user_modif = ".((int) $user->id);
831 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
832
833 if ($this->db->query($sql)) {
834 if (!$notrigger) {
835 // Call trigger
836 $result = $this->call_trigger('ORDER_CLOSE', $user);
837 if ($result < 0) {
838 $error++;
839 }
840 // End call triggers
841 }
842
843 if (!$error) {
844 $this->statut = self::STATUS_CLOSED; // deprecated
846
847 $this->db->commit();
848 return 1;
849 } else {
850 $this->db->rollback();
851 return -1;
852 }
853 } else {
854 $this->error = $this->db->lasterror();
855
856 $this->db->rollback();
857 return -1;
858 }
859 }
860 return 0;
861 }
862
874 public function setCategories($categories)
875 {
876 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
877 return parent::setCategoriesCommon($categories, Categorie::TYPE_ORDER);
878 }
879
887 public function cancel($idwarehouse = -1)
888 {
889 global $user, $langs;
890
891 $error = 0;
892
893 $this->db->begin();
894
895 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
896 $sql .= " SET fk_statut = ".self::STATUS_CANCELED.",";
897 $sql .= " fk_user_modif = ".((int) $user->id);
898 $sql .= " WHERE rowid = ".((int) $this->id);
899 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
900
901 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
902 if ($this->db->query($sql)) {
903 // If stock is decremented on validate order, we must reincrement it
904 if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
905 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
906 $langs->load("agenda");
907
908 $num = count($this->lines);
909 for ($i = 0; $i < $num; $i++) {
910 if ($this->lines[$i]->fk_product > 0) {
911 $mouvP = new MouvementStock($this->db);
912 $mouvP->setOrigin($this->element, $this->id);
913 // We increment stock of product (and sub-products)
914 $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
915 if ($result < 0) {
916 $error++;
917 $this->error = $mouvP->error;
918 break;
919 }
920 }
921 }
922 }
923
924 if (!$error) {
925 // Call trigger
926 $result = $this->call_trigger('ORDER_CANCEL', $user);
927 if ($result < 0) {
928 $error++;
929 }
930 // End call triggers
931 }
932
933 if (!$error) {
934 $this->statut = self::STATUS_CANCELED; // deprecated
936 $this->db->commit();
937 return 1;
938 } else {
939 foreach ($this->errors as $errmsg) {
940 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
941 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
942 }
943 $this->db->rollback();
944 return -1 * $error;
945 }
946 } else {
947 $this->error = $this->db->error();
948 $this->db->rollback();
949 return -1;
950 }
951 }
952
961 public function create($user, $notrigger = 0)
962 {
963 global $conf, $langs, $mysoc;
964 $error = 0;
965
966 // Clean parameters
967
968 // Set tmp vars
969 $date = ($this->date_commande ? $this->date_commande : $this->date);
970 $delivery_date = $this->delivery_date;
971
972 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
973 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
974 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
975 } else {
976 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
977 }
978 if (empty($this->fk_multicurrency)) {
979 $this->multicurrency_code = $conf->currency;
980 $this->fk_multicurrency = 0;
981 $this->multicurrency_tx = 1;
982 }
983 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
984 $this->entity = setEntity($this);
985
986 dol_syslog(get_class($this)."::create user=".$user->id);
987
988 // Check parameters
989 if (!empty($this->ref)) { // We check that ref is not already used
990 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
991 if ($result > 0) {
992 $this->error = 'ErrorRefAlreadyExists';
993 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
994 $this->db->rollback();
995 return -1;
996 }
997 }
998
999 $soc = new Societe($this->db);
1000 $result = $soc->fetch($this->socid);
1001 if ($result < 0) {
1002 $this->error = "Failed to fetch company";
1003 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1004 return -2;
1005 }
1006 if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
1007 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
1008 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1009 return -1;
1010 }
1011
1012 $now = dol_now();
1013
1014 $this->db->begin();
1015
1016 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1017 $sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
1018 $sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
1019 $sql .= ", fk_shipping_method";
1020 $sql .= ", fk_warehouse";
1021 $sql .= ", fk_incoterms, location_incoterms";
1022 $sql .= ", entity, module_source, pos_source";
1023 $sql .= ", fk_multicurrency";
1024 $sql .= ", multicurrency_code";
1025 $sql .= ", multicurrency_tx";
1026 $sql .= ")";
1027 $sql .= " VALUES ('(PROV)', ".((int) $this->socid).", '".$this->db->idate($now)."', ".((int) $user->id);
1028 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1029 $sql .= ", '".$this->db->idate($date)."'";
1030 $sql .= ", ".($this->source >= 0 && $this->source != '' ? $this->db->escape((string) $this->source) : 'null');
1031 $sql .= ", '".$this->db->escape($this->note_private)."'";
1032 $sql .= ", '".$this->db->escape($this->note_public)."'";
1033 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1034 $sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
1035 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1036 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
1037 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape((string) $this->deposit_percent)."'" : "null");
1038 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
1039 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1040 $sql .= ", ".($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
1041 $sql .= ", ".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
1042 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1043 $sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
1044 $sql .= ", ".(!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1045 $sql .= ", ".(!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
1046 $sql .= ", ".(int) $this->fk_incoterms;
1047 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1048 $sql .= ", ".(int) $this->entity;
1049 $sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
1050 $sql .= ", ".((!is_null($this->pos_source) && $this->pos_source != '') ? "'".$this->db->escape($this->pos_source)."'" : "null"); // Can be null, '', '0', '1'
1051 $sql .= ", ".(int) $this->fk_multicurrency;
1052 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1053 $sql .= ", ".(float) $this->multicurrency_tx;
1054 $sql .= ")";
1055
1056 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1057 $resql = $this->db->query($sql);
1058 if ($resql) {
1059 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'commande');
1060
1061 if ($this->id) {
1062 $fk_parent_line = 0;
1063 $num = count($this->lines);
1064
1065 /*
1066 * Insert products details into db
1067 */
1068 for ($i = 0; $i < $num; $i++) {
1069 $line = $this->lines[$i];
1070
1071 // Test and convert into OrderLine object this->lines[$i]. When coming from REST API, we may still have an array
1072 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
1073 if (!is_object($line)) {
1074 $lineobj = new OrderLine($this->db);
1075 foreach ($line as $key => $val) {
1076 $lineobj->$key = $val;
1077 }
1078 $line = $lineobj;
1079 $this->lines[$i] = $line;
1080 }
1081
1082 // Reset fk_parent_line for no child products and special product
1083 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1084 $fk_parent_line = 0;
1085 }
1086
1087 // Complete vat rate with code
1088 $vatrate = $line->tva_tx;
1089 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', (string) $vatrate)) {
1090 $vatrate .= ' ('.$line->vat_src_code.')';
1091 }
1092
1093 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1094 $originid = $line->origin_id;
1095 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1096 } else {
1097 $originid = $line->id;
1098 $origintype = $this->element;
1099 }
1100
1101 // ref_ext
1102 if (empty($line->ref_ext)) {
1103 $line->ref_ext = '';
1104 }
1105
1106 $result = $this->addline(
1107 $line->desc,
1108 $line->subprice,
1109 $line->qty,
1110 $vatrate,
1111 $line->localtax1_tx,
1112 $line->localtax2_tx,
1113 $line->fk_product,
1114 $line->remise_percent,
1115 $line->info_bits,
1116 $line->fk_remise_except,
1117 'HT',
1118 0,
1119 $line->date_start,
1120 $line->date_end,
1121 $line->product_type,
1122 $line->rang,
1123 $line->special_code,
1124 $fk_parent_line,
1125 $line->fk_fournprice,
1126 $line->pa_ht,
1127 $line->label,
1128 $line->array_options,
1129 $line->fk_unit,
1130 (string) $origintype,
1131 $originid,
1132 0,
1133 $line->ref_ext,
1134 1
1135 );
1136
1137 if ($result < 0) {
1138 if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
1139 $this->error = $this->db->lasterror();
1140 $this->errors[] = $this->error;
1141 dol_print_error($this->db);
1142 }
1143 $this->db->rollback();
1144 return -1;
1145 }
1146 // Defined the new fk_parent_line
1147 if ($result > 0 && $line->product_type == 9) {
1148 $fk_parent_line = $result;
1149 }
1150 }
1151
1152 $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.
1153
1154 // update ref
1155 $initialref = '(PROV'.$this->id.')';
1156 if (!empty($this->ref)) {
1157 $initialref = $this->ref;
1158 }
1159
1160 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($initialref)."' WHERE rowid=".((int) $this->id);
1161 if ($this->db->query($sql)) {
1162 $this->ref = $initialref;
1163
1164 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1165 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1166 }
1167
1168 // Add object linked
1169 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1170 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1171 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, ...))
1172 foreach ($tmp_origin_id as $origin_id) {
1173 $ret = $this->add_object_linked($origin, $origin_id);
1174 if (!$ret) {
1175 $this->error = $this->db->lasterror();
1176 $error++;
1177 }
1178 }
1179 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1180 $origin_id = $tmp_origin_id;
1181 $ret = $this->add_object_linked($origin, $origin_id);
1182 if (!$ret) {
1183 $this->error = $this->db->lasterror();
1184 $error++;
1185 }
1186 }
1187 }
1188 }
1189
1190 if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
1191 $originforcontact = empty($this->origin_type) ? $this->origin : $this->origin_type;
1192 $originidforcontact = $this->origin_id;
1193 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
1194 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
1195 $exp = new Expedition($this->db);
1196 $exp->fetch($this->origin_id);
1197 $exp->fetchObjectLinked();
1198 if (count($exp->linkedObjectsIds['commande']) > 0) {
1199 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1200 $originforcontact = 'commande';
1201 if (is_object($value)) {
1202 $originidforcontact = $value->id;
1203 } else {
1204 $originidforcontact = $value;
1205 }
1206 break; // We take first one
1207 }
1208 }
1209 }
1210
1211 $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";
1212 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
1213
1214 $resqlcontact = $this->db->query($sqlcontact);
1215 if ($resqlcontact) {
1216 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1217 //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1218 $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
1219 }
1220 } else {
1221 dol_print_error($this->db);
1222 }
1223 }
1224
1225 if (!$error) {
1226 $result = $this->insertExtraFields();
1227 if ($result < 0) {
1228 $error++;
1229 }
1230 }
1231
1232 if (!$error && !$notrigger) {
1233 // Call trigger
1234 $result = $this->call_trigger('ORDER_CREATE', $user);
1235 if ($result < 0) {
1236 $error++;
1237 }
1238 // End call triggers
1239 }
1240
1241 if (!$error) {
1242 $this->db->commit();
1243 return $this->id;
1244 } else {
1245 $this->db->rollback();
1246 return -1 * $error;
1247 }
1248 } else {
1249 $this->error = $this->db->lasterror();
1250 $this->db->rollback();
1251 return -1;
1252 }
1253 }
1254
1255 return 0;
1256 } else {
1257 $this->error = $this->db->lasterror();
1258 $this->db->rollback();
1259 return -1;
1260 }
1261 }
1262
1263
1271 public function createFromClone(User $user, $socid = 0)
1272 {
1273 global $user, $hookmanager;
1274
1275 $error = 0;
1276
1277 $this->db->begin();
1278
1279 // get lines so they will be clone
1280 foreach ($this->lines as $line) {
1281 $line->fetch_optionals();
1282 }
1283
1284 // Load source object
1285 $objFrom = clone $this;
1286
1287 // Change socid if needed
1288 if (!empty($socid) && $socid != $this->socid) {
1289 $objsoc = new Societe($this->db);
1290
1291 if ($objsoc->fetch($socid) > 0) {
1292 $this->socid = $objsoc->id;
1293 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1294 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
1295 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1296 $this->fk_project = 0;
1297 $this->fk_delivery_address = 0;
1298 }
1299
1300 // TODO Change product price if multi-prices
1301 }
1302
1303 $this->id = 0;
1304 $this->ref = '';
1305 $this->statut = self::STATUS_DRAFT; // deprecated
1306 $this->status = self::STATUS_DRAFT;
1307
1308 // Clear fields
1309 $this->user_author_id = $user->id;
1310 $this->user_validation_id = 0;
1311 $this->date = dol_now();
1312 $this->date_commande = dol_now();
1313 $this->date_creation = '';
1314 $this->date_validation = '';
1315 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1316 $this->ref_client = '';
1317 $this->ref_customer = '';
1318 }
1319
1320 // Do not clone ref_ext
1321 $num = count($this->lines);
1322 for ($i = 0; $i < $num; $i++) {
1323 $this->lines[$i]->ref_ext = '';
1324 }
1325
1326 // Create clone
1327 $this->context['createfromclone'] = 'createfromclone';
1328 $result = $this->create($user);
1329 if ($result < 0) {
1330 $error++;
1331 }
1332
1333 if (!$error) {
1334 // copy internal contacts
1335 if ($this->copy_linked_contact($objFrom, 'internal') < 0) {
1336 $error++;
1337 }
1338 }
1339
1340 if (!$error) {
1341 // copy external contacts if same company
1342 if ($this->socid == $objFrom->socid) {
1343 if ($this->copy_linked_contact($objFrom, 'external') < 0) {
1344 $error++;
1345 }
1346 }
1347 }
1348
1349 if (!$error) {
1350 // Hook of thirdparty module
1351 if (is_object($hookmanager)) {
1352 $parameters = array('objFrom' => $objFrom);
1353 $action = '';
1354 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1355 if ($reshook < 0) {
1356 $this->setErrorsFromObject($hookmanager);
1357 $error++;
1358 }
1359 }
1360 }
1361
1362 unset($this->context['createfromclone']);
1363
1364 // End
1365 if (!$error) {
1366 $this->db->commit();
1367 return $this->id;
1368 } else {
1369 $this->db->rollback();
1370 return -1;
1371 }
1372 }
1373
1374
1382 public function createFromProposal($object, User $user)
1383 {
1384 global $conf, $hookmanager;
1385
1386 require_once DOL_DOCUMENT_ROOT . '/multicurrency/class/multicurrency.class.php';
1387 require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
1388
1389 $error = 0;
1390
1391 $this->date_commande = dol_now();
1392 $this->date = dol_now();
1393 $this->source = 0;
1394
1395 $num = count($object->lines);
1396 for ($i = 0; $i < $num; $i++) {
1397 $line = new OrderLine($this->db);
1398
1399 $line->libelle = $object->lines[$i]->libelle;
1400 $line->label = $object->lines[$i]->label;
1401 $line->desc = $object->lines[$i]->desc;
1402 $line->price = $object->lines[$i]->price;
1403 $line->subprice = $object->lines[$i]->subprice;
1404 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1405 $line->tva_tx = $object->lines[$i]->tva_tx;
1406 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1407 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1408 $line->qty = $object->lines[$i]->qty;
1409 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1410 $line->remise_percent = $object->lines[$i]->remise_percent;
1411 $line->fk_product = $object->lines[$i]->fk_product;
1412 $line->info_bits = $object->lines[$i]->info_bits;
1413 $line->product_type = $object->lines[$i]->product_type;
1414 $line->rang = $object->lines[$i]->rang;
1415 $line->special_code = $object->lines[$i]->special_code;
1416 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1417 $line->fk_unit = $object->lines[$i]->fk_unit;
1418
1419 $line->date_start = $object->lines[$i]->date_start;
1420 $line->date_end = $object->lines[$i]->date_end;
1421
1422 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1423 $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);
1424 $line->pa_ht = $marginInfos[0];
1425 $line->marge_tx = $marginInfos[1];
1426 $line->marque_tx = $marginInfos[2];
1427
1428 $line->origin = $object->element;
1429 $line->origin_id = $object->lines[$i]->id;
1430
1431 // get extrafields from original line
1432 $object->lines[$i]->fetch_optionals();
1433 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1434 $line->array_options[$options_key] = $value;
1435 }
1436
1437 $this->lines[$i] = $line;
1438 }
1439
1440 $this->entity = $object->entity;
1441 $this->socid = $object->socid;
1442 $this->fk_project = $object->fk_project;
1443 $this->cond_reglement_id = $object->cond_reglement_id;
1444 $this->deposit_percent = $object->deposit_percent;
1445 $this->mode_reglement_id = $object->mode_reglement_id;
1446 $this->fk_account = $object->fk_account;
1447 $this->availability_id = $object->availability_id;
1448 $this->demand_reason_id = $object->demand_reason_id;
1449 $this->delivery_date = $object->delivery_date;
1450 $this->shipping_method_id = $object->shipping_method_id;
1451 $this->warehouse_id = $object->warehouse_id;
1452 $this->fk_delivery_address = $object->fk_delivery_address;
1453 $this->contact_id = $object->contact_id;
1454 $this->ref_client = $object->ref_client;
1455 $this->ref_customer = $object->ref_client;
1456
1457 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1458 $this->note_private = $object->note_private;
1459 $this->note_public = $object->note_public;
1460 }
1461
1462 $this->origin = $object->element;
1463 $this->origin_id = $object->id;
1464
1465 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1466 if (isModEnabled('multicurrency')) {
1467 if (!empty($object->multicurrency_code)) {
1468 $this->multicurrency_code = $object->multicurrency_code;
1469 }
1470 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
1471 $this->multicurrency_tx = $object->multicurrency_tx;
1472 }
1473
1474 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1475 $tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
1476 $this->fk_multicurrency = $tmparray[0];
1477 $this->multicurrency_tx = $tmparray[1];
1478 } else {
1479 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1480 }
1481 if (empty($this->fk_multicurrency)) {
1482 $this->multicurrency_code = $conf->currency;
1483 $this->fk_multicurrency = 0;
1484 $this->multicurrency_tx = 1;
1485 }
1486 }
1487
1488 // get extrafields from original line
1489 $object->fetch_optionals();
1490
1491 $e = new ExtraFields($this->db);
1492 $element_extrafields = $e->fetch_name_optionals_label($this->table_element);
1493
1494 foreach ($object->array_options as $options_key => $value) {
1495 if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
1496 $this->array_options[$options_key] = $value;
1497 }
1498 }
1499 // Possibility to add external linked objects with hooks
1500 $this->linked_objects[$this->origin] = $this->origin_id;
1501 if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
1502 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1503 }
1504
1505 $ret = $this->create($user);
1506
1507 if ($ret > 0) {
1508 // Actions hooked (by external module)
1509 $hookmanager->initHooks(array('orderdao'));
1510
1511 $parameters = array('objFrom' => $object);
1512 $action = '';
1513 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1514 if ($reshook < 0) {
1515 $this->setErrorsFromObject($hookmanager);
1516 $error++;
1517 }
1518
1519 if (!$error) {
1520 // Validate immediately the order
1521 if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
1522 $this->fetch($ret);
1523 $this->valid($user);
1524 }
1525 return $ret;
1526 } else {
1527 return -1;
1528 }
1529 } else {
1530 return -1;
1531 }
1532 }
1533
1534
1575 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 = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $ref_ext = '', $noupdateafterinsertline = 0)
1576 {
1577 global $mysoc, $langs, $user;
1578
1579 $logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1580 $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";
1581 $logtext .= ", date_end=$date_end, type=$type special_code=$special_code, fk_unit=$fk_unit, origin=$origin, origin_id=$origin_id, pu_ht_devise=$pu_ht_devise, ref_ext=$ref_ext rang=$rang";
1582 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
1583
1584 if ($this->status == self::STATUS_DRAFT) {
1585 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1586
1587 // Clean parameters
1588
1589 if (empty($remise_percent)) {
1590 $remise_percent = 0;
1591 }
1592 if (empty($qty)) {
1593 $qty = 0;
1594 }
1595 if (empty($info_bits)) {
1596 $info_bits = 0;
1597 }
1598 if (empty($rang)) {
1599 $rang = 0;
1600 }
1601 if (empty($txtva)) {
1602 $txtva = 0;
1603 }
1604 if (empty($txlocaltax1)) {
1605 $txlocaltax1 = 0;
1606 }
1607 if (empty($txlocaltax2)) {
1608 $txlocaltax2 = 0;
1609 }
1610 if (empty($fk_parent_line) || $fk_parent_line < 0) {
1611 $fk_parent_line = 0;
1612 }
1613 if (empty($this->fk_multicurrency)) {
1614 $this->fk_multicurrency = 0;
1615 }
1616 if (empty($ref_ext)) {
1617 $ref_ext = '';
1618 }
1619
1620 $remise_percent = (float) price2num($remise_percent);
1621 $qty = (float) price2num($qty);
1622 $pu_ht = price2num($pu_ht);
1623 $pu_ht_devise = price2num($pu_ht_devise);
1624 $pu_ttc = price2num($pu_ttc);
1625 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
1626 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
1627 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1628 }
1629 $txlocaltax1 = price2num($txlocaltax1);
1630 $txlocaltax2 = price2num($txlocaltax2);
1631 if ($price_base_type == 'HT') {
1632 $pu = $pu_ht;
1633 } else {
1634 $pu = $pu_ttc;
1635 }
1636 $label = trim($label);
1637 $desc = trim($desc);
1638
1639 // Check parameters
1640 if ($type < 0) {
1641 return -1;
1642 }
1643
1644 if ($date_start && $date_end && $date_start > $date_end) {
1645 $langs->load("errors");
1646 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1647 return -1;
1648 }
1649
1650 $this->db->begin();
1651
1652 $product_type = $type;
1653 if (!empty($fk_product) && $fk_product > 0) {
1654 $product = new Product($this->db);
1655 $result = $product->fetch($fk_product);
1656 $product_type = $product->type;
1657
1658 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0) {
1659 // get real stock
1660 $productChildrenNb = 0;
1661 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1662 $productChildrenNb = $product->hasFatherOrChild(1);
1663 }
1664 if ($productChildrenNb > 0) {
1665 // compute real stock from each subcomponent
1666 $product_stock = null;
1667 $product->loadStockForVirtualProduct('warehouseopen', $qty);
1668 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1669 if ($product_stock === null) {
1670 $product_stock = $componentStockWarehouse->real;
1671 } else {
1672 $product_stock = min($product_stock, $componentStockWarehouse->real);
1673 }
1674 }
1675 if ($product_stock === null) {
1676 $product_stock = 0;
1677 }
1678 } else {
1679 $product_stock = $product->stock_reel;
1680 }
1681
1682 if ($product_stock < $qty) {
1683 $langs->load("errors");
1684 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1685 $this->errors[] = $this->error;
1686 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
1687 $this->db->rollback();
1689 }
1690 }
1691 }
1692 // Calcul du total TTC et de la TVA pour la ligne a partir de
1693 // qty, pu, remise_percent et txtva
1694 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1695 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1696
1697 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1698
1699 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1700 $tmpproduct = new Product($this->db);
1701 $result = $tmpproduct->fetch($fk_product);
1702 if (abs($qty) < $tmpproduct->packaging) {
1703 $qty = (float) $tmpproduct->packaging;
1704 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
1705 } else {
1706 if (!empty($tmpproduct->packaging) && $qty > $tmpproduct->packaging) {
1707 $coeff = intval(abs($qty) / $tmpproduct->packaging) + 1;
1708 $qty = price2num((float) $tmpproduct->packaging * $coeff, 'MS');
1709 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
1710 }
1711 }
1712 }
1713
1714 // Clean vat code
1715 $reg = array();
1716 $vat_src_code = '';
1717 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
1718 $vat_src_code = $reg[1];
1719 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
1720 }
1721
1722 $tabprice = calcul_price_total($qty, (float) $pu, $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
1723
1724 /*var_dump($txlocaltax1);
1725 var_dump($txlocaltax2);
1726 var_dump($localtaxes_type);
1727 var_dump($tabprice);
1728 var_dump($tabprice[9]);
1729 var_dump($tabprice[10]);
1730 exit;*/
1731
1732 $total_ht = $tabprice[0];
1733 $total_tva = $tabprice[1];
1734 $total_ttc = $tabprice[2];
1735 $total_localtax1 = $tabprice[9];
1736 $total_localtax2 = $tabprice[10];
1737 $pu_ht = $tabprice[3];
1738
1739 // MultiCurrency
1740 $multicurrency_total_ht = $tabprice[16];
1741 $multicurrency_total_tva = $tabprice[17];
1742 $multicurrency_total_ttc = $tabprice[18];
1743 $pu_ht_devise = $tabprice[19];
1744
1745 // Rang to use
1746 $ranktouse = $rang;
1747
1748 if ($ranktouse == -1) {
1749 $rangmax = $this->line_max($fk_parent_line);
1750 $ranktouse = $rangmax + 1;
1751 }
1752
1753 // TODO A virer
1754 // Anciens indicateurs: $price, $remise (a ne plus utiliser)
1755 $price = $pu;
1756 $remise = 0;
1757 if ($remise_percent > 0) {
1758 $remise = round(((float) $pu * $remise_percent / 100), 2);
1759 $price = (float) $pu - $remise;
1760 }
1761
1762 // Insert line
1763 $this->line = new OrderLine($this->db);
1764
1765 $this->line->context = $this->context;
1766
1767 $this->line->fk_commande = $this->id;
1768 $this->line->label = $label;
1769 $this->line->desc = $desc;
1770 $this->line->qty = (float) $qty;
1771 $this->line->ref_ext = $ref_ext;
1772
1773 $this->line->vat_src_code = $vat_src_code;
1774 $this->line->tva_tx = $txtva;
1775 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1776 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1777 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1778 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1779 $this->line->fk_product = $fk_product;
1780 $this->line->product_type = $product_type;
1781 $this->line->fk_remise_except = $fk_remise_except;
1782 $this->line->remise_percent = $remise_percent;
1783 $this->line->subprice = (float) $pu_ht;
1784 $this->line->rang = $ranktouse;
1785 $this->line->info_bits = $info_bits;
1786 $this->line->total_ht = (float) $total_ht;
1787 $this->line->total_tva = (float) $total_tva;
1788 $this->line->total_localtax1 = (float) $total_localtax1;
1789 $this->line->total_localtax2 = (float) $total_localtax2;
1790 $this->line->total_ttc = (float) $total_ttc;
1791 $this->line->special_code = $special_code;
1792 if (empty($qty) && empty($special_code)) {
1793 $this->line->special_code = 3;
1794 }
1795 $this->line->origin = $origin;
1796 $this->line->origin_id = $origin_id;
1797 $this->line->fk_parent_line = $fk_parent_line;
1798 $this->line->fk_unit = $fk_unit;
1799
1800 $this->line->date_start = $date_start;
1801 $this->line->date_end = $date_end;
1802
1803 $this->line->fk_fournprice = $fk_fournprice;
1804 $this->line->pa_ht = $pa_ht; // Can be '' when not defined or 0 if defined to 0 or a price value
1805
1806 // Multicurrency
1807 $this->line->fk_multicurrency = $this->fk_multicurrency;
1808 $this->line->multicurrency_code = $this->multicurrency_code;
1809 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1810 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1811 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1812 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1813
1814 // TODO Do not use anymore
1815 $this->line->price = $price;
1816
1817 if (is_array($array_options) && count($array_options) > 0) {
1818 $this->line->array_options = $array_options;
1819 }
1820
1821 $result = $this->line->insert($user);
1822 if ($result > 0) {
1823 // Update denormalized fields at the order level
1824 if (empty($noupdateafterinsertline)) {
1825 $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.
1826 }
1827
1828 if ($result > 0) {
1829 if (!isset($this->context['createfromclone'])) {
1830 if (!empty($fk_parent_line)) {
1831 // Always reorder if child line
1832 $this->line_order(true, 'DESC');
1833 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1834 // Update all rank of all other lines starting from the same $ranktouse
1835 foreach ($this->lines as $tmpline) {
1836 if ($tmpline->rang >= $ranktouse) {
1837 if (!empty($tmpline->id)) {
1838 $this->updateRangOfLine($tmpline->id, $tmpline->rang + 1);
1839 }
1840 }
1841 }
1842 }
1843
1844 $this->lines[] = $this->line;
1845 } else {
1846 foreach ($this->lines as $line) {
1847 if ($line->id == $origin_id) {
1848 $this->line->extraparams = $line->extraparams;
1849 $this->line->setExtraParameters();
1850 }
1851 }
1852 }
1853
1854 $this->db->commit();
1855 return $this->line->id;
1856 } else {
1857 $this->db->rollback();
1858 return -1;
1859 }
1860 } else {
1861 $this->error = $this->line->error;
1862 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
1863 $this->db->rollback();
1864 return -2;
1865 }
1866 } else {
1867 dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1868 return -3;
1869 }
1870 }
1871
1872
1873 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1887 public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1888 {
1889 // phpcs:enable
1890 global $conf, $mysoc;
1891
1892 if (!$qty) {
1893 $qty = 1;
1894 }
1895
1896 if ($idproduct > 0) {
1897 $prod = new Product($this->db);
1898 $prod->fetch($idproduct);
1899
1900 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1901 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1902 if (empty($tva_tx)) {
1903 $tva_npr = 0;
1904 }
1905 $vat_src_code = ''; // May be defined into tva_tx
1906
1907 $localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1908 $localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1909
1910 // multiprix
1911 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
1912 $price = $prod->multiprices[$this->thirdparty->price_level];
1913 } else {
1914 $price = $prod->price;
1915 }
1916
1917 $line = new OrderLine($this->db);
1918
1919 $line->context = $this->context;
1920
1921 $line->fk_product = $idproduct;
1922 $line->desc = $prod->description;
1923 $line->qty = $qty;
1924 $line->subprice = $price;
1925 $line->remise_percent = $remise_percent;
1926 $line->vat_src_code = $vat_src_code;
1927 $line->tva_tx = $tva_tx;
1928 $line->localtax1_tx = $localtax1_tx;
1929 $line->localtax2_tx = $localtax2_tx;
1930
1931 $line->product_ref = $prod->ref;
1932 $line->product_label = $prod->label;
1933 $line->product_desc = $prod->description;
1934 $line->fk_unit = $prod->fk_unit;
1935
1936 // Save the start and end date of the line in the object
1937 if ($date_start) {
1938 $line->date_start = $date_start;
1939 }
1940 if ($date_end) {
1941 $line->date_end = $date_end;
1942 }
1943
1944 $this->lines[] = $line;
1945
1963 }
1964 }
1965
1966
1976 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
1977 {
1978 // Check parameters
1979 if (empty($id) && empty($ref) && empty($ref_ext)) {
1980 return -1;
1981 }
1982
1983 $sql = 'SELECT c.rowid, c.entity, c.date_creation, c.ref, c.fk_soc, c.fk_user_author, c.fk_user_valid, c.fk_user_modif, c.fk_statut as status';
1984 $sql .= ', c.amount_ht, c.total_ht, c.total_ttc, c.total_tva, c.localtax1 as total_localtax1, c.localtax2 as total_localtax2, c.fk_cond_reglement, c.deposit_percent, c.fk_mode_reglement, c.fk_availability, c.fk_input_reason';
1985 $sql .= ', c.fk_account';
1986 $sql .= ', c.date_commande, c.date_valid, c.tms';
1987 $sql .= ', c.date_livraison as delivery_date';
1988 $sql .= ', c.fk_shipping_method';
1989 $sql .= ', c.fk_warehouse';
1990 $sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
1991 $sql .= ', c.note_private, c.note_public, c.ref_client, c.ref_ext, c.model_pdf, c.last_main_doc, c.fk_delivery_address, c.extraparams';
1992 $sql .= ', c.fk_incoterms, c.location_incoterms';
1993 $sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
1994 $sql .= ", c.module_source, c.pos_source";
1995 $sql .= ", i.libelle as label_incoterms";
1996 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1997 $sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
1998 $sql .= ', ca.code as availability_code, ca.label as availability_label';
1999 $sql .= ', dr.code as demand_reason_code';
2000 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
2001 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
2002 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON c.fk_mode_reglement = p.id';
2003 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON c.fk_availability = ca.rowid';
2004 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
2005 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
2006
2007 if ($id) {
2008 $sql .= " WHERE c.rowid = ".((int) $id);
2009 } else {
2010 $sql .= " WHERE c.entity IN (".getEntity('commande').")"; // Don't use entity if you use rowid
2011 }
2012
2013 if ($ref) {
2014 $sql .= " AND c.ref = '".$this->db->escape($ref)."'";
2015 }
2016 if ($ref_ext) {
2017 $sql .= " AND c.ref_ext = '".$this->db->escape($ref_ext)."'";
2018 }
2019
2020 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
2021 $result = $this->db->query($sql);
2022 if ($result) {
2023 $obj = $this->db->fetch_object($result);
2024 if ($obj) {
2025 $this->id = $obj->rowid;
2026 $this->entity = $obj->entity;
2027
2028 $this->ref = $obj->ref;
2029 $this->ref_client = $obj->ref_client;
2030 $this->ref_customer = $obj->ref_client;
2031 $this->ref_ext = $obj->ref_ext;
2032
2033 $this->socid = $obj->fk_soc;
2034 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2035
2036 $this->fk_project = $obj->fk_project;
2037 $this->project = null; // Clear if another value was already set by fetch_projet
2038
2039 $this->statut = $obj->status; // deprecated
2040 $this->status = $obj->status;
2041
2042 $this->user_author_id = $obj->fk_user_author;
2043 $this->user_creation_id = $obj->fk_user_author;
2044 $this->user_validation_id = $obj->fk_user_valid;
2045 $this->user_modification_id = $obj->fk_user_modif;
2046 $this->total_ht = $obj->total_ht;
2047 $this->total_tva = $obj->total_tva;
2048 $this->total_localtax1 = $obj->total_localtax1;
2049 $this->total_localtax2 = $obj->total_localtax2;
2050 $this->total_ttc = $obj->total_ttc;
2051 $this->date = $this->db->jdate($obj->date_commande);
2052 $this->date_commande = $this->db->jdate($obj->date_commande);
2053 $this->date_creation = $this->db->jdate($obj->date_creation);
2054 $this->date_validation = $this->db->jdate($obj->date_valid);
2055 $this->date_modification = $this->db->jdate($obj->tms);
2056 $this->source = $obj->source;
2057 $this->billed = $obj->billed;
2058 $this->note = $obj->note_private; // deprecated
2059 $this->note_private = $obj->note_private;
2060 $this->note_public = $obj->note_public;
2061 $this->model_pdf = $obj->model_pdf;
2062 $this->last_main_doc = $obj->last_main_doc;
2063 $this->mode_reglement_id = $obj->fk_mode_reglement;
2064 $this->mode_reglement_code = $obj->mode_reglement_code;
2065 $this->mode_reglement = $obj->mode_reglement_libelle;
2066 $this->cond_reglement_id = $obj->fk_cond_reglement;
2067 $this->cond_reglement_code = $obj->cond_reglement_code;
2068 $this->cond_reglement = $obj->cond_reglement_libelle;
2069 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2070 $this->deposit_percent = $obj->deposit_percent;
2071 $this->fk_account = $obj->fk_account;
2072 $this->availability_id = $obj->fk_availability;
2073 $this->availability_code = $obj->availability_code;
2074 $this->availability = $obj->availability_label;
2075 $this->demand_reason_id = $obj->fk_input_reason;
2076 $this->demand_reason_code = $obj->demand_reason_code;
2077 $this->delivery_date = $this->db->jdate($obj->delivery_date);
2078 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
2079 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
2080 $this->fk_delivery_address = $obj->fk_delivery_address;
2081 $this->module_source = $obj->module_source;
2082 $this->pos_source = $obj->pos_source;
2083
2084 //Incoterms
2085 $this->fk_incoterms = $obj->fk_incoterms;
2086 $this->location_incoterms = $obj->location_incoterms;
2087 $this->label_incoterms = $obj->label_incoterms;
2088
2089 // Multicurrency
2090 $this->fk_multicurrency = $obj->fk_multicurrency;
2091 $this->multicurrency_code = $obj->multicurrency_code;
2092 $this->multicurrency_tx = $obj->multicurrency_tx;
2093 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2094 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
2095 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2096
2097 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2098
2099 $this->lines = array();
2100
2101 // Retrieve all extrafield
2102 // fetch optionals attributes and labels
2103 $this->fetch_optionals();
2104
2105 $this->db->free($result);
2106
2107 // Lines
2108 $result = $this->fetch_lines();
2109 if ($result < 0) {
2110 return -3;
2111 }
2112 return 1;
2113 } else {
2114 $this->error = 'Order with id '.$id.' not found sql='.$sql;
2115 return 0;
2116 }
2117 } else {
2118 $this->error = $this->db->error();
2119 return -1;
2120 }
2121 }
2122
2123
2124 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2131 public function insert_discount($idremise)
2132 {
2133 // phpcs:enable
2134 global $langs;
2135
2136 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2137 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2138
2139 $this->db->begin();
2140
2141 $remise = new DiscountAbsolute($this->db);
2142 $result = $remise->fetch($idremise);
2143
2144 if ($result > 0) {
2145 if ($remise->fk_facture) { // Protection against multiple submission
2146 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2147 $this->db->rollback();
2148 return -5;
2149 }
2150
2151 $line = new OrderLine($this->db);
2152
2153 $line->fk_commande = $this->id;
2154 $line->fk_remise_except = $remise->id;
2155 $line->desc = $remise->description; // Description ligne
2156 $line->vat_src_code = $remise->vat_src_code;
2157 $line->tva_tx = $remise->tva_tx;
2158 $line->subprice = -(float) $remise->amount_ht;
2159 $line->price = -(float) $remise->amount_ht;
2160 $line->fk_product = 0; // Id produit predefini
2161 $line->qty = 1;
2162 $line->remise_percent = 0;
2163 $line->rang = -1;
2164 $line->info_bits = 2;
2165
2166 $line->total_ht = -(float) $remise->amount_ht;
2167 $line->total_tva = -(float) $remise->amount_tva;
2168 $line->total_ttc = -(float) $remise->amount_ttc;
2169
2170 $result = $line->insert();
2171 if ($result > 0) {
2172 $result = $this->update_price(1);
2173 if ($result > 0) {
2174 $this->db->commit();
2175 return 1;
2176 } else {
2177 $this->db->rollback();
2178 return -1;
2179 }
2180 } else {
2181 $this->error = $line->error;
2182 $this->errors = $line->errors;
2183 $this->db->rollback();
2184 return -2;
2185 }
2186 } else {
2187 $this->db->rollback();
2188 return -2;
2189 }
2190 }
2191
2192
2193 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2201 public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2202 {
2203 // phpcs:enable
2204 $this->lines = array();
2205
2206 $sql = 'SELECT l.rowid, l.fk_product, l.fk_parent_line, l.product_type, l.fk_commande, l.label as custom_label, l.description, l.price, l.qty, l.vat_src_code, l.tva_tx, l.ref_ext,';
2207 $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,';
2208 $sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
2209 $sql .= ' l.fk_unit, l.extraparams,';
2210 $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2211 $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch, p.barcode as product_barcode,';
2212 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units, p.packaging';
2213 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as l';
2214 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = l.fk_product)';
2215 $sql .= ' WHERE l.fk_commande = '.((int) $this->id);
2216 if ($only_product) {
2217 $sql .= ' AND p.fk_product_type = 0';
2218 }
2219 $sql .= ' ORDER BY l.rang, l.rowid';
2220
2221 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2222 $result = $this->db->query($sql);
2223 if ($result) {
2224 $num = $this->db->num_rows($result);
2225
2226 $i = 0;
2227 while ($i < $num) {
2228 $objp = $this->db->fetch_object($result);
2229
2230 $line = new OrderLine($this->db);
2231
2232 $line->rowid = $objp->rowid;
2233 $line->id = $objp->rowid;
2234 $line->fk_commande = $objp->fk_commande;
2235 $line->commande_id = $objp->fk_commande;
2236 $line->label = $objp->custom_label;
2237 $line->desc = $objp->description;
2238 $line->description = $objp->description; // Description line
2239 $line->product_type = $objp->product_type;
2240 $line->qty = $objp->qty;
2241 $line->ref_ext = $objp->ref_ext;
2242
2243 $line->vat_src_code = $objp->vat_src_code;
2244 $line->tva_tx = $objp->tva_tx;
2245 $line->localtax1_tx = $objp->localtax1_tx;
2246 $line->localtax2_tx = $objp->localtax2_tx;
2247 $line->localtax1_type = $objp->localtax1_type;
2248 $line->localtax2_type = $objp->localtax2_type;
2249 $line->total_ht = $objp->total_ht;
2250 $line->total_ttc = $objp->total_ttc;
2251 $line->total_tva = $objp->total_tva;
2252 $line->total_localtax1 = $objp->total_localtax1;
2253 $line->total_localtax2 = $objp->total_localtax2;
2254 $line->subprice = $objp->subprice;
2255 $line->fk_remise_except = $objp->fk_remise_except;
2256 $line->remise_percent = $objp->remise_percent;
2257 $line->price = $objp->price;
2258 $line->fk_product = $objp->fk_product;
2259 $line->fk_fournprice = $objp->fk_fournprice;
2260 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2261 $line->pa_ht = $marginInfos[0];
2262 $line->marge_tx = $marginInfos[1];
2263 $line->marque_tx = $marginInfos[2];
2264 $line->rang = $objp->rang;
2265 $line->info_bits = $objp->info_bits;
2266 $line->special_code = $objp->special_code;
2267 $line->fk_parent_line = $objp->fk_parent_line;
2268
2269 $line->ref = $objp->product_ref;
2270 $line->libelle = $objp->product_label;
2271
2272 $line->product_ref = $objp->product_ref;
2273 $line->product_label = $objp->product_label;
2274 $line->product_tosell = $objp->product_tosell;
2275 $line->product_tobuy = $objp->product_tobuy;
2276 $line->product_desc = $objp->product_desc;
2277 $line->product_tobatch = $objp->product_tobatch;
2278 $line->product_barcode = $objp->product_barcode;
2279
2280 $line->fk_product_type = $objp->fk_product_type; // Produit ou service
2281 $line->fk_unit = $objp->fk_unit;
2282
2283 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2284
2285 $line->weight = $objp->weight;
2286 $line->weight_units = $objp->weight_units;
2287 $line->volume = $objp->volume;
2288 $line->volume_units = $objp->volume_units;
2289 $line->packaging = $objp->packaging;
2290
2291 $line->date_start = $this->db->jdate($objp->date_start);
2292 $line->date_end = $this->db->jdate($objp->date_end);
2293
2294 // Multicurrency
2295 $line->fk_multicurrency = $objp->fk_multicurrency;
2296 $line->multicurrency_code = $objp->multicurrency_code;
2297 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2298 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2299 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2300 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2301
2302 $line->fetch_optionals();
2303
2304 // multilangs
2305 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2306 $tmpproduct = new Product($this->db);
2307 $tmpproduct->fetch($objp->fk_product);
2308 $tmpproduct->getMultiLangs();
2309
2310 $line->multilangs = $tmpproduct->multilangs;
2311 }
2312
2313 $this->lines[$i] = $line;
2314
2315 $i++;
2316 }
2317
2318 $this->db->free($result);
2319
2320 return 1;
2321 } else {
2322 $this->error = $this->db->error();
2323 return -3;
2324 }
2325 }
2326
2327
2333 public function getNbOfProductsLines()
2334 {
2335 $nb = 0;
2336 foreach ($this->lines as $line) {
2337 if ($line->product_type == 0) {
2338 $nb++;
2339 }
2340 }
2341 return $nb;
2342 }
2343
2349 public function getNbOfServicesLines()
2350 {
2351 $nb = 0;
2352 foreach ($this->lines as $line) {
2353 if ($line->product_type == 1) {
2354 $nb++;
2355 }
2356 }
2357 return $nb;
2358 }
2359
2365 public function getNbOfShipments()
2366 {
2367 $nb = 0;
2368
2369 $sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2370 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2371 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2372 $sql .= ' WHERE';
2373 $sql .= ' ed.fk_elementdet = cd.rowid';
2374 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2375 //print $sql;
2376
2377 dol_syslog(get_class($this)."::getNbOfShipments", LOG_DEBUG);
2378 $resql = $this->db->query($sql);
2379 if ($resql) {
2380 $obj = $this->db->fetch_object($resql);
2381 if ($obj) {
2382 $nb = $obj->nb;
2383 }
2384
2385 $this->db->free($resql);
2386 return $nb;
2387 } else {
2388 $this->error = $this->db->lasterror();
2389 return -1;
2390 }
2391 }
2392
2401 public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2402 {
2403 $this->expeditions = array();
2404
2405 $sql = 'SELECT cd.rowid, cd.fk_product,';
2406 $sql .= ' sum(ed.qty) as qty';
2407 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2408 if ($filtre_statut >= 0) {
2409 $sql .= ' '.MAIN_DB_PREFIX.'expedition as e,';
2410 }
2411 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2412 $sql .= ' WHERE';
2413 if ($filtre_statut >= 0) {
2414 $sql .= ' ed.fk_expedition = e.rowid AND';
2415 }
2416 $sql .= ' ed.fk_elementdet = cd.rowid';
2417 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2418 if ($fk_product > 0) {
2419 $sql .= ' AND cd.fk_product = '.((int) $fk_product);
2420 }
2421 if ($filtre_statut >= 0) {
2422 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
2423 }
2424 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
2425 //print $sql;
2426
2427 dol_syslog(get_class($this)."::loadExpeditions", LOG_DEBUG);
2428 $resql = $this->db->query($sql);
2429 if ($resql) {
2430 $num = $this->db->num_rows($resql);
2431 $i = 0;
2432 while ($i < $num) {
2433 $obj = $this->db->fetch_object($resql);
2434 $this->expeditions[$obj->rowid] = $obj->qty;
2435 $i++;
2436 }
2437 $this->db->free($resql);
2438 return $num;
2439 } else {
2440 $this->error = $this->db->lasterror();
2441 return -1;
2442 }
2443 }
2444
2450 public function countNbOfShipments()
2451 {
2452 $sql = 'SELECT count(*)';
2453 $sql .= ' FROM '.MAIN_DB_PREFIX.'expedition as e';
2454 $sql .= ', '.MAIN_DB_PREFIX.'element_element as el';
2455 $sql .= ' WHERE el.fk_source = '.((int) $this->id);
2456 $sql .= " AND el.sourcetype = 'commande'";
2457 $sql .= " AND el.fk_target = e.rowid";
2458 $sql .= " AND el.targettype = 'shipping'";
2459
2460 $resql = $this->db->query($sql);
2461 if ($resql) {
2462 $row = $this->db->fetch_row($resql);
2463 return $row[0];
2464 } else {
2465 dol_print_error($this->db);
2466 }
2467
2468 return 0;
2469 }
2470
2479 public function deleteLine($user = null, $lineid = 0, $id = 0)
2480 {
2481 if ($this->status == self::STATUS_DRAFT) {
2482 $this->db->begin();
2483
2484 // Delete line
2485 $line = new OrderLine($this->db);
2486
2487 $line->context = $this->context;
2488
2489 // Load data
2490 $line->fetch($lineid);
2491
2492 if ($id > 0 && $line->fk_commande != $id) {
2493 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2494 return -1;
2495 }
2496
2497 // Memorize previous line for triggers
2498 $staticline = clone $line;
2499 $line->oldline = $staticline;
2500
2501 if ($line->delete($user) > 0) {
2502 $result = $this->update_price(1);
2503
2504 if ($result > 0) {
2505 $this->db->commit();
2506 return 1;
2507 } else {
2508 $this->db->rollback();
2509 $this->error = $this->db->lasterror();
2510 return -1;
2511 }
2512 } else {
2513 $this->db->rollback();
2514 $this->error = $line->error;
2515 return -1;
2516 }
2517 } else {
2518 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2519 return -1;
2520 }
2521 }
2522
2523 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2534 public function set_remise($user, $remise, $notrigger = 0)
2535 {
2536 // phpcs:enable
2537 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2538 // @phan-suppress-next-line PhanDeprecatedFunction
2539 return $this->setDiscount($user, $remise, $notrigger);
2540 }
2541
2550 public function setDiscount($user, $remise, $notrigger = 0)
2551 {
2552 $remise = trim((string) $remise) ? trim((string) $remise) : 0;
2553
2554 if ($user->hasRight('commande', 'creer')) {
2555 $error = 0;
2556
2557 $this->db->begin();
2558
2559 $remise = price2num($remise, 2);
2560
2561 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2562 $sql .= ' SET remise_percent = '.((float) $remise);
2563 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_DRAFT);
2564
2565 dol_syslog(__METHOD__, LOG_DEBUG);
2566 $resql = $this->db->query($sql);
2567 if (!$resql) {
2568 $this->errors[] = $this->db->error();
2569 $error++;
2570 }
2571
2572 if (!$error) {
2573 $this->oldcopy = clone $this;
2574 $this->remise_percent = $remise;
2575 $this->update_price(1);
2576 }
2577
2578 if (!$notrigger && empty($error)) {
2579 // Call trigger
2580 $result = $this->call_trigger('ORDER_MODIFY', $user);
2581 if ($result < 0) {
2582 $error++;
2583 }
2584 // End call triggers
2585 }
2586
2587 if (!$error) {
2588 $this->db->commit();
2589 return 1;
2590 } else {
2591 foreach ($this->errors as $errmsg) {
2592 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2593 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2594 }
2595 $this->db->rollback();
2596 return -1 * $error;
2597 }
2598 }
2599
2600 return 0;
2601 }
2602
2603
2604 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2613 public function set_date($user, $date, $notrigger = 0)
2614 {
2615 // phpcs:enable
2616 if ($user->hasRight('commande', 'creer')) {
2617 $error = 0;
2618
2619 $this->db->begin();
2620
2621 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2622 $sql .= " SET date_commande = ".($date ? "'".$this->db->idate($date)."'" : 'null');
2623 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2624
2625 dol_syslog(__METHOD__, LOG_DEBUG);
2626 $resql = $this->db->query($sql);
2627 if (!$resql) {
2628 $this->errors[] = $this->db->error();
2629 $error++;
2630 }
2631
2632 if (!$error) {
2633 $this->oldcopy = clone $this;
2634 $this->date = $date;
2635 }
2636
2637 if (!$notrigger && empty($error)) {
2638 // Call trigger
2639 $result = $this->call_trigger('ORDER_MODIFY', $user);
2640 if ($result < 0) {
2641 $error++;
2642 }
2643 // End call triggers
2644 }
2645
2646 if (!$error) {
2647 $this->db->commit();
2648 return 1;
2649 } else {
2650 foreach ($this->errors as $errmsg) {
2651 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2652 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2653 }
2654 $this->db->rollback();
2655 return -1 * $error;
2656 }
2657 } else {
2658 return -2;
2659 }
2660 }
2661
2662 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2672 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2673 {
2674 // phpcs:enable
2675 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2676 }
2677
2686 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2687 {
2688 if ($user->hasRight('commande', 'creer')) {
2689 $error = 0;
2690
2691 $this->db->begin();
2692
2693 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2694 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2695 $sql .= " WHERE rowid = ".((int) $this->id);
2696
2697 dol_syslog(__METHOD__, LOG_DEBUG);
2698 $resql = $this->db->query($sql);
2699 if (!$resql) {
2700 $this->errors[] = $this->db->error();
2701 $error++;
2702 }
2703
2704 if (!$error) {
2705 $this->oldcopy = clone $this;
2706 $this->delivery_date = $delivery_date;
2707 }
2708
2709 if (!$notrigger && empty($error)) {
2710 // Call trigger
2711 $result = $this->call_trigger('ORDER_MODIFY', $user);
2712 if ($result < 0) {
2713 $error++;
2714 }
2715 // End call triggers
2716 }
2717
2718 if (!$error) {
2719 $this->db->commit();
2720 return 1;
2721 } else {
2722 foreach ($this->errors as $errmsg) {
2723 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2724 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2725 }
2726 $this->db->rollback();
2727 return -1 * $error;
2728 }
2729 } else {
2730 return -2;
2731 }
2732 }
2733
2734 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2748 public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2749 {
2750 // phpcs:enable
2751 global $user;
2752
2753 $ga = array();
2754
2755 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2756 $sql .= " c.rowid as cid, c.ref";
2757 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2758 $sql .= ", sc.fk_soc, sc.fk_user";
2759 }
2760 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as c";
2761 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2762 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2763 }
2764 $sql .= " WHERE c.entity IN (".getEntity('commande').")";
2765 $sql .= " AND c.fk_soc = s.rowid";
2766 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2767 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2768 }
2769 if ($socid) {
2770 $sql .= " AND s.rowid = ".((int) $socid);
2771 }
2772 if ($draft) {
2773 $sql .= " AND c.fk_statut = ".self::STATUS_DRAFT;
2774 }
2775 if (is_object($excluser)) {
2776 $sql .= " AND c.fk_user_author <> ".((int) $excluser->id);
2777 }
2778 $sql .= $this->db->order($sortfield, $sortorder);
2779 $sql .= $this->db->plimit($limit, $offset);
2780
2781 $result = $this->db->query($sql);
2782 if ($result) {
2783 $numc = $this->db->num_rows($result);
2784 if ($numc) {
2785 $i = 0;
2786 while ($i < $numc) {
2787 $obj = $this->db->fetch_object($result);
2788
2789 if ($shortlist == 1) {
2790 $ga[$obj->cid] = $obj->ref;
2791 } elseif ($shortlist == 2) {
2792 $ga[$obj->cid] = $obj->ref.' ('.$obj->name.')';
2793 } else {
2794 $ga[$i]['id'] = $obj->cid;
2795 $ga[$i]['ref'] = $obj->ref;
2796 $ga[$i]['name'] = $obj->name;
2797 }
2798 $i++;
2799 }
2800 }
2801 return $ga;
2802 } else {
2803 dol_print_error($this->db);
2804 return -1;
2805 }
2806 }
2807
2815 public function availability($availability_id, $notrigger = 0)
2816 {
2817 global $user;
2818
2819 dol_syslog('Commande::availability('.$availability_id.')');
2820 if ($this->status >= self::STATUS_DRAFT) {
2821 $error = 0;
2822
2823 $this->db->begin();
2824
2825 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2826 $sql .= ' SET fk_availability = '.((int) $availability_id);
2827 $sql .= ' WHERE rowid='.((int) $this->id);
2828
2829 dol_syslog(__METHOD__, LOG_DEBUG);
2830 $resql = $this->db->query($sql);
2831 if (!$resql) {
2832 $this->errors[] = $this->db->error();
2833 $error++;
2834 }
2835
2836 if (!$error) {
2837 $this->oldcopy = clone $this;
2838 $this->availability_id = $availability_id;
2839 }
2840
2841 if (!$notrigger && empty($error)) {
2842 // Call trigger
2843 $result = $this->call_trigger('ORDER_MODIFY', $user);
2844 if ($result < 0) {
2845 $error++;
2846 }
2847 // End call triggers
2848 }
2849
2850 if (!$error) {
2851 $this->db->commit();
2852 return 1;
2853 } else {
2854 foreach ($this->errors as $errmsg) {
2855 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2856 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2857 }
2858 $this->db->rollback();
2859 return -1 * $error;
2860 }
2861 } else {
2862 $error_str = 'Command status do not meet requirement '.$this->status;
2863 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2864 $this->error = $error_str;
2865 $this->errors[] = $this->error;
2866 return -2;
2867 }
2868 }
2869
2870 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2878 public function demand_reason($demand_reason_id, $notrigger = 0)
2879 {
2880 // phpcs:enable
2881 global $user;
2882
2883 dol_syslog('Commande::demand_reason('.$demand_reason_id.')');
2884 if ($this->status >= self::STATUS_DRAFT) {
2885 $error = 0;
2886
2887 $this->db->begin();
2888
2889 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2890 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
2891 $sql .= ' WHERE rowid='.((int) $this->id);
2892
2893 dol_syslog(__METHOD__, LOG_DEBUG);
2894 $resql = $this->db->query($sql);
2895 if (!$resql) {
2896 $this->errors[] = $this->db->error();
2897 $error++;
2898 }
2899
2900 if (!$error) {
2901 $this->oldcopy = clone $this;
2902 $this->demand_reason_id = $demand_reason_id;
2903 }
2904
2905 if (!$notrigger && empty($error)) {
2906 // Call trigger
2907 $result = $this->call_trigger('ORDER_MODIFY', $user);
2908 if ($result < 0) {
2909 $error++;
2910 }
2911 // End call triggers
2912 }
2913
2914 if (!$error) {
2915 $this->db->commit();
2916 return 1;
2917 } else {
2918 foreach ($this->errors as $errmsg) {
2919 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2920 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2921 }
2922 $this->db->rollback();
2923 return -1 * $error;
2924 }
2925 } else {
2926 $error_str = 'order status do not meet requirement '.$this->status;
2927 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2928 $this->error = $error_str;
2929 $this->errors[] = $this->error;
2930 return -2;
2931 }
2932 }
2933
2934 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2943 public function set_ref_client($user, $ref_client, $notrigger = 0)
2944 {
2945 // phpcs:enable
2946 if ($user->hasRight('commande', 'creer')) {
2947 $error = 0;
2948
2949 $this->db->begin();
2950
2951 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
2952 $sql .= ' ref_client = '.(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2953 $sql .= ' WHERE rowid = '.((int) $this->id);
2954
2955 dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2956 $resql = $this->db->query($sql);
2957 if (!$resql) {
2958 $this->errors[] = $this->db->error();
2959 $error++;
2960 }
2961
2962 if (!$error) {
2963 $this->oldcopy = clone $this;
2964 $this->ref_client = $ref_client;
2965 $this->ref_customer = $ref_client;
2966 }
2967
2968 if (!$notrigger && empty($error)) {
2969 // Call trigger
2970 $result = $this->call_trigger('ORDER_MODIFY', $user);
2971 if ($result < 0) {
2972 $error++;
2973 }
2974 // End call triggers
2975 }
2976 if (!$error) {
2977 $this->db->commit();
2978 return 1;
2979 } else {
2980 foreach ($this->errors as $errmsg) {
2981 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2982 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2983 }
2984 $this->db->rollback();
2985 return -1 * $error;
2986 }
2987 } else {
2988 return -1;
2989 }
2990 }
2991
2999 public function classifyBilled(User $user, $notrigger = 0)
3000 {
3001 $error = 0;
3002
3003 if ($this->billed) {
3004 return 0;
3005 }
3006
3007 $this->db->begin();
3008
3009 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 1';
3010 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3011
3012 dol_syslog(get_class($this)."::classifyBilled", LOG_DEBUG);
3013 if ($this->db->query($sql)) {
3014 if (!$error) {
3015 $this->oldcopy = clone $this;
3016 $this->billed = 1;
3017 }
3018
3019 if (!$notrigger && empty($error)) {
3020 // Call trigger
3021 $result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
3022 if ($result < 0) {
3023 $error++;
3024 }
3025 // End call triggers
3026 }
3027
3028 if (!$error) {
3029 $this->db->commit();
3030 return 1;
3031 } else {
3032 foreach ($this->errors as $errmsg) {
3033 dol_syslog(get_class($this)."::classifyBilled ".$errmsg, LOG_ERR);
3034 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3035 }
3036 $this->db->rollback();
3037 return -1 * $error;
3038 }
3039 } else {
3040 $this->error = $this->db->error();
3041 $this->db->rollback();
3042 return -1;
3043 }
3044 }
3045
3053 public function classifyUnBilled(User $user, $notrigger = 0)
3054 {
3055 $error = 0;
3056
3057 $this->db->begin();
3058
3059 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 0';
3060 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3061
3062 dol_syslog(get_class($this)."::classifyUnBilled", LOG_DEBUG);
3063 if ($this->db->query($sql)) {
3064 if (!$error) {
3065 $this->oldcopy = clone $this;
3066 $this->billed = 1;
3067 }
3068
3069 if (!$notrigger && empty($error)) {
3070 // Call trigger
3071 $result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
3072 if ($result < 0) {
3073 $error++;
3074 }
3075 // End call triggers
3076 }
3077
3078 if (!$error) {
3079 $this->billed = 0;
3080
3081 $this->db->commit();
3082 return 1;
3083 } else {
3084 foreach ($this->errors as $errmsg) {
3085 dol_syslog(get_class($this)."::classifyUnBilled ".$errmsg, LOG_ERR);
3086 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3087 }
3088 $this->db->rollback();
3089 return -1 * $error;
3090 }
3091 } else {
3092 $this->error = $this->db->error();
3093 $this->db->rollback();
3094 return -1;
3095 }
3096 }
3097
3098
3129 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 = array(), $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
3130 {
3131 global $mysoc, $langs, $user;
3132
3133 dol_syslog(get_class($this)."::updateline id=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, date_start=$date_start, date_end=$date_end, type=$type, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, special_code=$special_code, ref_ext=$ref_ext");
3134 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3135
3136 if ($this->status == Commande::STATUS_DRAFT) {
3137 // Clean parameters
3138 if (empty($qty)) {
3139 $qty = 0;
3140 }
3141 if (empty($info_bits)) {
3142 $info_bits = 0;
3143 }
3144 if (empty($txtva)) {
3145 $txtva = 0;
3146 }
3147 if (empty($txlocaltax1)) {
3148 $txlocaltax1 = 0;
3149 }
3150 if (empty($txlocaltax2)) {
3151 $txlocaltax2 = 0;
3152 }
3153 if (empty($remise_percent)) {
3154 $remise_percent = 0;
3155 }
3156 if (empty($qty) && empty($special_code)) {
3157 $special_code = 3; // Set option tag
3158 }
3159 if (!empty($qty) && $special_code == 3) {
3160 $special_code = 0; // Remove option tag
3161 }
3162 if (empty($ref_ext)) {
3163 $ref_ext = '';
3164 }
3165
3166 if ($date_start && $date_end && $date_start > $date_end) {
3167 $langs->load("errors");
3168 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3169 return -1;
3170 }
3171
3172 $remise_percent = (float) price2num($remise_percent);
3173 $qty = (float) price2num($qty);
3174 $pu = price2num($pu);
3175 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
3176 $pu_ht_devise = price2num($pu_ht_devise);
3177 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3178 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3179 }
3180 $txlocaltax1 = (float) price2num($txlocaltax1);
3181 $txlocaltax2 = (float) price2num($txlocaltax2);
3182
3183 $this->db->begin();
3184
3185 // Calcul du total TTC et de la TVA pour la ligne a partir de
3186 // qty, pu, remise_percent et txtva
3187 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3188 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3189
3190 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3191
3192 // Clean vat code
3193 $vat_src_code = '';
3194 $reg = array();
3195 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3196 $vat_src_code = $reg[1];
3197 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3198 }
3199
3200 $tabprice = calcul_price_total($qty, (float) $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
3201
3202 $total_ht = $tabprice[0];
3203 $total_tva = $tabprice[1];
3204 $total_ttc = $tabprice[2];
3205 $total_localtax1 = $tabprice[9];
3206 $total_localtax2 = $tabprice[10];
3207 $pu_ht = $tabprice[3];
3208 $pu_tva = $tabprice[4];
3209 $pu_ttc = $tabprice[5];
3210
3211 // MultiCurrency
3212 $multicurrency_total_ht = $tabprice[16];
3213 $multicurrency_total_tva = $tabprice[17];
3214 $multicurrency_total_ttc = $tabprice[18];
3215 $pu_ht_devise = $tabprice[19];
3216
3217 // Anciens indicateurs: $price, $subprice (a ne plus utiliser)
3218 $price = $pu_ht;
3219 if ($price_base_type == 'TTC') {
3220 $subprice = $pu_ttc;
3221 } else {
3222 $subprice = $pu_ht;
3223 }
3224 $remise = 0;
3225 if ($remise_percent > 0) {
3226 $remise = round(((float) $pu * $remise_percent / 100), 2);
3227 $price = ((float) $pu - $remise);
3228 }
3229
3230 // Fetch current line from the database and then clone the object and set it in $oldline property
3231 $line = new OrderLine($this->db);
3232 $line->fetch($rowid);
3233 $line->fetch_optionals();
3234
3235 if (!empty($line->fk_product)) {
3236 $product = new Product($this->db);
3237 $result = $product->fetch($line->fk_product);
3238 $product_type = $product->type;
3239
3240 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product_type == 0) {
3241 // get real stock
3242 $productChildrenNb = 0;
3243 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
3244 $productChildrenNb = $product->hasFatherOrChild(1);
3245 }
3246 if ($productChildrenNb > 0) {
3247 // compute real stock from each subcomponent
3248 $product_stock = null;
3249 $product->loadStockForVirtualProduct('warehouseopen', $qty);
3250 foreach ($product->stock_warehouse as $componentStockWarehouse) {
3251 if ($product_stock === null) {
3252 $product_stock = $componentStockWarehouse->real;
3253 } else {
3254 $product_stock = min($product_stock, $componentStockWarehouse->real);
3255 }
3256 }
3257 if ($product_stock === null) {
3258 $product_stock = 0;
3259 }
3260 } else {
3261 $product_stock = $product->stock_reel;
3262 }
3263
3264 if ($product_stock < $qty) {
3265 $langs->load("errors");
3266 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
3267 $this->errors[] = $this->error;
3268
3269 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
3270
3271 $this->db->rollback();
3273 }
3274 }
3275 }
3276
3277 $staticline = clone $line;
3278
3279 $line->oldline = $staticline;
3280 $this->line = $line;
3281 $this->line->context = $this->context;
3282 $this->line->rang = $rang;
3283
3284 // Reorder if fk_parent_line change
3285 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
3286 $rangmax = $this->line_max($fk_parent_line);
3287 $this->line->rang = $rangmax + 1;
3288 }
3289
3290 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
3291 if ($qty < $this->line->packaging) {
3292 $qty = $this->line->packaging;
3293 } else {
3294 if (!empty($this->line->packaging)
3295 && is_numeric($this->line->packaging)
3296 && (float) $this->line->packaging > 0
3297 && fmod((float) $qty, (float) $this->line->packaging) > 0) {
3298 $coeff = intval($qty / $this->line->packaging) + 1;
3299 $qty = $this->line->packaging * $coeff;
3300 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
3301 }
3302 }
3303 }
3304
3305 $this->line->id = $rowid;
3306 $this->line->label = $label;
3307 $this->line->desc = $desc;
3308 $this->line->qty = $qty;
3309 $this->line->ref_ext = $ref_ext;
3310
3311 $this->line->vat_src_code = $vat_src_code;
3312 $this->line->tva_tx = $txtva;
3313 $this->line->localtax1_tx = $txlocaltax1;
3314 $this->line->localtax2_tx = $txlocaltax2;
3315 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3316 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3317 $this->line->remise_percent = $remise_percent;
3318 $this->line->subprice = (float) $pu_ht;
3319 $this->line->info_bits = $info_bits;
3320 $this->line->special_code = $special_code;
3321 $this->line->total_ht = (float) $total_ht;
3322 $this->line->total_tva = (float) $total_tva;
3323 $this->line->total_localtax1 = (float) $total_localtax1;
3324 $this->line->total_localtax2 = (float) $total_localtax2;
3325 $this->line->total_ttc = (float) $total_ttc;
3326 $this->line->date_start = $date_start;
3327 $this->line->date_end = $date_end;
3328 $this->line->product_type = $type;
3329 $this->line->fk_parent_line = $fk_parent_line;
3330 $this->line->skip_update_total = $skip_update_total;
3331 $this->line->fk_unit = $fk_unit;
3332
3333 $this->line->fk_fournprice = $fk_fournprice;
3334 $this->line->pa_ht = $pa_ht;
3335
3336 // Multicurrency
3337 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3338 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3339 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3340 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3341
3342 // TODO deprecated
3343 $this->line->price = $price;
3344
3345 if (is_array($array_options) && count($array_options) > 0) {
3346 // We replace values in this->line->array_options only for entries defined into $array_options
3347 foreach ($array_options as $key => $value) {
3348 $this->line->array_options[$key] = $array_options[$key];
3349 }
3350 }
3351
3352 $result = $this->line->update($user, $notrigger);
3353 if ($result > 0) {
3354 // Reorder if child line
3355 if (!empty($fk_parent_line)) {
3356 $this->line_order(true, 'DESC');
3357 }
3358
3359 // Mise a jour info denormalisees
3360 $this->update_price(1, 'auto');
3361
3362 $this->db->commit();
3363 return $result;
3364 } else {
3365 $this->error = $this->line->error;
3366
3367 $this->db->rollback();
3368 return -1;
3369 }
3370 } else {
3371 $this->error = get_class($this)."::updateline Order status makes operation forbidden";
3372 $this->errors = array('OrderStatusMakeOperationForbidden');
3373 return -2;
3374 }
3375 }
3376
3384 public function update(User $user, $notrigger = 0)
3385 {
3386 $error = 0;
3387
3388 // Clean parameters
3389 if (isset($this->ref)) {
3390 $this->ref = trim($this->ref);
3391 }
3392 if (isset($this->ref_client)) {
3393 $this->ref_client = trim($this->ref_client);
3394 }
3395 if (isset($this->ref_customer)) {
3396 $this->ref_customer = trim($this->ref_customer);
3397 }
3398 if (isset($this->note) || isset($this->note_private)) {
3399 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3400 }
3401 if (isset($this->note_public)) {
3402 $this->note_public = trim($this->note_public);
3403 }
3404 if (isset($this->model_pdf)) {
3405 $this->model_pdf = trim($this->model_pdf);
3406 }
3407 if (isset($this->import_key)) {
3408 $this->import_key = trim($this->import_key);
3409 }
3410 $delivery_date = $this->delivery_date;
3411
3412 // Check parameters
3413 // Put here code to add control on parameters values
3414
3415 // Update request
3416 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3417
3418 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
3419 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
3420 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
3421 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
3422 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
3423 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
3424 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
3425 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
3426 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
3427 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
3428 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
3429 $sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").",";
3430 $sql .= " fk_user_modif=".(isset($user->id) ? $user->id : "null").",";
3431 $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null").",";
3432 $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
3433 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
3434 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").",";
3435 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
3436 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
3437 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
3438 $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
3439 $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
3440 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
3441 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
3442 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
3443 $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? $this->warehouse_id : "null").",";
3444 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
3445 $sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").",";
3446 $sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null");
3447 $sql .= " WHERE rowid=".((int) $this->id);
3448
3449 $this->db->begin();
3450
3451 dol_syslog(get_class($this)."::update", LOG_DEBUG);
3452 $resql = $this->db->query($sql);
3453 if (!$resql) {
3454 $error++;
3455 $this->errors[] = "Error ".$this->db->lasterror();
3456 }
3457
3458 if (!$error) {
3459 $result = $this->insertExtraFields();
3460 if ($result < 0) {
3461 $error++;
3462 }
3463 }
3464
3465 if (!$error && !$notrigger) {
3466 // Call trigger
3467 $result = $this->call_trigger('ORDER_MODIFY', $user);
3468 if ($result < 0) {
3469 $error++;
3470 }
3471 // End call triggers
3472 }
3473
3474 // Commit or rollback
3475 if ($error) {
3476 foreach ($this->errors as $errmsg) {
3477 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3478 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3479 }
3480 $this->db->rollback();
3481 return -1 * $error;
3482 } else {
3483 $this->db->commit();
3484 return 1;
3485 }
3486 }
3487
3495 public function delete($user, $notrigger = 0)
3496 {
3497 global $conf, $langs;
3498 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3499
3500 $error = 0;
3501
3502 dol_syslog(get_class($this)."::delete ".$this->id, LOG_DEBUG);
3503
3504 $this->db->begin();
3505
3506 if (!$notrigger) {
3507 // Call trigger
3508 $result = $this->call_trigger('ORDER_DELETE', $user);
3509 if ($result < 0) {
3510 $error++;
3511 }
3512 // End call triggers
3513 }
3514
3515 // Test we can delete
3516 if ($this->countNbOfShipments() != 0) {
3517 $this->errors[] = $langs->trans('SomeShipmentExists');
3518 $error++;
3519 }
3520
3521 // Remove linked categories.
3522 if (!$error) {
3523 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_order";
3524 $sql .= " WHERE fk_order = ".((int) $this->id);
3525
3526 $result = $this->db->query($sql);
3527 if (!$result) {
3528 $error++;
3529 $this->errors[] = $this->db->lasterror();
3530 }
3531 }
3532
3533 // Delete extrafields of lines and lines
3534 if (!$error && !empty($this->table_element_line)) {
3535 $tabletodelete = $this->table_element_line;
3536 $sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id).")";
3537 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3538 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3539 $error++;
3540 $this->error = $this->db->lasterror();
3541 $this->errors[] = $this->error;
3542 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3543 }
3544 }
3545
3546 if (!$error) {
3547 // Delete linked object
3548 $res = $this->deleteObjectLinked();
3549 if ($res < 0) {
3550 $error++;
3551 }
3552 }
3553
3554 if (!$error) {
3555 // Delete linked contacts
3556 $res = $this->delete_linked_contact();
3557 if ($res < 0) {
3558 $error++;
3559 }
3560 }
3561
3562 // Removed extrafields of object
3563 if (!$error) {
3564 $result = $this->deleteExtraFields();
3565 if ($result < 0) {
3566 $error++;
3567 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3568 }
3569 }
3570
3571 // Delete main record
3572 if (!$error) {
3573 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3574 $res = $this->db->query($sql);
3575 if (!$res) {
3576 $error++;
3577 $this->error = $this->db->lasterror();
3578 $this->errors[] = $this->error;
3579 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3580 }
3581 }
3582
3583 // Delete record into ECM index and physically
3584 if (!$error) {
3585 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive, old method.
3586 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3587 if (!$res) {
3588 $error++;
3589 }
3590 }
3591
3592 if (!$error) {
3593 // We remove directory
3594 $ref = dol_sanitizeFileName($this->ref);
3595 if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
3596 $dir = $conf->commande->multidir_output[$this->entity]."/".$ref;
3597 $file = $dir."/".$ref.".pdf";
3598 if (file_exists($file)) {
3599 dol_delete_preview($this);
3600
3601 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3602 $this->error = 'ErrorFailToDeleteFile';
3603 $this->errors[] = $this->error;
3604 $this->db->rollback();
3605 return 0;
3606 }
3607 }
3608 if (file_exists($dir)) {
3609 $res = @dol_delete_dir_recursive($dir);
3610 if (!$res) {
3611 $this->error = 'ErrorFailToDeleteDir';
3612 $this->errors[] = $this->error;
3613 $this->db->rollback();
3614 return 0;
3615 }
3616 }
3617 }
3618 }
3619
3620 if (!$error) {
3621 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3622 $this->db->commit();
3623 return 1;
3624 } else {
3625 $this->db->rollback();
3626 return -1;
3627 }
3628 }
3629
3630
3631 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3639 public function load_board($user, $mode)
3640 {
3641 // phpcs:enable
3642 global $conf, $langs;
3643
3644 $clause = " WHERE";
3645
3646 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3647 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3648 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3649 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3650 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3651 $clause = " AND";
3652 }
3653 $sql .= $clause." c.entity IN (".getEntity('commande').")";
3654 //$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3655 if ($mode == 'toship') {
3656 // An order to ship is an open order (validated or in progress)
3657 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
3658 }
3659 if ($mode == 'tobill') {
3660 // An order to bill is an order not already billed
3661 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
3662 }
3663 if ($mode == 'shippedtobill') {
3664 // An order shipped and to bill is a delivered order not already billed
3665 $sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
3666 }
3667 if ($user->socid) {
3668 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3669 }
3670
3671 $resql = $this->db->query($sql);
3672 if ($resql) {
3673 $delay_warning = 0;
3674 $label = $labelShort = $url = '';
3675 if ($mode == 'toship') {
3676 $delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
3677 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
3678 $label = $langs->transnoentitiesnoconv("OrdersToProcess");
3679 $labelShort = $langs->transnoentitiesnoconv("Opened");
3680 }
3681 if ($mode == 'tobill') {
3682 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3683 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3684 $labelShort = $langs->trans("ToBill");
3685 }
3686 if ($mode == 'shippedtobill') {
3687 $url = DOL_URL_ROOT.'/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3688 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3689 $labelShort = $langs->trans("StatusOrderDelivered").' '.$langs->trans("and").' '.$langs->trans("ToBill");
3690 }
3691
3692 $response = new WorkboardResponse();
3693
3694 $response->warning_delay = $delay_warning;
3695 $response->label = $label;
3696 $response->labelShort = $labelShort;
3697 $response->url = $url;
3698 $response->url_late = DOL_URL_ROOT.'/commande/list.php?search_option=late&mainmenu=commercial&leftmenu=orders';
3699 $response->img = img_object('', "order");
3700
3701 $generic_commande = new Commande($this->db);
3702
3703 while ($obj = $this->db->fetch_object($resql)) {
3704 $response->nbtodo++;
3705 $response->total += $obj->total_ht;
3706
3707 $generic_commande->status = $obj->fk_statut;
3708 $generic_commande->date = $this->db->jdate($obj->date_commande);
3709 $generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
3710
3711 if ($mode == 'toship' && $generic_commande->hasDelay()) {
3712 $response->nbtodolate++;
3713 }
3714 }
3715
3716 return $response;
3717 } else {
3718 $this->error = $this->db->error();
3719 return -1;
3720 }
3721 }
3722
3728 public function getLabelSource()
3729 {
3730 global $langs;
3731
3732 $label = $langs->trans('OrderSource'.$this->source);
3733
3734 if ($label == 'OrderSource') {
3735 return '';
3736 }
3737 return $label;
3738 }
3739
3746 public function getLibStatut($mode)
3747 {
3748 return $this->LibStatut($this->status, $this->billed, $mode);
3749 }
3750
3751 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3761 public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
3762 {
3763 // phpcs:enable
3764 global $langs, $hookmanager;
3765
3766 $billedtext = '';
3767 if (empty($donotshowbilled)) {
3768 $billedtext .= ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3769 }
3770 $billedtextlong = ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3771
3772 $labelTooltip = '';
3773
3774 if ($status == self::STATUS_CANCELED) {
3775 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
3776 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
3777 $statusType = 'status9';
3778 } elseif ($status == self::STATUS_DRAFT) {
3779 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
3780 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
3781 $statusType = 'status0';
3782 } elseif ($status == self::STATUS_VALIDATED) {
3783 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated').$billedtextlong;
3784 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort').$billedtext;
3785 $statusType = 'status1';
3786 } elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
3787 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent').$billedtextlong;
3788 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort').$billedtext;
3789 $labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
3790 if (!empty($this->delivery_date)) {
3791 $labelTooltip .= ' - '.$langs->transnoentitiesnoconv("DateDeliveryPlanned").dol_print_date($this->delivery_date, 'day').$billedtext;
3792 }
3793 $statusType = 'status4';
3794 } elseif ($status == self::STATUS_CLOSED) {
3795 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered').$billedtextlong;
3796 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort').$billedtext;
3797 $statusType = 'status6';
3798 } else {
3799 $labelStatus = $langs->transnoentitiesnoconv('Unknown');
3800 $labelStatusShort = '';
3801 $statusType = '';
3802 $mode = 0;
3803 }
3804
3805 $parameters = array(
3806 'status' => $status,
3807 'mode' => $mode,
3808 'billed' => $billed,
3809 'donotshowbilled' => $donotshowbilled
3810 );
3811
3812 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3813
3814 if ($reshook > 0) {
3815 return $hookmanager->resPrint;
3816 }
3817
3818 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', array('tooltip' => $labelTooltip));
3819 }
3820
3828 public function getTooltipContentArray($params)
3829 {
3830 global $conf, $langs, $user;
3831
3832 $langs->load('orders');
3833 $datas = [];
3834 $nofetch = !empty($params['nofetch']);
3835
3836 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3837 return ['optimize' => $langs->trans("Order")];
3838 }
3839
3840 if ($user->hasRight('commande', 'lire')) {
3841 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Order").'</u>';
3842 if (isset($this->status)) {
3843 $datas['status'] = ' '.$this->getLibStatut(5);
3844 }
3845 $datas['Ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3846 if (!$nofetch) {
3847 $langs->load('companies');
3848 if (empty($this->thirdparty)) {
3849 $this->fetch_thirdparty();
3850 }
3851 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3852 }
3853 $datas['RefCustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.(empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
3854 if (!$nofetch) {
3855 $langs->load('project');
3856 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3857 $res = $this->fetchProject();
3858 if ($res > 0 && $this->project instanceof Project) {
3859 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3860 }
3861 }
3862 }
3863 if (!empty($this->total_ht)) {
3864 $datas['AmountHT'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3865 }
3866 if (!empty($this->total_tva)) {
3867 $datas['VAT'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3868 }
3869 if (!empty($this->total_ttc)) {
3870 $datas['AmountTTC'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3871 }
3872 if (!empty($this->date)) {
3873 $datas['Date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3874 }
3875 if (!empty($this->delivery_date)) {
3876 $datas['DeliveryDate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3877 }
3878 }
3879
3880 return $datas;
3881 }
3882
3896 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
3897 {
3898 global $conf, $langs, $user, $hookmanager;
3899
3900 if (!empty($conf->dol_no_mouse_hover)) {
3901 $notooltip = 1; // Force disable tooltips
3902 }
3903
3904 $result = '';
3905
3906 if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
3907 $url = DOL_URL_ROOT.'/expedition/shipment.php?id='.$this->id;
3908 } else {
3909 $url = DOL_URL_ROOT.'/commande/card.php?id='.$this->id;
3910 }
3911
3912 if (!$user->hasRight('commande', 'lire')) {
3913 $option = 'nolink';
3914 }
3915
3916 if ($option !== 'nolink') {
3917 // Add param to save lastsearch_values or not
3918 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3919 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3920 $add_save_lastsearch_values = 1;
3921 }
3922 if ($add_save_lastsearch_values) {
3923 $url .= '&save_lastsearch_values=1';
3924 }
3925 }
3926
3927 if ($short) {
3928 return $url;
3929 }
3930 $params = [
3931 'id' => $this->id,
3932 'objecttype' => $this->element,
3933 'option' => $option,
3934 'nofetch' => 1,
3935 ];
3936 $classfortooltip = 'classfortooltip';
3937 $dataparams = '';
3938 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3939 $classfortooltip = 'classforajaxtooltip';
3940 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3941 $label = '';
3942 } else {
3943 $label = implode($this->getTooltipContentArray($params));
3944 }
3945
3946 $linkclose = '';
3947 if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
3948 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3949 $label = $langs->trans("Order");
3950 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3951 }
3952 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3953 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3954
3955 $target_value = array('_self', '_blank', '_parent', '_top');
3956 if (in_array($target, $target_value)) {
3957 $linkclose .= ' target="'.dol_escape_htmltag($target).'"';
3958 }
3959 }
3960
3961 $linkstart = '<a href="'.$url.'"';
3962 $linkstart .= $linkclose.'>';
3963 $linkend = '</a>';
3964
3965 if ($option === 'nolink') {
3966 $linkstart = '';
3967 $linkend = '';
3968 }
3969
3970 $result .= $linkstart;
3971 if ($withpicto) {
3972 $result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3973 }
3974 if ($withpicto != 2) {
3975 $result .= $this->ref;
3976 }
3977 $result .= $linkend;
3978
3979 if ($addlinktonotes) {
3980 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
3981 if ($txttoshow) {
3982 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
3983 $result .= ' <span class="note inline-block">';
3984 $result .= '<a href="'.DOL_URL_ROOT.'/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
3985 $result .= img_picto('', 'note');
3986 $result .= '</a>';
3987 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
3988 //$result.='</a>';
3989 $result .= '</span>';
3990 }
3991 }
3992
3993 global $action;
3994 $hookmanager->initHooks(array($this->element . 'dao'));
3995 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
3996 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3997 if ($reshook > 0) {
3998 $result = $hookmanager->resPrint;
3999 } else {
4000 $result .= $hookmanager->resPrint;
4001 }
4002 return $result;
4003 }
4004
4005
4012 public function info($id)
4013 {
4014 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
4015 $sql .= ' date_valid as datev,';
4016 $sql .= ' date_cloture as datecloture,';
4017 $sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
4018 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
4019 $sql .= ' WHERE c.rowid = '.((int) $id);
4020 $result = $this->db->query($sql);
4021 if ($result) {
4022 if ($this->db->num_rows($result)) {
4023 $obj = $this->db->fetch_object($result);
4024 $this->id = $obj->rowid;
4025 if ($obj->fk_user_author) {
4026 $this->user_creation_id = $obj->fk_user_author;
4027 }
4028 if ($obj->fk_user_valid) {
4029 $this->user_validation_id = $obj->fk_user_valid;
4030 }
4031 if ($obj->fk_user_cloture) {
4032 $this->user_closing_id = $obj->fk_user_cloture;
4033 }
4034
4035 $this->date_creation = $this->db->jdate($obj->datec);
4036 $this->date_modification = $this->db->jdate($obj->datem);
4037 $this->date_validation = $this->db->jdate($obj->datev);
4038 $this->date_cloture = $this->db->jdate($obj->datecloture);
4039 }
4040
4041 $this->db->free($result);
4042 } else {
4043 dol_print_error($this->db);
4044 }
4045 }
4046
4047
4055 public function initAsSpecimen()
4056 {
4057 global $conf, $langs;
4058
4059 dol_syslog(get_class($this)."::initAsSpecimen");
4060
4061 // Load array of products prodids
4062 $num_prods = 0;
4063 $prodids = array();
4064 $sql = "SELECT rowid";
4065 $sql .= " FROM ".MAIN_DB_PREFIX."product";
4066 $sql .= " WHERE entity IN (".getEntity('product').")";
4067 $sql .= $this->db->plimit(100);
4068
4069 $resql = $this->db->query($sql);
4070 if ($resql) {
4071 $num_prods = $this->db->num_rows($resql);
4072 $i = 0;
4073 while ($i < $num_prods) {
4074 $i++;
4075 $row = $this->db->fetch_row($resql);
4076 $prodids[$i] = $row[0];
4077 }
4078 }
4079
4080 // Initialise parameters
4081 $this->id = 0;
4082 $this->ref = 'SPECIMEN';
4083 $this->specimen = 1;
4084 $this->entity = $conf->entity;
4085 $this->socid = 1;
4086 $this->date = time();
4087 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
4088 $this->cond_reglement_code = 'RECEP';
4089 $this->mode_reglement_code = 'CHQ';
4090 $this->availability_code = 'DSP';
4091 $this->demand_reason_code = 'SRC_00';
4092
4093 $this->note_public = 'This is a comment (public)';
4094 $this->note_private = 'This is a comment (private)';
4095
4096 $this->multicurrency_tx = 1;
4097 $this->multicurrency_code = $conf->currency;
4098
4099 $this->status = $this::STATUS_DRAFT;
4100
4101 // Lines
4102 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
4103 $xnbp = 0;
4104
4105 while ($xnbp < $nbp) {
4106 $line = new OrderLine($this->db);
4107
4108 $line->desc = $langs->trans("Description")." ".$xnbp;
4109 $line->qty = 1;
4110 $line->subprice = 100;
4111 $line->price = 100;
4112 $line->tva_tx = 20;
4113 if ($xnbp == 2) {
4114 $line->total_ht = 50;
4115 $line->total_ttc = 60;
4116 $line->total_tva = 10;
4117 $line->remise_percent = 50;
4118 } else {
4119 $line->total_ht = 100;
4120 $line->total_ttc = 120;
4121 $line->total_tva = 20;
4122 $line->remise_percent = 0;
4123 }
4124 if ($num_prods > 0) {
4125 $prodid = mt_rand(1, $num_prods);
4126 $line->fk_product = $prodids[$prodid];
4127 $line->product_ref = 'SPECIMEN';
4128 }
4129
4130 $this->lines[$xnbp] = $line;
4131
4132 $this->total_ht += $line->total_ht;
4133 $this->total_tva += $line->total_tva;
4134 $this->total_ttc += $line->total_ttc;
4135
4136 $xnbp++;
4137 }
4138
4139 return 1;
4140 }
4141
4142
4148 public function loadStateBoard()
4149 {
4150 global $user;
4151
4152 $this->nb = array();
4153 $clause = "WHERE";
4154
4155 $sql = "SELECT count(co.rowid) as nb";
4156 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as co";
4157 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON co.fk_soc = s.rowid";
4158 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
4159 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4160 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4161 $clause = "AND";
4162 }
4163 $sql .= " ".$clause." co.entity IN (".getEntity('commande').")";
4164
4165 $resql = $this->db->query($sql);
4166 if ($resql) {
4167 while ($obj = $this->db->fetch_object($resql)) {
4168 $this->nb["orders"] = $obj->nb;
4169 }
4170 $this->db->free($resql);
4171 return 1;
4172 } else {
4173 dol_print_error($this->db);
4174 $this->error = $this->db->error();
4175 return -1;
4176 }
4177 }
4178
4184 public function getLinesArray()
4185 {
4186 return $this->fetch_lines();
4187 }
4188
4200 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4201 {
4202 global $langs;
4203
4204 $langs->load("orders");
4205 $outputlangs->load("products");
4206
4207 if (!dol_strlen($modele)) {
4208 $modele = 'einstein';
4209
4210 if (!empty($this->model_pdf)) {
4211 $modele = $this->model_pdf;
4212 } elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
4213 $modele = getDolGlobalString('COMMANDE_ADDON_PDF');
4214 }
4215 }
4216
4217 $modelpath = "core/modules/commande/doc/";
4218
4219 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4220 }
4221
4222
4231 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4232 {
4233 $tables = array(
4234 'commande'
4235 );
4236
4237 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4238 }
4239
4248 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4249 {
4250 $tables = array(
4251 'commandedet',
4252 );
4253
4254 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4255 }
4256
4262 public function hasDelay()
4263 {
4264 global $conf;
4265
4266 if (!($this->status > Commande::STATUS_DRAFT && $this->status < Commande::STATUS_CLOSED)) {
4267 return false; // Never late if not inside this status range
4268 }
4269
4270 $now = dol_now();
4271
4272 return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
4273 }
4274
4280 public function showDelay()
4281 {
4282 global $conf, $langs;
4283
4284 if (empty($this->delivery_date)) {
4285 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date, 'day');
4286 } else {
4287 $text = $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
4288 }
4289 $text .= ' '.($conf->commande->client->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
4290
4291 return $text;
4292 }
4293
4303 public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
4304 {
4305 return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
4306 }
4307}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
$object ref
Definition info.php:90
Class to manage customers orders.
getNbOfServicesLines()
Return number of line with type service.
getNbOfShipments()
Count number of shipments for this order.
createFromProposal($object, User $user)
Load an object from a proposal and create a new order into database.
setDraft($user, $idwarehouse=-1)
Set draft status.
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set the planned delivery date.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
getLinesArray()
Create an array of order lines.
showDelay()
Show the customer delayed info.
set_date($user, $date, $notrigger=0)
Set the order date.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
loadStateBoard()
Load the indicators this->nb for the state board.
fetch_lines($only_product=0, $loadalsotranslation=0)
Load array lines.
createFromClone(User $user, $socid=0)
Load an object from its id and create a new one in database.
const STATUS_SHIPMENTONPROCESS
Shipment on process.
LibStatut($status, $billed, $mode, $donotshowbilled=0)
Return label of status.
getLibStatut($mode)
Return status label of Order.
hasDelay()
Is the sales order delayed?
const STATUS_CLOSED
Closed (Sent, billed or not)
valid($user, $idwarehouse=0, $notrigger=0)
Validate order.
getLabelSource()
Return source label of order.
set_remise($user, $remise, $notrigger=0)
Applique une remise relative.
const STATUS_CANCELED
Canceled status.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=0, $target='')
Return clickable link of object (with eventually picto)
loadExpeditions($filtre_statut=-1, $fk_product=0)
Load array this->expeditions of lines of shipments with nb of products sent for each order line Note:...
__construct($db)
Constructor.
availability($availability_id, $notrigger=0)
Update delivery delay.
set_reopen($user)
Tag the order as validated (opened) Function used when order is reopend after being closed.
set_ref_client($user, $ref_client, $notrigger=0)
Set customer ref.
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=array(), $fk_unit=null, $origin='', $origin_id=0, $pu_ht_devise=0, $ref_ext='', $noupdateafterinsertline=0)
Add an order line into database (linked to product/service or not)
getTooltipContentArray($params)
getTooltipContentArray
create($user, $notrigger=0)
Create sale order Note that this->ref can be set or empty.
demand_reason($demand_reason_id, $notrigger=0)
Update order demand_reason.
const STATUS_DRAFT
Draft status.
const STOCK_NOT_ENOUGH_FOR_ORDER
ERR Not enough stock.
initAsSpecimen()
Initialise an instance with random values.
insert_discount($idremise)
Add a discount line into a sale order (as a sale order line) using an existing absolute discount (Con...
getNbOfProductsLines()
Return number of line with type product.
update(User $user, $notrigger=0)
Update database.
classifyUnBilled(User $user, $notrigger=0)
Classify the order as not invoiced.
setDiscount($user, $remise, $notrigger=0)
Set a percentage discount.
cloture($user, $notrigger=0)
Close order.
cancel($idwarehouse=-1)
Cancel an order If stock is decremented on order validation, we must reincrement it.
setCategories($categories)
Sets object to given categories.
const STATUS_ACCEPTED
For backward compatibility.
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=array(), $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $ref_ext='', $rang=0)
Update a line in database.
classifyBilled(User $user, $notrigger=0)
Classify the order as invoiced.
info($id)
Charge les information d'ordre info dans l'objet commande.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
const STATUS_VALIDATED
Validated status.
deleteLine($user=null, $lineid=0, $id=0)
Delete an order line.
countNbOfShipments()
Returns an array with expeditions lines number.
liste_array($shortlist=0, $draft=0, $excluser=null, $socid=0, $limit=0, $offset=0, $sortfield='c.date_commande', $sortorder='DESC')
Return list of orders (eventuelly filtered on a user) into an array.
fetch($id, $ref='', $ref_ext='', $notused='')
Get object from database.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
add_product($idproduct, $qty, $remise_percent=0.0, $date_start='', $date_end='')
Add line into array $this->client must be loaded.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
setSignedStatus(User $user, int $status=0, int $notrigger=0, $triggercode='')
Set signed status.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
deleteExtraFields()
Delete all extra fields values for the current object.
copy_linked_contact($objFrom, $source='internal')
Copy contact from one element to current.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
call_trigger($triggerName, $user)
Call trigger based on this instance.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Superclass for orders classes.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
Class to manage standard extra fields.
Class to manage stock movements.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
Class to manage order lines.
Class to manage products or services.
Class to manage projects.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
setSignedStatusCommon(User $user, int $status, int $notrigger=0, string $triggercode='')
Set signed status & call trigger with context message.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:63
dol_delete_preview($object)
Delete all preview files linked to object instance.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90