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' => 'Fk multicurrency', '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
826 public function cloture($user, $notrigger = 0)
827 {
828 $error = 0;
829
830 $usercanclose = ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
831 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
832
833 if ($usercanclose) {
834 if ($this->status == self::STATUS_CLOSED) {
835 return 0;
836 }
837 $this->db->begin();
838
839 $now = dol_now();
840
841 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
842 $sql .= ' SET fk_statut = '.self::STATUS_CLOSED.',';
843 $sql .= ' fk_user_cloture = '.((int) $user->id).',';
844 $sql .= " date_cloture = '".$this->db->idate($now)."',";
845 $sql .= " fk_user_modif = ".((int) $user->id);
846 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
847
848 if ($this->db->query($sql)) {
849 if (!$notrigger) {
850 // Call trigger
851 $result = $this->call_trigger('ORDER_CLOSE', $user);
852 if ($result < 0) {
853 $error++;
854 }
855 // End call triggers
856 }
857
858 if (!$error) {
859 $this->statut = self::STATUS_CLOSED; // deprecated
861
862 $this->db->commit();
863 return 1;
864 } else {
865 $this->db->rollback();
866 return -1;
867 }
868 } else {
869 $this->error = $this->db->lasterror();
870
871 $this->db->rollback();
872 return -1;
873 }
874 }
875 return 0;
876 }
877
889 public function setCategories($categories)
890 {
891 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
892 return parent::setCategoriesCommon($categories, Categorie::TYPE_ORDER);
893 }
894
903 public function cancel($user, $idwarehouse = -1)
904 {
905 global $user, $langs;
906
907 $error = 0;
908
909 $this->db->begin();
910
911 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
912 $sql .= " SET fk_statut = ".self::STATUS_CANCELED.",";
913 $sql .= " fk_user_modif = ".((int) $user->id);
914 $sql .= " WHERE rowid = ".((int) $this->id);
915 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
916
917 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
918 if ($this->db->query($sql)) {
919 // If stock is decremented on validate order, we must reincrement it
920 if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
921 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
922 $langs->load("agenda");
923
924 $num = count($this->lines);
925 for ($i = 0; $i < $num; $i++) {
926 if ($this->lines[$i]->fk_product > 0) {
927 $mouvP = new MouvementStock($this->db);
928 $mouvP->setOrigin($this->element, $this->id);
929 // We increment stock of product (and sub-products)
930 $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
931 if ($result < 0) {
932 $error++;
933 $this->setErrorsFromObject($mouvP);
934 break;
935 }
936 }
937 }
938 }
939
940 if (!$error) {
941 // Call trigger
942 $result = $this->call_trigger('ORDER_CANCEL', $user);
943 if ($result < 0) {
944 $error++;
945 }
946 // End call triggers
947 }
948
949 if (!$error) {
950 $this->statut = self::STATUS_CANCELED; // deprecated
952 $this->db->commit();
953 return 1;
954 } else {
955 foreach ($this->errors as $errmsg) {
956 dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
957 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
958 }
959 $this->db->rollback();
960 return -1 * $error;
961 }
962 } else {
963 $this->error = $this->db->error();
964 $this->db->rollback();
965 return -1;
966 }
967 }
968
977 public function create($user, $notrigger = 0)
978 {
979 global $conf, $langs, $mysoc;
980 $error = 0;
981
982 // Clean parameters
983
984 if (empty($this->socid)) {
985 $this->error = 'ErrorWrongParameters';
986 return -1;
987 }
988
989 // Set tmp vars
990 $date = ($this->date_commande ? $this->date_commande : $this->date);
991 $this->import_key = trim((string) $this->import_key);
992 $delivery_date = $this->delivery_date;
993
994 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
995 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
996 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
997 } else {
998 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
999 }
1000 if (empty($this->fk_multicurrency)) {
1001 $this->multicurrency_code = $conf->currency;
1002 $this->fk_multicurrency = 0;
1003 $this->multicurrency_tx = 1;
1004 }
1005 // setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
1006 $this->entity = setEntity($this);
1007
1008 dol_syslog(get_class($this)."::create user=".$user->id);
1009
1010 // Check parameters
1011 if (!empty($this->ref)) { // We check that ref is not already used
1012 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1013 if ($result > 0) {
1014 $this->error = 'ErrorRefAlreadyExists';
1015 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1016 $this->db->rollback();
1017 return -1;
1018 }
1019 }
1020
1021 $soc = new Societe($this->db);
1022 $result = $soc->fetch($this->socid);
1023 if ($result < 0) {
1024 $this->error = "Failed to fetch company";
1025 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1026 return -2;
1027 }
1028 if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
1029 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
1030 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1031 return -1;
1032 }
1033
1034 $this->date_creation = dol_now();
1035
1036 $this->db->begin();
1037
1038 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1039 $sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
1040 $sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
1041 $sql .= ", fk_shipping_method";
1042 $sql .= ", fk_warehouse";
1043 $sql .= ", fk_incoterms, location_incoterms";
1044 $sql .= ", entity, module_source, pos_source";
1045 $sql .= ", fk_multicurrency";
1046 $sql .= ", multicurrency_code";
1047 $sql .= ", multicurrency_tx";
1048 $sql .= ", import_key";
1049 $sql .= ")";
1050 $sql .= " VALUES ('(PROV)', ".((int) $this->socid).", '".$this->db->idate($this->date_creation)."', ".($user->id > 0 ? ((int) $user->id) : "null");
1051 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1052 $sql .= ", '".$this->db->idate($date)."'";
1053 $sql .= ", ".($this->source >= 0 && $this->source != '' ? $this->db->escape((string) $this->source) : 'null');
1054 $sql .= ", '".$this->db->escape($this->note_private)."'";
1055 $sql .= ", '".$this->db->escape($this->note_public)."'";
1056 $sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
1057 $sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
1058 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1059 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
1060 $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null");
1061 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
1062 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1063 $sql .= ", ".($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
1064 $sql .= ", ".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
1065 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1066 $sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
1067 $sql .= ", ".(!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1068 $sql .= ", ".(!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
1069 $sql .= ", ".(int) $this->fk_incoterms;
1070 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1071 $sql .= ", ".(int) $this->entity;
1072 $sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
1073 $sql .= ", ".((!is_null($this->pos_source) && $this->pos_source != '') ? "'".$this->db->escape($this->pos_source)."'" : "null"); // Can be null, '', '0', '1'
1074 $sql .= ", ".(int) $this->fk_multicurrency;
1075 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1076 $sql .= ", ".(float) $this->multicurrency_tx;
1077 $sql .= ", '".$this->db->escape($this->import_key)."'";
1078 $sql .= ")";
1079
1080 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1081 $resql = $this->db->query($sql);
1082 if ($resql) {
1083 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'commande');
1084
1085 if ($this->id) {
1086 $fk_parent_line = 0;
1087 $num = count($this->lines);
1088
1089 /*
1090 * Insert products details into db
1091 */
1092 for ($i = 0; $i < $num; $i++) {
1093 $line = $this->lines[$i];
1094
1095 // Test and convert into OrderLine object this->lines[$i]. When coming from REST API, we may still have an array
1096 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
1097 if (!is_object($line)) {
1098 $lineobj = new OrderLine($this->db);
1099 foreach ($line as $key => $val) {
1100 $lineobj->$key = $val;
1101 }
1102 $line = $lineobj;
1103 $this->lines[$i] = $line;
1104 }
1105
1106 // Reset fk_parent_line for no child products and special product
1107 if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1108 $fk_parent_line = 0;
1109 }
1110
1111 // Complete vat rate with code
1112 $vatrate = $line->tva_tx;
1113 if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', (string) $vatrate)) {
1114 $vatrate .= ' ('.$line->vat_src_code.')';
1115 }
1116
1117 if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
1118 $originid = $line->origin_id;
1119 $origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
1120 } else { // old but bugged version (we store id of line and type of parent object)
1121 $originid = $line->id;
1122 $origintype = $this->element;
1123 }
1124
1125 // ref_ext
1126 if (empty($line->ref_ext)) {
1127 $line->ref_ext = '';
1128 }
1129
1130 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1131 $line_price_base_type = $line->getPriceBaseType();
1132 $result = $this->addline(
1133 $line->desc,
1134 (float) $line->subprice,
1135 $line->qty,
1136 $vatrate,
1137 $line->localtax1_tx,
1138 $line->localtax2_tx,
1139 $line->fk_product,
1140 $line->remise_percent,
1141 $line->info_bits,
1142 $line->fk_remise_except,
1143 $line_price_base_type,
1144 (float) $line->subprice_ttc,
1145 $line->date_start,
1146 $line->date_end,
1147 $line->product_type,
1148 $line->rang,
1149 $line->special_code,
1150 $fk_parent_line,
1151 $line->fk_fournprice,
1152 $line->pa_ht,
1153 $line->label,
1154 $line->array_options,
1155 $line->fk_unit,
1156 (string) $origintype,
1157 $originid,
1158 0,
1159 $line->ref_ext,
1160 1
1161 );
1162
1163 if ($result < 0) {
1164 if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
1165 $this->error = $this->db->lasterror();
1166 $this->errors[] = $this->error;
1167 dol_print_error($this->db);
1168 }
1169 $this->db->rollback();
1170 return -1;
1171 }
1172 // Defined the new fk_parent_line
1173 if ($result > 0 && $line->product_type == 9) {
1174 $fk_parent_line = $result;
1175 }
1176 }
1177
1178 $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.
1179
1180 // update ref
1181 $initialref = '(PROV'.$this->id.')';
1182 if (!empty($this->ref)) {
1183 $initialref = $this->ref;
1184 }
1185
1186 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($initialref)."' WHERE rowid=".((int) $this->id);
1187 if ($this->db->query($sql)) {
1188 $this->ref = $initialref;
1189
1190 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1191 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1192 }
1193
1194 // Add object linked
1195 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1196 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1197 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, ...))
1198 foreach ($tmp_origin_id as $origin_id) {
1199 $ret = $this->add_object_linked($origin, $origin_id);
1200 if (!$ret) {
1201 $this->error = $this->db->lasterror();
1202 $error++;
1203 }
1204 }
1205 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1206 $origin_id = $tmp_origin_id;
1207 $ret = $this->add_object_linked($origin, $origin_id);
1208 if (!$ret) {
1209 $this->error = $this->db->lasterror();
1210 $error++;
1211 }
1212 }
1213 }
1214 }
1215
1216 if (!$error && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
1217 $originforcontact = empty($this->origin_type) ? $this->origin : $this->origin_type;
1218 $originidforcontact = $this->origin_id;
1219 if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
1220 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
1221 $exp = new Expedition($this->db);
1222 $exp->fetch($this->origin_id);
1223 $exp->fetchObjectLinked();
1224 if (count($exp->linkedObjectsIds['commande']) > 0) {
1225 foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
1226 $originforcontact = 'commande';
1227 if (is_object($value)) {
1228 $originidforcontact = $value->id;
1229 } else {
1230 $originidforcontact = $value;
1231 }
1232 break; // We take first one
1233 }
1234 }
1235 }
1236
1237 $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";
1238 $sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
1239
1240 $resqlcontact = $this->db->query($sqlcontact);
1241 if ($resqlcontact) {
1242 while ($objcontact = $this->db->fetch_object($resqlcontact)) {
1243 //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
1244 $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
1245 }
1246 } else {
1247 dol_print_error($this->db);
1248 }
1249 }
1250
1251 if (!$error) {
1252 $result = $this->insertExtraFields();
1253 if ($result < 0) {
1254 $error++;
1255 }
1256 }
1257
1258 if (!$error && !$notrigger) {
1259 // Call trigger
1260 $result = $this->call_trigger('ORDER_CREATE', $user);
1261 if ($result < 0) {
1262 $error++;
1263 }
1264 // End call triggers
1265 }
1266
1267 if (!$error) {
1268 $this->db->commit();
1269 return $this->id;
1270 } else {
1271 $this->db->rollback();
1272 return -1 * $error;
1273 }
1274 } else {
1275 $this->error = $this->db->lasterror();
1276 $this->db->rollback();
1277 return -1;
1278 }
1279 }
1280
1281 return 0;
1282 } else {
1283 $this->error = $this->db->lasterror();
1284 $this->db->rollback();
1285 return -1;
1286 }
1287 }
1288
1289
1298 public function createFromClone(User $user, $socid = 0, $forceentity = null)
1299 {
1300 global $hookmanager;
1301
1302 $error = 0;
1303
1304 $object = new self($this->db);
1305
1306 $this->db->begin();
1307
1308 // Load source object
1309 $object->fetch($this->id);
1310
1311 $objFrom = clone $object;
1312
1313 // Change socid if needed
1314 if (!empty($socid) && $socid != $object->socid) {
1315 $objsoc = new Societe($this->db);
1316
1317 if ($objsoc->fetch($socid) > 0) {
1318 $object->socid = $objsoc->id;
1319 $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1320 $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1321 $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1322 $object->fk_project = 0;
1323 $object->fk_delivery_address = 0;
1324 }
1325
1326 // TODO Change product price if multi-prices
1327 }
1328
1329 $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1330
1331 $object->id = 0;
1332 $object->ref = '';
1333 $object->statut = self::STATUS_DRAFT; // deprecated
1334 $object->status = self::STATUS_DRAFT;
1335
1336 // Clear fields
1337 $object->user_author_id = $user->id;
1338 $object->user_validation_id = 0;
1339 $object->date = dol_now();
1340 $object->date_commande = dol_now();
1341 $object->date_creation = '';
1342 $object->date_validation = '';
1343 if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
1344 $object->ref_client = '';
1345 $object->ref_customer = '';
1346 }
1347
1348 // Do not clone ref_ext
1349 $num = count($object->lines);
1350 for ($i = 0; $i < $num; $i++) {
1351 $object->lines[$i]->ref_ext = '';
1352 }
1353
1354 // Create clone
1355 $object->context['createfromclone'] = 'createfromclone';
1356 $result = $object->create($user);
1357 if ($result < 0) {
1358 $error++;
1359 $this->setErrorsFromObject($object);
1360 }
1361
1362 if (!$error) {
1363 // copy internal contacts
1364 if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1365 $error++;
1366 }
1367 }
1368
1369 if (!$error) {
1370 // copy external contacts if same company
1371 if ($object->socid == $objFrom->socid) {
1372 if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1373 $error++;
1374 }
1375 }
1376 }
1377
1378 if (!$error) {
1379 // Hook of thirdparty module
1380 if (is_object($hookmanager)) {
1381 $parameters = array('objFrom' => $objFrom, 'clonedObj' => $object);
1382 $action = '';
1383 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1384 if ($reshook < 0) {
1385 $this->setErrorsFromObject($hookmanager);
1386 $error++;
1387 }
1388 }
1389 }
1390
1391 unset($object->context['createfromclone']);
1392
1393 // End
1394 if (!$error) {
1395 $this->db->commit();
1396 return $object->id;
1397 } else {
1398 $this->db->rollback();
1399 return -1;
1400 }
1401 }
1402
1403
1411 public function createFromProposal($object, User $user)
1412 {
1413 global $conf, $hookmanager, $langs;
1414
1415 require_once DOL_DOCUMENT_ROOT . '/multicurrency/class/multicurrency.class.php';
1416 require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
1417
1418 $error = 0;
1419
1420 $this->date_commande = dol_now();
1421 $this->date = dol_now();
1422 $this->source = 0;
1423
1424 $num = count($object->lines);
1425 for ($i = 0; $i < $num; $i++) {
1426 $line = new OrderLine($this->db);
1427
1428 $line->libelle = $object->lines[$i]->libelle;
1429 $line->label = $object->lines[$i]->label;
1430 $line->desc = $object->lines[$i]->desc;
1431 $line->price = $object->lines[$i]->price;
1432 $line->subprice = $object->lines[$i]->subprice;
1433 $line->subprice_ttc = $object->lines[$i]->subprice_ttc; // Preserve the TTC entry mode so create() keeps the typed value (no rounding drift).
1434 $line->vat_src_code = $object->lines[$i]->vat_src_code;
1435 $line->tva_tx = $object->lines[$i]->tva_tx;
1436 $line->localtax1_tx = $object->lines[$i]->localtax1_tx;
1437 $line->localtax2_tx = $object->lines[$i]->localtax2_tx;
1438 $line->qty = $object->lines[$i]->qty;
1439 $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1440 $line->remise_percent = $object->lines[$i]->remise_percent;
1441 $line->fk_product = $object->lines[$i]->fk_product;
1442 $line->info_bits = $object->lines[$i]->info_bits;
1443 $line->product_type = $object->lines[$i]->product_type;
1444 $line->rang = $object->lines[$i]->rang;
1445 $line->special_code = $object->lines[$i]->special_code;
1446 $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1447 $line->fk_unit = $object->lines[$i]->fk_unit;
1448
1449 $line->date_start = $object->lines[$i]->date_start;
1450 $line->date_end = $object->lines[$i]->date_end;
1451
1452 $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1453 $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);
1454 $line->pa_ht = $marginInfos[0];
1455 $line->marge_tx = $marginInfos[1];
1456 $line->marque_tx = $marginInfos[2];
1457
1458 $line->origin = $object->element;
1459 $line->origin_id = $object->lines[$i]->id;
1460
1461 // get extrafields from original line
1462 $object->lines[$i]->fetch_optionals();
1463 foreach ($object->lines[$i]->array_options as $options_key => $value) {
1464 $line->array_options[$options_key] = $value;
1465 }
1466
1467 $this->lines[$i] = $line;
1468 }
1469
1470 $this->entity = $object->entity;
1471 $this->socid = $object->socid;
1472 $this->fk_project = $object->fk_project;
1473 $this->cond_reglement_id = $object->cond_reglement_id;
1474 $this->deposit_percent = $object->deposit_percent;
1475 $this->mode_reglement_id = $object->mode_reglement_id;
1476 $this->fk_account = $object->fk_account;
1477 $this->fk_incoterms = $object->fk_incoterms;
1478 $this->location_incoterms = $object->location_incoterms;
1479 $this->availability_id = $object->availability_id;
1480 $this->demand_reason_id = $object->demand_reason_id;
1481 $this->delivery_date = $object->delivery_date;
1482 $this->shipping_method_id = $object->shipping_method_id;
1483 $this->warehouse_id = $object->warehouse_id;
1484 $this->fk_delivery_address = $object->fk_delivery_address;
1485 $this->contact_id = $object->contact_id;
1486 $this->ref_client = $object->ref_client;
1487 $this->ref_customer = $object->ref_client;
1488
1489 if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1490 $this->note_private = $object->note_private;
1491 $this->note_public = $object->note_public;
1492 }
1493
1494 $this->origin = $object->element;
1495 $this->origin_id = $object->id;
1496
1497 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1498 if (isModEnabled('multicurrency')) {
1499 if (!empty($object->multicurrency_code)) {
1500 $this->multicurrency_code = $object->multicurrency_code;
1501 }
1502 if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
1503 $this->multicurrency_tx = $object->multicurrency_tx;
1504 }
1505
1506 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1507 $tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
1508 $this->fk_multicurrency = $tmparray[0];
1509 $this->multicurrency_tx = $tmparray[1];
1510 } else {
1511 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1512 }
1513 if (empty($this->fk_multicurrency)) {
1514 $this->multicurrency_code = $conf->currency;
1515 $this->fk_multicurrency = 0;
1516 $this->multicurrency_tx = 1;
1517 }
1518 }
1519
1520 // get extrafields from original line
1521 $object->fetch_optionals();
1522
1523 $e = new ExtraFields($this->db);
1524 $element_extrafields = $e->fetch_name_optionals_label($this->table_element);
1525
1526 foreach ($object->array_options as $options_key => $value) {
1527 if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
1528 $this->array_options[$options_key] = $value;
1529 }
1530 }
1531 // Possibility to add external linked objects with hooks
1532 $this->linked_objects[$this->origin] = $this->origin_id;
1533 if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
1534 $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1535 }
1536
1537 $ret = $this->create($user);
1538
1539 if ($ret > 0) {
1540 // Actions hooked (by external module)
1541 $hookmanager->initHooks(array('orderdao'));
1542
1543 $parameters = array('objFrom' => $object);
1544 $action = '';
1545 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1546 if ($reshook < 0) {
1547 $this->setErrorsFromObject($hookmanager);
1548 $error++;
1549 }
1550
1551 if (!$error) {
1552 // Validate immediately the order
1553 if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
1554 $this->fetch($ret);
1555 $result = $this->valid($user);
1556 if ($result > 0 && !getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
1557 $this->fetch($this->id);
1558 $this->fetch_thirdparty();
1559
1560 $outputlangs = $langs;
1561 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($this->thirdparty->default_lang)) {
1562 $outputlangs = new Translate('', $conf);
1563 $outputlangs->setDefaultLang($this->thirdparty->default_lang);
1564 $outputlangs->loadLangs(array('main', 'orders'));
1565 }
1566
1567 $this->generateDocument($this->model_pdf, $outputlangs);
1568 }
1569 }
1570 return $ret;
1571 } else {
1572 return -1;
1573 }
1574 } else {
1575 return -1;
1576 }
1577 }
1578
1579
1619 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)
1620 {
1621 global $mysoc, $langs, $user;
1622
1623 $logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
1624 $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";
1625 $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";
1626 dol_syslog(get_class($this).$logtext, LOG_DEBUG);
1627
1628 if ($this->status == self::STATUS_DRAFT) {
1629 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1630
1631 // Clean parameters
1632
1633 if (empty($remise_percent)) {
1634 $remise_percent = 0;
1635 }
1636 if (empty($qty)) {
1637 $qty = 0;
1638 }
1639 if (empty($info_bits)) {
1640 $info_bits = 0;
1641 }
1642 if (empty($rang)) {
1643 $rang = 0;
1644 }
1645 if (empty($txtva)) {
1646 $txtva = 0;
1647 }
1648 if (empty($txlocaltax1)) {
1649 $txlocaltax1 = 0;
1650 }
1651 if (empty($txlocaltax2)) {
1652 $txlocaltax2 = 0;
1653 }
1654 if (empty($fk_parent_line) || $fk_parent_line < 0) {
1655 $fk_parent_line = 0;
1656 }
1657 if (empty($this->fk_multicurrency)) {
1658 $this->fk_multicurrency = 0;
1659 }
1660 if (empty($ref_ext)) {
1661 $ref_ext = '';
1662 }
1663
1664 $remise_percent = (float) price2num($remise_percent);
1665 $qty = (float) price2num($qty);
1666 $pu_ht = price2num($pu_ht);
1667 $pu_ht_devise = price2num($pu_ht_devise);
1668 $pu_ttc = price2num($pu_ttc);
1669 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
1670 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
1671 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
1672 }
1673 $txlocaltax1 = price2num($txlocaltax1);
1674 $txlocaltax2 = price2num($txlocaltax2);
1675 if ($price_base_type == 'HT') {
1676 $pu = $pu_ht;
1677 } else {
1678 $pu = $pu_ttc;
1679 }
1680 $label = trim($label);
1681 $desc = trim($desc);
1682
1683 // Check parameters
1684 if ($type < 0) {
1685 return -1;
1686 }
1687
1688 if ($date_start && $date_end && $date_start > $date_end) {
1689 $langs->load("errors");
1690 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1691 return -1;
1692 }
1693
1694 $this->db->begin();
1695
1696 $product_type = $type;
1697 if (!empty($fk_product) && $fk_product > 0) {
1698 $product = new Product($this->db);
1699 $result = $product->fetch($fk_product);
1700 $product_type = $product->type;
1701
1702 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
1703 // get real stock
1704 $productChildrenNb = 0;
1705 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
1706 $productChildrenNb = $product->hasFatherOrChild(1);
1707 }
1708 if ($productChildrenNb > 0) {
1709 // compute real stock from each subcomponent
1710 $product_stock = null;
1711 $product->loadStockForVirtualProduct('warehouseopen', $qty);
1712 foreach ($product->stock_warehouse as $componentStockWarehouse) {
1713 if ($product_stock === null) {
1714 $product_stock = $componentStockWarehouse->real;
1715 } else {
1716 $product_stock = min($product_stock, $componentStockWarehouse->real);
1717 }
1718 }
1719 if ($product_stock === null) {
1720 $product_stock = 0;
1721 }
1722 } else {
1723 $product_stock = $product->stock_reel;
1724 }
1725
1726 if ($product_stock < $qty) {
1727 $langs->load("errors");
1728 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
1729 $this->errors[] = $this->error;
1730 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
1731 $this->db->rollback();
1733 }
1734 }
1735 }
1736 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1737 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1738 // and this is done at the line level, which has its own VAT rate
1739
1740 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1741
1742 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
1743 $tmpproduct = new Product($this->db);
1744 $result = $tmpproduct->fetch($fk_product);
1745 if (abs((float) $qty) < $tmpproduct->packaging) {
1746 $qty = (float) $tmpproduct->packaging;
1747 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
1748 } else {
1749 if (!empty($tmpproduct->packaging) && (float) price2num(fmod((float) $qty, (float) $tmpproduct->packaging), 'MS')) {
1750 $coeff = intval(abs((float) $qty) / $tmpproduct->packaging) + 1;
1751 $qty = price2num((float) $tmpproduct->packaging * $coeff, 'MS');
1752 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
1753 }
1754 }
1755 }
1756
1757 // Clean vat code
1758 $reg = array();
1759 $vat_src_code = '';
1760 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
1761 $vat_src_code = $reg[1];
1762 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
1763 }
1764
1765 $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);
1766
1767 $total_ht = $tabprice[0];
1768 $total_tva = $tabprice[1];
1769 $total_ttc = $tabprice[2];
1770 $total_localtax1 = $tabprice[9];
1771 $total_localtax2 = $tabprice[10];
1772 $pu_ht = $tabprice[3];
1773
1774 // MultiCurrency
1775 $multicurrency_total_ht = $tabprice[16];
1776 $multicurrency_total_tva = $tabprice[17];
1777 $multicurrency_total_ttc = $tabprice[18];
1778 $pu_ht_devise = $tabprice[19];
1779
1780 // Rang to use
1781 $ranktouse = $rang;
1782
1783 if (empty($ranktouse) || $ranktouse == -1) {
1784 $rangmax = $this->line_max($fk_parent_line);
1785 $ranktouse = $rangmax + 1;
1786 }
1787
1788 // Insert line
1789 $this->line = new OrderLine($this->db);
1790
1791 $this->line->context = $this->context;
1792
1793 $this->line->fk_commande = $this->id;
1794 $this->line->label = $label;
1795 $this->line->desc = $desc;
1796 $this->line->qty = (float) $qty;
1797 $this->line->ref_ext = $ref_ext;
1798
1799 $this->line->vat_src_code = $vat_src_code;
1800 $this->line->tva_tx = $txtva;
1801 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
1802 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
1803 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1804 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1805 $this->line->fk_product = $fk_product;
1806 $this->line->product_type = $product_type;
1807 $this->line->fk_remise_except = $fk_remise_except;
1808 $this->line->remise_percent = $remise_percent;
1809 $this->line->subprice = (float) $pu_ht;
1810 // Persist the original entry mode of the line so updateline() can preserve it later.
1811 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
1812 $this->line->rang = $ranktouse;
1813 $this->line->info_bits = $info_bits;
1814 $this->line->total_ht = (float) $total_ht;
1815 $this->line->total_tva = (float) $total_tva;
1816 $this->line->total_localtax1 = (float) $total_localtax1;
1817 $this->line->total_localtax2 = (float) $total_localtax2;
1818 $this->line->total_ttc = (float) $total_ttc;
1819 $this->line->special_code = $special_code;
1820 if (empty($qty) && empty($special_code)) {
1821 $this->line->special_code = 3;
1822 }
1823 $this->line->origin = $origin;
1824 $this->line->origin_type = $origin;
1825 $this->line->origin_id = $origin_id;
1826
1827 $this->line->fk_parent_line = $fk_parent_line;
1828 $this->line->fk_unit = $fk_unit;
1829
1830 $this->line->date_start = $date_start;
1831 $this->line->date_end = $date_end;
1832
1833 $this->line->fk_fournprice = $fk_fournprice;
1834 $this->line->pa_ht = $pa_ht; // Can be '' when not defined or 0 if defined to 0 or a price value
1835
1836 // Multicurrency
1837 $this->line->fk_multicurrency = $this->fk_multicurrency;
1838 $this->line->multicurrency_code = $this->multicurrency_code;
1839 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
1840 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
1841 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
1842 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
1843
1844 if (is_array($array_options) && count($array_options) > 0) {
1845 $this->line->array_options = $array_options;
1846 }
1847
1848 $result = $this->line->insert($user);
1849 if ($result > 0) {
1850 // Update denormalized fields at the order level
1851 if (empty($noupdateafterinsertline)) {
1852 $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.
1853 }
1854
1855 if ($result > 0) {
1856 if (!isset($this->context['createfromclone'])) {
1857 if (!empty($fk_parent_line)) {
1858 // Always reorder if child line
1859 $this->line_order(true, 'DESC');
1860 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
1861 // Update all rank of all other lines starting from the same $ranktouse
1862 foreach ($this->lines as $tmpline) {
1863 if ($tmpline->rang >= $ranktouse) {
1864 if (!empty($tmpline->id)) {
1865 $this->updateRangOfLine($tmpline->id, $tmpline->rang + 1);
1866 }
1867 }
1868 }
1869 }
1870
1871 $this->lines[] = $this->line;
1872 } else {
1873 // Loop on all lines of parent object
1874 foreach ($this->lines as $tmpline) {
1875 if ($tmpline->id == $origin_id && $tmpline->element = $origin) {
1876 $this->line->extraparams = $tmpline->extraparams;
1877 $this->line->setExtraParameters();
1878 }
1879 }
1880 }
1881
1882 $this->db->commit();
1883 return $this->line->id;
1884 } else {
1885 $this->db->rollback();
1886 return -1;
1887 }
1888 } else {
1889 $this->setErrorsFromObject($this->line);
1890 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
1891 $this->db->rollback();
1892 return -2;
1893 }
1894 } else {
1895 dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
1896 return -3;
1897 }
1898 }
1899
1900
1901 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1915 public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
1916 {
1917 // phpcs:enable
1918 global $conf, $mysoc;
1919
1920 if (!$qty) {
1921 $qty = 1;
1922 }
1923
1924 if ($idproduct > 0) {
1925 $prod = new Product($this->db);
1926 $prod->fetch($idproduct);
1927
1928 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
1929 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
1930 if (empty($tva_tx)) {
1931 $tva_npr = 0;
1932 }
1933 $vat_src_code = ''; // May be defined into tva_tx
1934
1935 $localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
1936 $localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
1937
1938 // multiprix
1939 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
1940 $price = $prod->multiprices[$this->thirdparty->price_level];
1941 $price_ttc = $prod->multiprices_ttc[$this->thirdparty->price_level];
1942 } else {
1943 $price = $prod->price;
1944 $price_ttc = $prod->price_ttc;
1945 }
1946
1947 $line = new OrderLine($this->db);
1948
1949 $line->context = $this->context;
1950
1951 $line->fk_product = $idproduct;
1952 $line->desc = $prod->description;
1953 $line->qty = $qty;
1954 $line->subprice = $price;
1955 $line->subprice_ttc = $price_ttc;
1956 $line->remise_percent = $remise_percent;
1957 $line->vat_src_code = $vat_src_code;
1958 $line->tva_tx = $tva_tx;
1959 $line->localtax1_tx = $localtax1_tx;
1960 $line->localtax2_tx = $localtax2_tx;
1961
1962 $line->product_ref = $prod->ref;
1963 $line->product_label = $prod->label;
1964 $line->product_desc = $prod->description;
1965 $line->fk_unit = $prod->fk_unit;
1966
1967 // Save the start and end date of the line in the object
1968 if ($date_start) {
1969 $line->date_start = $date_start;
1970 }
1971 if ($date_end) {
1972 $line->date_end = $date_end;
1973 }
1974
1975 $this->lines[] = $line;
1976
1994 }
1995 }
1996
1997
2007 public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
2008 {
2009 // Check parameters
2010 if (empty($id) && empty($ref) && empty($ref_ext)) {
2011 return -1;
2012 }
2013
2014 $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';
2015 $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';
2016 $sql .= ', c.fk_account';
2017 $sql .= ', c.date_commande, c.date_valid, c.tms';
2018 $sql .= ', c.date_livraison as delivery_date';
2019 $sql .= ', c.fk_shipping_method';
2020 $sql .= ', c.fk_warehouse';
2021 $sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
2022 $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';
2023 $sql .= ', c.fk_incoterms, c.location_incoterms';
2024 $sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
2025 $sql .= ", c.module_source, c.pos_source";
2026 $sql .= ", i.libelle as label_incoterms";
2027 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
2028 $sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
2029 $sql .= ', ca.code as availability_code, ca.label as availability_label';
2030 $sql .= ', dr.code as demand_reason_code';
2031 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
2032 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
2033 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON c.fk_mode_reglement = p.id';
2034 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON c.fk_availability = ca.rowid';
2035 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
2036 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
2037
2038 if ($id) {
2039 $sql .= " WHERE c.rowid = ".((int) $id);
2040 } else {
2041 $sql .= " WHERE c.entity IN (".getEntity('commande').")"; // Don't use entity if you use rowid
2042 }
2043
2044 if ($ref) {
2045 $sql .= " AND c.ref = '".$this->db->escape($ref)."'";
2046 }
2047 if ($ref_ext) {
2048 $sql .= " AND c.ref_ext = '".$this->db->escape($ref_ext)."'";
2049 }
2050
2051 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
2052 $result = $this->db->query($sql);
2053 if ($result) {
2054 $obj = $this->db->fetch_object($result);
2055 if ($obj) {
2056 $this->id = $obj->rowid;
2057 $this->entity = $obj->entity;
2058
2059 $this->ref = $obj->ref;
2060 $this->ref_client = $obj->ref_client;
2061 $this->ref_customer = $obj->ref_client;
2062 $this->ref_ext = $obj->ref_ext;
2063
2064 $this->socid = $obj->fk_soc;
2065 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2066
2067 $this->fk_project = $obj->fk_project;
2068 $this->project = null; // Clear if another value was already set by fetch_projet
2069
2070 $this->statut = $obj->status; // deprecated
2071 $this->status = $obj->status;
2072
2073 $this->user_author_id = $obj->fk_user_author;
2074 $this->user_creation_id = $obj->fk_user_author;
2075 $this->user_validation_id = $obj->fk_user_valid;
2076 $this->user_modification_id = $obj->fk_user_modif;
2077 $this->total_ht = $obj->total_ht;
2078 $this->total_tva = $obj->total_tva;
2079 $this->total_localtax1 = $obj->total_localtax1;
2080 $this->total_localtax2 = $obj->total_localtax2;
2081 $this->total_ttc = $obj->total_ttc;
2082 $this->date = $this->db->jdate($obj->date_commande);
2083 $this->date_commande = $this->db->jdate($obj->date_commande);
2084 $this->date_creation = $this->db->jdate($obj->date_creation);
2085 $this->date_validation = $this->db->jdate($obj->date_valid);
2086 $this->date_modification = $this->db->jdate($obj->tms);
2087 $this->source = $obj->source;
2088 $this->billed = $obj->billed;
2089 $this->note = $obj->note_private; // deprecated
2090 $this->note_private = $obj->note_private;
2091 $this->note_public = $obj->note_public;
2092 $this->model_pdf = $obj->model_pdf;
2093 $this->last_main_doc = $obj->last_main_doc;
2094 $this->mode_reglement_id = $obj->fk_mode_reglement;
2095 $this->mode_reglement_code = $obj->mode_reglement_code;
2096 $this->mode_reglement = $obj->mode_reglement_libelle;
2097 $this->cond_reglement_id = $obj->fk_cond_reglement;
2098 $this->cond_reglement_code = $obj->cond_reglement_code;
2099 $this->cond_reglement = $obj->cond_reglement_libelle;
2100 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2101 $this->deposit_percent = $obj->deposit_percent;
2102 $this->fk_account = $obj->fk_account;
2103 $this->availability_id = $obj->fk_availability;
2104 $this->availability_code = $obj->availability_code;
2105 $this->availability = $obj->availability_label;
2106 $this->demand_reason_id = $obj->fk_input_reason;
2107 $this->demand_reason_code = $obj->demand_reason_code;
2108 $this->delivery_date = $this->db->jdate($obj->delivery_date);
2109 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
2110 $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
2111 $this->fk_delivery_address = $obj->fk_delivery_address;
2112 $this->module_source = $obj->module_source;
2113 $this->pos_source = $obj->pos_source;
2114
2115 //Incoterms
2116 $this->fk_incoterms = $obj->fk_incoterms;
2117 $this->location_incoterms = $obj->location_incoterms;
2118 $this->label_incoterms = $obj->label_incoterms;
2119
2120 // Multicurrency
2121 $this->fk_multicurrency = $obj->fk_multicurrency;
2122 $this->multicurrency_code = $obj->multicurrency_code;
2123 $this->multicurrency_tx = $obj->multicurrency_tx;
2124 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2125 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
2126 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2127
2128 $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2129
2130 $this->lines = array();
2131
2132 // Retrieve all extrafield
2133 // fetch optionals attributes and labels
2134 $this->fetch_optionals();
2135
2136 $this->db->free($result);
2137
2138 // Lines
2139 $result = $this->fetch_lines();
2140 if ($result < 0) {
2141 return -3;
2142 }
2143 return 1;
2144 } else {
2145 $this->error = 'Order with id '.$id.' not found sql='.$sql;
2146 return 0;
2147 }
2148 } else {
2149 $this->error = $this->db->error();
2150 return -1;
2151 }
2152 }
2153
2154
2155 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2162 public function insert_discount($idremise)
2163 {
2164 // phpcs:enable
2165 global $langs;
2166
2167 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2168 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2169
2170 $this->db->begin();
2171
2172 $remise = new DiscountAbsolute($this->db);
2173 $result = $remise->fetch($idremise);
2174
2175 if ($result > 0) {
2176 if ($remise->fk_facture) { // Protection against multiple submissions
2177 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2178 $this->db->rollback();
2179 return -5;
2180 }
2181
2182 $line = new OrderLine($this->db);
2183
2184 $line->fk_commande = $this->id;
2185 $line->fk_remise_except = $remise->id;
2186 $line->desc = $remise->description; // Description for the order line
2187 $line->vat_src_code = $remise->vat_src_code;
2188 $line->tva_tx = $remise->tva_tx;
2189 $line->localtax1_tx = $remise->localtax1_tx;
2190 $line->localtax1_type = $remise->localtax1_type;
2191 $line->localtax2_tx = $remise->localtax1_tx;
2192 $line->localtax2_type = $remise->localtax1_type;
2193 $line->subprice = -(float) $remise->amount_ht;
2194 $line->price = -(float) $remise->amount_ht;
2195 $line->fk_product = 0; // Predefined Product ID
2196 $line->qty = 1;
2197 $line->remise_percent = 0;
2198 $line->rang = -1;
2199 $line->info_bits = 2;
2200
2201 $line->total_ht = -(float) $remise->amount_ht;
2202 $line->total_tva = -(float) $remise->amount_tva;
2203 $line->total_ttc = -(float) $remise->amount_ttc;
2204 $line->total_localtax1 = -(float) $remise->total_localtax1;
2205 $line->total_localtax2 = -(float) $remise->total_localtax2;
2206
2207 $result = $line->insert();
2208 if ($result > 0) {
2209 $result = $this->update_price(1);
2210 if ($result > 0) {
2211 $this->db->commit();
2212 return 1;
2213 } else {
2214 $this->db->rollback();
2215 return -1;
2216 }
2217 } else {
2218 $this->setErrorsFromObject($line);
2219 $this->db->rollback();
2220 return -2;
2221 }
2222 } else {
2223 $this->db->rollback();
2224 return -2;
2225 }
2226 }
2227
2228
2229 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2237 public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2238 {
2239 // phpcs:enable
2240 $this->lines = array();
2241
2242 $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,';
2243 $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,';
2244 $sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
2245 $sql .= ' l.fk_unit, l.extraparams,';
2246 $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,';
2247 $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,';
2248 $sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
2249 $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units, p.packaging';
2250 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as l';
2251 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = l.fk_product)';
2252 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
2253 $sql .= ' WHERE l.fk_commande = '.((int) $this->id);
2254 if ($only_product) {
2255 $sql .= ' AND p.fk_product_type = 0';
2256 }
2257 $sql .= ' ORDER BY l.rang, l.rowid';
2258
2259 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
2260 $result = $this->db->query($sql);
2261 if ($result) {
2262 $num = $this->db->num_rows($result);
2263
2264 $i = 0;
2265 while ($i < $num) {
2266 $objp = $this->db->fetch_object($result);
2267
2268 $line = new OrderLine($this->db);
2269
2270 $line->rowid = $objp->rowid;
2271 $line->id = $objp->rowid;
2272 $line->fk_commande = $objp->fk_commande;
2273 $line->commande_id = $objp->fk_commande;
2274 $line->label = $objp->custom_label;
2275 $line->desc = $objp->description;
2276 $line->description = $objp->description; // Description line
2277 $line->product_type = $objp->product_type;
2278 $line->qty = $objp->qty;
2279 $line->ref_ext = $objp->ref_ext;
2280
2281 $line->vat_src_code = $objp->vat_src_code;
2282 $line->tva_tx = $objp->tva_tx;
2283 $line->localtax1_tx = $objp->localtax1_tx;
2284 $line->localtax2_tx = $objp->localtax2_tx;
2285 $line->localtax1_type = $objp->localtax1_type;
2286 $line->localtax2_type = $objp->localtax2_type;
2287 $line->total_ht = $objp->total_ht;
2288 $line->total_ttc = $objp->total_ttc;
2289 $line->total_tva = $objp->total_tva;
2290 $line->total_localtax1 = $objp->total_localtax1;
2291 $line->total_localtax2 = $objp->total_localtax2;
2292 $line->subprice = (float) $objp->subprice;
2293 $line->subprice_ttc = (float) $objp->subprice_ttc;
2294 $line->fk_remise_except = $objp->fk_remise_except;
2295 $line->remise_percent = $objp->remise_percent;
2296 $line->price = $objp->price;
2297 $line->fk_product = $objp->fk_product;
2298 $line->fk_fournprice = $objp->fk_fournprice;
2299 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2300 $line->pa_ht = $marginInfos[0];
2301 $line->marge_tx = $marginInfos[1];
2302 $line->marque_tx = $marginInfos[2];
2303 $line->rang = $objp->rang;
2304 $line->info_bits = $objp->info_bits;
2305 $line->special_code = $objp->special_code;
2306 $line->fk_parent_line = $objp->fk_parent_line;
2307
2308 $line->ref = $objp->product_ref;
2309 $line->libelle = $objp->product_label;
2310
2311 $line->product_ref = $objp->product_ref;
2312 $line->product_label = $objp->product_label;
2313 $line->product_tosell = $objp->product_tosell;
2314 $line->product_tobuy = $objp->product_tobuy;
2315 $line->product_desc = $objp->product_desc;
2316 $line->product_tobatch = $objp->product_tobatch;
2317 $line->product_barcode = $objp->product_barcode;
2318 $line->product_custom_code = $objp->customcode;
2319 $line->product_custom_country_id = $objp->country_id;
2320 $line->product_custom_country_code = $objp->country_code;
2321
2322 $line->fk_product_type = $objp->fk_product_type; // Product or service
2323 $line->fk_unit = $objp->fk_unit;
2324
2325 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2326
2327 $line->weight = $objp->weight;
2328 $line->weight_units = $objp->weight_units;
2329 $line->volume = $objp->volume;
2330 $line->volume_units = $objp->volume_units;
2331 $line->packaging = $objp->packaging;
2332
2333 $line->date_start = $this->db->jdate($objp->date_start);
2334 $line->date_end = $this->db->jdate($objp->date_end);
2335
2336 // Multicurrency
2337 $line->fk_multicurrency = $objp->fk_multicurrency;
2338 $line->multicurrency_code = $objp->multicurrency_code;
2339 $line->multicurrency_subprice = $objp->multicurrency_subprice;
2340 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
2341 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
2342 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
2343 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2344
2345 $line->fetch_optionals();
2346
2347 // multilangs
2348 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2349 $tmpproduct = new Product($this->db);
2350 $tmpproduct->fetch($objp->fk_product);
2351 $tmpproduct->getMultiLangs();
2352
2353 $line->multilangs = $tmpproduct->multilangs;
2354 }
2355
2356 $this->lines[$i] = $line;
2357
2358 $i++;
2359 }
2360
2361 $this->db->free($result);
2362
2363 return 1;
2364 } else {
2365 $this->error = $this->db->error();
2366 return -3;
2367 }
2368 }
2369
2370
2376 public function getNbOfProductsLines()
2377 {
2378 $nb = 0;
2379 foreach ($this->lines as $line) {
2380 if ($line->product_type == 0) {
2381 $nb++;
2382 }
2383 }
2384 return $nb;
2385 }
2386
2392 public function getNbOfServicesLines()
2393 {
2394 $nb = 0;
2395 foreach ($this->lines as $line) {
2396 if ($line->product_type == 1) {
2397 $nb++;
2398 }
2399 }
2400 return $nb;
2401 }
2402
2408 public function getNbOfShipments()
2409 {
2410 $nb = 0;
2411
2412 $sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
2413 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2414 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2415 $sql .= ' WHERE';
2416 $sql .= ' ed.fk_elementdet = cd.rowid';
2417 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2418 //print $sql;
2419
2420 dol_syslog(get_class($this)."::getNbOfShipments", LOG_DEBUG);
2421 $resql = $this->db->query($sql);
2422 if ($resql) {
2423 $obj = $this->db->fetch_object($resql);
2424 if ($obj) {
2425 $nb = (int) $obj->nb;
2426 }
2427
2428 $this->db->free($resql);
2429 return $nb;
2430 } else {
2431 $this->error = $this->db->lasterror();
2432 return -1;
2433 }
2434 }
2435
2444 public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2445 {
2446 $this->expeditions = array();
2447
2448 $sql = 'SELECT cd.rowid, cd.fk_product,';
2449 $sql .= ' sum(ed.qty) as qty';
2450 $sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2451 if ($filtre_statut >= 0) {
2452 $sql .= ' '.MAIN_DB_PREFIX.'expedition as e,';
2453 }
2454 $sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
2455 $sql .= ' WHERE';
2456 if ($filtre_statut >= 0) {
2457 $sql .= ' ed.fk_expedition = e.rowid AND';
2458 }
2459 $sql .= ' ed.fk_elementdet = cd.rowid';
2460 $sql .= ' AND cd.fk_commande = '.((int) $this->id);
2461 if ($fk_product > 0) {
2462 $sql .= ' AND cd.fk_product = '.((int) $fk_product);
2463 }
2464 if ($filtre_statut >= 0) {
2465 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
2466 }
2467 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
2468 //print $sql;
2469
2470 dol_syslog(get_class($this)."::loadExpeditions", LOG_DEBUG);
2471 $resql = $this->db->query($sql);
2472 if ($resql) {
2473 $num = $this->db->num_rows($resql);
2474 $i = 0;
2475 while ($i < $num) {
2476 $obj = $this->db->fetch_object($resql);
2477 $this->expeditions[$obj->rowid] = $obj->qty;
2478 $i++;
2479 }
2480 $this->db->free($resql);
2481 return $num;
2482 } else {
2483 $this->error = $this->db->lasterror();
2484 return -1;
2485 }
2486 }
2487
2493 public function countNbOfShipments()
2494 {
2495 $sql = 'SELECT count(*)';
2496 $sql .= ' FROM '.MAIN_DB_PREFIX.'expedition as e';
2497 $sql .= ', '.MAIN_DB_PREFIX.'element_element as el';
2498 $sql .= ' WHERE el.fk_source = '.((int) $this->id);
2499 $sql .= " AND el.sourcetype = 'commande'";
2500 $sql .= " AND el.fk_target = e.rowid";
2501 $sql .= " AND el.targettype = 'shipping'";
2502
2503 $resql = $this->db->query($sql);
2504 if ($resql) {
2505 $row = $this->db->fetch_row($resql);
2506 return $row[0];
2507 } else {
2508 dol_print_error($this->db);
2509 }
2510
2511 return 0;
2512 }
2513
2522 public function deleteLine($user = null, $lineid = 0, $id = 0)
2523 {
2524 if ($this->status == self::STATUS_DRAFT) {
2525 $this->db->begin();
2526
2527 // Delete line
2528 $line = new OrderLine($this->db);
2529
2530 $line->context = $this->context;
2531
2532 // Load data
2533 $line->fetch($lineid);
2534
2535 if ($id > 0 && $line->fk_commande != $id) {
2536 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2537 return -1;
2538 }
2539
2540 // Memorize previous line for triggers
2541 $staticline = clone $line;
2542 $line->oldline = $staticline;
2543
2544 if ($line->delete($user) > 0) {
2545 $result = $this->update_price(1);
2546
2547 if ($result > 0) {
2548 $this->db->commit();
2549 return 1;
2550 } else {
2551 $this->db->rollback();
2552 $this->error = $this->db->lasterror();
2553 return -1;
2554 }
2555 } else {
2556 $this->db->rollback();
2557 $this->setErrorsFromObject($line);
2558 return -1;
2559 }
2560 } else {
2561 $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2562 return -1;
2563 }
2564 }
2565
2566 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2577 public function set_remise($user, $remise, $notrigger = 0)
2578 {
2579 // phpcs:enable
2580 dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2581 // @phan-suppress-next-line PhanDeprecatedFunction
2582 return $this->setDiscount($user, $remise, $notrigger);
2583 }
2584
2593 public function setDiscount($user, $remise, $notrigger = 0)
2594 {
2595 $remise = trim((string) $remise) ? trim((string) $remise) : 0;
2596
2597 if ($user->hasRight('commande', 'creer')) {
2598 $error = 0;
2599
2600 $this->db->begin();
2601
2602 $remise = price2num($remise, 2);
2603
2604 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2605 $sql .= ' SET remise_percent = '.((float) $remise);
2606 $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_DRAFT);
2607
2608 dol_syslog(__METHOD__, LOG_DEBUG);
2609 $resql = $this->db->query($sql);
2610 if (!$resql) {
2611 $this->errors[] = $this->db->error();
2612 $error++;
2613 }
2614
2615 if (!$error) {
2616 $this->oldcopy = clone $this;
2617 $this->remise_percent = $remise;
2618 $this->update_price(1);
2619 }
2620
2621 if (!$notrigger && empty($error)) {
2622 // Call trigger
2623 $result = $this->call_trigger('ORDER_MODIFY', $user);
2624 if ($result < 0) {
2625 $error++;
2626 }
2627 // End call triggers
2628 }
2629
2630 if (!$error) {
2631 $this->db->commit();
2632 return 1;
2633 } else {
2634 foreach ($this->errors as $errmsg) {
2635 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2636 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2637 }
2638 $this->db->rollback();
2639 return -1 * $error;
2640 }
2641 }
2642
2643 return 0;
2644 }
2645
2646
2647 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2656 public function set_date($user, $date, $notrigger = 0)
2657 {
2658 // phpcs:enable
2659 if ($user->hasRight('commande', 'creer')) {
2660 $error = 0;
2661
2662 $this->db->begin();
2663
2664 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2665 $sql .= " SET date_commande = ".($date ? "'".$this->db->idate($date)."'" : 'null');
2666 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2667
2668 dol_syslog(__METHOD__, LOG_DEBUG);
2669 $resql = $this->db->query($sql);
2670 if (!$resql) {
2671 $this->errors[] = $this->db->error();
2672 $error++;
2673 }
2674
2675 if (!$error) {
2676 $this->oldcopy = clone $this;
2677 $this->date = $date;
2678 }
2679
2680 if (!$notrigger && empty($error)) {
2681 // Call trigger
2682 $result = $this->call_trigger('ORDER_MODIFY', $user);
2683 if ($result < 0) {
2684 $error++;
2685 }
2686 // End call triggers
2687 }
2688
2689 if (!$error) {
2690 $this->db->commit();
2691 return 1;
2692 } else {
2693 foreach ($this->errors as $errmsg) {
2694 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2695 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2696 }
2697 $this->db->rollback();
2698 return -1 * $error;
2699 }
2700 } else {
2701 return -2;
2702 }
2703 }
2704
2705 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2715 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2716 {
2717 // phpcs:enable
2718 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2719 }
2720
2729 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2730 {
2731 if ($user->hasRight('commande', 'creer')) {
2732 $error = 0;
2733
2734 $this->db->begin();
2735
2736 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2737 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2738 $sql .= " WHERE rowid = ".((int) $this->id);
2739
2740 dol_syslog(__METHOD__, LOG_DEBUG);
2741 $resql = $this->db->query($sql);
2742 if (!$resql) {
2743 $this->errors[] = $this->db->error();
2744 $error++;
2745 }
2746
2747 if (!$error) {
2748 $this->oldcopy = clone $this;
2749 $this->delivery_date = $delivery_date;
2750 }
2751
2752 if (!$notrigger && empty($error)) {
2753 // Call trigger
2754 $result = $this->call_trigger('ORDER_MODIFY', $user);
2755 if ($result < 0) {
2756 $error++;
2757 }
2758 // End call triggers
2759 }
2760
2761 if (!$error) {
2762 $this->db->commit();
2763 return 1;
2764 } else {
2765 foreach ($this->errors as $errmsg) {
2766 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2767 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2768 }
2769 $this->db->rollback();
2770 return -1 * $error;
2771 }
2772 } else {
2773 return -2;
2774 }
2775 }
2776
2777 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2791 public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2792 {
2793 // phpcs:enable
2794 global $user;
2795
2796 $ga = array();
2797
2798 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2799 $sql .= " c.rowid as cid, c.ref";
2800 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2801 $sql .= ", sc.fk_soc, sc.fk_user";
2802 }
2803 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as c";
2804 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2805 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2806 }
2807 $sql .= " WHERE c.entity IN (".getEntity('commande').")";
2808 $sql .= " AND c.fk_soc = s.rowid";
2809 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2810 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2811 }
2812 if ($socid) {
2813 $sql .= " AND s.rowid = ".((int) $socid);
2814 }
2815 if ($draft) {
2816 $sql .= " AND c.fk_statut = ".self::STATUS_DRAFT;
2817 }
2818 if (is_object($excluser)) {
2819 $sql .= " AND c.fk_user_author <> ".((int) $excluser->id);
2820 }
2821 $sql .= $this->db->order($sortfield, $sortorder);
2822 $sql .= $this->db->plimit($limit, $offset);
2823
2824 $result = $this->db->query($sql);
2825 if ($result) {
2826 $numc = $this->db->num_rows($result);
2827 if ($numc) {
2828 $i = 0;
2829 while ($i < $numc) {
2830 $obj = $this->db->fetch_object($result);
2831
2832 if ($shortlist == 1) {
2833 $ga[$obj->cid] = $obj->ref;
2834 } elseif ($shortlist == 2) {
2835 $ga[$obj->cid] = $obj->ref.' ('.$obj->name.')';
2836 } else {
2837 $ga[$i]['id'] = $obj->cid;
2838 $ga[$i]['ref'] = $obj->ref;
2839 $ga[$i]['name'] = $obj->name;
2840 }
2841 $i++;
2842 }
2843 }
2844 return $ga;
2845 } else {
2846 dol_print_error($this->db);
2847 return -1;
2848 }
2849 }
2850
2858 public function availability($availability_id, $notrigger = 0)
2859 {
2860 global $user;
2861
2862 dol_syslog('Commande::availability('.$availability_id.')');
2863 if ($this->status >= self::STATUS_DRAFT) {
2864 $error = 0;
2865
2866 $this->db->begin();
2867
2868 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2869 $sql .= ' SET fk_availability = '.((int) $availability_id);
2870 $sql .= ' WHERE rowid='.((int) $this->id);
2871
2872 dol_syslog(__METHOD__, LOG_DEBUG);
2873 $resql = $this->db->query($sql);
2874 if (!$resql) {
2875 $this->errors[] = $this->db->error();
2876 $error++;
2877 }
2878
2879 if (!$error) {
2880 $this->oldcopy = clone $this;
2881 $this->availability_id = $availability_id;
2882 }
2883
2884 if (!$notrigger && empty($error)) {
2885 // Call trigger
2886 $result = $this->call_trigger('ORDER_MODIFY', $user);
2887 if ($result < 0) {
2888 $error++;
2889 }
2890 // End call triggers
2891 }
2892
2893 if (!$error) {
2894 $this->db->commit();
2895 return 1;
2896 } else {
2897 foreach ($this->errors as $errmsg) {
2898 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2899 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2900 }
2901 $this->db->rollback();
2902 return -1 * $error;
2903 }
2904 } else {
2905 $error_str = 'Command status do not meet requirement '.$this->status;
2906 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2907 $this->error = $error_str;
2908 $this->errors[] = $this->error;
2909 return -2;
2910 }
2911 }
2912
2913 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2921 public function demand_reason($demand_reason_id, $notrigger = 0)
2922 {
2923 // phpcs:enable
2924 global $user;
2925
2926 dol_syslog('Commande::demand_reason('.$demand_reason_id.')');
2927 if ($this->status >= self::STATUS_DRAFT) {
2928 $error = 0;
2929
2930 $this->db->begin();
2931
2932 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2933 $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
2934 $sql .= ' WHERE rowid='.((int) $this->id);
2935
2936 dol_syslog(__METHOD__, LOG_DEBUG);
2937 $resql = $this->db->query($sql);
2938 if (!$resql) {
2939 $this->errors[] = $this->db->error();
2940 $error++;
2941 }
2942
2943 if (!$error) {
2944 $this->oldcopy = clone $this;
2945 $this->demand_reason_id = $demand_reason_id;
2946 }
2947
2948 if (!$notrigger && empty($error)) {
2949 // Call trigger
2950 $result = $this->call_trigger('ORDER_MODIFY', $user);
2951 if ($result < 0) {
2952 $error++;
2953 }
2954 // End call triggers
2955 }
2956
2957 if (!$error) {
2958 $this->db->commit();
2959 return 1;
2960 } else {
2961 foreach ($this->errors as $errmsg) {
2962 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2963 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2964 }
2965 $this->db->rollback();
2966 return -1 * $error;
2967 }
2968 } else {
2969 $error_str = 'order status do not meet requirement '.$this->status;
2970 dol_syslog(__METHOD__.$error_str, LOG_ERR);
2971 $this->error = $error_str;
2972 $this->errors[] = $this->error;
2973 return -2;
2974 }
2975 }
2976
2977 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2986 public function set_ref_client($user, $ref_client, $notrigger = 0)
2987 {
2988 // phpcs:enable
2989 if ($user->hasRight('commande', 'creer')) {
2990 $error = 0;
2991
2992 $this->db->begin();
2993
2994 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
2995 $sql .= ' ref_client = '.(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2996 $sql .= ' WHERE rowid = '.((int) $this->id);
2997
2998 dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2999 $resql = $this->db->query($sql);
3000 if (!$resql) {
3001 $this->errors[] = $this->db->error();
3002 $error++;
3003 }
3004
3005 if (!$error) {
3006 $this->oldcopy = clone $this;
3007 $this->ref_client = $ref_client;
3008 $this->ref_customer = $ref_client;
3009 }
3010
3011 if (!$notrigger && empty($error)) {
3012 // Call trigger
3013 $result = $this->call_trigger('ORDER_MODIFY', $user);
3014 if ($result < 0) {
3015 $error++;
3016 }
3017 // End call triggers
3018 }
3019 if (!$error) {
3020 $this->db->commit();
3021 return 1;
3022 } else {
3023 foreach ($this->errors as $errmsg) {
3024 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3025 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3026 }
3027 $this->db->rollback();
3028 return -1 * $error;
3029 }
3030 } else {
3031 return -1;
3032 }
3033 }
3034
3042 public function classifyBilled(User $user, $notrigger = 0)
3043 {
3044 $error = 0;
3045
3046 if ($this->billed) {
3047 return 0;
3048 }
3049
3050 $this->db->begin();
3051
3052 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 1';
3053 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3054
3055 dol_syslog(get_class($this)."::classifyBilled", LOG_DEBUG);
3056 if ($this->db->query($sql)) {
3057 $this->oldcopy = clone $this;
3058 $this->billed = 1;
3059
3060 if (!$notrigger && empty($error)) {
3061 // Call trigger
3062 $result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
3063 if ($result < 0) {
3064 $error++;
3065 }
3066 // End call triggers
3067 }
3068
3069 if (!$error) {
3070 $this->db->commit();
3071 return 1;
3072 } else {
3073 foreach ($this->errors as $errmsg) {
3074 dol_syslog(get_class($this)."::classifyBilled ".$errmsg, LOG_ERR);
3075 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3076 }
3077 $this->db->rollback();
3078 return -1 * $error;
3079 }
3080 } else {
3081 $this->error = $this->db->error();
3082 $this->db->rollback();
3083 return -1;
3084 }
3085 }
3086
3094 public function classifyUnBilled(User $user, $notrigger = 0)
3095 {
3096 $error = 0;
3097
3098 $this->db->begin();
3099
3100 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 0';
3101 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
3102
3103 dol_syslog(get_class($this)."::classifyUnBilled", LOG_DEBUG);
3104 if ($this->db->query($sql)) {
3105 $this->oldcopy = clone $this;
3106 $this->billed = 1;
3107
3108 if (!$notrigger && empty($error)) {
3109 // Call trigger
3110 $result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
3111 if ($result < 0) {
3112 $error++;
3113 }
3114 // End call triggers
3115 }
3116
3117 if (!$error) {
3118 $this->billed = 0;
3119
3120 $this->db->commit();
3121 return 1;
3122 } else {
3123 foreach ($this->errors as $errmsg) {
3124 dol_syslog(get_class($this)."::classifyUnBilled ".$errmsg, LOG_ERR);
3125 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3126 }
3127 $this->db->rollback();
3128 return -1 * $error;
3129 }
3130 } else {
3131 $this->error = $this->db->error();
3132 $this->db->rollback();
3133 return -1;
3134 }
3135 }
3136
3137
3168 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)
3169 {
3170 global $mysoc, $langs, $user;
3171
3172 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");
3173 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3174
3175 if ($this->status == Commande::STATUS_DRAFT) {
3176 // Clean parameters
3177 if (empty($qty)) {
3178 $qty = 0;
3179 }
3180 if (empty($info_bits)) {
3181 $info_bits = 0;
3182 }
3183 if (empty($txtva)) {
3184 $txtva = 0;
3185 }
3186 if (empty($txlocaltax1)) {
3187 $txlocaltax1 = 0;
3188 }
3189 if (empty($txlocaltax2)) {
3190 $txlocaltax2 = 0;
3191 }
3192 if (empty($remise_percent)) {
3193 $remise_percent = 0;
3194 }
3195 if (empty($qty) && empty($special_code)) {
3196 $special_code = 3; // Set option tag
3197 }
3198 if (!empty($qty) && $special_code == 3) {
3199 $special_code = 0; // Remove option tag
3200 }
3201 if (empty($ref_ext)) {
3202 $ref_ext = '';
3203 }
3204
3205 if ($date_start && $date_end && $date_start > $date_end) {
3206 $langs->load("errors");
3207 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3208 return -1;
3209 }
3210
3211 $remise_percent = (float) price2num($remise_percent);
3212 $qty = (float) price2num($qty);
3213 $pu = price2num($pu);
3214 $pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
3215 $pu_ht_devise = price2num($pu_ht_devise);
3216 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3217 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3218 }
3219 $txlocaltax1 = (float) price2num($txlocaltax1);
3220 $txlocaltax2 = (float) price2num($txlocaltax2);
3221
3222 $this->db->begin();
3223
3224 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3225 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3226 // and this is done at the line level, which has its own VAT rate
3227
3228 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3229
3230 // Clean vat code
3231 $vat_src_code = '';
3232 $reg = array();
3233 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3234 $vat_src_code = $reg[1];
3235 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3236 }
3237
3238 $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);
3239
3240 $total_ht = $tabprice[0];
3241 $total_tva = $tabprice[1];
3242 $total_ttc = $tabprice[2];
3243 $total_localtax1 = $tabprice[9];
3244 $total_localtax2 = $tabprice[10];
3245 $pu_ht = $tabprice[3];
3246 $pu_tva = $tabprice[4];
3247 $pu_ttc = $tabprice[5];
3248
3249 // MultiCurrency
3250 $multicurrency_total_ht = $tabprice[16];
3251 $multicurrency_total_tva = $tabprice[17];
3252 $multicurrency_total_ttc = $tabprice[18];
3253 $pu_ht_devise = $tabprice[19];
3254
3255 // Fetch current line from the database and then clone the object and set it in $oldline property
3256 $line = new OrderLine($this->db);
3257 $line->fetch($rowid);
3258 $line->fetch_optionals();
3259
3260 if (!empty($line->fk_product)) {
3261 $product = new Product($this->db);
3262 $result = $product->fetch($line->fk_product);
3263 $product_type = $product->type;
3264
3265 if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
3266 // get real stock
3267 $productChildrenNb = 0;
3268 if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
3269 $productChildrenNb = $product->hasFatherOrChild(1);
3270 }
3271 if ($productChildrenNb > 0) {
3272 // compute real stock from each subcomponent
3273 $product_stock = null;
3274 $product->loadStockForVirtualProduct('warehouseopen', $qty);
3275 foreach ($product->stock_warehouse as $componentStockWarehouse) {
3276 if ($product_stock === null) {
3277 $product_stock = $componentStockWarehouse->real;
3278 } else {
3279 $product_stock = min($product_stock, $componentStockWarehouse->real);
3280 }
3281 }
3282 if ($product_stock === null) {
3283 $product_stock = 0;
3284 }
3285 } else {
3286 $product_stock = $product->stock_reel;
3287 }
3288
3289 if ($product_stock < $qty) {
3290 $langs->load("errors");
3291 $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', (string) $product->ref);
3292 $this->errors[] = $this->error;
3293
3294 dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
3295
3296 $this->db->rollback();
3298 }
3299 }
3300 }
3301
3302 $staticline = clone $line;
3303
3304 $line->oldline = $staticline;
3305 $this->line = $line;
3306 $this->line->context = $this->context;
3307 $this->line->rang = $rang;
3308
3309 // Reorder if fk_parent_line change
3310 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
3311 $rangmax = $this->line_max($fk_parent_line);
3312 $this->line->rang = $rangmax + 1;
3313 }
3314
3315 if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
3316 if (abs((float) $qty) < $this->line->packaging) {
3317 $qty = $this->line->packaging;
3318 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3319 } else {
3320 if (!empty($this->line->packaging)
3321 && is_numeric($this->line->packaging)
3322 && (float) $this->line->packaging > 0
3323 && (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
3324 // Use abs() to keep the rounding consistent for negative qty,
3325 // matching what addline() at line 1725 already does (#38782 bug 5).
3326 $coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
3327 $qty = price2num((float) $this->line->packaging * $coeff, 'MS');
3328 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3329 }
3330 }
3331 }
3332
3333 $this->line->id = $rowid;
3334 $this->line->label = $label;
3335 $this->line->desc = $desc;
3336 $this->line->qty = $qty;
3337 $this->line->ref_ext = $ref_ext;
3338
3339 $this->line->vat_src_code = $vat_src_code;
3340 $this->line->tva_tx = $txtva;
3341 $this->line->localtax1_tx = $txlocaltax1;
3342 $this->line->localtax2_tx = $txlocaltax2;
3343 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3344 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3345 $this->line->remise_percent = $remise_percent;
3346 $this->line->subprice = (float) $pu_ht;
3347 // Persist the original entry mode of the line so a no-op edit can preserve it later.
3348 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
3349 $this->line->info_bits = $info_bits;
3350 $this->line->special_code = $special_code;
3351 $this->line->total_ht = (float) $total_ht;
3352 $this->line->total_tva = (float) $total_tva;
3353 $this->line->total_localtax1 = (float) $total_localtax1;
3354 $this->line->total_localtax2 = (float) $total_localtax2;
3355 $this->line->total_ttc = (float) $total_ttc;
3356 $this->line->date_start = $date_start;
3357 $this->line->date_end = $date_end;
3358 $this->line->product_type = $type;
3359 $this->line->fk_parent_line = $fk_parent_line;
3360 $this->line->skip_update_total = $skip_update_total;
3361 $this->line->fk_unit = $fk_unit;
3362
3363 $this->line->fk_fournprice = $fk_fournprice;
3364 $this->line->pa_ht = $pa_ht;
3365
3366 // Multicurrency
3367 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3368 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3369 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3370 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3371
3372 if (is_array($array_options) && count($array_options) > 0) {
3373 // We replace values in this->line->array_options only for entries defined into $array_options
3374 foreach ($array_options as $key => $value) {
3375 $this->line->array_options[$key] = $array_options[$key];
3376 }
3377 }
3378
3379 $result = $this->line->update($user, $notrigger);
3380 if ($result > 0) {
3381 // Reorder if child line
3382 if (!empty($fk_parent_line)) {
3383 $this->line_order(true, 'DESC');
3384 }
3385
3386 // Mise a jour info denormalisees
3387 $this->update_price(1, 'auto');
3388
3389 $this->db->commit();
3390 return $result;
3391 } else {
3392 $this->setErrorsFromObject($this->line);
3393
3394 $this->db->rollback();
3395 return -1;
3396 }
3397 } else {
3398 $this->error = get_class($this)."::updateline Order status makes operation forbidden";
3399 $this->errors = array('OrderStatusMakeOperationForbidden');
3400 return -2;
3401 }
3402 }
3403
3411 public function update(User $user, $notrigger = 0)
3412 {
3413 $error = 0;
3414
3415 // Clean parameters
3416 if (isset($this->ref)) {
3417 $this->ref = trim($this->ref);
3418 }
3419 if (isset($this->ref_client)) {
3420 $this->ref_client = trim($this->ref_client);
3421 }
3422 if (isset($this->ref_customer)) {
3423 $this->ref_customer = trim($this->ref_customer);
3424 }
3425 if (isset($this->note) || isset($this->note_private)) {
3426 $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
3427 }
3428 if (isset($this->note_public)) {
3429 $this->note_public = trim($this->note_public);
3430 }
3431 if (isset($this->model_pdf)) {
3432 $this->model_pdf = trim($this->model_pdf);
3433 }
3434 if (isset($this->import_key)) {
3435 $this->import_key = trim($this->import_key);
3436 }
3437 $delivery_date = $this->delivery_date;
3438
3439 // Check parameters
3440 // Put here code to add control on parameters values
3441
3442 // Update request
3443 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
3444
3445 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
3446 $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
3447 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
3448 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
3449 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
3450 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
3451
3452 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
3453 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
3454 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
3455 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
3456 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
3457 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
3458 $sql .= " fk_user_modif=".(isset($user->id) ? ((int) $user->id) : "null").",";
3459 $sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? ((int) $this->user_validation_id) : "null").",";
3460 $sql .= " fk_projet=".(isset($this->fk_project) ? ((int) $this->fk_project) : "null").",";
3461 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
3462 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape((string) $this->deposit_percent)."'" : "null").",";
3463 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
3464 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
3465 $sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? ((int) $this->shipping_method_id) : "null").",";
3466 $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").",";
3467 $sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null").",";
3468 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
3469 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
3470 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
3471 $sql .= " fk_warehouse=".($this->warehouse_id > 0 ? ((int) $this->warehouse_id) : "null").",";
3472 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
3473 $sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").",";
3474 $sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null");
3475 $sql .= " WHERE rowid=".((int) $this->id);
3476
3477 $this->db->begin();
3478
3479 dol_syslog(get_class($this)."::update", LOG_DEBUG);
3480 $resql = $this->db->query($sql);
3481 if (!$resql) {
3482 $error++;
3483 $this->errors[] = "Error ".$this->db->lasterror();
3484 }
3485
3486 if (!$error) {
3487 $result = $this->insertExtraFields();
3488 if ($result < 0) {
3489 $error++;
3490 }
3491 }
3492
3493 if (!$error && !$notrigger) {
3494 // Call trigger
3495 $result = $this->call_trigger('ORDER_MODIFY', $user);
3496 if ($result < 0) {
3497 $error++;
3498 }
3499 // End call triggers
3500 }
3501
3502 // Commit or rollback
3503 if ($error) {
3504 foreach ($this->errors as $errmsg) {
3505 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
3506 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3507 }
3508 $this->db->rollback();
3509 return -1 * $error;
3510 } else {
3511 $this->db->commit();
3512 return 1;
3513 }
3514 }
3515
3523 public function delete($user, $notrigger = 0)
3524 {
3525 global $conf, $langs;
3526 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3527
3528 $error = 0;
3529
3530 dol_syslog(get_class($this)."::delete ".$this->id, LOG_DEBUG);
3531
3532 $this->db->begin();
3533
3534 if (!$notrigger) {
3535 // Call trigger
3536 $result = $this->call_trigger('ORDER_DELETE', $user);
3537 if ($result < 0) {
3538 $error++;
3539 }
3540 // End call triggers
3541 }
3542
3543 // Test we can delete
3544 if ($this->countNbOfShipments() != 0) {
3545 $this->errors[] = $langs->trans('SomeShipmentExists');
3546 $error++;
3547 }
3548
3549 // Remove linked categories.
3550 if (!$error) {
3551 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_order";
3552 $sql .= " WHERE fk_order = ".((int) $this->id);
3553
3554 $result = $this->db->query($sql);
3555 if (!$result) {
3556 $error++;
3557 $this->errors[] = $this->db->lasterror();
3558 }
3559 }
3560
3561 // Delete extrafields of lines and lines
3562 if (!$error && !empty($this->table_element_line)) {
3563 $tabletodelete = $this->table_element_line;
3564 $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).")";
3565 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3566 if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3567 $error++;
3568 $this->error = $this->db->lasterror();
3569 $this->errors[] = $this->error;
3570 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3571 }
3572 }
3573
3574 if (!$error) {
3575 // Delete linked object
3576 $res = $this->deleteObjectLinked();
3577 if ($res < 0) {
3578 $error++;
3579 }
3580 }
3581
3582 if (!$error) {
3583 // Delete linked contacts
3584 $res = $this->delete_linked_contact();
3585 if ($res < 0) {
3586 $error++;
3587 }
3588 }
3589
3590 // Removed extrafields of object
3591 if (!$error) {
3592 $result = $this->deleteExtraFields();
3593 if ($result < 0) {
3594 $error++;
3595 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3596 }
3597 }
3598
3599 // Delete main record
3600 if (!$error) {
3601 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3602 $res = $this->db->query($sql);
3603 if (!$res) {
3604 $error++;
3605 $this->error = $this->db->lasterror();
3606 $this->errors[] = $this->error;
3607 dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3608 }
3609 }
3610
3611 // Delete record into ECM index and physically
3612 if (!$error) {
3613 $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive, old method.
3614 $res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
3615 if (!$res) {
3616 $error++;
3617 }
3618 }
3619
3620 if (!$error) {
3621 // We remove directory
3622 $ref = dol_sanitizeFileName($this->ref);
3623 if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
3624 $dir = $conf->commande->multidir_output[$this->entity]."/".$ref;
3625 $file = $dir."/".$ref.".pdf";
3626 if (file_exists($file)) {
3627 dol_delete_preview($this);
3628
3629 if (!dol_delete_file($file, 0, 0, 0, $this)) {
3630 $this->error = 'ErrorFailToDeleteFile';
3631 $this->errors[] = $this->error;
3632 $this->db->rollback();
3633 return 0;
3634 }
3635 }
3636 if (file_exists($dir)) {
3637 $res = @dol_delete_dir_recursive($dir);
3638 if (!$res) {
3639 $this->error = 'ErrorFailToDeleteDir';
3640 $this->errors[] = $this->error;
3641 $this->db->rollback();
3642 return 0;
3643 }
3644 }
3645 }
3646 }
3647
3648 if (!$error) {
3649 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3650 $this->db->commit();
3651 return 1;
3652 } else {
3653 $this->db->rollback();
3654 return -1;
3655 }
3656 }
3657
3658
3659 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3667 public function load_board($user, $mode)
3668 {
3669 // phpcs:enable
3670 global $conf, $langs, $hookmanager;
3671
3672 $clause = " WHERE";
3673
3674 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
3675 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
3676 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
3677 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
3678 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3679 $clause = " AND";
3680 }
3681 $sql .= $clause." c.entity IN (".getEntity('commande').")";
3682 //$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
3683 if ($mode == 'toship') {
3684 // An order to ship is an open order (validated or in progress)
3685 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
3686 }
3687 if ($mode == 'tobill') {
3688 // An order to bill is an order not already billed
3689 $sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
3690 }
3691 if ($mode == 'shippedtobill') {
3692 // An order shipped and to bill is a delivered order not already billed
3693 $sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
3694 }
3695 if ($user->socid) {
3696 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3697 }
3698 // Add where from hooks
3699 $parameters = array('socid' => $user->socid);
3700 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
3701 $sql .= $hookmanager->resPrint;
3702 $resql = $this->db->query($sql);
3703 if ($resql) {
3704 $delay_warning = 0;
3705 $label = $labelShort = $url = '';
3706 if ($mode == 'toship') {
3707 $delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
3708 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
3709 $label = $langs->transnoentitiesnoconv("OrdersToProcess");
3710 $labelShort = $langs->transnoentitiesnoconv("Opened");
3711 }
3712 if ($mode == 'tobill') {
3713 $url = DOL_URL_ROOT.'/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3714 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3715 $labelShort = $langs->trans("ToBill");
3716 }
3717 if ($mode == 'shippedtobill') {
3718 $url = DOL_URL_ROOT.'/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
3719 $label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
3720 $labelShort = $langs->trans("StatusOrderDelivered").' '.$langs->trans("and").' '.$langs->trans("ToBill");
3721 }
3722
3723 $response = new WorkboardResponse();
3724
3725 $response->warning_delay = $delay_warning;
3726 $response->label = $label;
3727 $response->labelShort = $labelShort;
3728 $response->url = $url;
3729 $response->url_late = DOL_URL_ROOT.'/commande/list.php?search_option=late&mainmenu=commercial&leftmenu=orders';
3730 $response->img = img_object('', "order");
3731
3732 $generic_commande = new Commande($this->db);
3733
3734 while ($obj = $this->db->fetch_object($resql)) {
3735 $response->nbtodo++;
3736 $response->total += $obj->total_ht;
3737
3738 $generic_commande->status = $obj->fk_statut;
3739 $generic_commande->date = $this->db->jdate($obj->date_commande);
3740 $generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
3741
3742 if ($mode == 'toship' && $generic_commande->hasDelay()) {
3743 $response->nbtodolate++;
3744 }
3745 }
3746
3747 return $response;
3748 } else {
3749 $this->error = $this->db->error();
3750 return -1;
3751 }
3752 }
3753
3759 public function getLabelSource()
3760 {
3761 global $langs;
3762
3763 $label = $langs->trans('OrderSource'.$this->source);
3764
3765 if ($label == 'OrderSource') {
3766 return '';
3767 }
3768 return $label;
3769 }
3770
3777 public function getLibStatut($mode)
3778 {
3779 return $this->LibStatut($this->status, $this->billed, $mode);
3780 }
3781
3782 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3792 public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
3793 {
3794 // phpcs:enable
3795 global $langs, $hookmanager;
3796
3797 $billedtext = '';
3798 if (empty($donotshowbilled)) {
3799 $billedtext .= ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3800 }
3801 $billedtextlong = ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
3802
3803 $labelTooltip = '';
3804
3805 $paramsBadge = array('badgeParams' => array('attr' => array(
3806 'data-status-element' => $this->element,
3807 'data-billed' => (int) $billed,
3808 'data-status' => (int) $status
3809 )));
3810
3811 if ($status == self::STATUS_CANCELED) {
3812 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
3813 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
3814 $statusType = 'status9';
3815 } elseif ($status == self::STATUS_DRAFT) {
3816 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
3817 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
3818 $statusType = 'status0';
3819 } elseif ($status == self::STATUS_VALIDATED) {
3820 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated').$billedtextlong;
3821 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort').$billedtext;
3822 $statusType = 'status1';
3823 } elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
3824 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent').$billedtextlong;
3825 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort').$billedtext;
3826 $labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
3827 if (!empty($this->delivery_date)) {
3828 $labelTooltip .= ' - '.$langs->transnoentitiesnoconv("DateDeliveryPlanned").dol_print_date($this->delivery_date, 'day').$billedtext;
3829 }
3830 $statusType = 'status4';
3831 } elseif ($status == self::STATUS_CLOSED) {
3832 $labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered').$billedtextlong;
3833 $labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort').$billedtext;
3834 $statusType = 'status6';
3835 } else {
3836 $labelStatus = $langs->transnoentitiesnoconv('Unknown');
3837 $labelStatusShort = '';
3838 $statusType = '';
3839 $mode = 0;
3840 }
3841
3842 $paramsBadge['tooltip'] = $labelTooltip;
3843
3844 $parameters = array(
3845 'status' => $status,
3846 'mode' => $mode,
3847 'billed' => $billed,
3848 'donotshowbilled' => $donotshowbilled,
3849 'paramsBadge' => & $paramsBadge
3850 );
3851
3852 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3853
3854 if ($reshook > 0) {
3855 return $hookmanager->resPrint;
3856 }
3857
3858 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsBadge);
3859 }
3860
3868 public function getTooltipContentArray($params)
3869 {
3870 global $conf, $langs, $user;
3871
3872 $langs->load('orders');
3873 $datas = [];
3874 $nofetch = !empty($params['nofetch']);
3875
3876 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3877 return ['optimize' => $langs->trans("Order")];
3878 }
3879
3880 if ($user->hasRight('commande', 'lire')) {
3881 $datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Order").'</u>';
3882 if (isset($this->status)) {
3883 $datas['status'] = ' '.$this->getLibStatut(5);
3884 }
3885 $datas['Ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3886 if (!$nofetch) {
3887 $langs->load('companies');
3888 if (empty($this->thirdparty)) {
3889 $this->fetch_thirdparty();
3890 }
3891 $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3892 }
3893 $datas['RefCustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.(empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
3894 if (!$nofetch) {
3895 $langs->load('project');
3896 if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
3897 $res = $this->fetchProject();
3898 if ($res > 0 && $this->project instanceof Project) {
3899 $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
3900 }
3901 }
3902 }
3903 if (!empty($this->total_ht)) {
3904 $datas['AmountHT'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3905 }
3906 if (!empty($this->total_tva)) {
3907 $datas['VAT'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3908 }
3909 if (!empty($this->total_ttc)) {
3910 $datas['AmountTTC'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3911 }
3912 if (!empty($this->date)) {
3913 $datas['Date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3914 }
3915 if (!empty($this->delivery_date)) {
3916 $datas['DeliveryDate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3917 }
3918 }
3919
3920 return $datas;
3921 }
3922
3936 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
3937 {
3938 global $conf, $langs, $user, $hookmanager;
3939
3940 if (!empty($conf->dol_no_mouse_hover)) {
3941 $notooltip = 1; // Force disable tooltips
3942 }
3943
3944 $result = '';
3945
3946 if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
3947 $baseurl = DOL_URL_ROOT . '/expedition/shipment.php';
3948 } else {
3949 $baseurl = DOL_URL_ROOT . '/commande/card.php';
3950 }
3951 $query = ['id' => $this->id];
3952 if (!$user->hasRight('commande', 'lire')) {
3953 $option = 'nolink';
3954 }
3955
3956 if ($option !== 'nolink') {
3957 // Add param to save lastsearch_values or not
3958 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3959 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3960 $add_save_lastsearch_values = 1;
3961 }
3962 if ($add_save_lastsearch_values) {
3963 $query = array_merge($query, ['save_lastsearch_values' => 1]);
3964 }
3965 }
3966 $url = dolBuildUrl($baseurl, $query);
3967
3968 if ($short) {
3969 return $url;
3970 }
3971 $params = [
3972 'id' => $this->id,
3973 'objecttype' => $this->element,
3974 'option' => $option,
3975 'nofetch' => 1,
3976 ];
3977 $classfortooltip = 'classfortooltip';
3978 $dataparams = '';
3979 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3980 $classfortooltip = 'classforajaxtooltip';
3981 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3982 $label = '';
3983 } else {
3984 $label = implode($this->getTooltipContentArray($params));
3985 }
3986
3987 $linkclose = '';
3988 if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
3989 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3990 $label = $langs->trans("Order");
3991 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
3992 }
3993 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
3994 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3995
3996 $target_value = array('_self', '_blank', '_parent', '_top');
3997 if (in_array($target, $target_value)) {
3998 $linkclose .= ' target="'.dol_escape_htmltag($target).'"';
3999 }
4000 }
4001
4002 $linkstart = '<a href="'.$url.'"';
4003 $linkstart .= $linkclose.'>';
4004 $linkend = '</a>';
4005
4006 if ($option === 'nolink') {
4007 $linkstart = '';
4008 $linkend = '';
4009 }
4010
4011 $result .= $linkstart;
4012 if ($withpicto) {
4013 $result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
4014 }
4015 if ($withpicto != 2) {
4016 $result .= $this->ref;
4017 }
4018 $result .= $linkend;
4019
4020 if ($addlinktonotes) {
4021 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
4022 if ($txttoshow) {
4023 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
4024 $result .= ' <span class="note inline-block">';
4025 $result .= '<a href="'.DOL_URL_ROOT.'/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
4026 $result .= img_picto('', 'note');
4027 $result .= '</a>';
4028 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
4029 //$result.='</a>';
4030 $result .= '</span>';
4031 }
4032 }
4033
4034 global $action;
4035 $hookmanager->initHooks(array($this->element . 'dao'));
4036 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
4037 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
4038 if ($reshook > 0) {
4039 $result = $hookmanager->resPrint;
4040 } else {
4041 $result .= $hookmanager->resPrint;
4042 }
4043 return $result;
4044 }
4045
4046
4053 public function info($id)
4054 {
4055 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
4056 $sql .= ' date_valid as datev,';
4057 $sql .= ' date_cloture as datecloture,';
4058 $sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
4059 $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
4060 $sql .= ' WHERE c.rowid = '.((int) $id);
4061 $result = $this->db->query($sql);
4062 if ($result) {
4063 if ($this->db->num_rows($result)) {
4064 $obj = $this->db->fetch_object($result);
4065 $this->id = $obj->rowid;
4066 if ($obj->fk_user_author) {
4067 $this->user_creation_id = $obj->fk_user_author;
4068 }
4069 if ($obj->fk_user_valid) {
4070 $this->user_validation_id = $obj->fk_user_valid;
4071 }
4072 if ($obj->fk_user_cloture) {
4073 $this->user_closing_id = $obj->fk_user_cloture;
4074 }
4075
4076 $this->date_creation = $this->db->jdate($obj->datec);
4077 $this->date_modification = $this->db->jdate($obj->datem);
4078 $this->date_validation = $this->db->jdate($obj->datev);
4079 $this->date_cloture = $this->db->jdate($obj->datecloture);
4080 }
4081
4082 $this->db->free($result);
4083 } else {
4084 dol_print_error($this->db);
4085 }
4086 }
4087
4088
4097 public function initAsSpecimen($param = array())
4098 {
4099 global $conf, $langs;
4100
4101 dol_syslog(get_class($this)."::initAsSpecimen");
4102
4103 // Load array of products prodids
4104 $num_prods = 0;
4105 $prodids = array();
4106 $sql = "SELECT rowid";
4107 $sql .= " FROM ".MAIN_DB_PREFIX."product";
4108 $sql .= " WHERE entity IN (".getEntity('product').")";
4109 if (array_key_exists('tosell', $param)) {
4110 $sql .= " AND tosell = ".((int) $param['tosell']);
4111 }
4112 $sql .= $this->db->plimit(100);
4113
4114 $resql = $this->db->query($sql);
4115 if ($resql) {
4116 $num_prods = $this->db->num_rows($resql);
4117 $i = 0;
4118 while ($i < $num_prods) {
4119 $i++;
4120 $row = $this->db->fetch_row($resql);
4121 $prodids[$i] = $row[0];
4122 }
4123 }
4124
4125 // Initialise parameters
4126 $this->id = 0;
4127 $this->ref = 'SPECIMEN';
4128 $this->specimen = 1;
4129 $this->entity = $conf->entity;
4130 $this->socid = 1;
4131 $this->date = time();
4132 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
4133 $this->cond_reglement_code = 'RECEP';
4134 $this->mode_reglement_code = 'CHQ';
4135 $this->availability_code = 'DSP';
4136 $this->demand_reason_code = 'SRC_00';
4137
4138 $this->note_public = 'This is a comment (public)';
4139 $this->note_private = 'This is a comment (private)';
4140
4141 $this->multicurrency_tx = 1;
4142 $this->multicurrency_code = $conf->currency;
4143
4144 $this->status = $this::STATUS_DRAFT;
4145
4146 // Lines
4147 $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)
4148 $xnbp = 0;
4149
4150 while ($xnbp < $nbp) {
4151 $line = new OrderLine($this->db);
4152
4153 $line->desc = $langs->trans("Description")." ".$xnbp;
4154 $line->qty = 1;
4155 $line->subprice = 100;
4156 $line->price = 100;
4157 $line->tva_tx = 20;
4158 if ($xnbp == 2) {
4159 $line->total_ht = 50;
4160 $line->total_ttc = 60;
4161 $line->total_tva = 10;
4162 $line->remise_percent = 50;
4163 } else {
4164 $line->total_ht = 100;
4165 $line->total_ttc = 120;
4166 $line->total_tva = 20;
4167 $line->remise_percent = 0;
4168 }
4169 if ($num_prods > 0) {
4170 $prodid = mt_rand(1, $num_prods);
4171 $line->fk_product = $prodids[$prodid];
4172 $line->product_ref = 'SPECIMEN';
4173 }
4174
4175 $this->lines[$xnbp] = $line;
4176
4177 $this->total_ht += $line->total_ht;
4178 $this->total_tva += $line->total_tva;
4179 $this->total_ttc += $line->total_ttc;
4180
4181 $xnbp++;
4182 }
4183
4184 return 1;
4185 }
4186
4187
4193 public function loadStateBoard()
4194 {
4195 global $user, $hookmanager;
4196
4197 $this->nb = array();
4198 $clause = "WHERE";
4199
4200 $sql = "SELECT count(c.rowid) as nb";
4201 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
4202 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid";
4203 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
4204 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4205 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
4206 $clause = "AND";
4207 }
4208 $sql .= " ".$clause." c.entity IN (".getEntity('commande').")";
4209 // Add where from hooks
4210 $parameters = array();
4211 $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
4212 $sql .= $hookmanager->resPrint;
4213 $resql = $this->db->query($sql);
4214 if ($resql) {
4215 while ($obj = $this->db->fetch_object($resql)) {
4216 $this->nb["orders"] = $obj->nb;
4217 }
4218 $this->db->free($resql);
4219 return 1;
4220 } else {
4221 dol_print_error($this->db);
4222 $this->error = $this->db->error();
4223 return -1;
4224 }
4225 }
4226
4232 public function getLinesArray()
4233 {
4234 return $this->fetch_lines();
4235 }
4236
4248 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4249 {
4250 global $langs;
4251
4252 $langs->load("orders");
4253 $outputlangs->load("products");
4254
4255 if (!dol_strlen($modele)) {
4256 $modele = 'einstein';
4257
4258 if (!empty($this->model_pdf)) {
4259 $modele = $this->model_pdf;
4260 } elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
4261 $modele = getDolGlobalString('COMMANDE_ADDON_PDF');
4262 }
4263 }
4264
4265 $modelpath = "core/modules/commande/doc/";
4266
4267 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4268 }
4269
4270
4279 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
4280 {
4281 $tables = array(
4282 'commande'
4283 );
4284
4285 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
4286 }
4287
4296 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
4297 {
4298 $tables = array(
4299 'commandedet',
4300 );
4301
4302 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4303 }
4304
4310 public function hasDelay()
4311 {
4312 global $conf;
4313
4314 if (!($this->status > Commande::STATUS_DRAFT && $this->status < Commande::STATUS_CLOSED)) {
4315 return false; // Never late if not inside this status range
4316 }
4317
4318 $now = dol_now();
4319
4320 return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
4321 }
4322
4328 public function showDelay()
4329 {
4330 global $conf, $langs;
4331
4332 if (empty($this->delivery_date)) {
4333 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date, 'day');
4334 } else {
4335 $text = $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
4336 }
4337 $text .= ' '.($conf->commande->client->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
4338
4339 return $text;
4340 }
4341
4351 public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
4352 {
4353 return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
4354 }
4355
4366 public function getShippableInfos(array $options = array()): array
4367 {
4368 global $conf, $langs, $hookmanager;
4369
4370 $langs->loadLangs(array('orders', 'sendings', 'stocks', 'products'));
4371
4372 $result = array(
4373 'has_product' => false,
4374 'shippable' => false,
4375 'texticon' => '',
4376 'textinfo' => '',
4377 'warning' => false,
4378 );
4379
4380 if (is_object($hookmanager)) {
4381 $parameters = array('options' => $options);
4382 $reshook = $hookmanager->executeHooks('getShippableInfos', $parameters, $this);
4383 if ($reshook < 0) {
4384 dol_syslog(__METHOD__.' hook getShippableInfos failed: '.$hookmanager->error, LOG_ERR);
4385 }
4386 if (!empty($hookmanager->resArray['shippableinfos'])
4387 && is_array($hookmanager->resArray['shippableinfos'])) {
4388 return array_merge($result, $hookmanager->resArray['shippableinfos']);
4389 }
4390 }
4391
4392 // Requested naming for statuses
4393 if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_CLOSED) {
4394 return $result;
4395 }
4396
4397
4398 $genericCommande = $this;
4399 $genericProduct = new Product($this->db);
4400
4401
4402 $productstatcache = array();
4403 $productstatcachevirtual = array();
4404
4405
4406 $genericCommande->getLinesArray(); // Load array ->lines
4407 $genericCommande->loadExpeditions(); // Load array ->expeditions
4408
4409 $notshippable = 0;
4410 $has_reliquat = 0;
4411 $warning = 0;
4412 $textinfo = '';
4413 $textwarning = '';
4414 $nbprod = 0;
4415
4416 $genericProduct = new Product($this->db);
4417
4418 $numlines = count($genericCommande->lines);
4419 for ($lig = 0; $lig < $numlines; $lig++) {
4420 $orderLine = $genericCommande->lines[$lig]; // @phan-var-force OrderLine $orderLine
4421
4422 if (isset($genericCommande->expeditions[$orderLine->id])) {
4423 $reliquat = $orderLine->qty - $genericCommande->expeditions[$orderLine->id];
4424 } else {
4425 $reliquat = $orderLine->qty;
4426 }
4427
4428 if ($orderLine->product_type == 0 && $orderLine->fk_product > 0) { // product, not service
4429 $nbprod = 1;
4430
4431 if (empty($productstatcache[$orderLine->fk_product])) {
4432 $genericProduct->fetch($orderLine->fk_product);
4433 $genericProduct->load_stock('nobatch,warehouseopen'); // loadvirtualstock included
4434
4435 $productstatcache[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_reel;
4436 $productstatcachevirtual[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_theorique;
4437 }
4438
4439 $genericProduct->stock_reel = $productstatcache[$orderLine->fk_product]['stockreel'];
4440 $genericProduct->stock_theorique = $productstatcachevirtual[$orderLine->fk_product]['stockreel'];
4441
4442 if ($reliquat > 0) {
4443 $has_reliquat = 1;
4444 if (!getDolGlobalString('SHIPPABLE_ORDER_ICON_IN_LIST')) {
4445 $textinfo .= $reliquat . ' x ' . $orderLine->product_ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
4446 $textinfo .= ' - ' . $langs->trans("Stock") . ': <span class="' . ($genericProduct->stock_reel >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_reel . '</span>';
4447 $textinfo .= ' - ' . $langs->trans("VirtualStock") . ': <span class="' . ($genericProduct->stock_theorique >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_theorique . '</span>';
4448 if ($reliquat != $orderLine->qty) {
4449 $textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
4450 }
4451 $textinfo .= '<br>';
4452 } else {
4453 // BUGGED CODE (kept for backward compatibility and hidden conf)
4454 $stockorder = 0;
4455 $stockordersupplier = 0;
4456
4457 if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
4458 if (isModEnabled('order')) {
4459 if (empty($productstatcache[$orderLine->fk_product]['statsordercustomer'])) {
4460 $genericProduct->fetch($orderLine->fk_product);
4461 $genericProduct->load_stats_commande(0, '1,2');
4462 $productstatcache[$orderLine->fk_product]['statsordercustomer'] = $genericProduct->stats_commande['qty'];
4463 }
4464 $genericProduct->stats_commande['qty'] = $productstatcache[$orderLine->fk_product]['statsordercustomer'];
4465 $stockorder = $genericProduct->stats_commande['qty'];
4466
4467 if (isModEnabled('supplier_order')) {
4468 if (empty($productstatcache[$orderLine->fk_product]['statsordersupplier'])) {
4469 $genericProduct->load_stats_commande_fournisseur(0, '3');
4470 $productstatcache[$orderLine->fk_product]['statsordersupplier'] = $genericProduct->stats_commande_fournisseur['qty'];
4471 }
4472 $genericProduct->stats_commande_fournisseur['qty'] = $productstatcache[$orderLine->fk_product]['statsordersupplier'];
4473 $stockordersupplier = $genericProduct->stats_commande_fournisseur['qty'];
4474 }
4475 }
4476 }
4477
4478 $textinfo .= $reliquat . ' x ' . $orderLine->ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
4479 $textinfo .= ' ' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder;
4480
4481 if ($stockorder && $genericProduct->stock_reel < ($genericProduct->stock_reel - $stockorder + $reliquat)) {
4482 $warning++;
4483 $textwarning .= '<span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder . '</span>';
4484 } else {
4485 if ($reliquat > $genericProduct->stock_reel) {
4486 $textinfo .= ' <span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
4487 } else {
4488 $textinfo .= ' <span class="ok">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
4489 }
4490 }
4491
4492 if (isModEnabled('supplier_order')) {
4493 $textinfo .= '&nbsp;' . $langs->trans("SupplierOrder") . '&nbsp;&nbsp;' . $stockordersupplier;
4494 }
4495 if ($reliquat != $orderLine->qty) {
4496 $textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
4497 }
4498 $textinfo .= '<br>';
4499 }
4500
4501 if ($reliquat > $genericProduct->stock_reel) {
4502 $notshippable++;
4503 }
4504 }
4505 }
4506 }
4507
4508 if ($nbprod) {
4509 if (!$has_reliquat) {
4510 $texticon = img_picto('', 'statut5', '', 0, 0, 0, '', 'paddingleft');
4511 $textinfo = $texticon . ' ' . $langs->trans("Shipped");
4512 $result['shippable'] = true;
4513 } elseif ($notshippable) {
4514 $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'error paddingleft');
4515 $textinfo = $texticon . ' ' . $langs->trans("NonShippable") . '<br>' . $textinfo;
4516 $result['shippable'] = false;
4517 } else {
4518 $texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'green paddingleft');
4519 $textinfo = $texticon . ' ' . $langs->trans("Shippable") . '<br>' . $textinfo;
4520 $result['shippable'] = true;
4521 }
4522 $result['has_product'] = true;
4523 $result['texticon'] = $texticon;
4524 $result['textinfo'] = $textinfo;
4525 $result['warning'] = !empty($warning);
4526 }
4527
4528 return $result;
4529 }
4530}
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.
cloture($user, $notrigger=0)
Close order.
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.
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.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetchProject()
Load the project with id $this->fk_project into this->project.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
Definition html.lib.php:172
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
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:487