dolibarr 25.0.0-alpha
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-2026 Frédéric France <frederic.france@free.fr>
15 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
16 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
17 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
18 * Copyright (C) 2026 Vincent de Grandpré <vincent@de-grandpre.quebec>
19 * Copyright (C) 2026 Lionel Vessiller <lvessiller@open-dsi.fr>
20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 3 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program. If not, see <https://www.gnu.org/licenses/>.
33 */
34
41require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
42require_once DOL_DOCUMENT_ROOT.'/commande/class/orderline.class.php';
43require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
44require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
45require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
46require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
47require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
48
49
53class Commande extends CommonOrder
54{
55 use CommonSubtotal;
56
60 public $element = 'commande';
61
66 public $TRIGGER_PREFIX = 'ORDER';
67
71 public $table_element = 'commande';
72
76 public $table_element_line = 'commandedet';
77
81 public $class_element_line = 'OrderLine';
82
86 public $fk_element = 'fk_commande';
87
91 public $picto = 'order';
92
97 public $restrictiononfksoc = 1;
98
102 protected $table_ref_field = 'ref';
103
107 public $ref;
108
112 public $socid;
113
117 public $ref_client;
118
122 public $ref_customer;
123
127 public $contactid;
128
134 public $statut;
135
140 public $status;
141
145 public $billed;
146
150 public $date_lim_reglement;
154 public $cond_reglement_code;
155
159 public $cond_reglement_doc;
160
165 public $deposit_percent;
166
170 public $fk_account;
171
175 public $mode_reglement;
176
180 public $mode_reglement_id;
181
185 public $mode_reglement_code;
186
191 public $availability_id;
192
197 public $availability_code;
198
203 public $availability;
204
208 public $demand_reason_id;
209
213 public $demand_reason_code;
214
218 public $date;
219
225 public $date_commande;
226
230 public $delivery_date;
231
235 public $fk_remise_except;
236
241 public $remise_percent;
242
246 public $source;
247
252 public $signed_status = 0;
253
257 public $warehouse_id;
258
262 public $extraparams = array();
263
267 public $user_author_id;
268
272 public $line;
273
277 public $lines = array();
278
279
283 public $module_source;
287 public $pos_source;
288
292 public $expeditions;
293
297 public $online_payment_url;
298
299
300
326 // BEGIN MODULEBUILDER PROPERTIES
330 public $fields = array(
331 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
332 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
333 'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 25),
334 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 26),
335 'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 28),
336 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 20),
337 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 25),
338 'date_commande' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => 1, 'position' => 60, 'csslist' => 'nowraponall'),
339 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 62, 'csslist' => 'nowraponall'),
340 'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 65, 'csslist' => 'nowraponall'),
341 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 85),
342 'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 90),
343 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => -1, 'position' => 95),
344 'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
345 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
346 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
347 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
348 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
349 'signed_status' => array('type' => 'smallint(6)', 'label' => 'SignedStatus', 'enabled' => 1, 'visible' => -1, 'position' => 146, 'arrayofkeyval' => array(0 => 'NoSignature', 1 => 'SignedSender', 2 => 'SignedReceiver', 9 => 'SignedAll')),
350 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 150),
351 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 155),
352 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 160),
353 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 170),
354 'fk_currency' => array('type' => 'varchar(3)', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 175),
355 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 180),
356 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 181),
357 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 185),
358 'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 190, 'csslist' => 'nowraponall'),
359 'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 195),
360 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'DefaultWarehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 200, 'nodepth' => 1),
361 'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 205),
362 'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 210),
363 //'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
364 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 225),
365 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 230),
366 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 235),
367 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
368 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245),
369 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
370 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
371 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 260, 'isameasure' => 1),
372 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 265, 'isameasure' => 1),
373 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 270),
374 'module_source' => array('type' => 'varchar(32)', 'label' => 'POSModule', 'enabled' => 1, 'visible' => -1, 'position' => 275),
375 'pos_source' => array('type' => 'varchar(32)', 'label' => 'POSTerminal', 'enabled' => 1, 'visible' => -1, 'position' => 280),
376 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 300),
377 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 302),
378 'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 304, 'csslist' => 'nowraponall'),
379 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 306),
380 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 400),
381 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'position' => 500),
382 );
383 // END MODULEBUILDER PROPERTIES
384
389
393 const STATUS_CANCELED = -1;
397 const STATUS_DRAFT = 0;
405 const STATUS_SHIPMENTONPROCESS = 2; // We set this status when a shipment is validated
406
412
416 const STATUS_CLOSED = 3;
417
418 /*
419 * No signature
420 */
421 const STATUS_NO_SIGNATURE = 0;
422
423 /*
424 * Signed by sender
425 */
426 const STATUS_SIGNED_SENDER = 1;
427
428 /*
429 * Signed by receiver
430 */
431 const STATUS_SIGNED_RECEIVER = 2;
432
433 /*
434 * Signed by all
435 */
436 const STATUS_SIGNED_ALL = 9; // To handle future kind of signature (ex: tripartite contract)
437
438
444 public function __construct($db)
445 {
446 $this->db = $db;
447
448 $this->ismultientitymanaged = 1;
449 $this->isextrafieldmanaged = 1;
450
451 $this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
452 }
453
461 public function getNextNumRef($soc)
462 {
463 global $langs, $conf;
464 $langs->load("order");
465
466 if (getDolGlobalString('COMMANDE_ADDON')) {
467 $mybool = false;
468
469 $file = getDolGlobalString('COMMANDE_ADDON') . ".php";
470 $classname = getDolGlobalString('COMMANDE_ADDON');
471
472 // Include file with class
473 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
474 foreach ($dirmodels as $reldir) {
475 $dir = dol_buildpath($reldir."core/modules/commande/");
476
477 // Load file with numbering class (if found)
478 $mybool = ((bool) @include_once $dir.$file) || $mybool;
479 }
480
481 if (!$mybool) {
482 dol_print_error(null, "Failed to include file ".$file);
483 return '';
484 }
485
486 $obj = new $classname();
488 '@phan-var-force ModeleNumRefCommandes $obj';
489
490 $numref = $obj->getNextValue($soc, $this);
491
492 if ($numref != "") {
493 return $numref;
494 } else {
495 $this->error = $obj->error;
496 //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
497 return "";
498 }
499 } else {
500 print $langs->trans("Error")." ".$langs->trans("Error_COMMANDE_ADDON_NotDefined");
501 return "";
502 }
503 }
504
505
514 public function valid($user, $idwarehouse = 0, $notrigger = 0)
515 {
516 global $conf, $langs;
517
518 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
519
520 $error = 0;
521
522 // Protection
523 if ($this->status == self::STATUS_VALIDATED) {
524 dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
525 return 0;
526 }
527
528 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
529 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
530 $this->error = 'NotEnoughPermissions';
531 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
532 return -1;
533 }
534 if (empty($this->socid)) {
535 $this->error = 'ErrorWrongParameters';
536 return -1;
537 }
538 if (!getDolGlobalBool('ORDER_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
539 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
540 return -1;
541 }
542 $now = dol_now();
543
544 $this->db->begin();
545
546 // Definition du nom de module de numerotation de commande
547 $soc = new Societe($this->db);
548 $soc->fetch($this->socid);
549
550 // Class of company linked to order
551 $result = $soc->setAsCustomer();
552
553 // Define new ref
554 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
555 $num = $this->getNextNumRef($soc);
556 } else {
557 $num = (string) $this->ref;
558 }
559 $this->newref = dol_sanitizeFileName($num);
560
561 // Validate
562 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
563 $sql .= " SET ref = '".$this->db->escape($num)."',";
564 $sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
565 $sql .= " date_valid = '".$this->db->idate($now)."',";
566 $sql .= " fk_user_valid = ".($user->id > 0 ? (int) $user->id : "null").",";
567 $sql .= " fk_user_modif = ".((int) $user->id);
568 $sql .= " WHERE rowid = ".((int) $this->id);
569
570 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
571 $resql = $this->db->query($sql);
572 if (!$resql) {
573 dol_print_error($this->db);
574 $this->error = $this->db->lasterror();
575 $error++;
576 }
577
578 if (!$error) {
579 // If stock is incremented on validate order, we must increment it
580 if ($result >= 0 && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
581 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
582 $langs->load("agenda");
583
584 // Loop on each line
585 $cpt = count($this->lines);
586 for ($i = 0; $i < $cpt; $i++) {
587 if ($this->lines[$i]->fk_product > 0) {
588 $mouvP = new MouvementStock($this->db);
589 $mouvP->origin = &$this;
590 $mouvP->setOrigin($this->element, $this->id);
591 // We decrement stock of product (and sub-products)
592 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
593 if ($result < 0) {
594 $error++;
595 $this->setErrorsFromObject($mouvP);
596 }
597 }
598 if ($error) {
599 break;
600 }
601 }
602 }
603 }
604
605 if (!$error && !$notrigger) {
606 // Call trigger
607 $result = $this->call_trigger('ORDER_VALIDATE', $user);
608 if ($result < 0) {
609 $error++;
610 }
611 // End call triggers
612 }
613
614 if (!$error) {
615 $this->oldref = $this->ref;
616
617 // Rename directory if dir was a temporary ref
618 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
619 // Now we rename also files into index
620 $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)."'";
621 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
622 $resql = $this->db->query($sql);
623 if (!$resql) {
624 $error++;
625 $this->error = $this->db->lasterror();
626 }
627 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'commande/".$this->db->escape($this->newref)."'";
628 $sql .= " WHERE filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
629 $resql = $this->db->query($sql);
630 if (!$resql) {
631 $error++;
632 $this->error = $this->db->lasterror();
633 }
634
635 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
636 $oldref = dol_sanitizeFileName($this->ref);
637 $newref = dol_sanitizeFileName($num);
638 $dirsource = getMultidirOutput($this).'/'.$oldref;
639 $dirdest = getMultidirOutput($this).'/'.$newref;
640 if (!$error && file_exists($dirsource)) {
641 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
642
643 if (@rename($dirsource, $dirdest)) {
644 dol_syslog("Rename ok");
645 // Rename docs starting with $oldref with $newref
646 $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
647 foreach ($listoffiles as $fileentry) {
648 $dirsource = $fileentry['name'];
649 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
650 $dirsource = $fileentry['path'].'/'.$dirsource;
651 $dirdest = $fileentry['path'].'/'.$dirdest;
652 @rename($dirsource, $dirdest);
653 }
654 }
655 }
656 }
657 }
658
659 // Set new ref and current status
660 if (!$error) {
661 $this->ref = $num;
662 $this->statut = self::STATUS_VALIDATED; // deprecated
664 }
665
666 if (!$error) {
667 $this->db->commit();
668 return 1;
669 } else {
670 $this->db->rollback();
671 return -1;
672 }
673 }
674
675 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
683 public function setDraft($user, $idwarehouse = -1)
684 {
685 //phpcs:enable
686 global $langs;
687
688 $error = 0;
689
690 // Protection
691 if ($this->status <= self::STATUS_DRAFT && !getDolGlobalInt('ORDER_REOPEN_TO_DRAFT')) {
692 return 0;
693 }
694
695 if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
696 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
697 $this->error = 'Permission denied';
698 return -1;
699 }
700
701 dol_syslog(__METHOD__, LOG_DEBUG);
702
703 $this->db->begin();
704
705 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
706 $sql .= " SET fk_statut = ".((int) self::STATUS_DRAFT).",";
707 $sql .= " fk_user_modif = ".((int) $user->id);
708 $sql .= " WHERE rowid = ".((int) $this->id);
709
710 if ($this->db->query($sql)) {
711 $this->oldcopy = clone $this;
712
713 // If stock is decremented on validate order, we must reincrement it
714 if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
715 $result = 0;
716
717 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
718 $langs->load("agenda");
719
720 $num = count($this->lines);
721 for ($i = 0; $i < $num; $i++) {
722 if ($this->lines[$i]->fk_product > 0) {
723 $mouvP = new MouvementStock($this->db);
724 $mouvP->origin = &$this;
725 $mouvP->setOrigin($this->element, $this->id);
726 // We increment stock of product (and sub-products)
727 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
728 if ($result < 0) {
729 $error++;
730 $this->setErrorsFromObject($mouvP);
731 break;
732 }
733 }
734 }
735 }
736
737 if (!$error) {
738 // Call trigger
739 $result = $this->call_trigger('ORDER_UNVALIDATE', $user);
740 if ($result < 0) {
741 $error++;
742 }
743 }
744
745 if (!$error) {
746 $this->statut = self::STATUS_DRAFT; // deprecated
747 $this->status = self::STATUS_DRAFT;
748 $this->db->commit();
749 return 1;
750 } else {
751 $this->db->rollback();
752 return -1;
753 }
754 } else {
755 $this->error = $this->db->error();
756 $this->db->rollback();
757 return -1;
758 }
759 }
760
761
762 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
770 public function set_reopen($user)
771 {
772 // phpcs:enable
773 $error = 0;
774
775 if ($this->status != self::STATUS_CANCELED && $this->status != self::STATUS_CLOSED) {
776 dol_syslog(get_class($this)."::set_reopen order has not status closed", LOG_WARNING);
777 return 0;
778 }
779
780 $this->db->begin();
781
782 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
783 $sql .= ' SET fk_statut='.self::STATUS_VALIDATED.', facture=0,';
784 $sql .= " fk_user_modif = ".((int) $user->id);
785 $sql .= " WHERE rowid = ".((int) $this->id);
786
787 dol_syslog(get_class($this)."::set_reopen", LOG_DEBUG);
788 $resql = $this->db->query($sql);
789 if ($resql) {
790 // Call trigger
791 $result = $this->call_trigger('ORDER_REOPEN', $user);
792 if ($result < 0) {
793 $error++;
794 }
795 // End call triggers
796 } else {
797 $error++;
798 $this->error = $this->db->lasterror();
799 dol_print_error($this->db);
800 }
801
802 if (!$error) {
803 $this->statut = self::STATUS_VALIDATED; // deprecated
805 $this->billed = 0;
806
807 $this->db->commit();
808 return 1;
809 } else {
810 foreach ($this->errors as $errmsg) {
811 dol_syslog(get_class($this)."::set_reopen ".$errmsg, LOG_ERR);
812 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
813 }
814 $this->db->rollback();
815 return -1 * $error;
816 }
817 }
818
827 public function cloture($user, $notrigger = 0, $checkpermission = 1)
828 {
829 $error = 0;
830
831 $usercanclose = (!$checkpermission
832 || (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
833 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
834
835 if ($usercanclose) {
836 if ($this->status == self::STATUS_CLOSED) {
837 return 0;
838 }
839 $this->db->begin();
840
841 $now = dol_now();
842
843 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
844 $sql .= ' SET fk_statut = '.self::STATUS_CLOSED.',';
845 $sql .= ' fk_user_cloture = '.((int) $user->id).',';
846 $sql .= " date_cloture = '".$this->db->idate($now)."',";
847 $sql .= " fk_user_modif = ".((int) $user->id);
848 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
849
850 if ($this->db->query($sql)) {
851 if (!$notrigger) {
852 // Call trigger
853 $result = $this->call_trigger('ORDER_CLOSE', $user);
854 if ($result < 0) {
855 $error++;
856 }
857 // End call triggers
858 }
859
860 if (!$error) {
861 $this->statut = self::STATUS_CLOSED; // deprecated
863
864 $this->db->commit();
865 return 1;
866 } else {
867 $this->db->rollback();
868 return -1;
869 }
870 } else {
871 $this->error = $this->db->lasterror();
872
873 $this->db->rollback();
874 return -1;
875 }
876 }
877 return 0;
878 }
879
891 public function setCategories($categories)
892 {
893 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
894 return parent::setCategoriesCommon($categories, Categorie::TYPE_ORDER);
895 }
896
905 public function cancel($user, $idwarehouse = -1)
906 {
907 global $user, $langs;
908
909 $error = 0;
910
911 $this->db->begin();
912
913 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
914 $sql .= " SET fk_statut = ".self::STATUS_CANCELED.",";
915 $sql .= " fk_user_modif = ".((int) $user->id);
916 $sql .= " WHERE rowid = ".((int) $this->id);
917 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
918
919 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
920 if ($this->db->query($sql)) {
921 // If stock is decremented on validate order, we must reincrement it
922 if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
923 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
924 $langs->load("agenda");
925
926 $num = count($this->lines);
927 for ($i = 0; $i < $num; $i++) {
928 if ($this->lines[$i]->fk_product > 0) {
929 $mouvP = new MouvementStock($this->db);
930 $mouvP->setOrigin($this->element, $this->id);
931 // We increment stock of product (and sub-products)
932 $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
933 if ($result < 0) {
934 $error++;
935 $this->setErrorsFromObject($mouvP);
936 break;
937 }
938 }
939 }
940 }
941
942 if (!$error) {
943 // Call trigger
944 $result = $this->call_trigger('ORDER_CANCEL', $user);
945 if ($result < 0) {
946 $error++;
947 }
948 // End call triggers
949 }
950
951 if (!$error) {
952 $this->statut = self::STATUS_CANCELED; // deprecated
954 $this->db->commit();
955 return 1;
956 } else {
957 foreach ($this->errors as $errmsg) {
958 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
959 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
960 }
961 $this->db->rollback();
962 return -1 * $error;
963 }
964 } else {
965 $this->error = $this->db->error();
966 $this->db->rollback();
967 return -1;
968 }
969 }
970
979 public function create($user, $notrigger = 0)
980 {
981 global $conf, $langs, $mysoc;
982 $error = 0;
983
984 // Clean parameters
985
986 if (empty($this->socid)) {
987 $this->error = 'ErrorWrongParameters';
988 return -1;
989 }
990
991 // Set tmp vars
992 $date = ($this->date_commande ? $this->date_commande : $this->date);
993 $this->import_key = trim((string) $this->import_key);
994 $delivery_date = $this->delivery_date;
995
996 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
997 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
998 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
999 } else {
1000 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1001 }
1002 if (empty($this->fk_multicurrency)) {
1003 $this->multicurrency_code = $conf->currency;
1004 $this->fk_multicurrency = 0;
1005 $this->multicurrency_tx = 1;
1006 }
1007 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1008 $this->entity = setEntity($this);
1009
1010 dol_syslog(get_class($this)."::create user=".$user->id);
1011
1012 // Check parameters
1013 if (!empty($this->ref)) { // We check that ref is not already used
1014 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1015 if ($result > 0) {
1016 $this->error = 'ErrorRefAlreadyExists';
1017 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1018 $this->db->rollback();
1019 return -1;
1020 }
1021 }
1022
1023 if ($this->isRefClientAlreadyUsed((string) $this->ref_client, (int) $this->socid) != 0) {
1024 return -1;
1025 }
1026
1027 $soc = new Societe($this->db);
1028 $result = $soc->fetch($this->socid);
1029 if ($result < 0) {
1030 $this->error = "Failed to fetch company";
1031 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1032 return -2;
1033 }
1034 if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
1035 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
1036 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1037 return -1;
1038 }
1039
1040 $this->date_creation = dol_now();
1041
1042 $this->db->begin();
1043
1044 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1045 $sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
1046 $sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
1047 $sql .= ", fk_shipping_method";
1048 $sql .= ", fk_warehouse";
1049 $sql .= ", fk_incoterms, location_incoterms";
1050 $sql .= ", entity, module_source, pos_source";
1051 $sql .= ", fk_multicurrency";
1052 $sql .= ", multicurrency_code";
1053 $sql .= ", multicurrency_tx";
1054 $sql .= ", import_key";
1055 $sql .= ")";
1056 $sql .= " VALUES ('(PROV)', ".((int) $this->socid).", '".$this->db->idate($this->date_creation)."', ".($user->id > 0 ? ((int) $user->id) : "null");
1057 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1058 $sql .= ", '".$this->db->idate($date)."'";
1059 $sql .= ", ".($this->source >= 0 && $this->source != '' ? $this->db->escape((string) $this->source) : 'null');
1060 $sql .= ", '".$this->db->escape($this->note_private)."'";
1061 $sql .= ", '".$this->db->escape($this->note_public)."'";
1062 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1063 $sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
1064 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1065 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
1066 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null");
1067 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
1068 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1069 $sql .= ", ".($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
1070 $sql .= ", ".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
1071 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1072 $sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
1073 $sql .= ", ".(!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1074 $sql .= ", ".(!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
1075 $sql .= ", ".(int) $this->fk_incoterms;
1076 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1077 $sql .= ", ".(int) $this->entity;
1078 $sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
1079 $sql .= ", ".((!is_null($this->pos_source) && $this->pos_source != '') ? "'".$this->db->escape($this->pos_source)."'" : "null"); // Can be null, '', '0', '1'
1080 $sql .= ", ".(int) $this->fk_multicurrency;
1081 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1082 $sql .= ", ".(float) $this->multicurrency_tx;
1083 $sql .= ", '".$this->db->escape($this->import_key)."'";
1084 $sql .= ")";
1085
1086 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1087 $resql = $this->db->query($sql);
1088 if ($resql) {
1089 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'commande');
1090
1091 if ($this->id) {
1092 $fk_parent_line = 0;
1093 $num = count($this->lines);
1094
1095 /*
1096 * Insert products details into db
1097 */
1098 for ($i = 0; $i < $num; $i++) {
1099 $line = $this->lines[$i];
1100
1101 // Test and convert into OrderLine object this->lines[$i]. When coming from REST API, we may still have an array
1102 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
1103 if (!is_object($line)) {
1104 $lineobj = new OrderLine($this->db);
1105 foreach ($line as $key => $val) {
1106 $lineobj->$key = $val;
1107 }
1108 $line = $lineobj;
1109 $this->lines[$i] = $line;
1110 }
1111
1112 // Reset fk_parent_line for no child products and special product
1113 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1114 $fk_parent_line = 0;
1115 }
1116
1117 // Complete vat rate with code
1118 $vatrate = $line->tva_tx;
1119 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', (string) $vatrate)) {
1120 $vatrate .= ' ('.$line->vat_src_code.')';
1121 }
1122
1123 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1124 $originid = $line->origin_id;
1125 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1126 } else { // old but bugged version (we store id of line and type of parent object)
1127 $originid = $line->id;
1128 $origintype = $this->element;
1129 }
1130
1131 // ref_ext
1132 if (empty($line->ref_ext)) {
1133 $line->ref_ext = '';
1134 }
1135
1136 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1137 $line_price_base_type = $line->getPriceBaseType();
1138 $result = $this->addline(
1139 $line->desc,
1140 (float) $line->subprice,
1141 $line->qty,
1142 $vatrate,
1143 $line->localtax1_tx,
1144 $line->localtax2_tx,
1145 $line->fk_product,
1146 $line->remise_percent,
1147 $line->info_bits,
1148 $line->fk_remise_except,
1149 $line_price_base_type,
1150 (float) $line->subprice_ttc,
1151 $line->date_start,
1152 $line->date_end,
1153 $line->product_type,
1154 $line->rang,
1155 $line->special_code,
1156 $fk_parent_line,
1157 $line->fk_fournprice,
1158 $line->pa_ht,
1159 $line->label,
1160 $line->array_options,
1161 $line->fk_unit,
1162 (string) $origintype,
1163 $originid,
1164 0,
1165 $line->ref_ext,
1166 1
1167 );
1168
1169 if ($result < 0) {
1170 if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
1171 $this->error = $this->db->lasterror();
1172 $this->errors[] = $this->error;
1173 dol_print_error($this->db);
1174 }
1175 $this->db->rollback();
1176 return -1;
1177 }
1178 // Defined the new fk_parent_line
1179 if ($result > 0 && $line->product_type == 9) {
1180 $fk_parent_line = $result;
1181 }
1182 }
1183
1184 $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.
1185
1186 // update ref
1187 $initialref = '(PROV'.$this->id.')';
1188 if (!empty($this->ref)) {
1189 $initialref = $this->ref;
1190 }
1191
1192 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($initialref)."' WHERE rowid=".((int) $this->id);
1193 if ($this->db->query($sql)) {
1194 $this->ref = $initialref;
1195
1196 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1197 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1198 }
1199
1200 // Add object linked
1201 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1202 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1203 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, ...))
1204 foreach ($tmp_origin_id as $origin_id) {
1205 $ret = $this->add_object_linked($origin, $origin_id);
1206 if (!$ret) {
1207 $this->error = $this->db->lasterror();
1208 $error++;
1209 }
1210 }
1211 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1212 $origin_id = $tmp_origin_id;
1213 $ret = $this->add_object_linked($origin, $origin_id);
1214 if (!$ret) {
1215 $this->error = $this->db->lasterror();
1216 $error++;
1217 }
1218 }
1219 }
1220 }
1221
1222 if (!$error && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
1223 $originforcontact = empty($this->origin_type) ? $this->origin : $this->origin_type;
1224 $originidforcontact = $this->origin_id;
1225 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
1226 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
1227 $exp = new Expedition($this->db);
1228 $exp->fetch($this->origin_id);
1229 $exp->fetchObjectLinked();
1230 if (count($exp->linkedObjectsIds['commande']) > 0) {
1231 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1232 $originforcontact = 'commande';
1233 if (is_object($value)) {
1234 $originidforcontact = $value->id;
1235 } else {
1236 $originidforcontact = $value;
1237 }
1238 break; // We take first one
1239 }
1240 }
1241 }
1242
1243 $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";
1244 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
1245
1246 $resqlcontact = $this->db->query($sqlcontact);
1247 if ($resqlcontact) {
1248 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1249 //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1250 $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
1251 }
1252 } else {
1253 dol_print_error($this->db);
1254 }
1255 }
1256
1257 if (!$error) {
1258 $result = $this->insertExtraFields();
1259 if ($result < 0) {
1260 $error++;
1261 }
1262 }
1263
1264 if (!$error && !$notrigger) {
1265 // Call trigger
1266 $result = $this->call_trigger('ORDER_CREATE', $user);
1267 if ($result < 0) {
1268 $error++;
1269 }
1270 // End call triggers
1271 }
1272
1273 if (!$error) {
1274 $this->db->commit();
1275 return $this->id;
1276 } else {
1277 $this->db->rollback();
1278 return -1 * $error;
1279 }
1280 } else {
1281 $this->error = $this->db->lasterror();
1282 $this->db->rollback();
1283 return -1;
1284 }
1285 }
1286
1287 return 0;
1288 } else {
1289 $this->error = $this->db->lasterror();
1290 $this->db->rollback();
1291 return -1;
1292 }
1293 }
1294
1295
1304 public function createFromClone(User $user, $socid = 0, $forceentity = null)
1305 {
1306 global $hookmanager;
1307
1308 $error = 0;
1309
1310 $object = new self($this->db);
1311
1312 $this->db->begin();
1313
1314 // Load source object
1315 $object->fetch($this->id);
1316
1317 $objFrom = clone $object;
1318
1319 // Change socid if needed
1320 if (!empty($socid) && $socid != $object->socid) {
1321 $objsoc = new Societe($this->db);
1322
1323 if ($objsoc->fetch($socid) > 0) {
1324 $object->socid = $objsoc->id;
1325 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1326 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1327 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1328 $object->fk_project = 0;
1329 $object->fk_delivery_address = 0;
1330 }
1331
1332 // TODO Change product price if multi-prices
1333 }
1334
1335 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1336
1337 $object->id = 0;
1338 $object->ref = '';
1339 $object->statut = self::STATUS_DRAFT; // deprecated
1340 $object->status = self::STATUS_DRAFT;
1341
1342 // Clear fields
1343 $object->user_author_id = $user->id;
1344 $object->user_validation_id = 0;
1345 $object->date = dol_now();
1346 $object->date_commande = dol_now();
1347 $object->date_creation = '';
1348 $object->date_validation = '';
1349 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1350 $object->ref_client = '';
1351 $object->ref_customer = '';
1352 }
1353
1354 // Do not clone ref_ext
1355 $num = count($object->lines);
1356 for ($i = 0; $i < $num; $i++) {
1357 $object->lines[$i]->ref_ext = '';
1358 }
1359
1360 // Create clone
1361 $object->context['createfromclone'] = 'createfromclone';
1362 $result = $object->create($user);
1363 if ($result < 0) {
1364 $error++;
1365 $this->setErrorsFromObject($object);
1366 }
1367
1368 if (!$error) {
1369 // copy internal contacts
1370 if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1371 $error++;
1372 }
1373 }
1374
1375 if (!$error) {
1376 // copy external contacts if same company
1377 if ($object->socid == $objFrom->socid) {
1378 if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1379 $error++;
1380 }
1381 }
1382 }
1383
1384 if (!$error) {
1385 // Hook of thirdparty module
1386 if (is_object($hookmanager)) {
1387 $parameters = array('objFrom' => $objFrom, 'clonedObj' => $object);
1388 $action = '';
1389 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1390 if ($reshook < 0) {
1391 $this->setErrorsFromObject($hookmanager);
1392 $error++;
1393 }
1394 }
1395 }
1396
1397 unset($object->context['createfromclone']);
1398
1399 // End
1400 if (!$error) {
1401 $this->db->commit();
1402 return $object->id;
1403 } else {
1404 $this->db->rollback();
1405 return -1;
1406 }
1407 }
1408
1409
1417 public function createFromProposal($object, User $user)
1418 {
1419 global $conf, $hookmanager, $langs;
1420
1421 require_once DOL_DOCUMENT_ROOT . '/multicurrency/class/multicurrency.class.php';
1422 require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
1423
1424 $error = 0;
1425
1426 $this->date_commande = dol_now();
1427 $this->date = dol_now();
1428 $this->source = 0;
1429
1430 $num = count($object->lines);
1431 for ($i = 0; $i < $num; $i++) {
1432 $line = new OrderLine($this->db);
1433
1434 $line->libelle = $object->lines[$i]->libelle;
1435 $line->label = $object->lines[$i]->label;
1436 $line->desc = $object->lines[$i]->desc;
1437 $line->price = $object->lines[$i]->price;
1438 $line->subprice = $object->lines[$i]->subprice;
1439 $line->subprice_ttc = $object->lines[$i]->subprice_ttc; // Preserve the TTC entry mode so create() keeps the typed value (no rounding drift).
1440 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1441 $line->tva_tx = $object->lines[$i]->tva_tx;
1442 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1443 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1444 $line->qty = $object->lines[$i]->qty;
1445 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1446 $line->remise_percent = $object->lines[$i]->remise_percent;
1447 $line->fk_product = $object->lines[$i]->fk_product;
1448 $line->info_bits = $object->lines[$i]->info_bits;
1449 $line->product_type = $object->lines[$i]->product_type;
1450 $line->rang = $object->lines[$i]->rang;
1451 $line->special_code = $object->lines[$i]->special_code;
1452 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1453 $line->fk_unit = $object->lines[$i]->fk_unit;
1454
1455 $line->date_start = $object->lines[$i]->date_start;
1456 $line->date_end = $object->lines[$i]->date_end;
1457
1458 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1459 $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);
1460 $line->pa_ht = $marginInfos[0];
1461 $line->marge_tx = $marginInfos[1];
1462 $line->marque_tx = $marginInfos[2];
1463
1464 $line->origin = $object->element;
1465 $line->origin_id = $object->lines[$i]->id;
1466
1467 // get extrafields from original line
1468 $object->lines[$i]->fetch_optionals();
1469 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1470 $line->array_options[$options_key] = $value;
1471 }
1472
1473 $this->lines[$i] = $line;
1474 }
1475
1476 $this->entity = $object->entity;
1477 $this->socid = $object->socid;
1478 $this->fk_project = $object->fk_project;
1479 $this->cond_reglement_id = $object->cond_reglement_id;
1480 $this->deposit_percent = $object->deposit_percent;
1481 $this->mode_reglement_id = $object->mode_reglement_id;
1482 $this->fk_account = $object->fk_account;
1483 $this->fk_incoterms = $object->fk_incoterms;
1484 $this->location_incoterms = $object->location_incoterms;
1485 $this->availability_id = $object->availability_id;
1486 $this->demand_reason_id = $object->demand_reason_id;
1487 $this->delivery_date = $object->delivery_date;
1488 $this->shipping_method_id = $object->shipping_method_id;
1489 $this->warehouse_id = $object->warehouse_id;
1490 $this->fk_delivery_address = $object->fk_delivery_address;
1491 $this->contact_id = $object->contact_id;
1492 $this->ref_client = $object->ref_client;
1493 $this->ref_customer = $object->ref_client;
1494
1495 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1496 $this->note_private = $object->note_private;
1497 $this->note_public = $object->note_public;
1498 }
1499
1500 $this->origin = $object->element;
1501 $this->origin_id = $object->id;
1502
1503 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1504 if (isModEnabled('multicurrency')) {
1505 if (!empty($object->multicurrency_code)) {
1506 $this->multicurrency_code = $object->multicurrency_code;
1507 }
1508 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
1509 $this->multicurrency_tx = $object->multicurrency_tx;
1510 }
1511
1512 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1513 $tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
1514 $this->fk_multicurrency = $tmparray[0];
1515 $this->multicurrency_tx = $tmparray[1];
1516 } else {
1517 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1518 }
1519 if (empty($this->fk_multicurrency)) {
1520 $this->multicurrency_code = $conf->currency;
1521 $this->fk_multicurrency = 0;
1522 $this->multicurrency_tx = 1;
1523 }
1524 }
1525
1526 // get extrafields from original line
1527 $object->fetch_optionals();
1528
1529 $e = new ExtraFields($this->db);
1530 $element_extrafields = $e->fetch_name_optionals_label($this->table_element);
1531
1532 foreach ($object->array_options as $options_key => $value) {
1533 if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
1534 $this->array_options[$options_key] = $value;
1535 }
1536 }
1537 // Possibility to add external linked objects with hooks
1538 $this->linked_objects[$this->origin] = $this->origin_id;
1539 if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
1540 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1541 }
1542
1543 $ret = $this->create($user);
1544
1545 if ($ret > 0) {
1546 // Actions hooked (by external module)
1547 $hookmanager->initHooks(array('orderdao'));
1548
1549 $parameters = array('objFrom' => $object);
1550 $action = '';
1551 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1552 if ($reshook < 0) {
1553 $this->setErrorsFromObject($hookmanager);
1554 $error++;
1555 }
1556
1557 if (!$error) {
1558 // Validate immediately the order
1559 if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
1560 $this->fetch($ret);
1561 $result = $this->valid($user);
1562 if ($result > 0 && !getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
1563 $this->fetch($this->id);
1564 $this->fetch_thirdparty();
1565
1566 $outputlangs = $langs;
1567 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($this->thirdparty->default_lang)) {
1568 $outputlangs = new Translate('', $conf);
1569 $outputlangs->setDefaultLang($this->thirdparty->default_lang);
1570 $outputlangs->loadLangs(array('main', 'orders'));
1571 }
1572
1573 $this->generateDocument($this->model_pdf, $outputlangs);
1574 }
1575 }
1576 return $ret;
1577 } else {
1578 return -1;
1579 }
1580 } else {
1581 return -1;
1582 }
1583 }
1584
1585
1625 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)
1626 {
1627 global $mysoc, $langs, $user;
1628
1629 $logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1630 $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";
1631 $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";
1632 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
1633
1634 if ($this->status == self::STATUS_DRAFT) {
1635 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1636
1637 // Clean parameters
1638
1639 if (empty($remise_percent)) {
1640 $remise_percent = 0;
1641 }
1642 if (empty($qty)) {
1643 $qty = 0;
1644 }
1645 if (empty($info_bits)) {
1646 $info_bits = 0;
1647 }
1648 if (empty($rang)) {
1649 $rang = 0;
1650 }
1651 if (empty($txtva)) {
1652 $txtva = 0;
1653 }
1654 if (empty($txlocaltax1)) {
1655 $txlocaltax1 = 0;
1656 }
1657 if (empty($txlocaltax2)) {
1658 $txlocaltax2 = 0;
1659 }
1660 if (empty($fk_parent_line) || $fk_parent_line < 0) {
1661 $fk_parent_line = 0;
1662 }
1663 if (empty($this->fk_multicurrency)) {
1664 $this->fk_multicurrency = 0;
1665 }
1666 if (empty($ref_ext)) {
1667 $ref_ext = '';
1668 }
1669
1670 $remise_percent = (float) price2num($remise_percent);
1671 $qty = (float) price2num($qty);
1672 $pu_ht = price2num($pu_ht);
1673 $pu_ht_devise = price2num($pu_ht_devise);
1674 $pu_ttc = price2num($pu_ttc);
1675 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
1676 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
1677 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1678 }
1679 $txlocaltax1 = price2num($txlocaltax1);
1680 $txlocaltax2 = price2num($txlocaltax2);
1681 if ($price_base_type == 'HT') {
1682 $pu = $pu_ht;
1683 } else {
1684 $pu = $pu_ttc;
1685 }
1686 $label = trim((string) $label);
1687 $desc = trim((string) $desc);
1688
1689 // Check parameters
1690 if ($type < 0) {
1691 return -1;
1692 }
1693
1694 if ($date_start && $date_end && $date_start > $date_end) {
1695 $langs->load("errors");
1696 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1697 return -1;
1698 }
1699
1700 $this->db->begin();
1701
1702 $product_type = $type;
1703 if (!empty($fk_product) && $fk_product > 0) {
1704 $product = new Product($this->db);
1705 $result = $product->fetch($fk_product);
1706 $product_type = $product->type;
1707
1708 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
1709 // get real stock
1710 $productChildrenNb = 0;
1711 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1712 $productChildrenNb = $product->hasFatherOrChild(1);
1713 }
1714 if ($productChildrenNb > 0) {
1715 // compute real stock from each subcomponent
1716 $product_stock = null;
1717 $product->loadStockForVirtualProduct('warehouseopen', $qty);
1718 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1719 if ($product_stock === null) {
1720 $product_stock = $componentStockWarehouse->real;
1721 } else {
1722 $product_stock = min($product_stock, $componentStockWarehouse->real);
1723 }
1724 }
1725 if ($product_stock === null) {
1726 $product_stock = 0;
1727 }
1728 } else {
1729 $product_stock = $product->stock_reel;
1730 }
1731
1732 if ($product_stock < $qty) {
1733 $langs->load("errors");
1734 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1735 $this->errors[] = $this->error;
1736 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
1737 $this->db->rollback();
1739 }
1740 }
1741 }
1742 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1743 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1744 // and this is done at the line level, which has its own VAT rate
1745
1746 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1747
1748 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1749 $tmpproduct = new Product($this->db);
1750 $result = $tmpproduct->fetch($fk_product);
1751 $newqty = $this->roundQtyToPackaging($qty, $tmpproduct->packaging);
1752 if ($newqty != $qty) {
1753 $qty = $newqty;
1754 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
1755 }
1756 }
1757
1758 // Clean vat code
1759 $reg = array();
1760 $vat_src_code = '';
1761 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
1762 $vat_src_code = $reg[1];
1763 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
1764 }
1765
1766 $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);
1767
1768 $total_ht = $tabprice[0];
1769 $total_tva = $tabprice[1];
1770 $total_ttc = $tabprice[2];
1771 $total_localtax1 = $tabprice[9];
1772 $total_localtax2 = $tabprice[10];
1773 $pu_ht = $tabprice[3];
1774
1775 // MultiCurrency
1776 $multicurrency_total_ht = $tabprice[16];
1777 $multicurrency_total_tva = $tabprice[17];
1778 $multicurrency_total_ttc = $tabprice[18];
1779 $pu_ht_devise = $tabprice[19];
1780
1781 // Rang to use
1782 $ranktouse = $rang;
1783
1784 if (empty($ranktouse) || $ranktouse == -1) {
1785 $rangmax = $this->line_max($fk_parent_line);
1786 $ranktouse = $rangmax + 1;
1787 }
1788
1789 // Insert line
1790 $this->line = new OrderLine($this->db);
1791
1792 $this->line->context = $this->context;
1793
1794 $this->line->fk_commande = $this->id;
1795 $this->line->label = $label;
1796 $this->line->desc = $desc;
1797 $this->line->qty = (float) $qty;
1798 $this->line->ref_ext = $ref_ext;
1799
1800 $this->line->vat_src_code = $vat_src_code;
1801 $this->line->tva_tx = $txtva;
1802 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1803 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1804 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1805 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1806 $this->line->fk_product = $fk_product;
1807 $this->line->product_type = $product_type;
1808 $this->line->fk_remise_except = $fk_remise_except;
1809 $this->line->remise_percent = $remise_percent;
1810 $this->line->subprice = (float) $pu_ht;
1811 // Persist the original entry mode of the line so updateline() can preserve it later.
1812 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
1813 $this->line->rang = $ranktouse;
1814 $this->line->info_bits = $info_bits;
1815 $this->line->total_ht = (float) $total_ht;
1816 $this->line->total_tva = (float) $total_tva;
1817 $this->line->total_localtax1 = (float) $total_localtax1;
1818 $this->line->total_localtax2 = (float) $total_localtax2;
1819 $this->line->total_ttc = (float) $total_ttc;
1820 $this->line->special_code = $special_code;
1821 if (empty($qty) && empty($special_code)) {
1822 $this->line->special_code = 3;
1823 }
1824 $this->line->origin = $origin;
1825 $this->line->origin_type = $origin;
1826 $this->line->origin_id = $origin_id;
1827
1828 $this->line->fk_parent_line = $fk_parent_line;
1829 $this->line->fk_unit = $fk_unit;
1830
1831 $this->line->date_start = $date_start;
1832 $this->line->date_end = $date_end;
1833
1834 $this->line->fk_fournprice = $fk_fournprice;
1835 $this->line->pa_ht = $pa_ht; // Can be '' when not defined or 0 if defined to 0 or a price value
1836
1837 // Multicurrency
1838 $this->line->fk_multicurrency = $this->fk_multicurrency;
1839 $this->line->multicurrency_code = $this->multicurrency_code;
1840 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1841 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1842 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1843 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1844
1845 if (is_array($array_options) && count($array_options) > 0) {
1846 $this->line->array_options = $array_options;
1847 }
1848
1849 $result = $this->line->insert($user);
1850 if ($result > 0) {
1851 // Update denormalized fields at the order level
1852 if (empty($noupdateafterinsertline)) {
1853 $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.
1854 }
1855
1856 if ($result > 0) {
1857 if (!isset($this->context['createfromclone'])) {
1858 if (!empty($fk_parent_line)) {
1859 // Always reorder if child line
1860 $this->line_order(true, 'DESC');
1861 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1862 // Update all rank of all other lines starting from the same $ranktouse
1863 foreach ($this->lines as $tmpline) {
1864 if ($tmpline->rang >= $ranktouse) {
1865 if (!empty($tmpline->id)) {
1866 $this->updateRangOfLine($tmpline->id, $tmpline->rang + 1);
1867 }
1868 }
1869 }
1870 }
1871
1872 $this->lines[] = $this->line;
1873 } else {
1874 // Loop on all lines of parent object
1875 foreach ($this->lines as $tmpline) {
1876 if ($tmpline->id == $origin_id && $tmpline->element = $origin) {
1877 $this->line->extraparams = $tmpline->extraparams;
1878 $this->line->setExtraParameters();
1879 }
1880 }
1881 }
1882
1883 $this->db->commit();
1884 return $this->line->id;
1885 } else {
1886 $this->db->rollback();
1887 return -1;
1888 }
1889 } else {
1890 $this->setErrorsFromObject($this->line);
1891 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
1892 $this->db->rollback();
1893 return -2;
1894 }
1895 } else {
1896 dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1897 return -3;
1898 }
1899 }
1900
1901
1902 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1916 public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1917 {
1918 // phpcs:enable
1919 global $conf, $mysoc;
1920
1921 if (!$qty) {
1922 $qty = 1;
1923 }
1924
1925 if ($idproduct > 0) {
1926 $prod = new Product($this->db);
1927 $prod->fetch($idproduct);
1928
1929 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1930 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1931 if (empty($tva_tx)) {
1932 $tva_npr = 0;
1933 }
1934 $vat_src_code = ''; // May be defined into tva_tx
1935
1936 $localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1937 $localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1938
1939 // multiprix
1940 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
1941 $price = $prod->multiprices[$this->thirdparty->price_level];
1942 $price_ttc = $prod->multiprices_ttc[$this->thirdparty->price_level];
1943 } else {
1944 $price = $prod->price;
1945 $price_ttc = $prod->price_ttc;
1946 }
1947
1948 $line = new OrderLine($this->db);
1949
1950 $line->context = $this->context;
1951
1952 $line->fk_product = $idproduct;
1953 $line->desc = $prod->description;
1954 $line->qty = $qty;
1955 $line->subprice = $price;
1956 $line->subprice_ttc = $price_ttc;
1957 $line->remise_percent = $remise_percent;
1958 $line->vat_src_code = $vat_src_code;
1959 $line->tva_tx = $tva_tx;
1960 $line->localtax1_tx = $localtax1_tx;
1961 $line->localtax2_tx = $localtax2_tx;
1962
1963 $line->product_ref = $prod->ref;
1964 $line->product_label = $prod->label;
1965 $line->product_desc = $prod->description;
1966 $line->fk_unit = $prod->fk_unit;
1967
1968 // Save the start and end date of the line in the object
1969 if ($date_start) {
1970 $line->date_start = $date_start;
1971 }
1972 if ($date_end) {
1973 $line->date_end = $date_end;
1974 }
1975
1976 $this->lines[] = $line;
1977
1995 }
1996 }
1997
1998
2008 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
2009 {
2010 // Check parameters
2011 if (empty($id) && empty($ref) && empty($ref_ext)) {
2012 return -1;
2013 }
2014
2015 $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';
2016 $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';
2017 $sql .= ', c.fk_account';
2018 $sql .= ', c.date_commande, c.date_valid, c.tms';
2019 $sql .= ', c.date_livraison as delivery_date';
2020 $sql .= ', c.fk_shipping_method';
2021 $sql .= ', c.fk_warehouse';
2022 $sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
2023 $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';
2024 $sql .= ', c.fk_incoterms, c.location_incoterms';
2025 $sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
2026 $sql .= ", c.module_source, c.pos_source";
2027 $sql .= ", i.libelle as label_incoterms";
2028 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
2029 $sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
2030 $sql .= ', ca.code as availability_code, ca.label as availability_label';
2031 $sql .= ', dr.code as demand_reason_code';
2032 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
2033 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
2034 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON c.fk_mode_reglement = p.id';
2035 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON c.fk_availability = ca.rowid';
2036 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
2037 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
2038
2039 if ($id) {
2040 $sql .= " WHERE c.rowid = ".((int) $id);
2041 } else {
2042 $sql .= " WHERE c.entity IN (".getEntity('commande').")"; // Don't use entity if you use rowid
2043 }
2044
2045 if ($ref) {
2046 $sql .= " AND c.ref = '".$this->db->escape($ref)."'";
2047 }
2048 if ($ref_ext) {
2049 $sql .= " AND c.ref_ext = '".$this->db->escape($ref_ext)."'";
2050 }
2051
2052 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
2053 $result = $this->db->query($sql);
2054 if ($result) {
2055 $obj = $this->db->fetch_object($result);
2056 if ($obj) {
2057 $this->id = $obj->rowid;
2058 $this->entity = $obj->entity;
2059
2060 $this->ref = $obj->ref;
2061 $this->ref_client = $obj->ref_client;
2062 $this->ref_customer = $obj->ref_client;
2063 $this->ref_ext = $obj->ref_ext;
2064
2065 $this->socid = $obj->fk_soc;
2066 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2067
2068 $this->fk_project = $obj->fk_project;
2069 $this->project = null; // Clear if another value was already set by fetch_projet
2070
2071 $this->statut = $obj->status; // deprecated
2072 $this->status = $obj->status;
2073
2074 $this->user_author_id = $obj->fk_user_author;
2075 $this->user_creation_id = $obj->fk_user_author;
2076 $this->user_validation_id = $obj->fk_user_valid;
2077 $this->user_modification_id = $obj->fk_user_modif;
2078 $this->total_ht = $obj->total_ht;
2079 $this->total_tva = $obj->total_tva;
2080 $this->total_localtax1 = $obj->total_localtax1;
2081 $this->total_localtax2 = $obj->total_localtax2;
2082 $this->total_ttc = $obj->total_ttc;
2083 $this->date = $this->db->jdate($obj->date_commande);
2084 $this->date_commande = $this->db->jdate($obj->date_commande);
2085 $this->date_creation = $this->db->jdate($obj->date_creation);
2086 $this->date_validation = $this->db->jdate($obj->date_valid);
2087 $this->date_modification = $this->db->jdate($obj->tms);
2088 $this->source = $obj->source;
2089 $this->billed = $obj->billed;
2090 $this->note = $obj->note_private; // deprecated
2091 $this->note_private = $obj->note_private;
2092 $this->note_public = $obj->note_public;
2093 $this->model_pdf = $obj->model_pdf;
2094 $this->last_main_doc = $obj->last_main_doc;
2095 $this->mode_reglement_id = $obj->fk_mode_reglement;
2096 $this->mode_reglement_code = $obj->mode_reglement_code;
2097 $this->mode_reglement = $obj->mode_reglement_libelle;
2098 $this->cond_reglement_id = $obj->fk_cond_reglement;
2099 $this->cond_reglement_code = $obj->cond_reglement_code;
2100 $this->cond_reglement = $obj->cond_reglement_libelle;
2101 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2102 $this->deposit_percent = $obj->deposit_percent;
2103 $this->fk_account = $obj->fk_account;
2104 $this->availability_id = $obj->fk_availability;
2105 $this->availability_code = $obj->availability_code;
2106 $this->availability = $obj->availability_label;
2107 $this->demand_reason_id = $obj->fk_input_reason;
2108 $this->demand_reason_code = $obj->demand_reason_code;
2109 $this->delivery_date = $this->db->jdate($obj->delivery_date);
2110 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
2111 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
2112 $this->fk_delivery_address = $obj->fk_delivery_address;
2113 $this->module_source = $obj->module_source;
2114 $this->pos_source = $obj->pos_source;
2115
2116 //Incoterms
2117 $this->fk_incoterms = $obj->fk_incoterms;
2118 $this->location_incoterms = $obj->location_incoterms;
2119 $this->label_incoterms = $obj->label_incoterms;
2120
2121 // Multicurrency
2122 $this->fk_multicurrency = $obj->fk_multicurrency;
2123 $this->multicurrency_code = $obj->multicurrency_code;
2124 $this->multicurrency_tx = $obj->multicurrency_tx;
2125 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2126 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
2127 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2128
2129 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2130
2131 $this->lines = array();
2132
2133 // Retrieve all extrafield
2134 // fetch optionals attributes and labels
2135 $this->fetch_optionals();
2136
2137 $this->db->free($result);
2138
2139 // Lines
2140 $result = $this->fetch_lines();
2141 if ($result < 0) {
2142 return -3;
2143 }
2144 return 1;
2145 } else {
2146 $this->error = 'Order with id '.$id.' not found sql='.$sql;
2147 return 0;
2148 }
2149 } else {
2150 $this->error = $this->db->error();
2151 return -1;
2152 }
2153 }
2154
2155
2156 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2163 public function insert_discount($idremise)
2164 {
2165 // phpcs:enable
2166 global $langs;
2167
2168 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2169 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2170
2171 $this->db->begin();
2172
2173 $remise = new DiscountAbsolute($this->db);
2174 $result = $remise->fetch($idremise);
2175
2176 if ($result > 0) {
2177 if ($remise->fk_facture) { // Protection against multiple submissions
2178 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2179 $this->db->rollback();
2180 return -5;
2181 }
2182
2183 $line = new OrderLine($this->db);
2184
2185 $line->fk_commande = $this->id;
2186 $line->fk_remise_except = $remise->id;
2187 $line->desc = $remise->description; // Description for the order line
2188 $line->vat_src_code = $remise->vat_src_code;
2189 $line->tva_tx = $remise->tva_tx;
2190 $line->localtax1_tx = $remise->localtax1_tx;
2191 $line->localtax1_type = $remise->localtax1_type;
2192 $line->localtax2_tx = $remise->localtax1_tx;
2193 $line->localtax2_type = $remise->localtax1_type;
2194 $line->subprice = -(float) $remise->amount_ht;
2195 $line->price = -(float) $remise->amount_ht;
2196 $line->fk_product = 0; // Predefined Product ID
2197 $line->qty = 1;
2198 $line->remise_percent = 0;
2199 $line->rang = -1;
2200 $line->info_bits = 2;
2201
2202 $line->total_ht = -(float) $remise->amount_ht;
2203 $line->total_tva = -(float) $remise->amount_tva;
2204 $line->total_ttc = -(float) $remise->amount_ttc;
2205 $line->total_localtax1 = -(float) $remise->total_localtax1;
2206 $line->total_localtax2 = -(float) $remise->total_localtax2;
2207
2208 $result = $line->insert();
2209 if ($result > 0) {
2210 $result = $this->update_price(1);
2211 if ($result > 0) {
2212 $this->db->commit();
2213 return 1;
2214 } else {
2215 $this->db->rollback();
2216 return -1;
2217 }
2218 } else {
2219 $this->setErrorsFromObject($line);
2220 $this->db->rollback();
2221 return -2;
2222 }
2223 } else {
2224 $this->db->rollback();
2225 return -2;
2226 }
2227 }
2228
2229
2230 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2238 public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2239 {
2240 // phpcs:enable
2241 $this->lines = array();
2242
2243 $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,';
2244 $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.fk_remise_except, l.remise_percent, l.subprice, l.subprice_ttc, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht, l.rang, l.info_bits, l.special_code,';
2245 $sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
2246 $sql .= ' l.fk_unit, l.extraparams,';
2247 $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_subprice_ttc, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2248 $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,';
2249 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
2250 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units, p.packaging';
2251 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as l';
2252 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = l.fk_product)';
2253 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
2254 $sql .= ' WHERE l.fk_commande = '.((int) $this->id);
2255 if ($only_product) {
2256 $sql .= ' AND p.fk_product_type = 0';
2257 }
2258 $sql .= ' ORDER BY l.rang, l.rowid';
2259
2260 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2261 $result = $this->db->query($sql);
2262 if ($result) {
2263 $num = $this->db->num_rows($result);
2264
2265 $i = 0;
2266 while ($i < $num) {
2267 $objp = $this->db->fetch_object($result);
2268
2269 $line = new OrderLine($this->db);
2270
2271 $line->rowid = $objp->rowid;
2272 $line->id = $objp->rowid;
2273 $line->fk_commande = $objp->fk_commande;
2274 $line->commande_id = $objp->fk_commande;
2275 $line->label = $objp->custom_label;
2276 $line->desc = $objp->description;
2277 $line->description = $objp->description; // Description line
2278 $line->product_type = $objp->product_type;
2279 $line->qty = $objp->qty;
2280 $line->ref_ext = $objp->ref_ext;
2281
2282 $line->vat_src_code = $objp->vat_src_code;
2283 $line->tva_tx = $objp->tva_tx;
2284 $line->localtax1_tx = $objp->localtax1_tx;
2285 $line->localtax2_tx = $objp->localtax2_tx;
2286 $line->localtax1_type = $objp->localtax1_type;
2287 $line->localtax2_type = $objp->localtax2_type;
2288 $line->total_ht = $objp->total_ht;
2289 $line->total_ttc = $objp->total_ttc;
2290 $line->total_tva = $objp->total_tva;
2291 $line->total_localtax1 = $objp->total_localtax1;
2292 $line->total_localtax2 = $objp->total_localtax2;
2293 $line->subprice = (float) $objp->subprice;
2294 $line->subprice_ttc = (float) $objp->subprice_ttc;
2295 $line->fk_remise_except = $objp->fk_remise_except;
2296 $line->remise_percent = $objp->remise_percent;
2297 $line->price = $objp->price;
2298 $line->fk_product = $objp->fk_product;
2299 $line->fk_fournprice = $objp->fk_fournprice;
2300 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2301 $line->pa_ht = $marginInfos[0];
2302 $line->marge_tx = $marginInfos[1];
2303 $line->marque_tx = $marginInfos[2];
2304 $line->rang = $objp->rang;
2305 $line->info_bits = $objp->info_bits;
2306 $line->special_code = $objp->special_code;
2307 $line->fk_parent_line = $objp->fk_parent_line;
2308
2309 $line->ref = $objp->product_ref;
2310 $line->libelle = $objp->product_label;
2311
2312 $line->product_ref = $objp->product_ref;
2313 $line->product_label = $objp->product_label;
2314 $line->product_tosell = $objp->product_tosell;
2315 $line->product_tobuy = $objp->product_tobuy;
2316 $line->product_desc = $objp->product_desc;
2317 $line->product_tobatch = $objp->product_tobatch;
2318 $line->product_barcode = $objp->product_barcode;
2319 $line->product_custom_code = $objp->customcode;
2320 $line->product_custom_country_id = $objp->country_id;
2321 $line->product_custom_country_code = $objp->country_code;
2322
2323 $line->fk_product_type = $objp->fk_product_type; // Product or service
2324 $line->fk_unit = $objp->fk_unit;
2325
2326 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2327
2328 $line->weight = $objp->weight;
2329 $line->weight_units = $objp->weight_units;
2330 $line->volume = $objp->volume;
2331 $line->volume_units = $objp->volume_units;
2332 $line->packaging = $objp->packaging;
2333
2334 $line->date_start = $this->db->jdate($objp->date_start);
2335 $line->date_end = $this->db->jdate($objp->date_end);
2336
2337 // Multicurrency
2338 $line->fk_multicurrency = $objp->fk_multicurrency;
2339 $line->multicurrency_code = $objp->multicurrency_code;
2340 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2341 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
2342 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2343 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2344 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2345
2346 $line->fetch_optionals();
2347
2348 // multilangs
2349 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2350 $tmpproduct = new Product($this->db);
2351 $tmpproduct->fetch($objp->fk_product);
2352 $tmpproduct->getMultiLangs();
2353
2354 $line->multilangs = $tmpproduct->multilangs;
2355 }
2356
2357 $this->lines[$i] = $line;
2358
2359 $i++;
2360 }
2361
2362 $this->db->free($result);
2363
2364 return 1;
2365 } else {
2366 $this->error = $this->db->error();
2367 return -3;
2368 }
2369 }
2370
2371
2377 public function getNbOfProductsLines()
2378 {
2379 $nb = 0;
2380 foreach ($this->lines as $line) {
2381 if ($line->product_type == 0) {
2382 $nb++;
2383 }
2384 }
2385 return $nb;
2386 }
2387
2393 public function getNbOfServicesLines()
2394 {
2395 $nb = 0;
2396 foreach ($this->lines as $line) {
2397 if ($line->product_type == 1) {
2398 $nb++;
2399 }
2400 }
2401 return $nb;
2402 }
2403
2409 public function getNbOfShipments()
2410 {
2411 $nb = 0;
2412
2413 $sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2414 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2415 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2416 $sql .= ' WHERE';
2417 $sql .= ' ed.fk_elementdet = cd.rowid';
2418 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2419 //print $sql;
2420
2421 dol_syslog(get_class($this)."::getNbOfShipments", LOG_DEBUG);
2422 $resql = $this->db->query($sql);
2423 if ($resql) {
2424 $obj = $this->db->fetch_object($resql);
2425 if ($obj) {
2426 $nb = (int) $obj->nb;
2427 }
2428
2429 $this->db->free($resql);
2430 return $nb;
2431 } else {
2432 $this->error = $this->db->lasterror();
2433 return -1;
2434 }
2435 }
2436
2445 public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2446 {
2447 $this->expeditions = array();
2448
2449 $sql = 'SELECT cd.rowid, cd.fk_product,';
2450 $sql .= ' sum(ed.qty) as qty';
2451 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2452 if ($filtre_statut >= 0) {
2453 $sql .= ' '.MAIN_DB_PREFIX.'expedition as e,';
2454 }
2455 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2456 $sql .= ' WHERE';
2457 if ($filtre_statut >= 0) {
2458 $sql .= ' ed.fk_expedition = e.rowid AND';
2459 }
2460 $sql .= ' ed.fk_elementdet = cd.rowid';
2461 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2462 if ($fk_product > 0) {
2463 $sql .= ' AND cd.fk_product = '.((int) $fk_product);
2464 }
2465 if ($filtre_statut >= 0) {
2466 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
2467 }
2468 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
2469 //print $sql;
2470
2471 dol_syslog(get_class($this)."::loadExpeditions", LOG_DEBUG);
2472 $resql = $this->db->query($sql);
2473 if ($resql) {
2474 $num = $this->db->num_rows($resql);
2475 $i = 0;
2476 while ($i < $num) {
2477 $obj = $this->db->fetch_object($resql);
2478 $this->expeditions[$obj->rowid] = $obj->qty;
2479 $i++;
2480 }
2481 $this->db->free($resql);
2482 return $num;
2483 } else {
2484 $this->error = $this->db->lasterror();
2485 return -1;
2486 }
2487 }
2488
2494 public function countNbOfShipments()
2495 {
2496 $sql = 'SELECT count(*)';
2497 $sql .= ' FROM '.MAIN_DB_PREFIX.'expedition as e';
2498 $sql .= ', '.MAIN_DB_PREFIX.'element_element as el';
2499 $sql .= ' WHERE el.fk_source = '.((int) $this->id);
2500 $sql .= " AND el.sourcetype = 'commande'";
2501 $sql .= " AND el.fk_target = e.rowid";
2502 $sql .= " AND el.targettype = 'shipping'";
2503
2504 $resql = $this->db->query($sql);
2505 if ($resql) {
2506 $row = $this->db->fetch_row($resql);
2507 return $row[0];
2508 } else {
2509 dol_print_error($this->db);
2510 }
2511
2512 return 0;
2513 }
2514
2523 public function deleteLine($user = null, $lineid = 0, $id = 0)
2524 {
2525 if ($this->status == self::STATUS_DRAFT) {
2526 $this->db->begin();
2527
2528 // Delete line
2529 $line = new OrderLine($this->db);
2530
2531 $line->context = $this->context;
2532
2533 // Load data
2534 $line->fetch($lineid);
2535
2536 if ($id <= 0) {
2537 $id = $this->id;
2538 }
2539 if ($id > 0 && (int) $line->fk_commande !== (int) $id) {
2540 $this->db->rollback();
2541 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2542 return -1;
2543 }
2544
2545 // Memorize previous line for triggers
2546 $staticline = clone $line;
2547 $line->oldline = $staticline;
2548
2549 if ($line->delete($user) > 0) {
2550 $result = $this->update_price(1);
2551
2552 if ($result > 0) {
2553 $this->db->commit();
2554 return 1;
2555 } else {
2556 $this->db->rollback();
2557 $this->error = $this->db->lasterror();
2558 return -1;
2559 }
2560 } else {
2561 $this->db->rollback();
2562 $this->setErrorsFromObject($line);
2563 return -1;
2564 }
2565 } else {
2566 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2567 return -1;
2568 }
2569 }
2570
2571 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2582 public function set_remise($user, $remise, $notrigger = 0)
2583 {
2584 // phpcs:enable
2585 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2586 // @phan-suppress-next-line PhanDeprecatedFunction
2587 return $this->setDiscount($user, $remise, $notrigger);
2588 }
2589
2598 public function setDiscount($user, $remise, $notrigger = 0)
2599 {
2600 $remise = trim((string) $remise) ? trim((string) $remise) : 0;
2601
2602 if ($user->hasRight('commande', 'creer')) {
2603 $error = 0;
2604
2605 $this->db->begin();
2606
2607 $remise = price2num($remise, 2);
2608
2609 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2610 $sql .= ' SET remise_percent = '.((float) $remise);
2611 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_DRAFT);
2612
2613 dol_syslog(__METHOD__, LOG_DEBUG);
2614 $resql = $this->db->query($sql);
2615 if (!$resql) {
2616 $this->errors[] = $this->db->error();
2617 $error++;
2618 }
2619
2620 if (!$error) {
2621 $this->oldcopy = clone $this;
2622 $this->remise_percent = $remise;
2623 $this->update_price(1);
2624 }
2625
2626 if (!$notrigger && empty($error)) {
2627 // Call trigger
2628 $result = $this->call_trigger('ORDER_MODIFY', $user);
2629 if ($result < 0) {
2630 $error++;
2631 }
2632 // End call triggers
2633 }
2634
2635 if (!$error) {
2636 $this->db->commit();
2637 return 1;
2638 } else {
2639 foreach ($this->errors as $errmsg) {
2640 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2641 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2642 }
2643 $this->db->rollback();
2644 return -1 * $error;
2645 }
2646 }
2647
2648 return 0;
2649 }
2650
2651
2652 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2661 public function set_date($user, $date, $notrigger = 0)
2662 {
2663 // phpcs:enable
2664 if ($user->hasRight('commande', 'creer')) {
2665 $error = 0;
2666
2667 $this->db->begin();
2668
2669 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2670 $sql .= " SET date_commande = ".($date ? "'".$this->db->idate($date)."'" : 'null');
2671 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2672
2673 dol_syslog(__METHOD__, LOG_DEBUG);
2674 $resql = $this->db->query($sql);
2675 if (!$resql) {
2676 $this->errors[] = $this->db->error();
2677 $error++;
2678 }
2679
2680 if (!$error) {
2681 $this->oldcopy = clone $this;
2682 $this->date = $date;
2683 }
2684
2685 if (!$notrigger && empty($error)) {
2686 // Call trigger
2687 $result = $this->call_trigger('ORDER_MODIFY', $user);
2688 if ($result < 0) {
2689 $error++;
2690 }
2691 // End call triggers
2692 }
2693
2694 if (!$error) {
2695 $this->db->commit();
2696 return 1;
2697 } else {
2698 foreach ($this->errors as $errmsg) {
2699 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2700 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2701 }
2702 $this->db->rollback();
2703 return -1 * $error;
2704 }
2705 } else {
2706 return -2;
2707 }
2708 }
2709
2710 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2720 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2721 {
2722 // phpcs:enable
2723 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2724 }
2725
2734 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2735 {
2736 if ($user->hasRight('commande', 'creer')) {
2737 $error = 0;
2738
2739 $this->db->begin();
2740
2741 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2742 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2743 $sql .= " WHERE rowid = ".((int) $this->id);
2744
2745 dol_syslog(__METHOD__, LOG_DEBUG);
2746 $resql = $this->db->query($sql);
2747 if (!$resql) {
2748 $this->errors[] = $this->db->error();
2749 $error++;
2750 }
2751
2752 if (!$error) {
2753 $this->oldcopy = clone $this;
2754 $this->delivery_date = $delivery_date;
2755 }
2756
2757 if (!$notrigger && empty($error)) {
2758 // Call trigger
2759 $result = $this->call_trigger('ORDER_MODIFY', $user);
2760 if ($result < 0) {
2761 $error++;
2762 }
2763 // End call triggers
2764 }
2765
2766 if (!$error) {
2767 $this->db->commit();
2768 return 1;
2769 } else {
2770 foreach ($this->errors as $errmsg) {
2771 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2772 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2773 }
2774 $this->db->rollback();
2775 return -1 * $error;
2776 }
2777 } else {
2778 return -2;
2779 }
2780 }
2781
2782 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2796 public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2797 {
2798 // phpcs:enable
2799 global $user;
2800
2801 $ga = array();
2802
2803 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2804 $sql .= " c.rowid as cid, c.ref";
2805 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2806 $sql .= ", sc.fk_soc, sc.fk_user";
2807 }
2808 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as c";
2809 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2810 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2811 }
2812 $sql .= " WHERE c.entity IN (".getEntity('commande').")";
2813 $sql .= " AND c.fk_soc = s.rowid";
2814 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2815 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2816 }
2817 if ($socid) {
2818 $sql .= " AND s.rowid = ".((int) $socid);
2819 }
2820 if ($draft) {
2821 $sql .= " AND c.fk_statut = ".self::STATUS_DRAFT;
2822 }
2823 if (is_object($excluser)) {
2824 $sql .= " AND c.fk_user_author <> ".((int) $excluser->id);
2825 }
2826 $sql .= $this->db->order($sortfield, $sortorder);
2827 $sql .= $this->db->plimit($limit, $offset);
2828
2829 $result = $this->db->query($sql);
2830 if ($result) {
2831 $numc = $this->db->num_rows($result);
2832 if ($numc) {
2833 $i = 0;
2834 while ($i < $numc) {
2835 $obj = $this->db->fetch_object($result);
2836
2837 if ($shortlist == 1) {
2838 $ga[$obj->cid] = $obj->ref;
2839 } elseif ($shortlist == 2) {
2840 $ga[$obj->cid] = $obj->ref.' ('.$obj->name.')';
2841 } else {
2842 $ga[$i]['id'] = $obj->cid;
2843 $ga[$i]['ref'] = $obj->ref;
2844 $ga[$i]['name'] = $obj->name;
2845 }
2846 $i++;
2847 }
2848 }
2849 return $ga;
2850 } else {
2851 dol_print_error($this->db);
2852 return -1;
2853 }
2854 }
2855
2863 public function availability($availability_id, $notrigger = 0)
2864 {
2865 global $user;
2866
2867 dol_syslog('Commande::availability('.$availability_id.')');
2868 if ($this->status >= self::STATUS_DRAFT) {
2869 $error = 0;
2870
2871 $this->db->begin();
2872
2873 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2874 $sql .= ' SET fk_availability = '.((int) $availability_id);
2875 $sql .= ' WHERE rowid='.((int) $this->id);
2876
2877 dol_syslog(__METHOD__, LOG_DEBUG);
2878 $resql = $this->db->query($sql);
2879 if (!$resql) {
2880 $this->errors[] = $this->db->error();
2881 $error++;
2882 }
2883
2884 if (!$error) {
2885 $this->oldcopy = clone $this;
2886 $this->availability_id = $availability_id;
2887 }
2888
2889 if (!$notrigger && empty($error)) {
2890 // Call trigger
2891 $result = $this->call_trigger('ORDER_MODIFY', $user);
2892 if ($result < 0) {
2893 $error++;
2894 }
2895 // End call triggers
2896 }
2897
2898 if (!$error) {
2899 $this->db->commit();
2900 return 1;
2901 } else {
2902 foreach ($this->errors as $errmsg) {
2903 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2904 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2905 }
2906 $this->db->rollback();
2907 return -1 * $error;
2908 }
2909 } else {
2910 $error_str = 'Command status do not meet requirement '.$this->status;
2911 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2912 $this->error = $error_str;
2913 $this->errors[] = $this->error;
2914 return -2;
2915 }
2916 }
2917
2918 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2926 public function demand_reason($demand_reason_id, $notrigger = 0)
2927 {
2928 // phpcs:enable
2929 global $user;
2930
2931 dol_syslog('Commande::demand_reason('.$demand_reason_id.')');
2932 if ($this->status >= self::STATUS_DRAFT) {
2933 $error = 0;
2934
2935 $this->db->begin();
2936
2937 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2938 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
2939 $sql .= ' WHERE rowid='.((int) $this->id);
2940
2941 dol_syslog(__METHOD__, LOG_DEBUG);
2942 $resql = $this->db->query($sql);
2943 if (!$resql) {
2944 $this->errors[] = $this->db->error();
2945 $error++;
2946 }
2947
2948 if (!$error) {
2949 $this->oldcopy = clone $this;
2950 $this->demand_reason_id = $demand_reason_id;
2951 }
2952
2953 if (!$notrigger && empty($error)) {
2954 // Call trigger
2955 $result = $this->call_trigger('ORDER_MODIFY', $user);
2956 if ($result < 0) {
2957 $error++;
2958 }
2959 // End call triggers
2960 }
2961
2962 if (!$error) {
2963 $this->db->commit();
2964 return 1;
2965 } else {
2966 foreach ($this->errors as $errmsg) {
2967 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2968 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2969 }
2970 $this->db->rollback();
2971 return -1 * $error;
2972 }
2973 } else {
2974 $error_str = 'order status do not meet requirement '.$this->status;
2975 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2976 $this->error = $error_str;
2977 $this->errors[] = $this->error;
2978 return -2;
2979 }
2980 }
2981
2994 public function isRefClientAlreadyUsed(string $ref_client, int $socid, int $excludeid = 0): int
2995 {
2996 global $langs;
2997
2998 if (getDolGlobalInt('ORDER_ALLOW_DUPLICATE_REF_CLIENT')) {
2999 return 0;
3000 }
3001
3002 $ref_client = trim($ref_client);
3003 if ($ref_client === '' || $socid <= 0) {
3004 return 0;
3005 }
3006
3007 $sql = "SELECT ref FROM ".$this->db->prefix()."commande";
3008 $sql .= " WHERE ref_client = '".$this->db->escape($ref_client)."'";
3009 $sql .= " AND fk_soc = ".((int) $socid);
3010 $sql .= " AND entity IN (".getEntity('commande').")";
3011 if ($excludeid > 0) {
3012 $sql .= " AND rowid <> ".((int) $excludeid);
3013 }
3014 $sql .= " ORDER BY rowid ASC";
3015 $sql .= $this->db->plimit(1);
3016
3017 dol_syslog(get_class($this)."::isRefClientAlreadyUsed", LOG_DEBUG);
3018 $resql = $this->db->query($sql);
3019 if (!$resql) {
3020 $this->error = $this->db->lasterror();
3021 $this->errors[] = $this->error;
3022 dol_syslog(get_class($this)."::isRefClientAlreadyUsed ".$this->error, LOG_ERR);
3023 return -1;
3024 }
3025
3026 $obj = $this->db->fetch_object($resql);
3027 $this->db->free($resql);
3028
3029 if (!is_object($obj)) {
3030 return 0;
3031 }
3032
3033 $langs->load('orders');
3034 $this->error = $langs->trans('ErrorRefCustomerAlreadyUsedOnOrder', $ref_client, $obj->ref);
3035 $this->errors[] = $this->error;
3036 dol_syslog(get_class($this)."::isRefClientAlreadyUsed ref_client=".$ref_client." already used by ".$obj->ref, LOG_WARNING);
3037
3038 return 1;
3039 }
3040
3041 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3050 public function set_ref_client($user, $ref_client, $notrigger = 0)
3051 {
3052 // phpcs:enable
3053 if ($user->hasRight('commande', 'creer')) {
3054 $error = 0;
3055
3056 if ($this->isRefClientAlreadyUsed((string) $ref_client, (int) $this->socid, (int) $this->id) != 0) {
3057 return -1;
3058 }
3059
3060 $this->db->begin();
3061
3062 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
3063 $sql .= ' ref_client = '.(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
3064 $sql .= ' WHERE rowid = '.((int) $this->id);
3065
3066 dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
3067 $resql = $this->db->query($sql);
3068 if (!$resql) {
3069 $this->errors[] = $this->db->error();
3070 $error++;
3071 }
3072
3073 if (!$error) {
3074 $this->oldcopy = clone $this;
3075 $this->ref_client = $ref_client;
3076 $this->ref_customer = $ref_client;
3077 }
3078
3079 if (!$notrigger && empty($error)) {
3080 // Call trigger
3081 $result = $this->call_trigger('ORDER_MODIFY', $user);
3082 if ($result < 0) {
3083 $error++;
3084 }
3085 // End call triggers
3086 }
3087 if (!$error) {
3088 $this->db->commit();
3089 return 1;
3090 } else {
3091 foreach ($this->errors as $errmsg) {
3092 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3093 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3094 }
3095 $this->db->rollback();
3096 return -1 * $error;
3097 }
3098 } else {
3099 return -1;
3100 }
3101 }
3102
3110 public function classifyBilled(User $user, $notrigger = 0)
3111 {
3112 $error = 0;
3113
3114 if ($this->billed) {
3115 return 0;
3116 }
3117
3118 $this->db->begin();
3119
3120 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 1';
3121 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3122
3123 dol_syslog(get_class($this)."::classifyBilled", LOG_DEBUG);
3124 if ($this->db->query($sql)) {
3125 $this->oldcopy = clone $this;
3126 $this->billed = 1;
3127
3128 if (!$notrigger && empty($error)) {
3129 // Call trigger
3130 $result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
3131 if ($result < 0) {
3132 $error++;
3133 }
3134 // End call triggers
3135 }
3136
3137 if (!$error) {
3138 $this->db->commit();
3139 return 1;
3140 } else {
3141 foreach ($this->errors as $errmsg) {
3142 dol_syslog(get_class($this)."::classifyBilled ".$errmsg, LOG_ERR);
3143 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3144 }
3145 $this->db->rollback();
3146 return -1 * $error;
3147 }
3148 } else {
3149 $this->error = $this->db->error();
3150 $this->db->rollback();
3151 return -1;
3152 }
3153 }
3154
3162 public function classifyUnBilled(User $user, $notrigger = 0)
3163 {
3164 $error = 0;
3165
3166 $this->db->begin();
3167
3168 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 0';
3169 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3170
3171 dol_syslog(get_class($this)."::classifyUnBilled", LOG_DEBUG);
3172 if ($this->db->query($sql)) {
3173 $this->oldcopy = clone $this;
3174 $this->billed = 1;
3175
3176 if (!$notrigger && empty($error)) {
3177 // Call trigger
3178 $result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
3179 if ($result < 0) {
3180 $error++;
3181 }
3182 // End call triggers
3183 }
3184
3185 if (!$error) {
3186 $this->billed = 0;
3187
3188 $this->db->commit();
3189 return 1;
3190 } else {
3191 foreach ($this->errors as $errmsg) {
3192 dol_syslog(get_class($this)."::classifyUnBilled ".$errmsg, LOG_ERR);
3193 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3194 }
3195 $this->db->rollback();
3196 return -1 * $error;
3197 }
3198 } else {
3199 $this->error = $this->db->error();
3200 $this->db->rollback();
3201 return -1;
3202 }
3203 }
3204
3205
3236 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)
3237 {
3238 global $mysoc, $langs, $user;
3239
3240 if (!$this->isLineOfObject($rowid)) {
3241 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
3242 return -1;
3243 }
3244
3245 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");
3246 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3247
3248 if ($this->status == Commande::STATUS_DRAFT) {
3249 // Clean parameters
3250 if (empty($qty)) {
3251 $qty = 0;
3252 }
3253 if (empty($info_bits)) {
3254 $info_bits = 0;
3255 }
3256 if (empty($txtva)) {
3257 $txtva = 0;
3258 }
3259 if (empty($txlocaltax1)) {
3260 $txlocaltax1 = 0;
3261 }
3262 if (empty($txlocaltax2)) {
3263 $txlocaltax2 = 0;
3264 }
3265 if (empty($remise_percent)) {
3266 $remise_percent = 0;
3267 }
3268 if (empty($qty) && empty($special_code)) {
3269 $special_code = 3; // Set option tag
3270 }
3271 if (!empty($qty) && $special_code == 3) {
3272 $special_code = 0; // Remove option tag
3273 }
3274 if (empty($ref_ext)) {
3275 $ref_ext = '';
3276 }
3277
3278 if ($date_start && $date_end && $date_start > $date_end) {
3279 $langs->load("errors");
3280 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3281 return -1;
3282 }
3283
3284 $remise_percent = (float) price2num($remise_percent);
3285 $qty = (float) price2num($qty);
3286 $pu = price2num($pu);
3287 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
3288 $pu_ht_devise = price2num($pu_ht_devise);
3289 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3290 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3291 }
3292 $txlocaltax1 = (float) price2num($txlocaltax1);
3293 $txlocaltax2 = (float) price2num($txlocaltax2);
3294
3295 $this->db->begin();
3296
3297 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3298 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3299 // and this is done at the line level, which has its own VAT rate
3300
3301 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3302
3303 // Clean vat code
3304 $vat_src_code = '';
3305 $reg = array();
3306 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3307 $vat_src_code = $reg[1];
3308 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3309 }
3310
3311 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
3312 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
3313 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
3314 $tmpline = new OrderLine($this->db);
3315 if ($tmpline->fetch($rowid) > 0) {
3316 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
3317 if ($newqty != $qty) {
3318 $qty = $newqty;
3319 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
3320 }
3321 }
3322 }
3323
3324 $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);
3325
3326 $total_ht = $tabprice[0];
3327 $total_tva = $tabprice[1];
3328 $total_ttc = $tabprice[2];
3329 $total_localtax1 = $tabprice[9];
3330 $total_localtax2 = $tabprice[10];
3331 $pu_ht = $tabprice[3];
3332 $pu_tva = $tabprice[4];
3333 $pu_ttc = $tabprice[5];
3334
3335 // MultiCurrency
3336 $multicurrency_total_ht = $tabprice[16];
3337 $multicurrency_total_tva = $tabprice[17];
3338 $multicurrency_total_ttc = $tabprice[18];
3339 $pu_ht_devise = $tabprice[19];
3340
3341 // Fetch current line from the database and then clone the object and set it in $oldline property
3342 $line = new OrderLine($this->db);
3343 $line->fetch($rowid);
3344 $line->fetch_optionals();
3345
3346 if (!empty($line->fk_product)) {
3347 $product = new Product($this->db);
3348 $result = $product->fetch($line->fk_product);
3349 $product_type = $product->type;
3350
3351 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
3352 // get real stock
3353 $productChildrenNb = 0;
3354 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
3355 $productChildrenNb = $product->hasFatherOrChild(1);
3356 }
3357 if ($productChildrenNb > 0) {
3358 // compute real stock from each subcomponent
3359 $product_stock = null;
3360 $product->loadStockForVirtualProduct('warehouseopen', $qty);
3361 foreach ($product->stock_warehouse as $componentStockWarehouse) {
3362 if ($product_stock === null) {
3363 $product_stock = $componentStockWarehouse->real;
3364 } else {
3365 $product_stock = min($product_stock, $componentStockWarehouse->real);
3366 }
3367 }
3368 if ($product_stock === null) {
3369 $product_stock = 0;
3370 }
3371 } else {
3372 $product_stock = $product->stock_reel;
3373 }
3374
3375 if ($product_stock < $qty) {
3376 $langs->load("errors");
3377 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', (string) $product->ref);
3378 $this->errors[] = $this->error;
3379
3380 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
3381
3382 $this->db->rollback();
3384 }
3385 }
3386 }
3387
3388 $staticline = clone $line;
3389
3390 $line->oldline = $staticline;
3391 $this->line = $line;
3392 $this->line->context = $this->context;
3393 $this->line->rang = $rang;
3394
3395 // Reorder if fk_parent_line change
3396 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
3397 $rangmax = $this->line_max($fk_parent_line);
3398 $this->line->rang = $rangmax + 1;
3399 }
3400
3401
3402 $this->line->id = $rowid;
3403 $this->line->label = $label;
3404 $this->line->desc = $desc;
3405 $this->line->qty = $qty;
3406 $this->line->ref_ext = $ref_ext;
3407
3408 $this->line->vat_src_code = $vat_src_code;
3409 $this->line->tva_tx = $txtva;
3410 $this->line->localtax1_tx = $txlocaltax1;
3411 $this->line->localtax2_tx = $txlocaltax2;
3412 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3413 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3414 $this->line->remise_percent = $remise_percent;
3415 $this->line->subprice = (float) $pu_ht;
3416 // Persist the original entry mode of the line so a no-op edit can preserve it later.
3417 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
3418 $this->line->info_bits = $info_bits;
3419 $this->line->special_code = $special_code;
3420 $this->line->total_ht = (float) $total_ht;
3421 $this->line->total_tva = (float) $total_tva;
3422 $this->line->total_localtax1 = (float) $total_localtax1;
3423 $this->line->total_localtax2 = (float) $total_localtax2;
3424 $this->line->total_ttc = (float) $total_ttc;
3425 $this->line->date_start = $date_start;
3426 $this->line->date_end = $date_end;
3427 $this->line->product_type = $type;
3428 $this->line->fk_parent_line = $fk_parent_line;
3429 $this->line->skip_update_total = $skip_update_total;
3430 $this->line->fk_unit = $fk_unit;
3431
3432 $this->line->fk_fournprice = $fk_fournprice;
3433 $this->line->pa_ht = $pa_ht;
3434
3435 // Multicurrency
3436 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3437 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3438 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3439 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3440
3441 if (is_array($array_options) && count($array_options) > 0) {
3442 // We replace values in this->line->array_options only for entries defined into $array_options
3443 foreach ($array_options as $key => $value) {
3444 $this->line->array_options[$key] = $array_options[$key];
3445 }
3446 }
3447
3448 $result = $this->line->update($user, $notrigger);
3449 if ($result > 0) {
3450 // Reorder if child line
3451 if (!empty($fk_parent_line)) {
3452 $this->line_order(true, 'DESC');
3453 }
3454
3455 // Mise a jour info denormalisees
3456 $this->update_price(1, 'auto');
3457
3458 $this->db->commit();
3459 return $result;
3460 } else {
3461 $this->setErrorsFromObject($this->line);
3462
3463 $this->db->rollback();
3464 return -1;
3465 }
3466 } else {
3467 $this->error = get_class($this)."::updateline Order status makes operation forbidden";
3468 $this->errors = array('OrderStatusMakeOperationForbidden');
3469 return -2;
3470 }
3471 }
3472
3480 public function update(User $user, $notrigger = 0)
3481 {
3482 $error = 0;
3483
3484 // Clean parameters
3485 if (isset($this->ref)) {
3486 $this->ref = trim($this->ref);
3487 }
3488 if (isset($this->ref_client)) {
3489 $this->ref_client = trim($this->ref_client);
3490 }
3491 if (isset($this->ref_customer)) {
3492 $this->ref_customer = trim($this->ref_customer);
3493 }
3494 if (isset($this->note) || isset($this->note_private)) {
3495 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3496 }
3497 if (isset($this->note_public)) {
3498 $this->note_public = trim($this->note_public);
3499 }
3500 if (isset($this->model_pdf)) {
3501 $this->model_pdf = trim($this->model_pdf);
3502 }
3503 if (isset($this->import_key)) {
3504 $this->import_key = trim($this->import_key);
3505 }
3506 $delivery_date = $this->delivery_date;
3507
3508 // Check parameters
3509 // Put here code to add control on parameters values
3510 if ($this->isRefClientAlreadyUsed((string) $this->ref_client, (int) $this->socid, (int) $this->id) != 0) {
3511 return -1;
3512 }
3513
3514 // Update request
3515 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3516
3517 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
3518 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
3519 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
3520 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
3521 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
3522 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
3523
3524 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
3525 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
3526 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
3527 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
3528 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
3529 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
3530 $sql .= " fk_user_modif=".(isset($user->id) ? ((int) $user->id) : "null").",";
3531 $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? ((int) $this->user_validation_id) : "null").",";
3532 $sql .= " fk_projet=".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").",";
3533 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
3534 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape((string) $this->deposit_percent)."'" : "null").",";
3535 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
3536 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
3537 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? ((int) $this->shipping_method_id) : "null").",";
3538 $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").",";
3539 $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null").",";
3540 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
3541 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
3542 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
3543 $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : "null").",";
3544 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
3545 $sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").",";
3546 $sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null");
3547 $sql .= " WHERE rowid=".((int) $this->id);
3548
3549 $this->db->begin();
3550
3551 dol_syslog(get_class($this)."::update", LOG_DEBUG);
3552 $resql = $this->db->query($sql);
3553 if (!$resql) {
3554 $error++;
3555 $this->errors[] = "Error ".$this->db->lasterror();
3556 }
3557
3558 if (!$error) {
3559 $result = $this->insertExtraFields();
3560 if ($result < 0) {
3561 $error++;
3562 }
3563 }
3564
3565 if (!$error && !$notrigger) {
3566 // Call trigger
3567 $result = $this->call_trigger('ORDER_MODIFY', $user);
3568 if ($result < 0) {
3569 $error++;
3570 }
3571 // End call triggers
3572 }
3573
3574 // Commit or rollback
3575 if ($error) {
3576 foreach ($this->errors as $errmsg) {
3577 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3578 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3579 }
3580 $this->db->rollback();
3581 return -1 * $error;
3582 } else {
3583 $this->db->commit();
3584 return 1;
3585 }
3586 }
3587
3596 public function delete($user, $notrigger = 0, $idwarehouse = -1)
3597 {
3598 global $conf, $langs;
3599 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3600
3601 $error = 0;
3602
3603 dol_syslog(get_class($this)."::delete ".$this->id, LOG_DEBUG);
3604
3605 $this->db->begin();
3606
3607 if (!$notrigger) {
3608 // Call trigger
3609 $result = $this->call_trigger('ORDER_DELETE', $user);
3610 if ($result < 0) {
3611 $error++;
3612 }
3613 // End call triggers
3614 }
3615
3616 // Test we can delete
3617 if ($this->countNbOfShipments() != 0) {
3618 $this->errors[] = $langs->trans('SomeShipmentExists');
3619 $error++;
3620 }
3621
3622 // Put the stock back, the validation had decreased it. Only when a warehouse was chosen, so
3623 // deleting an order just to clean the database still leaves the stock alone.
3624 // Must run before the lines are removed.
3625 if (!$error && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1 && $this->status != self::STATUS_DRAFT && $idwarehouse > 0) {
3626 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3627 $langs->load("agenda");
3628
3629 $this->fetch_lines();
3630
3631 $num = count($this->lines);
3632 for ($i = 0; $i < $num; $i++) {
3633 if ($this->lines[$i]->fk_product > 0) {
3634 $mouvP = new MouvementStock($this->db);
3635 $mouvP->origin = &$this;
3636 $mouvP->setOrigin($this->element, $this->id);
3637 // 0 as price so the weighted average value is not changed
3638 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderDeletedInDolibarr", $this->ref));
3639 if ($result < 0) {
3640 $error++;
3641 $this->error = $mouvP->error;
3642 $this->errors = array_merge($this->errors, $mouvP->errors);
3643 break;
3644 }
3645 }
3646 }
3647 }
3648
3649 // Remove linked categories.
3650 if (!$error) {
3651 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_order";
3652 $sql .= " WHERE fk_order = ".((int) $this->id);
3653
3654 $result = $this->db->query($sql);
3655 if (!$result) {
3656 $error++;
3657 $this->errors[] = $this->db->lasterror();
3658 }
3659 }
3660
3661 // Delete extrafields of lines and lines
3662 if (!$error && !empty($this->table_element_line)) {
3663 $tabletodelete = $this->table_element_line;
3664 $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).")";
3665 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3666 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3667 $error++;
3668 $this->error = $this->db->lasterror();
3669 $this->errors[] = $this->error;
3670 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3671 }
3672 }
3673
3674 if (!$error) {
3675 // Delete linked object
3676 $res = $this->deleteObjectLinked();
3677 if ($res < 0) {
3678 $error++;
3679 }
3680 }
3681
3682 if (!$error) {
3683 // Delete linked contacts
3684 $res = $this->delete_linked_contact();
3685 if ($res < 0) {
3686 $error++;
3687 }
3688 }
3689
3690 // Removed extrafields of object
3691 if (!$error) {
3692 $result = $this->deleteExtraFields();
3693 if ($result < 0) {
3694 $error++;
3695 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3696 }
3697 }
3698
3699 // Delete main record
3700 if (!$error) {
3701 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3702 $res = $this->db->query($sql);
3703 if (!$res) {
3704 $error++;
3705 $this->error = $this->db->lasterror();
3706 $this->errors[] = $this->error;
3707 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3708 }
3709 }
3710
3711 // Delete record into ECM index and physically
3712 if (!$error) {
3713 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive, old method.
3714 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3715 if (!$res) {
3716 $error++;
3717 }
3718 }
3719
3720 if (!$error) {
3721 // We remove directory
3722 $ref = dol_sanitizeFileName($this->ref);
3723 if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
3724 $dir = $conf->commande->multidir_output[$this->entity]."/".$ref;
3725 $file = $dir."/".$ref.".pdf";
3726 if (file_exists($file)) {
3727 dol_delete_preview($this);
3728
3729 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3730 $this->error = 'ErrorFailToDeleteFile';
3731 $this->errors[] = $this->error;
3732 $this->db->rollback();
3733 return 0;
3734 }
3735 }
3736 if (file_exists($dir)) {
3737 $res = @dol_delete_dir_recursive($dir);
3738 if (!$res) {
3739 $this->error = 'ErrorFailToDeleteDir';
3740 $this->errors[] = $this->error;
3741 $this->db->rollback();
3742 return 0;
3743 }
3744 }
3745 }
3746 }
3747
3748 if (!$error) {
3749 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3750 $this->db->commit();
3751 return 1;
3752 } else {
3753 $this->db->rollback();
3754 return -1;
3755 }
3756 }
3757
3758
3759 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3767 public function load_board($user, $mode)
3768 {
3769 // phpcs:enable
3770 global $conf, $langs, $hookmanager;
3771
3772 $clause = " WHERE";
3773
3774 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3775 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3776 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3777 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3778 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3779 $clause = " AND";
3780 }
3781 $sql .= $clause." c.entity IN (".getEntity('commande').")";
3782 //$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3783 if ($mode == 'toship') {
3784 // An order to ship is an open order (validated or in progress)
3785 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
3786 }
3787 if ($mode == 'tobill') {
3788 // An order to bill is an order not already billed
3789 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
3790 }
3791 if ($mode == 'shippedtobill') {
3792 // An order shipped and to bill is a delivered order not already billed
3793 $sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
3794 }
3795 if ($user->socid) {
3796 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3797 }
3798 // Add where from hooks
3799 $parameters = array('socid' => $user->socid);
3800 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3801 $sql .= $hookmanager->resPrint;
3802 $resql = $this->db->query($sql);
3803 if ($resql) {
3804 $delay_warning = 0;
3805 $label = $labelShort = $url = '';
3806 if ($mode == 'toship') {
3807 $delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
3808 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
3809 $label = $langs->transnoentitiesnoconv("OrdersToProcess");
3810 $labelShort = $langs->transnoentitiesnoconv("Opened");
3811 }
3812 if ($mode == 'tobill') {
3813 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3814 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3815 $labelShort = $langs->trans("ToBill");
3816 }
3817 if ($mode == 'shippedtobill') {
3818 $url = DOL_URL_ROOT.'/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3819 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3820 $labelShort = $langs->trans("StatusOrderDelivered").' '.$langs->trans("and").' '.$langs->trans("ToBill");
3821 }
3822
3823 $response = new WorkboardResponse();
3824
3825 $response->warning_delay = $delay_warning;
3826 $response->label = $label;
3827 $response->labelShort = $labelShort;
3828 $response->url = $url;
3829 $response->url_late = DOL_URL_ROOT.'/commande/list.php?search_option=late&mainmenu=commercial&leftmenu=orders';
3830 $response->img = img_object('', "order");
3831
3832 $generic_commande = new Commande($this->db);
3833
3834 while ($obj = $this->db->fetch_object($resql)) {
3835 $response->nbtodo++;
3836 $response->total += $obj->total_ht;
3837
3838 $generic_commande->status = $obj->fk_statut;
3839 $generic_commande->date = $this->db->jdate($obj->date_commande);
3840 $generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
3841
3842 if ($mode == 'toship' && $generic_commande->hasDelay()) {
3843 $response->nbtodolate++;
3844 }
3845 }
3846
3847 return $response;
3848 } else {
3849 $this->error = $this->db->error();
3850 return -1;
3851 }
3852 }
3853
3859 public function getLabelSource()
3860 {
3861 global $langs;
3862
3863 $label = $langs->trans('OrderSource'.$this->source);
3864
3865 if ($label == 'OrderSource') {
3866 return '';
3867 }
3868 return $label;
3869 }
3870
3877 public function getLibStatut($mode)
3878 {
3879 return $this->LibStatut($this->status, $this->billed, $mode);
3880 }
3881
3882 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3892 public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
3893 {
3894 // phpcs:enable
3895 global $langs, $hookmanager;
3896
3897 $billedtext = '';
3898 if (empty($donotshowbilled)) {
3899 $billedtext .= ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3900 }
3901 $billedtextlong = ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3902
3903 $labelTooltip = '';
3904
3905 $paramsBadge = array('badgeParams' => array('attr' => array(
3906 'data-status-element' => $this->element,
3907 'data-billed' => (int) $billed,
3908 'data-status' => (int) $status
3909 )));
3910
3911 if ($status == self::STATUS_CANCELED) {
3912 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
3913 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
3914 $statusType = 'status9';
3915 } elseif ($status == self::STATUS_DRAFT) {
3916 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
3917 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
3918 $statusType = 'status0';
3919 } elseif ($status == self::STATUS_VALIDATED) {
3920 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated').$billedtextlong;
3921 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort').$billedtext;
3922 $statusType = 'status1';
3923 } elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
3924 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent').$billedtextlong;
3925 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort').$billedtext;
3926 $labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
3927 if (!empty($this->delivery_date)) {
3928 $labelTooltip .= ' - '.$langs->transnoentitiesnoconv("DateDeliveryPlanned").dol_print_date($this->delivery_date, 'day').$billedtext;
3929 }
3930 $statusType = 'status4';
3931 } elseif ($status == self::STATUS_CLOSED) {
3932 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered').$billedtextlong;
3933 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort').$billedtext;
3934 $statusType = 'status6';
3935 } else {
3936 $labelStatus = $langs->transnoentitiesnoconv('Unknown');
3937 $labelStatusShort = '';
3938 $statusType = '';
3939 $mode = 0;
3940 }
3941
3942 $paramsBadge['tooltip'] = $labelTooltip;
3943
3944 $parameters = array(
3945 'status' => $status,
3946 'mode' => $mode,
3947 'billed' => $billed,
3948 'donotshowbilled' => $donotshowbilled,
3949 'paramsBadge' => & $paramsBadge
3950 );
3951
3952 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3953
3954 if ($reshook > 0) {
3955 return $hookmanager->resPrint;
3956 }
3957
3958 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsBadge);
3959 }
3960
3968 public function getTooltipContentArray($params)
3969 {
3970 global $conf, $langs, $user;
3971
3972 $langs->load('orders');
3973 $datas = [];
3974 $nofetch = !empty($params['nofetch']);
3975
3976 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3977 return ['optimize' => $langs->trans("Order")];
3978 }
3979
3980 if ($user->hasRight('commande', 'lire')) {
3981 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Order").'</u>';
3982 if (isset($this->status)) {
3983 $datas['status'] = ' '.$this->getLibStatut(5);
3984 }
3985 $datas['Ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3986 if (!$nofetch) {
3987 $langs->load('companies');
3988 if (empty($this->thirdparty)) {
3989 $this->fetch_thirdparty();
3990 }
3991 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3992 }
3993 $datas['RefCustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.(empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
3994 if (!$nofetch) {
3995 $langs->load('project');
3996 if (is_null($this->project) || (is_object($this->project) && empty($this->project->id))) {
3997 $res = $this->fetchProject();
3998 if ($res > 0 && $this->project instanceof Project) {
3999 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
4000 }
4001 }
4002 }
4003 if (!empty($this->total_ht)) {
4004 $datas['AmountHT'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
4005 }
4006 if (!empty($this->total_tva)) {
4007 $datas['VAT'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
4008 }
4009 if (!empty($this->total_ttc)) {
4010 $datas['AmountTTC'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
4011 }
4012 if (!empty($this->date)) {
4013 $datas['Date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
4014 }
4015 if (!empty($this->delivery_date)) {
4016 $datas['DeliveryDate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
4017 }
4018 }
4019
4020 return $datas;
4021 }
4022
4036 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
4037 {
4038 global $conf, $langs, $user, $hookmanager;
4039
4040 if (!empty($conf->dol_no_mouse_hover)) {
4041 $notooltip = 1; // Force disable tooltips
4042 }
4043
4044 $result = '';
4045
4046 if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
4047 $baseurl = DOL_URL_ROOT . '/expedition/shipment.php';
4048 } else {
4049 $baseurl = DOL_URL_ROOT . '/commande/card.php';
4050 }
4051 $query = ['id' => $this->id];
4052 if (!$user->hasRight('commande', 'lire')) {
4053 $option = 'nolink';
4054 }
4055
4056 if ($option !== 'nolink') {
4057 // Add param to save lastsearch_values or not
4058 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
4059 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
4060 $add_save_lastsearch_values = 1;
4061 }
4062 if ($add_save_lastsearch_values) {
4063 $query = array_merge($query, ['save_lastsearch_values' => 1]);
4064 }
4065 }
4066 $url = dolBuildUrl($baseurl, $query);
4067
4068 if ($short) {
4069 return $url;
4070 }
4071 $params = [
4072 'id' => $this->id,
4073 'objecttype' => $this->element,
4074 'option' => $option,
4075 'nofetch' => 1,
4076 ];
4077 $classfortooltip = 'classfortooltip';
4078 $dataparams = '';
4079 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
4080 $classfortooltip = 'classforajaxtooltip';
4081 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
4082 $label = '';
4083 } else {
4084 $label = implode($this->getTooltipContentArray($params));
4085 }
4086
4087 $linkclose = '';
4088 if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
4089 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
4090 $label = $langs->trans("Order");
4091 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
4092 }
4093 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
4094 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
4095
4096 $target_value = array('_self', '_blank', '_parent', '_top');
4097 if (in_array($target, $target_value)) {
4098 $linkclose .= ' target="'.dol_escape_htmltag($target).'"';
4099 }
4100 }
4101
4102 $linkstart = '<a href="'.$url.'"';
4103 $linkstart .= $linkclose.'>';
4104 $linkend = '</a>';
4105
4106 if ($option === 'nolink') {
4107 $linkstart = '';
4108 $linkend = '';
4109 }
4110
4111 $result .= $linkstart;
4112 if ($withpicto) {
4113 $result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
4114 }
4115 if ($withpicto != 2) {
4116 $result .= $this->ref;
4117 }
4118 $result .= $linkend;
4119
4120 if ($addlinktonotes) {
4121 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
4122 if ($txttoshow) {
4123 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
4124 $result .= ' <span class="note inline-block">';
4125 $result .= '<a href="'.DOL_URL_ROOT.'/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
4126 $result .= img_picto('', 'note');
4127 $result .= '</a>';
4128 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
4129 //$result.='</a>';
4130 $result .= '</span>';
4131 }
4132 }
4133
4134 global $action;
4135 $hookmanager->initHooks(array($this->element . 'dao'));
4136 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4137 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4138 if ($reshook > 0) {
4139 $result = $hookmanager->resPrint;
4140 } else {
4141 $result .= $hookmanager->resPrint;
4142 }
4143 return $result;
4144 }
4145
4146
4153 public function info($id)
4154 {
4155 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
4156 $sql .= ' date_valid as datev,';
4157 $sql .= ' date_cloture as datecloture,';
4158 $sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
4159 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
4160 $sql .= ' WHERE c.rowid = '.((int) $id);
4161 $result = $this->db->query($sql);
4162 if ($result) {
4163 if ($this->db->num_rows($result)) {
4164 $obj = $this->db->fetch_object($result);
4165 $this->id = $obj->rowid;
4166 if ($obj->fk_user_author) {
4167 $this->user_creation_id = $obj->fk_user_author;
4168 }
4169 if ($obj->fk_user_valid) {
4170 $this->user_validation_id = $obj->fk_user_valid;
4171 }
4172 if ($obj->fk_user_cloture) {
4173 $this->user_closing_id = $obj->fk_user_cloture;
4174 }
4175
4176 $this->date_creation = $this->db->jdate($obj->datec);
4177 $this->date_modification = $this->db->jdate($obj->datem);
4178 $this->date_validation = $this->db->jdate($obj->datev);
4179 $this->date_cloture = $this->db->jdate($obj->datecloture);
4180 }
4181
4182 $this->db->free($result);
4183 } else {
4184 dol_print_error($this->db);
4185 }
4186 }
4187
4188
4197 public function initAsSpecimen($param = array())
4198 {
4199 global $conf, $langs;
4200
4201 dol_syslog(get_class($this)."::initAsSpecimen");
4202
4203 // Load array of products prodids
4204 $num_prods = 0;
4205 $prodids = array();
4206 $sql = "SELECT rowid";
4207 $sql .= " FROM ".MAIN_DB_PREFIX."product";
4208 $sql .= " WHERE entity IN (".getEntity('product').")";
4209 if (array_key_exists('tosell', $param)) {
4210 $sql .= " AND tosell = ".((int) $param['tosell']);
4211 }
4212 $sql .= $this->db->plimit(100);
4213
4214 $resql = $this->db->query($sql);
4215 if ($resql) {
4216 $num_prods = $this->db->num_rows($resql);
4217 $i = 0;
4218 while ($i < $num_prods) {
4219 $i++;
4220 $row = $this->db->fetch_row($resql);
4221 $prodids[$i] = $row[0];
4222 }
4223 }
4224
4225 // Initialise parameters
4226 $this->id = 0;
4227 $this->ref = 'SPECIMEN';
4228 $this->specimen = 1;
4229 $this->entity = $conf->entity;
4230 $this->socid = 1;
4231 $this->date = dol_now();
4232 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
4233 $this->cond_reglement_code = 'RECEP';
4234 $this->mode_reglement_code = 'CHQ';
4235 $this->availability_code = 'DSP';
4236 $this->demand_reason_code = 'SRC_00';
4237
4238 $this->note_public = 'This is a comment (public)';
4239 $this->note_private = 'This is a comment (private)';
4240
4241 $this->multicurrency_tx = 1;
4242 $this->multicurrency_code = $conf->currency;
4243
4244 $this->status = $this::STATUS_DRAFT;
4245
4246 // Lines
4247 $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)
4248 $xnbp = 0;
4249
4250 while ($xnbp < $nbp) {
4251 $line = new OrderLine($this->db);
4252
4253 $line->desc = $langs->trans("Description")." ".$xnbp;
4254 $line->qty = 1;
4255 $line->subprice = 100;
4256 $line->price = 100;
4257 $line->tva_tx = 20;
4258 if ($xnbp == 2) {
4259 $line->total_ht = 50;
4260 $line->total_ttc = 60;
4261 $line->total_tva = 10;
4262 $line->remise_percent = 50;
4263 } else {
4264 $line->total_ht = 100;
4265 $line->total_ttc = 120;
4266 $line->total_tva = 20;
4267 $line->remise_percent = 0;
4268 }
4269 if ($num_prods > 0) {
4270 $prodid = mt_rand(1, $num_prods);
4271 $line->fk_product = $prodids[$prodid];
4272 $line->product_ref = 'SPECIMEN';
4273 }
4274
4275 $this->lines[$xnbp] = $line;
4276
4277 $this->total_ht += $line->total_ht;
4278 $this->total_tva += $line->total_tva;
4279 $this->total_ttc += $line->total_ttc;
4280
4281 $xnbp++;
4282 }
4283
4284 return 1;
4285 }
4286
4287
4293 public function loadStateBoard()
4294 {
4295 global $user, $hookmanager;
4296
4297 $this->nb = array();
4298 $clause = "WHERE";
4299
4300 $sql = "SELECT count(c.rowid) as nb";
4301 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
4302 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid";
4303 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
4304 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4305 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4306 $clause = "AND";
4307 }
4308 $sql .= " ".$clause." c.entity IN (".getEntity('commande').")";
4309 // Add where from hooks
4310 $parameters = array();
4311 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
4312 $sql .= $hookmanager->resPrint;
4313 $resql = $this->db->query($sql);
4314 if ($resql) {
4315 while ($obj = $this->db->fetch_object($resql)) {
4316 $this->nb["orders"] = $obj->nb;
4317 }
4318 $this->db->free($resql);
4319 return 1;
4320 } else {
4321 dol_print_error($this->db);
4322 $this->error = $this->db->error();
4323 return -1;
4324 }
4325 }
4326
4332 public function getLinesArray()
4333 {
4334 return $this->fetch_lines();
4335 }
4336
4348 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4349 {
4350 global $langs;
4351
4352 $langs->load("orders");
4353 $outputlangs->load("products");
4354
4355 if (!dol_strlen($modele)) {
4356 $modele = 'eratosthene';
4357
4358 if (!empty($this->model_pdf)) {
4359 $modele = $this->model_pdf;
4360 } elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
4361 $modele = getDolGlobalString('COMMANDE_ADDON_PDF');
4362 }
4363 }
4364
4365 $modelpath = "core/modules/commande/doc/";
4366
4367 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4368 }
4369
4370
4379 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4380 {
4381 $tables = array(
4382 'commande'
4383 );
4384
4385 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4386 }
4387
4396 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4397 {
4398 $tables = array(
4399 'commandedet',
4400 );
4401
4402 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4403 }
4404
4410 public function hasDelay()
4411 {
4412 global $conf;
4413
4414 if (!($this->status > Commande::STATUS_DRAFT && $this->status < Commande::STATUS_CLOSED)) {
4415 return false; // Never late if not inside this status range
4416 }
4417
4418 $now = dol_now();
4419
4420 return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
4421 }
4422
4428 public function showDelay()
4429 {
4430 global $conf, $langs;
4431
4432 if (empty($this->delivery_date)) {
4433 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date, 'day');
4434 } else {
4435 $text = $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
4436 }
4437 $text .= ' '.($conf->commande->client->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
4438
4439 return $text;
4440 }
4441
4451 public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
4452 {
4453 return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
4454 }
4455
4466 public function getShippableInfos(array $options = array()): array
4467 {
4468 global $conf, $langs, $hookmanager;
4469
4470 $langs->loadLangs(array('orders', 'sendings', 'stocks', 'products'));
4471
4472 $result = array(
4473 'has_product' => false,
4474 'shippable' => false,
4475 'texticon' => '',
4476 'textinfo' => '',
4477 'warning' => false,
4478 );
4479
4480 if (is_object($hookmanager)) {
4481 $parameters = array('options' => $options);
4482 $reshook = $hookmanager->executeHooks('getShippableInfos', $parameters, $this);
4483 if ($reshook < 0) {
4484 dol_syslog(__METHOD__.' hook getShippableInfos failed: '.$hookmanager->error, LOG_ERR);
4485 }
4486 if (!empty($hookmanager->resArray['shippableinfos'])
4487 && is_array($hookmanager->resArray['shippableinfos'])) {
4488 return array_merge($result, $hookmanager->resArray['shippableinfos']);
4489 }
4490 }
4491
4492 // Requested naming for statuses
4493 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_CLOSED) {
4494 return $result;
4495 }
4496
4497
4498 $genericCommande = $this;
4499 $genericProduct = new Product($this->db);
4500
4501
4502 $productstatcache = array();
4503 $productstatcachevirtual = array();
4504
4505
4506 $genericCommande->getLinesArray(); // Load array ->lines
4507 $genericCommande->loadExpeditions(); // Load array ->expeditions
4508
4509 $notshippable = 0;
4510 $has_reliquat = 0;
4511 $warning = 0;
4512 $textinfo = '';
4513 $textwarning = '';
4514 $nbprod = 0;
4515
4516 $genericProduct = new Product($this->db);
4517
4518 $numlines = count($genericCommande->lines);
4519 for ($lig = 0; $lig < $numlines; $lig++) {
4520 $orderLine = $genericCommande->lines[$lig]; // @phan-var-force OrderLine $orderLine
4521
4522 if (isset($genericCommande->expeditions[$orderLine->id])) {
4523 $reliquat = $orderLine->qty - $genericCommande->expeditions[$orderLine->id];
4524 } else {
4525 $reliquat = $orderLine->qty;
4526 }
4527
4528 if ($orderLine->product_type == 0 && $orderLine->fk_product > 0) { // product, not service
4529 $nbprod = 1;
4530
4531 if (empty($productstatcache[$orderLine->fk_product])) {
4532 $genericProduct->fetch($orderLine->fk_product);
4533 $genericProduct->load_stock('nobatch,warehouseopen'); // loadvirtualstock included
4534
4535 $productstatcache[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_reel;
4536 $productstatcachevirtual[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_theorique;
4537 }
4538
4539 $genericProduct->stock_reel = $productstatcache[$orderLine->fk_product]['stockreel'];
4540 $genericProduct->stock_theorique = $productstatcachevirtual[$orderLine->fk_product]['stockreel'];
4541
4542 if ($reliquat > 0) {
4543 $has_reliquat = 1;
4544 if (!getDolGlobalString('SHIPPABLE_ORDER_ICON_IN_LIST')) {
4545 $textinfo .= $reliquat . ' x ' . $orderLine->product_ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
4546 $textinfo .= ' - ' . $langs->trans("Stock") . ': <span class="' . ($genericProduct->stock_reel >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_reel . '</span>';
4547 $textinfo .= ' - ' . $langs->trans("VirtualStock") . ': <span class="' . ($genericProduct->stock_theorique >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_theorique . '</span>';
4548 if ($reliquat != $orderLine->qty) {
4549 $textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
4550 }
4551 $textinfo .= '<br>';
4552 } else {
4553 // BUGGED CODE (kept for backward compatibility and hidden conf)
4554 $stockorder = 0;
4555 $stockordersupplier = 0;
4556
4557 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
4558 if (isModEnabled('order')) {
4559 if (empty($productstatcache[$orderLine->fk_product]['statsordercustomer'])) {
4560 $genericProduct->fetch($orderLine->fk_product);
4561 $genericProduct->load_stats_commande(0, '1,2');
4562 $productstatcache[$orderLine->fk_product]['statsordercustomer'] = $genericProduct->stats_commande['qty'];
4563 }
4564 $genericProduct->stats_commande['qty'] = $productstatcache[$orderLine->fk_product]['statsordercustomer'];
4565 $stockorder = $genericProduct->stats_commande['qty'];
4566
4567 if (isModEnabled('supplier_order')) {
4568 if (empty($productstatcache[$orderLine->fk_product]['statsordersupplier'])) {
4569 $genericProduct->load_stats_commande_fournisseur(0, '3');
4570 $productstatcache[$orderLine->fk_product]['statsordersupplier'] = $genericProduct->stats_commande_fournisseur['qty'];
4571 }
4572 $genericProduct->stats_commande_fournisseur['qty'] = $productstatcache[$orderLine->fk_product]['statsordersupplier'];
4573 $stockordersupplier = $genericProduct->stats_commande_fournisseur['qty'];
4574 }
4575 }
4576 }
4577
4578 $textinfo .= $reliquat . ' x ' . $orderLine->ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
4579 $textinfo .= ' ' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder;
4580
4581 if ($stockorder && $genericProduct->stock_reel < ($genericProduct->stock_reel - $stockorder + $reliquat)) {
4582 $warning++;
4583 $textwarning .= '<span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder . '</span>';
4584 } else {
4585 if ($reliquat > $genericProduct->stock_reel) {
4586 $textinfo .= ' <span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
4587 } else {
4588 $textinfo .= ' <span class="ok">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
4589 }
4590 }
4591
4592 if (isModEnabled('supplier_order')) {
4593 $textinfo .= '&nbsp;' . $langs->trans("SupplierOrder") . '&nbsp;&nbsp;' . $stockordersupplier;
4594 }
4595 if ($reliquat != $orderLine->qty) {
4596 $textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
4597 }
4598 $textinfo .= '<br>';
4599 }
4600
4601 if ($reliquat > $genericProduct->stock_reel) {
4602 $notshippable++;
4603 }
4604 }
4605 }
4606 }
4607
4608 if ($nbprod) {
4609 if (!$has_reliquat) {
4610 $texticon = img_picto('', 'statut5', '', 0, 0, 0, '', 'paddingleft');
4611 $textinfo = $texticon . ' ' . $langs->trans("Shipped");
4612 $result['shippable'] = true;
4613 } elseif ($notshippable) {
4614 $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'error paddingleft');
4615 $textinfo = $texticon . ' ' . $langs->trans("NonShippable") . '<br>' . $textinfo;
4616 $result['shippable'] = false;
4617 } else {
4618 $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'green paddingleft');
4619 $textinfo = $texticon . ' ' . $langs->trans("Shippable") . '<br>' . $textinfo;
4620 $result['shippable'] = true;
4621 }
4622 $result['has_product'] = true;
4623 $result['texticon'] = $texticon;
4624 $result['textinfo'] = $textinfo;
4625 $result['warning'] = !empty($warning);
4626 }
4627
4628 return $result;
4629 }
4630}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$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.
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.
initAsSpecimen($param=array())
Initialise an instance with random values.
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.
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.
isRefClientAlreadyUsed(string $ref_client, int $socid, int $excludeid=0)
Check if a customer ref is already used by another customer order of the same third party.
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.
getShippableInfos(array $options=array())
Compute shippable status and tooltip/icon for the order.
cloture($user, $notrigger=0, $checkpermission=1)
Close order.
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.
cancel($user, $idwarehouse=-1)
Cancel an order If stock is decremented on order validation, we must reincrement it.
createFromClone(User $user, $socid=0, $forceentity=null)
Load an object from its id and create a new one in database.
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.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
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
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
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)
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
deleteExtraFields()
Delete all extra fields values for the current object.
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.
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 translations.
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:169
setSignedStatusCommon(User $user, int $status, int $notrigger=0, string $triggercode='')
Set signed status & call trigger with context message.
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
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:65
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
setEntity($currentobject)
Set entity id to use when to create an object.
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 '.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
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.
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.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
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)
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
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 rate, 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)
Definition html.lib.php:181
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $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
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492