dolibarr 23.0.4
fournisseur.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-2017 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
6 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
7 * Copyright (C) 2010-2018 Philippe Grand <philippe.grand@atoo-net.com>
8 * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
9 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
10 * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
11 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
12 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
13 * Copyright (C) 2018-2022 Ferran Marcet <fmarcet@2byte.es>
14 * Copyright (C) 2021 Josep Lluís Amador <joseplluis@lliuretic.cat>
15 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
16 * Copyright (C) 2024 Solution Libre SAS <contact@solution-libre.fr>
17 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
18 * Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
19 * Copyright (C) 2025 Noé Cendrier <noe.cendrier@altairis.fr>
20 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
21 *
22 * This program is free software; you can redistribute it and/or modify
23 * it under the terms of the GNU General Public License as published by
24 * the Free Software Foundation; either version 3 of the License, or
25 * (at your option) any later version.
26 *
27 * This program is distributed in the hope that it will be useful,
28 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 * GNU General Public License for more details.
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program. If not, see <https://www.gnu.org/licenses/>.
34 */
35
42require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
43require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
44require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.orderline.class.php';
45require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
46if (isModEnabled('productbatch')) {
47 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
48}
49
50
55{
59 public $element = 'order_supplier';
60
64 public $table_element = 'commande_fournisseur';
65
69 public $table_element_line = 'commande_fournisseurdet';
70
74 public $class_element_line = 'CommandeFournisseurLigne';
75
79 public $fk_element = 'fk_commande';
80
84 public $picto = 'supplier_order';
85
90 public $restrictiononfksoc = 1;
91
95 protected $table_ref_field = 'ref';
96
100 public $id;
101
105 public $ref;
106
110 public $ref_supplier;
111
117 public $ref_fourn;
118
124 public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
125 // -> 7=Canceled/Never received -> (reopen) 3=Process running
126 // -> 6=Canceled -> (reopen) 2=Approved
127 // -> 9=Refused -> (reopen) 1=Validated
128 // Note: billed or not is on another field "billed"
129
133 public $billed;
134
138 public $socid;
139
143 public $fourn_id;
144
148 public $date;
149
153 public $date_valid;
154
158 public $date_approve;
159
164 public $date_approve2;
165
169 public $date_commande;
170
175 public $remise_percent;
179 public $methode_commande_id;
183 public $methode_commande;
184
188 public $delivery_date;
189
193 public $total_ht;
194
198 public $total_tva;
199
203 public $total_localtax1;
204
208 public $total_localtax2;
209
213 public $total_ttc;
214
218 public $source;
219
223 public $cond_reglement_id;
224
228 public $cond_reglement_code;
229
233 public $cond_reglement_label;
234
238 public $cond_reglement_doc;
239
245 public $deposit_percent;
246
250 public $fk_account;
251
255 public $mode_reglement_id;
256
260 public $mode_reglement_code;
261
265 public $mode_reglement;
266
270 public $user_author_id;
271
275 public $user_approve_id;
276
281 public $user_approve_id2;
282
286 public $refuse_note;
287
291 public $extraparams = array();
292
296 public $lines = array();
297
301 public $line;
302
306 public $origin;
307
311 public $origin_id;
312
316 public $date_lim_reglement;
317
321 public $receptions = array();
322
323 // Multicurrency
327 public $fk_multicurrency;
328
332 public $multicurrency_code;
333
337 public $multicurrency_tx;
338
342 public $multicurrency_total_ht;
343
347 public $multicurrency_total_tva;
348
352 public $multicurrency_total_ttc;
353
381 public $fields = array(
382 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
383 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
384 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 35),
385 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
386 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
387 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
388 'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
389 'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
390 'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
391 'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'getDolGlobalInt("ORDER_DISABLE_DELIVERY_DATE") ? 0 : 1', 'visible' => 1, 'position' => 74),
392 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
393 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
394 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
395 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
396 'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
397 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
398 'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
399 'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
400 'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
401 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
402 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
403 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
404 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
405 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
406 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
407 'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
408 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
409 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 176),
410 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
411 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
412 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
413 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
414 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
415 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
416 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
417 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
418 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
419 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
420 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
421 'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
422 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
423 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
424 'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
425 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
426 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
427 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
428 );
429
430
434 const STATUS_DRAFT = 0;
435
440
445
450
455
460
465
470
474 const STATUS_REFUSED = 9;
475
476
481
487 public function __construct($db)
488 {
489 $this->db = $db;
490
491 $this->ismultientitymanaged = 1;
492 }
493
494
502 public function fetch($id, $ref = '')
503 {
504 // Check parameters
505 if (empty($id) && empty($ref)) {
506 return -1;
507 }
508
509 $sql = "SELECT c.rowid, c.entity, c.ref, ref_supplier, c.fk_soc, c.fk_statut as status, c.amount_ht, c.total_ht, c.total_ttc, c.total_tva,";
510 $sql .= " c.localtax1, c.localtax2, ";
511 $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
512 $sql .= " c.fk_user_author as user_author_id, c.fk_user_valid as user_validation_id, c.fk_user_approve as user_approve_id, c.fk_user_approve2 as user_approve_id2,";
513 $sql .= " c.date_commande as date_commande, c.date_livraison as delivery_date, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
514 $sql .= " c.fk_account,";
515 $sql .= " c.note_private, c.note_public, c.model_pdf, c.last_main_doc, c.extraparams, c.billed,";
516 $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
517 $sql .= " cm.libelle as methode_commande,";
518 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc, c.deposit_percent,";
519 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
520 $sql .= ', c.fk_incoterms, c.location_incoterms';
521 $sql .= ', c.last_main_doc';
522 $sql .= ', i.libelle as label_incoterms';
523 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
524 $sql .= " LEFT JOIN ".$this->db->prefix()."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
525 $sql .= " LEFT JOIN ".$this->db->prefix()."c_paiement as p ON c.fk_mode_reglement = p.id";
526 $sql .= " LEFT JOIN ".$this->db->prefix()."c_input_method as cm ON cm.rowid = c.fk_input_method";
527 $sql .= ' LEFT JOIN '.$this->db->prefix().'c_incoterms as i ON c.fk_incoterms = i.rowid';
528
529 if (empty($id)) {
530 $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
531 } else {
532 $sql .= " WHERE c.rowid=".((int) $id);
533 }
534
535 if ($ref) {
536 $sql .= " AND c.ref='".$this->db->escape($ref)."'";
537 }
538
539 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
540 $resql = $this->db->query($sql);
541 if ($resql) {
542 $obj = $this->db->fetch_object($resql);
543 if (!$obj) {
544 $this->error = 'Bill with id '.$id.' not found';
545 dol_syslog(get_class($this).'::fetch '.$this->error);
546 return 0;
547 }
548
549 $this->id = $obj->rowid;
550 $this->entity = $obj->entity;
551
552 $this->ref = $obj->ref;
553 $this->ref_supplier = $obj->ref_supplier;
554
555 $this->socid = $obj->fk_soc;
556 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
557
558 $this->fourn_id = $obj->fk_soc;
559 $this->statut = $obj->status; // deprecated
560 $this->status = $obj->status;
561 $this->billed = $obj->billed;
562 $this->last_main_doc = $obj->last_main_doc;
563 $this->user_author_id = $obj->user_author_id;
564 $this->user_validation_id = $obj->user_validation_id;
565 $this->user_approve_id = $obj->user_approve_id;
566 $this->user_approve_id2 = $obj->user_approve_id2;
567 $this->total_ht = $obj->total_ht;
568 $this->total_tva = $obj->total_tva;
569 $this->total_localtax1 = $obj->localtax1;
570 $this->total_localtax2 = $obj->localtax2;
571 $this->total_ttc = $obj->total_ttc;
572 $this->date_creation = $this->db->jdate($obj->date_creation);
573 $this->date_valid = $this->db->jdate($obj->date_valid);
574 $this->date_approve = $this->db->jdate($obj->date_approve);
575 $this->date_approve2 = $this->db->jdate($obj->date_approve2);
576 $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
577 if (isset($obj->date_commande)) {
578 $this->date = $this->date_commande;
579 } else {
580 $this->date = $this->date_creation;
581 }
582 $this->delivery_date = $this->db->jdate($obj->delivery_date);
583 $this->remise_percent = $obj->remise_percent;
584 $this->methode_commande_id = $obj->fk_input_method;
585 $this->methode_commande = $obj->methode_commande;
586
587 $this->source = $obj->source;
588 $this->fk_project = $obj->fk_project;
589 $this->cond_reglement_id = $obj->fk_cond_reglement;
590 $this->cond_reglement_code = $obj->cond_reglement_code;
591 $this->cond_reglement_label = $obj->cond_reglement_label;
592 $this->cond_reglement_doc = $obj->cond_reglement_doc;
593 $this->deposit_percent = $obj->deposit_percent;
594 $this->fk_account = $obj->fk_account;
595 $this->mode_reglement_id = $obj->fk_mode_reglement;
596 $this->mode_reglement_code = $obj->mode_reglement_code;
597 $this->mode_reglement = $obj->mode_reglement_libelle;
598 $this->note = $obj->note_private; // deprecated
599 $this->note_private = $obj->note_private;
600 $this->note_public = $obj->note_public;
601 $this->model_pdf = $obj->model_pdf;
602 $this->last_main_doc = $obj->last_main_doc;
603
604 //Incoterms
605 $this->fk_incoterms = $obj->fk_incoterms;
606 $this->location_incoterms = $obj->location_incoterms;
607 $this->label_incoterms = $obj->label_incoterms;
608
609 // Multicurrency
610 $this->fk_multicurrency = $obj->fk_multicurrency;
611 $this->multicurrency_code = $obj->multicurrency_code;
612 $this->multicurrency_tx = $obj->multicurrency_tx;
613 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
614 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
615 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
616
617 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
618
619 $this->db->free($resql);
620
621 // Retrieve all extrafield
622 // fetch optionals attributes and labels
623 $this->fetch_optionals();
624
625 // Lines
626 $result = $this->fetch_lines();
627
628 if ($result < 0) {
629 return -1;
630 } else {
631 return 1;
632 }
633 } else {
634 $this->error = $this->db->error()." sql=".$sql;
635 return -1;
636 }
637 }
638
639 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
646 public function fetch_lines($only_product = 0)
647 {
648 // phpcs:enable
649
650 $this->lines = array();
651
652 $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
653 $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice, l.subprice_ttc,";
654 $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
655 $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.info_bits, l.special_code, l.fk_parent_line, l.rang,";
656 $sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.description as product_desc, p.tobatch as product_tobatch, p.barcode as product_barcode,";
657 $sql .= " l.fk_unit, l.extraparams,";
658 $sql .= " l.date_start, l.date_end,";
659 $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';
660 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as l";
661 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON l.fk_product = p.rowid';
662 $sql .= " WHERE l.fk_commande = ".((int) $this->id);
663 if ($only_product) {
664 $sql .= ' AND p.fk_product_type = 0';
665 }
666 $sql .= " ORDER BY l.rang, l.rowid";
667 //print $sql;
668
669 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
670
671 $result = $this->db->query($sql);
672 if ($result) {
673 $num = $this->db->num_rows($result);
674 $i = 0;
675
676 while ($i < $num) {
677 $objp = $this->db->fetch_object($result);
678
679 $line = new CommandeFournisseurLigne($this->db);
680
681 $line->id = $objp->rowid;
682 $line->fk_commande = $objp->fk_commande;
683 $line->desc = $objp->description;
684 $line->description = $objp->description;
685 $line->qty = $objp->qty;
686 $line->tva_tx = $objp->tva_tx;
687 $line->localtax1_tx = $objp->localtax1_tx;
688 $line->localtax2_tx = $objp->localtax2_tx;
689 $line->localtax1_type = $objp->localtax1_type;
690 $line->localtax2_type = $objp->localtax2_type;
691 $line->subprice = $objp->subprice;
692 $line->pu_ht = $objp->subprice; // deprecated
693 $line->subprice_ttc = $objp->subprice_ttc;
694 $line->pu_ttc = $objp->subprice_ttc; // deprecated
695 $line->remise_percent = $objp->remise_percent;
696
697 $line->vat_src_code = $objp->vat_src_code;
698 $line->total_ht = $objp->total_ht;
699 $line->total_tva = $objp->total_tva;
700 $line->total_localtax1 = $objp->total_localtax1;
701 $line->total_localtax2 = $objp->total_localtax2;
702 $line->total_ttc = $objp->total_ttc;
703 $line->product_type = $objp->product_type;
704
705 $line->fk_product = $objp->fk_product;
706
707 $line->libelle = $objp->product_label; // deprecated
708 $line->product_label = $objp->product_label;
709 $line->product_desc = $objp->product_desc;
710 $line->product_tobatch = $objp->product_tobatch;
711 $line->product_barcode = $objp->product_barcode;
712
713 $line->ref = $objp->product_ref; // Ref of product
714 $line->product_ref = $objp->product_ref; // Ref of product
715 $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
716 $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
717
718 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
719 // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
720 // Move this into another method and call it when required.
721
722 // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
723 $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
724 $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('productsupplierprice').")";
725 $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
726 $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
727 $sqlsearchpackage .= " AND quantity <= ".abs((float) $objp->qty); // required to be qualified
728 $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".abs((float) $objp->qty).")"; // required to be qualified
729 $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
730 $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
731 $sqlsearchpackage .= " LIMIT 1";
732
733 $resqlsearchpackage = $this->db->query($sqlsearchpackage);
734 if ($resqlsearchpackage) {
735 $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
736 if ($objsearchpackage) {
737 $line->fk_fournprice = $objsearchpackage->rowid;
738 $line->packaging = (float) $objsearchpackage->packaging;
739 }
740 } else {
741 $this->error = $this->db->lasterror();
742 return -1;
743 }
744 }
745
746 $line->date_start = $this->db->jdate($objp->date_start);
747 $line->date_end = $this->db->jdate($objp->date_end);
748 $line->fk_unit = $objp->fk_unit;
749
750 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
751
752 // Multicurrency
753 $line->fk_multicurrency = $objp->fk_multicurrency;
754 $line->multicurrency_code = $objp->multicurrency_code;
755 $line->multicurrency_subprice = $objp->multicurrency_subprice;
756 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
757 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
758 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
759 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
760
761 $line->info_bits = $objp->info_bits;
762 $line->special_code = $objp->special_code;
763 $line->fk_parent_line = $objp->fk_parent_line;
764
765 $line->rang = $objp->rang;
766
767 // Retrieve all extrafield
768 // fetch optionals attributes and labels
769 $line->fetch_optionals();
770
771 $this->lines[$i] = $line;
772
773 $i++;
774 }
775 $this->db->free($result);
776
777 return $num;
778 } else {
779 $this->error = $this->db->error()." sql=".$sql;
780 return -1;
781 }
782 }
783
792 public function valid($user, $idwarehouse = 0, $notrigger = 0)
793 {
794 global $conf;
795 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
796
797 $error = 0;
798
799 dol_syslog(get_class($this)."::valid");
800 $result = 0;
801 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
802 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
803 $this->db->begin();
804
805 if (!getDolGlobalBool('SUPPLIER_ORDER_NOCHECK_ONBUY_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines('onbuy')) {
806 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
807 return -1;
808 }
809 // Definition of supplier order numbering model name
810 $soc = new Societe($this->db);
811 $soc->fetch((int) $this->fourn_id);
812
813 // Check if object has a temporary ref
814 if (preg_match('/^[\‍(]?PROV/i', (string) $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
815 $num = $this->getNextNumRef($soc);
816 } else {
817 $num = (string) $this->ref;
818 }
819 $this->newref = dol_sanitizeFileName($num);
820
821 $sql = 'UPDATE '.$this->db->prefix()."commande_fournisseur";
822 $sql .= " SET ref='".$this->db->escape($num)."',";
823 $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
824 $sql .= " date_valid='".$this->db->idate(dol_now())."',";
825 $sql .= " fk_user_valid = ".((int) $user->id);
826 $sql .= " WHERE rowid = ".((int) $this->id);
827 $sql .= " AND fk_statut = ".((int) self::STATUS_DRAFT);
828
829 $resql = $this->db->query($sql);
830 if (!$resql) {
831 dol_print_error($this->db);
832 $error++;
833 }
834
835 if (!$error && !$notrigger) {
836 // Call trigger
837 $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
838 if ($result < 0) {
839 $error++;
840 }
841 // End call triggers
842 }
843
844 if (!$error) {
845 $this->oldref = $this->ref;
846
847 // Rename directory if dir was a temporary ref
848 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
849 // Now we rename also files into index
850 $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
851 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
852 $resql = $this->db->query($sql);
853 if (!$resql) {
854 $error++;
855 $this->error = $this->db->lasterror();
856 }
857 $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
858 $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
859 $resql = $this->db->query($sql);
860 if (!$resql) {
861 $error++;
862 $this->error = $this->db->lasterror();
863 }
864
865 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
866 $oldref = dol_sanitizeFileName($this->ref);
867 $newref = dol_sanitizeFileName($num);
868 $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
869 $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
870 if (!$error && file_exists($dirsource)) {
871 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
872
873 if (@rename($dirsource, $dirdest)) {
874 dol_syslog("Rename ok");
875 // Rename docs starting with $oldref with $newref
876 $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
877 foreach ($listoffiles as $fileentry) {
878 $dirsource = $fileentry['name'];
879 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
880 $dirsource = $fileentry['path'].'/'.$dirsource;
881 $dirdest = $fileentry['path'].'/'.$dirdest;
882 @rename($dirsource, $dirdest);
883 }
884 }
885 }
886 }
887 }
888
889 if (!$error) {
890 $result = 1;
892 $this->statut = self::STATUS_VALIDATED; // deprecated
893 $this->ref = $num;
894 }
895
896 if (!$error) {
897 $this->db->commit();
898 return 1;
899 } else {
900 $this->db->rollback();
901 return -1;
902 }
903 } else {
904 $this->error = 'NotAuthorized';
905 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
906 return -1;
907 }
908 }
909
916 public function getLibStatut($mode = 0)
917 {
918 return $this->LibStatut(isset($this->status) ? $this->status : $this->statut, $mode, $this->billed);
919 }
920
921 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
930 public function LibStatut($status, $mode = 0, $billed = 0)
931 {
932 // phpcs:enable
933 global $langs, $hookmanager;
934
935 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
936 $langs->load('orders');
937
938 $this->labelStatus[0] = 'StatusSupplierOrderDraft';
939 $this->labelStatus[1] = 'StatusSupplierOrderValidated';
940 $this->labelStatus[2] = 'StatusSupplierOrderApproved';
941 if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
942 $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
943 } else {
944 $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
945 }
946 $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
947 $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
948 $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
949 $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
950 $this->labelStatus[9] = 'StatusSupplierOrderRefused';
951
952 // List of language codes for status
953 $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
954 $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
955 $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
956 $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
957 $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
958 $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
959 $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
960 $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
961 $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
962 }
963
964 $statustrans = array(
965 0 => 'status0',
966 1 => 'status1b',
967 2 => 'status1',
968 3 => 'status4',
969 4 => 'status4b',
970 5 => 'status6',
971 6 => 'status9',
972 7 => 'status9',
973 9 => 'status9',
974 );
975
976 $statusClass = 'status0';
977 if (!empty($statustrans[$status])) {
978 $statusClass = $statustrans[$status];
979 }
980
981 $billedtext = '';
982 if ($billed) {
983 $billedtext = ' - '.$langs->trans("Billed");
984 }
985 if ($status == 5 && $billed) {
986 $statusClass = 'status6';
987 }
988
989 $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]).$billedtext;
990 $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
991
992 $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
993 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
994 if ($reshook > 0) {
995 return $hookmanager->resPrint;
996 }
997
998 return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
999 }
1000
1007 public function getTooltipContentArray($params)
1008 {
1009 global $conf, $langs, $user;
1010
1011 $langs->loadLangs(['bills', 'orders']);
1012
1013 $datas = [];
1014 $nofetch = !empty($params['nofetch']);
1015
1016 if ($user->hasRight("fournisseur", "commande", "read")) {
1017 $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
1018 if ($this->status) {
1019 $datas['picto'] .= ' '.$this->getLibStatut(5);
1020 }
1021 if (!empty($this->ref)) {
1022 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1023 }
1024 if (!empty($this->ref_supplier)) {
1025 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
1026 }
1027 if (!$nofetch) {
1028 $langs->load('companies');
1029 if (empty($this->thirdparty)) {
1030 $this->fetch_thirdparty();
1031 }
1032 $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1033 }
1034 if (!empty($this->total_ht)) {
1035 $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1036 }
1037 if (!empty($this->total_tva)) {
1038 $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1039 }
1040 if (!empty($this->total_ttc)) {
1041 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1042 }
1043 if (!empty($this->date)) {
1044 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1045 }
1046 if (!empty($this->delivery_date)) {
1047 $langs->load("sendings");
1048 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
1049 }
1050 }
1051 return $datas;
1052 }
1053
1064 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1065 {
1066 global $langs, $user, $hookmanager;
1067
1068 $result = '';
1069 $params = [
1070 'id' => $this->id,
1071 'objecttype' => $this->element,
1072 'option' => $option,
1073 'nofetch' => 1
1074 ];
1075 $classfortooltip = 'classfortooltip';
1076 $dataparams = '';
1077 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1078 $classfortooltip = 'classforajaxtooltip';
1079 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1080 $label = '';
1081 } else {
1082 $label = implode($this->getTooltipContentArray($params));
1083 }
1084
1085 $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
1086
1087 if ($option !== 'nolink') {
1088 // Add param to save lastsearch_values or not
1089 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1090 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1091 $add_save_lastsearch_values = 1;
1092 }
1093 if ($add_save_lastsearch_values) {
1094 $url .= '&save_lastsearch_values=1';
1095 }
1096 }
1097
1098 $linkclose = '';
1099 if (empty($notooltip)) {
1100 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1101 $label = $langs->trans("ShowOrder");
1102 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1103 }
1104 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1105 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1106 }
1107
1108 $linkstart = '<a href="'.$url.'"';
1109 $linkstart .= $linkclose.'>';
1110 $linkend = '</a>';
1111
1112 $result .= $linkstart;
1113 if ($withpicto) {
1114 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1115 }
1116 if ($withpicto != 2) {
1117 $result .= $this->ref;
1118 }
1119 $result .= $linkend;
1120
1121 if ($addlinktonotes) {
1122 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1123 if ($txttoshow) {
1124 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1125 $result .= ' <span class="note inline-block">';
1126 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1127 $result .= img_picto('', 'note');
1128 $result .= '</a>';
1129 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1130 //$result.='</a>';
1131 $result .= '</span>';
1132 }
1133 }
1134
1135 global $action;
1136 $hookmanager->initHooks(array($this->element . 'dao'));
1137 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1138 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1139 if ($reshook > 0) {
1140 $result = $hookmanager->resPrint;
1141 } else {
1142 $result .= $hookmanager->resPrint;
1143 }
1144 return $result;
1145 }
1146
1147
1155 public function getNextNumRef($soc)
1156 {
1157 global $langs, $conf;
1158 $langs->load("orders");
1159
1160 if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1161 $mybool = false;
1162
1163 $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
1164 $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1165
1166 // Include file with class
1167 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1168
1169 foreach ($dirmodels as $reldir) {
1170 $dir = dol_buildpath($reldir."core/modules/supplier_order/");
1171
1172 // Load file with numbering class (if found)
1173 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1174 }
1175
1176 if (!$mybool) {
1177 dol_print_error(null, "Failed to include file ".$file);
1178 return '';
1179 }
1180
1181 $obj = new $classname();
1182 '@phan-var-force ModeleNumRefSuppliersOrders $obj';
1184 $numref = $obj->getNextValue($soc, $this);
1185
1186 if ($numref != "") {
1187 return $numref;
1188 } else {
1189 $this->error = $obj->error;
1190 return -1;
1191 }
1192 } else {
1193 $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1194 return -2;
1195 }
1196 }
1197
1204 public function classifyBilled(User $user)
1205 {
1206 $error = 0;
1207
1208 if ($this->billed) {
1209 return 0;
1210 }
1211
1212 $this->db->begin();
1213
1214 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 1';
1215 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1216
1217 if ($this->db->query($sql)) {
1218 // Call trigger
1219 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1220 if ($result < 0) {
1221 $error++;
1222 }
1223 // End call triggers
1224
1225 if (!$error) {
1226 $this->billed = 1;
1227
1228 $this->db->commit();
1229 return 1;
1230 } else {
1231 $this->db->rollback();
1232 return -1;
1233 }
1234 } else {
1235 dol_print_error($this->db);
1236
1237 $this->db->rollback();
1238 return -1;
1239 }
1240 }
1241
1242
1249 public function classifyUnBilled(User $user)
1250 {
1251 if (empty($this->billed)) {
1252 return 0;
1253 }
1254
1255 $this->db->begin();
1256
1257 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 0';
1258 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1259 ;
1260
1261 if (!$this->db->query($sql)) {
1262 dol_print_error($this->db);
1263 $this->db->rollback();
1264 return -1;
1265 }
1266
1267 // Call trigger
1268 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_UNBILLED', $user);
1269 if ($result < 0) {
1270 $this->db->rollback();
1271 return -1;
1272 }
1273 // End call triggers
1274
1275 $this->billed = 1;
1276 $this->db->commit();
1277 return 1;
1278 }
1279
1280
1289 public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1290 {
1291 global $langs;
1292
1293 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1294
1295 $error = 0;
1296
1297 dol_syslog(get_class($this)."::approve");
1298
1299 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1300 $now = dol_now();
1301
1302 $this->db->begin();
1303
1304 // Definition of order numbering model name
1305 $soc = new Societe($this->db);
1306 $soc->fetch((int) $this->fourn_id);
1307
1308 // Check if object has a temporary ref
1309 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1310 $num = $this->getNextNumRef($soc);
1311 } else {
1312 $num = (string) $this->ref;
1313 }
1314 $this->newref = dol_sanitizeFileName($num);
1315
1316 // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1317 $movetoapprovestatus = true;
1318 $comment = '';
1319
1320 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1321 $sql .= " SET ref='".$this->db->escape($num)."',";
1322 if (empty($secondlevel)) { // standard or first level approval
1323 $sql .= " date_approve='".$this->db->idate($now)."',";
1324 $sql .= " fk_user_approve = ".((int) $user->id);
1325 if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= getDolGlobalFloat('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED')) {
1326 if (empty($this->user_approve_id2)) {
1327 $movetoapprovestatus = false; // second level approval not done
1328 $comment = ' (first level)';
1329 }
1330 }
1331 } else { // request a second level approval
1332 $sql .= " date_approve2='".$this->db->idate($now)."',";
1333 $sql .= " fk_user_approve2 = ".((int) $user->id);
1334 if (empty($this->user_approve_id)) {
1335 $movetoapprovestatus = false; // first level approval not done
1336 }
1337 $comment = ' (second level)';
1338 }
1339 // If double approval is required and first approval, we keep status to 1 = validated
1340 if ($movetoapprovestatus) {
1341 $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1342 } else {
1343 $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1344 }
1345 $sql .= " WHERE rowid = ".((int) $this->id);
1346 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1347
1348 if ($this->db->query($sql)) {
1349 if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1350 $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1351 if ($result < 0 && $result != -2) { // -2 means already exists
1352 $error++;
1353 }
1354 }
1355
1356 // If stock is incremented on validate order, we must increment it
1357 if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1358 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1359 $langs->load("agenda");
1360
1361 $cpt = count($this->lines);
1362 for ($i = 0; $i < $cpt; $i++) {
1363 // Product with reference
1364 if ($this->lines[$i]->fk_product > 0) {
1365 $this->line = $this->lines[$i];
1366 $mouvP = new MouvementStock($this->db);
1367 $mouvP->origin = &$this;
1368 $mouvP->setOrigin($this->element, $this->id);
1369 // We decrement stock of product (and sub-products)
1370 $up_ht_disc = $this->lines[$i]->subprice;
1371 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1372 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1373 }
1374 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1375 if ($result < 0) {
1376 $error++;
1377 }
1378 unset($this->line);
1379 }
1380 }
1381 }
1382
1383 if (!$error) {
1384 // Call trigger
1385 $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1386 if ($result < 0) {
1387 $error++;
1388 }
1389 // End call triggers
1390 }
1391
1392 if (!$error) {
1393 $this->ref = $this->newref;
1394
1395 if ($movetoapprovestatus) {
1396 $this->statut = self::STATUS_ACCEPTED; // deprecated
1398 } else {
1399 $this->statut = self::STATUS_VALIDATED; // deprecated
1401 }
1402 if (empty($secondlevel)) { // standard or first level approval
1403 $this->date_approve = $now;
1404 $this->user_approve_id = $user->id;
1405 } else { // request a second level approval
1406 $this->date_approve2 = $now;
1407 $this->user_approve_id2 = $user->id;
1408 }
1409
1410 $this->db->commit();
1411 return 1;
1412 } else {
1413 $this->db->rollback();
1414 return -1;
1415 }
1416 } else {
1417 $this->db->rollback();
1418 $this->error = $this->db->lasterror();
1419 return -1;
1420 }
1421 } else {
1422 dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1423 }
1424 return -1;
1425 }
1426
1433 public function refuse($user)
1434 {
1435 $error = 0;
1436
1437 dol_syslog(get_class($this)."::refuse");
1438 $result = 0;
1439 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1440 $this->db->begin();
1441
1442 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1443 $sql .= " WHERE rowid = ".((int) $this->id);
1444
1445 if ($this->db->query($sql)) {
1446 $result = 0;
1447
1448 if ($error == 0) {
1449 // Call trigger
1450 $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1451 if ($result < 0) {
1452 $error++;
1453 $this->db->rollback();
1454 } else {
1455 $this->db->commit();
1456 }
1457 // End call triggers
1458 }
1459 } else {
1460 $this->db->rollback();
1461 $this->error = $this->db->lasterror();
1462 dol_syslog(get_class($this)."::refuse Error -1");
1463 $result = -1;
1464 }
1465 } else {
1466 dol_syslog(get_class($this)."::refuse Not Authorized");
1467 }
1468 return $result;
1469 }
1470
1471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1480 public function cancel($user, $idwarehouse = -1)
1481 {
1482 // phpcs:enable
1483 $error = 0;
1484
1485 //dol_syslog("CommandeFournisseur::Cancel");
1486 $result = 0;
1487 if ($user->hasRight("fournisseur", "commande", "commander")) {
1488 $statut = self::STATUS_CANCELED;
1489
1490 $this->db->begin();
1491
1492 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".((int) $statut);
1493 $sql .= " WHERE rowid = ".((int) $this->id);
1494 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1495 if ($this->db->query($sql)) {
1496 $result = 0;
1497
1498 // Call trigger
1499 $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1500 if ($result < 0) {
1501 $error++;
1502 }
1503 // End call triggers
1504
1505 if ($error == 0) {
1506 $this->db->commit();
1507 return 1;
1508 } else {
1509 $this->db->rollback();
1510 return -1;
1511 }
1512 } else {
1513 $this->db->rollback();
1514 $this->error = $this->db->lasterror();
1515 dol_syslog(get_class($this)."::cancel ".$this->error);
1516 return -1;
1517 }
1518 } else {
1519 dol_syslog(get_class($this)."::cancel Not Authorized");
1520 return -1;
1521 }
1522 }
1523
1533 public function commande($user, $date, $methode, $comment = '')
1534 {
1535 global $langs;
1536 dol_syslog(get_class($this)."::commande");
1537 $error = 0;
1538 if ($user->hasRight("fournisseur", "commande", "commander")) {
1539 $this->db->begin();
1540
1541 $newnoteprivate = $this->note_private;
1542 if ($comment) {
1543 $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1544 }
1545
1546 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1547 $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', ";
1548 $sql .= " note_private='".$this->db->escape((string) $newnoteprivate)."'";
1549 $sql .= " WHERE rowid=".((int) $this->id);
1550
1551 dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1552 if ($this->db->query($sql)) {
1553 $this->statut = self::STATUS_ORDERSENT; // deprecated
1555 $this->methode_commande_id = $methode;
1556 $this->date_commande = $date;
1557 $this->context['comments'] = $comment;
1558
1559 // Call trigger
1560 $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1561 if ($result < 0) {
1562 $error++;
1563 }
1564 // End call triggers
1565 } else {
1566 $error++;
1567 $this->error = $this->db->lasterror();
1568 $this->errors[] = $this->db->lasterror();
1569 }
1570
1571 if (!$error) {
1572 $this->db->commit();
1573 } else {
1574 $this->db->rollback();
1575 }
1576 } else {
1577 $error++;
1578 $this->error = $langs->trans('NotAuthorized');
1579 $this->errors[] = $langs->trans('NotAuthorized');
1580 dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1581 }
1582
1583 return ($error ? -1 : 1);
1584 }
1585
1593 public function create($user, $notrigger = 0)
1594 {
1595 global $conf;
1596
1597 $this->db->begin();
1598
1599 $error = 0;
1600 $now = dol_now();
1601
1602 // set tmp vars
1603 $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1604 if (empty($date)) {
1605 $date = $now;
1606 }
1607 $delivery_date = $this->delivery_date;
1608
1609 // Clean parameters
1610 if (empty($this->source)) {
1611 $this->source = 0;
1612 }
1613
1614 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1615 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1616 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1617 } else {
1618 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1619 }
1620 if (empty($this->fk_multicurrency)) {
1621 $this->multicurrency_code = $conf->currency;
1622 $this->fk_multicurrency = 0;
1623 $this->multicurrency_tx = 1;
1624 }
1625 $this->entity = setEntity($this);
1626
1627 // We set order into draft status
1628 $this->statut = self::STATUS_DRAFT; // deprecated
1629 $this->status = self::STATUS_DRAFT;
1630
1631 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseur (";
1632 $sql .= "ref";
1633 $sql .= ", ref_supplier";
1634 $sql .= ", note_private";
1635 $sql .= ", note_public";
1636 $sql .= ", entity";
1637 $sql .= ", fk_soc";
1638 $sql .= ", fk_projet";
1639 $sql .= ", date_creation";
1640 $sql .= ", date_livraison";
1641 $sql .= ", fk_user_author";
1642 $sql .= ", fk_statut";
1643 $sql .= ", source";
1644 $sql .= ", model_pdf";
1645 $sql .= ", fk_mode_reglement";
1646 $sql .= ", deposit_percent";
1647 $sql .= ", fk_cond_reglement";
1648 $sql .= ", fk_account";
1649 $sql .= ", fk_incoterms, location_incoterms";
1650 $sql .= ", fk_multicurrency";
1651 $sql .= ", multicurrency_code";
1652 $sql .= ", multicurrency_tx";
1653 $sql .= ") ";
1654 $sql .= " VALUES (";
1655 $sql .= "'(PROV)'";
1656 $sql .= ", ".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "NULL");
1657 $sql .= ", '".$this->db->escape($this->note_private)."'";
1658 $sql .= ", '".$this->db->escape($this->note_public)."'";
1659 $sql .= ", ".((int) $this->entity);
1660 $sql .= ", ".((int) $this->socid);
1661 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1662 $sql .= ", '".$this->db->idate($date)."'";
1663 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1664 $sql .= ", ".((int) $user->id);
1665 $sql .= ", ".self::STATUS_DRAFT;
1666 $sql .= ", ".((int) $this->source);
1667 $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
1668 $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1669 $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
1670 $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1671 $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
1672 $sql .= ", ".(int) $this->fk_incoterms;
1673 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1674 $sql .= ", ".(int) $this->fk_multicurrency;
1675 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1676 $sql .= ", ".(float) $this->multicurrency_tx;
1677 $sql .= ")";
1678
1679 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1680 if ($this->db->query($sql)) {
1681 $this->id = $this->db->last_insert_id($this->db->prefix()."commande_fournisseur");
1682
1683 if ($this->id) {
1684 $num = count($this->lines);
1685
1686 // insert products details into database
1687 for ($i = 0; $i < $num; $i++) {
1688 $line = $this->lines[$i];
1689 if (!is_object($line)) {
1690 $line = (object) $line;
1691 }
1692
1693 //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1694
1695 // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1696 $result = $this->addline(
1697 (string) $line->desc,
1698 (float) $line->subprice,
1699 (float) $line->qty,
1700 $line->tva_tx,
1701 (float) $line->localtax1_tx,
1702 (float) $line->localtax2_tx,
1703 (int) $line->fk_product,
1704 0,
1705 (string) ($line->ref_supplier ? $line->ref_supplier : $line->ref_fourn), // $line->ref_fourn comes from field ref into table of lines. Value may be a ref that does not exists anymore, so we first try with value of product
1706 (float) $line->remise_percent,
1707 'HT',
1708 (float) $line->subprice_ttc,
1709 (int) $line->product_type,
1710 (int) $line->info_bits,
1711 0,
1712 $line->date_start ?? null,
1713 $line->date_end ?? null,
1714 $line->array_options ?? [],
1715 $line->fk_unit ?? null,
1716 (float) $line->multicurrency_subprice, // pu_ht_devise
1717 (string) $line->origin, // origin
1718 (int) $line->origin_id, // origin_id
1719 (int) ($line->rang ?? -1), // rang
1720 (int) $line->special_code
1721 );
1722 if ($result < 0) {
1723 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1724 $this->db->rollback();
1725 return -1;
1726 }
1727 }
1728
1729 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1730 $sql .= " SET ref='(PROV".$this->id.")'";
1731 $sql .= " WHERE rowid=".((int) $this->id);
1732
1733 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1734 if ($this->db->query($sql)) {
1735 // Add link with price request and supplier order
1736 if ($this->id) {
1737 $this->ref = "(PROV".$this->id.")";
1738
1739 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1740 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1741 }
1742
1743 // Add object linked
1744 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1745 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1746 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, ...))
1747 foreach ($tmp_origin_id as $origin_id) {
1748 $ret = $this->add_object_linked($origin, $origin_id);
1749 if (!$ret) {
1750 dol_print_error($this->db);
1751 $error++;
1752 }
1753 }
1754 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1755 $origin_id = $tmp_origin_id;
1756 $ret = $this->add_object_linked($origin, $origin_id);
1757 if (!$ret) {
1758 dol_print_error($this->db);
1759 $error++;
1760 }
1761 }
1762 }
1763 }
1764 }
1765
1766 if (!$error) {
1767 $result = $this->insertExtraFields();
1768 if ($result < 0) {
1769 $error++;
1770 }
1771 }
1772
1773 if (!$error && !$notrigger) {
1774 // Call trigger
1775 $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1776 if ($result < 0) {
1777 $this->db->rollback();
1778
1779 return -1;
1780 }
1781 // End call triggers
1782 }
1783
1784 $this->db->commit();
1785 return $this->id;
1786 } else {
1787 $this->error = $this->db->lasterror();
1788 $this->db->rollback();
1789
1790 return -2;
1791 }
1792 } else {
1793 $this->error = 'Failed to get ID of inserted line';
1794
1795 return -1;
1796 }
1797 } else {
1798 $this->error = $this->db->lasterror();
1799 $this->db->rollback();
1800
1801 return -1;
1802 }
1803 }
1804
1812 public function update(User $user, $notrigger = 0)
1813 {
1814 $error = 0;
1815
1816 // Clean parameters
1817 if (isset($this->ref)) {
1818 $this->ref = trim($this->ref);
1819 }
1820 if (isset($this->ref_supplier)) {
1821 $this->ref_supplier = trim($this->ref_supplier);
1822 }
1823 if (isset($this->note_private)) {
1824 $this->note_private = trim($this->note_private);
1825 }
1826 if (isset($this->note_public)) {
1827 $this->note_public = trim($this->note_public);
1828 }
1829 if (isset($this->model_pdf)) {
1830 $this->model_pdf = trim($this->model_pdf);
1831 }
1832 if (isset($this->import_key)) {
1833 $this->import_key = trim($this->import_key);
1834 }
1835
1836 // Update request
1837 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1838
1839 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1840 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1841 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1842 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1843 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1844 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1845 $sql .= " total_tva=".(isset($this->total_tva) ? (float) $this->total_tva : "null").",";
1846 $sql .= " localtax1=".(isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null").",";
1847 $sql .= " localtax2=".(isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null").",";
1848 $sql .= " total_ht=".(isset($this->total_ht) ? (float) $this->total_ht : "null").",";
1849 $sql .= " total_ttc=".(isset($this->total_ttc) ? (float) $this->total_ttc : "null").",";
1850 $sql .= " fk_statut=".(isset($this->status) ? (int) $this->status : "null").",";
1851 $sql .= " fk_user_author=".(isset($this->user_author_id) ? (int) $this->user_author_id : "null").",";
1852 $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? (int) $this->user_validation_id : "null").",";
1853 $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? (int) $this->fk_project : "null").",";
1854 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null").",";
1855 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1856 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null").",";
1857 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1858 //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1859 $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
1860 //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1861 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1862 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1863 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1864 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1865
1866 $sql .= " WHERE rowid=".((int) $this->id);
1867
1868 $this->db->begin();
1869
1870 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1871 $resql = $this->db->query($sql);
1872 if (!$resql) {
1873 $error++;
1874 $this->errors[] = "Error ".$this->db->lasterror();
1875 }
1876
1877 if (!$error) {
1878 $result = $this->insertExtraFields();
1879 if ($result < 0) {
1880 $error++;
1881 }
1882 }
1883
1884 if (!$error && !$notrigger) {
1885 // Call trigger
1886 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1887 if ($result < 0) {
1888 $error++;
1889 }
1890 // End call triggers
1891 }
1892
1893 // Commit or rollback
1894 if ($error) {
1895 foreach ($this->errors as $errmsg) {
1896 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1897 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1898 }
1899 $this->db->rollback();
1900 return -1 * $error;
1901 } else {
1902 $this->db->commit();
1903 return 1;
1904 }
1905 }
1906
1915 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1916 {
1917 global $conf, $user, $hookmanager;
1918
1919 $error = 0;
1920
1921 $this->db->begin();
1922
1923 // get extrafields so they will be clone
1924 foreach ($this->lines as $line) {
1925 $line->fetch_optionals();
1926 }
1927
1928 // Load source object
1929 $objFrom = clone $this;
1930
1931 // Change socid if needed
1932 if (!empty($socid) && $socid != $this->socid) {
1933 $objsoc = new Societe($this->db);
1934
1935 if ($objsoc->fetch($socid) > 0) {
1936 $this->socid = $objsoc->id;
1937 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1938 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1939 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1940 $this->fk_project = 0;
1941 $this->fk_delivery_address = 0;
1942 }
1943
1944 // TODO Change product price if multi-prices
1945 }
1946
1947 $this->id = 0;
1948 $this->statut = self::STATUS_DRAFT; // deprecated
1949 $this->status = self::STATUS_DRAFT;
1950
1951 // Clear fields
1952 $this->user_author_id = $user->id;
1953 $this->user_validation_id = 0;
1954
1955 $this->date = dol_now();
1956 $this->date_creation = 0;
1957 $this->date_validation = 0;
1958 $this->date_commande = null;
1959 $this->ref_supplier = '';
1960 $this->user_approve_id = 0;
1961 $this->user_approve_id2 = 0;
1962 $this->date_approve = 0;
1963 $this->date_approve2 = 0;
1964
1965 // Create clone
1966 $this->context['createfromclone'] = 'createfromclone';
1967 $result = $this->create($user, $notrigger);
1968 if ($result < 0) {
1969 $error++;
1970 }
1971
1972 if (!$error) {
1973 // Hook of thirdparty module
1974 if (is_object($hookmanager)) {
1975 $parameters = array('objFrom' => $objFrom);
1976 $action = '';
1977 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1978 if ($reshook < 0) {
1979 $this->setErrorsFromObject($hookmanager);
1980 $error++;
1981 }
1982 }
1983 }
1984
1985 unset($this->context['createfromclone']);
1986
1987 // End
1988 if (!$error) {
1989 $this->db->commit();
1990 return $this->id;
1991 } else {
1992 $this->db->rollback();
1993 return -1;
1994 }
1995 }
1996
2026 public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $fk_prod_fourn_price = 0, $ref_supplier = '', $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $type = 0, $info_bits = 0, $notrigger = 0, $date_start = null, $date_end = null, $array_options = [], $fk_unit = null, $pu_ht_devise = 0, $origin = '', $origin_id = 0, $rang = -1, $special_code = 0)
2027 {
2028 global $langs, $mysoc;
2029
2030 dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $fk_prod_fourn_price, $ref_supplier, $remise_percent, $price_base_type, $pu_ttc, $type, $info_bits, $notrigger, $date_start, $date_end, $fk_unit, $pu_ht_devise, $origin, $origin_id");
2031 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2032
2033 if ($this->status == self::STATUS_DRAFT) {
2034 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2035
2036 // Clean parameters
2037 if (empty($qty)) {
2038 $qty = 0;
2039 }
2040 if (!$info_bits) {
2041 $info_bits = 0;
2042 }
2043 if (empty($txtva)) {
2044 $txtva = 0;
2045 }
2046 if (empty($rang)) {
2047 $rang = 0;
2048 }
2049 if (empty($txlocaltax1)) {
2050 $txlocaltax1 = 0;
2051 }
2052 if (empty($txlocaltax2)) {
2053 $txlocaltax2 = 0;
2054 }
2055 if (empty($remise_percent)) {
2056 $remise_percent = 0;
2057 }
2058
2059 $remise_percent = price2num($remise_percent);
2060 $qty = price2num($qty);
2061 $pu_ht = price2num($pu_ht);
2062 $pu_ht_devise = price2num($pu_ht_devise);
2063 $pu_ttc = price2num($pu_ttc);
2064 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2065 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2066 }
2067 $txlocaltax1 = price2num($txlocaltax1);
2068 $txlocaltax2 = price2num($txlocaltax2);
2069 if ($price_base_type == 'HT') {
2070 $pu = $pu_ht;
2071 } else {
2072 $pu = $pu_ttc;
2073 }
2074 $desc = trim((string) $desc);
2075
2076 // Check parameters
2077 if ($qty < 0 && !$fk_product) {
2078 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
2079 return -1;
2080 }
2081 if ($type < 0) {
2082 return -1;
2083 }
2084 if ($date_start && $date_end && $date_start > $date_end) {
2085 $langs->load("errors");
2086 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2087 return -1;
2088 }
2089
2090
2091 $this->db->begin();
2092
2093 $product_type = $type;
2094 $label = ''; // deprecated
2095
2096 if ($fk_product > 0) {
2097 if (getDolGlobalInt('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2098 // Check quantity is enough
2099 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." fk_prod_fourn_price=".$fk_prod_fourn_price." qty=".$qty." ref_supplier=".$ref_supplier);
2100 $prod = new ProductFournisseur($this->db);
2101 if ($prod->fetch($fk_product) > 0) {
2102 $product_type = $prod->type;
2103 $label = $prod->label;
2104
2105 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2106 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2107 $result = $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (isset($this->fk_soc) ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
2108
2109 // If supplier order created from sales order, we take best supplier price
2110 // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2111 if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2112 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2113 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2114 // is remise percent not keyed but present for the product we add it
2115 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2116 $remise_percent = $prod->remise_percent;
2117 }
2118 }
2119 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2120 $langs->load("errors");
2121 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2122 $this->db->rollback();
2123 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2124 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2125 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2126 return -1;
2127 }
2128 if ($result == -1) {
2129 $langs->load("errors");
2130 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2131 $this->db->rollback();
2132 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2133 return -1;
2134 }
2135 if ($result < -1) {
2136 $this->error = $prod->error;
2137 $this->db->rollback();
2138 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2139 return -1;
2140 }
2141 } else {
2142 $this->error = $prod->error;
2143 $this->db->rollback();
2144 return -1;
2145 }
2146 }
2147
2148 // Predefine quantity according to packaging
2149 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2150 $prod = new Product($this->db);
2151 $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2152
2153 // Align messaging, type and float-safety with the customer-order path at commande.class.php:1720
2154 // (#38782 bugs 1, 2, 6).
2155 $newqty = $this->roundQtyToPackaging($qty, $prod->packaging);
2156 if ($newqty != $qty) {
2157 $qty = $newqty;
2158 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2159 }
2160
2161 // Enforce the supplier minimum purchase quantity on top of packaging
2162 // rounding. The packaging block above only ensures qty is a packaging
2163 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2164 // is below the supplier minimum, round qty_min itself up to the next
2165 // packaging multiple so we end up with the smallest valid order qty (#38783).
2166 if (!empty($prod->fourn_qty) && (float) $qty > 0 && (float) $qty < (float) $prod->fourn_qty) { // A negative quantity (return) is not concerned by the minimum purchase quantity
2167 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2168 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2169 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2170 } else {
2171 $qty = (float) $prod->fourn_qty;
2172 }
2173 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2174 }
2175 }
2176 }
2177
2178 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2179 $pu = 0;
2180 }
2181
2182 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2183
2184 // Clean vat code
2185 $reg = array();
2186 $vat_src_code = '';
2187 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2188 $vat_src_code = $reg[1];
2189 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2190 }
2191
2192 // Calcul du total TTC et de la TVA pour la ligne a partir de
2193 // qty, pu, remise_percent et txtva
2194 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2195 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2196
2197 $tabprice = calcul_price_total((float) $qty, $pu, $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
2198
2199 $total_ht = $tabprice[0];
2200 $total_tva = $tabprice[1];
2201 $total_ttc = $tabprice[2];
2202 $total_localtax1 = $tabprice[9];
2203 $total_localtax2 = $tabprice[10];
2204 $pu = $pu_ht = $tabprice[3];
2205 $pu_tva = $tabprice[4];
2206 $pu_ttc = $tabprice[5];
2207
2208 // MultiCurrency
2209 $multicurrency_total_ht = $tabprice[16];
2210 $multicurrency_total_tva = $tabprice[17];
2211 $multicurrency_total_ttc = $tabprice[18];
2212 $pu_ht_devise = $tabprice[19];
2213 $multicurrency_pu_ttc = $tabprice[21];
2214
2215 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2216 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2217
2218 if ($rang < 0) {
2219 $rangmax = $this->line_max();
2220 $rang = $rangmax + 1;
2221 }
2222
2223 // Insert line
2224 $this->line = new CommandeFournisseurLigne($this->db);
2225
2226 $this->line->context = $this->context;
2227
2228 $this->line->fk_commande = $this->id;
2229 $this->line->label = $label;
2230 $this->line->ref_fourn = $ref_supplier;
2231 $this->line->ref_supplier = $ref_supplier;
2232 $this->line->desc = $desc;
2233 $this->line->qty = $qty;
2234 $this->line->tva_tx = $txtva;
2235 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2236 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2237 $this->line->localtax1_type = $localtax1_type;
2238 $this->line->localtax2_type = $localtax2_type;
2239 $this->line->fk_product = $fk_product;
2240 $this->line->product_type = $product_type;
2241 $this->line->remise_percent = $remise_percent;
2242 $this->line->subprice = (float) $pu_ht;
2243 $this->line->subprice_ttc = (float) $pu_ttc;
2244
2245 $this->line->rang = $rang;
2246 $this->line->info_bits = $info_bits;
2247
2248 $this->line->vat_src_code = $vat_src_code;
2249 $this->line->total_ht = (float) $total_ht;
2250 $this->line->total_tva = (float) $total_tva;
2251 $this->line->total_localtax1 = (float) $total_localtax1;
2252 $this->line->total_localtax2 = (float) $total_localtax2;
2253 $this->line->total_ttc = (float) $total_ttc;
2254 $this->line->product_type = $type;
2255 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2256 $this->line->origin = $origin;
2257 $this->line->origin_type = $origin;
2258 $this->line->origin_id = $origin_id;
2259 $this->line->fk_unit = $fk_unit;
2260
2261 $this->line->date_start = $date_start;
2262 $this->line->date_end = $date_end;
2263
2264 // Multicurrency
2265 $this->line->fk_multicurrency = $this->fk_multicurrency;
2266 $this->line->multicurrency_code = $this->multicurrency_code;
2267 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2268 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2269 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2270 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2271 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2272
2273 $this->line->subprice = (float) $pu_ht;
2274 $this->line->price = $this->line->subprice;
2275
2276 $this->line->remise_percent = $remise_percent;
2277
2278 if (is_array($array_options) && count($array_options) > 0) {
2279 $this->line->array_options = $array_options;
2280 }
2281
2282 $result = $this->line->insert($notrigger);
2283 if ($result > 0) {
2284 // Update denormalized fields at the order level
2285 $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2286
2287 if ($result > 0) {
2288 if (!isset($this->context['createfromclone'])) {
2289 if (!empty($this->line->fk_parent_line)) {
2290 // Always reorder if child line
2291 $this->line_order(true, 'DESC');
2292 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2293 // Update all rank of all other lines starting from the same $ranktouse
2294 $linecount = count($this->lines);
2295 for ($ii = $rang; $ii <= $linecount; $ii++) {
2296 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2297 }
2298 }
2299
2300 $this->lines[] = $this->line;
2301 }
2302
2303 $this->db->commit();
2304 return $this->line->id;
2305 } else {
2306 $this->db->rollback();
2307 return -1;
2308 }
2309 } else {
2310 $this->setErrorsFromObject($this->line);
2311 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2312 $this->db->rollback();
2313 return -1;
2314 }
2315 }
2316 return -1;
2317 }
2318
2319
2337 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2338 {
2339 global $conf, $langs;
2340
2341 $error = 0;
2342 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2343
2344 // Check parameters (if test are wrong here, there is bug into caller)
2345 if ($entrepot <= 0) {
2346 $this->error = 'ErrorBadValueForParameterWarehouse';
2347 return -1;
2348 }
2349 if ($qty == 0) {
2350 $this->error = 'ErrorBadValueForParameterQty';
2351 return -1;
2352 }
2353
2354 $dispatchstatus = 1;
2355 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2356 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2357 }
2358
2359 $now = dol_now();
2360
2361 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2362
2363 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2364 $this->db->begin();
2365
2366 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2367 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2368 $sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', ";
2369 $sql .= ($eatby ? "'".$this->db->idate($eatby)."'" : "null").", ".($sellby ? "'".$this->db->idate($sellby)."'" : "null").", ".($batch ? "'".$this->db->escape($batch)."'" : "null").", ".($fk_reception > 0 ? "'".$this->db->escape((string) $fk_reception)."'" : "null");
2370 $sql .= ")";
2371
2372 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2373 $resql = $this->db->query($sql);
2374 if ($resql) {
2375 if (!$notrigger) {
2376 global $conf, $langs, $user;
2377 // Call trigger
2378 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2379 if ($result < 0) {
2380 $error++;
2381 }
2382 // End call triggers
2383 }
2384 } else {
2385 $this->error = $this->db->lasterror();
2386 $error++;
2387 }
2388
2389 // If module stock is enabled and the stock increase is done on purchase order dispatching
2390 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2391 $mouv = new MouvementStock($this->db);
2392 if ($product > 0) {
2393 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2394 $mouv->origin = &$this;
2395 $mouv->setOrigin($this->element, $this->id);
2396
2397 // Method change if qty < 0
2398 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2399 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2400 } else {
2401 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2402 }
2403
2404 if ($result < 0) {
2405 $this->error = $mouv->error;
2406 $this->errors = $mouv->errors;
2407 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2408 $error++;
2409 }
2410 }
2411 }
2412
2413 if ($error == 0) {
2414 $this->db->commit();
2415 return 1;
2416 } else {
2417 $this->db->rollback();
2418 return -1;
2419 }
2420 } else {
2421 $this->error = 'BadStatusForObject';
2422 return -2;
2423 }
2424 }
2425
2433 public function deleteLine($idline, $notrigger = 0)
2434 {
2435 global $user;
2436
2437 if ($this->status == 0) {
2438 $line = new CommandeFournisseurLigne($this->db);
2439
2440 if ($line->fetch($idline) <= 0) {
2441 return 0;
2442 }
2443 if ($this->id > 0 && (int) $line->fk_commande !== (int) $this->id) {
2444 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2445 return -1;
2446 }
2447
2448 // check if not yet received
2449 $dispatchedLines = $this->getDispachedLines();
2450 foreach ($dispatchedLines as $dispatchLine) {
2451 if ($dispatchLine['orderlineid'] == $idline) {
2452 $this->error = "LineAlreadyDispatched";
2453 $this->errors[] = $this->error;
2454 return -3;
2455 }
2456 }
2457
2458 if ($line->delete($user, $notrigger) > 0) {
2459 $this->update_price(1);
2460 return 1;
2461 } else {
2462 $this->setErrorsFromObject($line);
2463 return -1;
2464 }
2465 } else {
2466 return -2;
2467 }
2468 }
2469
2477 public function delete(User $user, $notrigger = 0)
2478 {
2479 global $langs, $conf;
2480 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2481
2482 $error = 0;
2483
2484 $this->db->begin();
2485
2486 if (empty($notrigger)) {
2487 // Call trigger
2488 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2489 if ($result < 0) {
2490 $this->errors[] = 'ErrorWhenRunningTrigger';
2491 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2492 $this->db->rollback();
2493 return -1;
2494 }
2495 // End call triggers
2496 }
2497
2498 // Test we can delete
2499 $this->fetchObjectLinked(null, 'order_supplier');
2500 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2501 foreach ($this->linkedObjects['reception'] as $element) {
2502 if ($element->statut >= 0) {
2503 $this->errors[] = $langs->trans('ReceptionExist');
2504 $error++;
2505 break;
2506 }
2507 }
2508 }
2509
2510 // Remove linked categories.
2511 if (!$error) {
2512 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2513 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2514
2515 $result = $this->db->query($sql);
2516 if (!$result) {
2517 $error++;
2518 $this->errors[] = $this->db->lasterror();
2519 }
2520 }
2521
2522 $main = $this->db->prefix().'commande_fournisseurdet';
2523
2524 if (!$error) {
2525 $sql1 = "UPDATE ".$this->db->prefix()."commandedet SET fk_commandefourndet = NULL WHERE fk_commandefourndet IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2526 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2527 if (!$this->db->query($sql1)) {
2528 $error++;
2529 $this->error = $this->db->lasterror();
2530 $this->errors[] = $this->db->lasterror();
2531 }
2532 }
2533
2534 if (!$error) {
2535 $ef = $main."_extrafields";
2536 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2537 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2538 if (!$this->db->query($sql)) {
2539 $this->error = $this->db->lasterror();
2540 $this->errors[] = $this->db->lasterror();
2541 $error++;
2542 }
2543 }
2544
2545 if (!$error) {
2546 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2547 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2548 if (!$this->db->query($sql)) {
2549 $this->error = $this->db->lasterror();
2550 $this->errors[] = $this->db->lasterror();
2551 $error++;
2552 }
2553 }
2554
2555 if (!$error) {
2556 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2557 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2558 if ($resql = $this->db->query($sql)) {
2559 if ($this->db->affected_rows($resql) < 1) {
2560 $this->error = $this->db->lasterror();
2561 $this->errors[] = $this->db->lasterror();
2562 $error++;
2563 }
2564 } else {
2565 $this->error = $this->db->lasterror();
2566 $this->errors[] = $this->db->lasterror();
2567 $error++;
2568 }
2569 }
2570
2571 // Remove extrafields
2572 if (!$error) {
2573 $result = $this->deleteExtraFields();
2574 if ($result < 0) {
2575 $this->error = 'FailToDeleteExtraFields';
2576 $this->errors[] = 'FailToDeleteExtraFields';
2577 $error++;
2578 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2579 }
2580 }
2581
2582 // Delete linked object
2583 $res = $this->deleteObjectLinked();
2584 if ($res < 0) {
2585 $this->error = 'FailToDeleteObjectLinked';
2586 $this->errors[] = 'FailToDeleteObjectLinked';
2587 $error++;
2588 }
2589
2590 if (!$error) {
2591 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2592 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2593 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2594
2595 // We remove directory
2596 $ref = dol_sanitizeFileName($this->ref);
2597 if ($conf->fournisseur->commande->dir_output && !empty($ref)) {
2598 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2599 $file = $dir."/".$ref.".pdf";
2600 if (file_exists($file)) {
2601 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2602 $this->error = 'ErrorFailToDeleteFile';
2603 $this->errors[] = 'ErrorFailToDeleteFile';
2604 $error++;
2605 }
2606 }
2607 if (file_exists($dir)) {
2608 $res = @dol_delete_dir_recursive($dir);
2609 if (!$res) {
2610 $this->error = 'ErrorFailToDeleteDir';
2611 $this->errors[] = 'ErrorFailToDeleteDir';
2612 $error++;
2613 }
2614 }
2615 }
2616 }
2617
2618 if (!$error) {
2619 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2620 $this->db->commit();
2621 return 1;
2622 } else {
2623 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2624 $this->db->rollback();
2625 return -$error;
2626 }
2627 }
2628
2629
2638 public function getDispachedLines($status = -1)
2639 {
2640 $ret = array();
2641
2642 // List of already dispatched lines
2643 $sql = "SELECT p.ref, p.label,";
2644 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2645 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2646 $sql .= " FROM ".$this->db->prefix()."product as p,";
2647 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2648 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2649 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2650 $sql .= " AND cfd.fk_product = p.rowid";
2651 if ($status >= 0) {
2652 $sql .= " AND cfd.status = ".((int) $status);
2653 }
2654 $sql .= " ORDER BY cfd.rowid ASC";
2655
2656 $resql = $this->db->query($sql);
2657 if ($resql) {
2658 $num = $this->db->num_rows($resql);
2659 $i = 0;
2660
2661 while ($i < $num) {
2662 $objp = $this->db->fetch_object($resql);
2663 if ($objp) {
2664 $ret[] = array(
2665 'id' => $objp->dispatchedlineid,
2666 'productid' => $objp->fk_product,
2667 'warehouseid' => $objp->warehouse_id,
2668 'qty' => $objp->qty,
2669 'orderlineid' => $objp->fk_elementdet
2670 );
2671 }
2672
2673 $i++;
2674 }
2675 } else {
2676 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2677 }
2678
2679 return $ret;
2680 }
2681
2682 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2692 public function Livraison($user, $date, $type, $comment)
2693 {
2694 // phpcs:enable
2695 global $conf, $langs;
2696
2697 $result = 0;
2698 $error = 0;
2699 $dispatchedlinearray = array();
2700
2701 dol_syslog(get_class($this)."::Livraison");
2702
2703 $usercanreceive = 0;
2704 if (!isModEnabled('reception')) {
2705 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2706 } else {
2707 $usercanreceive = $user->hasRight("reception", "creer");
2708 }
2709
2710 if ($usercanreceive) {
2711 // Define the new status
2712 if ($type == 'par') {
2714 } elseif ($type == 'tot') {
2716 } elseif ($type == 'nev') {
2718 } elseif ($type == 'can') {
2720 } else {
2721 $error++;
2722 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2723 return -2;
2724 }
2725
2726 // Some checks to accept the record
2727 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2728 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2729 if ($type == 'tot') {
2730 $dispatchedlinearray = $this->getDispachedLines(0);
2731 if (count($dispatchedlinearray) > 0) {
2732 $result = -1;
2733 $error++;
2734 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2735 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2736 }
2737 }
2738 if (!$error && getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS_NEED_APPROVE') && ($type == 'tot')) { // Accept to move to reception done, only if status of all line are ok (refuse denied)
2739 $dispatcheddenied = $this->getDispachedLines(2);
2740 if (count($dispatchedlinearray) > 0) {
2741 $result = -1;
2742 $error++;
2743 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2744 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2745 }
2746 }
2747 }
2748
2749 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2750
2751 if (empty($error)) {
2752 $this->db->begin();
2753
2754 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2755 $sql .= " SET fk_statut = ".((int) $statut);
2756 $sql .= " WHERE rowid = ".((int) $this->id);
2757 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2758
2759 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2760 $resql = $this->db->query($sql);
2761 if ($resql) {
2762 $result = 1;
2763 $old_statut = $this->status;
2764 $this->status = $statut;
2765 $this->context['actionmsg2'] = $comment;
2766
2767 // Call trigger
2768 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2769 if ($result_trigger < 0) {
2770 $error++;
2771 }
2772 // End call triggers
2773
2774 if (empty($error)) {
2775 $this->db->commit();
2776 } else {
2777 $this->status = $old_statut;
2778 $this->db->rollback();
2779 $this->error = $this->db->lasterror();
2780 $result = -1;
2781 }
2782 } else {
2783 $this->db->rollback();
2784 $this->error = $this->db->lasterror();
2785 $result = -1;
2786 }
2787 }
2788 } else {
2789 $this->error = $langs->trans('NotAuthorized');
2790 $this->errors[] = $langs->trans('NotAuthorized');
2791 dol_syslog(get_class($this)."::Livraison Not Authorized");
2792 $result = -3;
2793 }
2794 return $result;
2795 }
2796
2797 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2807 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2808 {
2809 // phpcs:enable
2810 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2811 }
2812
2821 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2822 {
2823 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2824 $error = 0;
2825
2826 $this->db->begin();
2827
2828 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2829 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2830 $sql .= " WHERE rowid = ".((int) $this->id);
2831
2832 dol_syslog(__METHOD__, LOG_DEBUG);
2833 $resql = $this->db->query($sql);
2834 if (!$resql) {
2835 $this->errors[] = $this->db->error();
2836 $error++;
2837 }
2838
2839 if (!$error) {
2840 $this->oldcopy = clone $this;
2841 $this->delivery_date = $delivery_date;
2842 }
2843
2844 if (!$notrigger && empty($error)) {
2845 // Call trigger
2846 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2847 if ($result < 0) {
2848 $error++;
2849 }
2850 // End call triggers
2851 }
2852
2853 if (!$error) {
2854 $this->db->commit();
2855 return 1;
2856 } else {
2857 foreach ($this->errors as $errmsg) {
2858 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2859 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2860 }
2861 $this->db->rollback();
2862 return -1 * $error;
2863 }
2864 } else {
2865 return -2;
2866 }
2867 }
2868
2875 public function setReopen(User $user): int
2876 {
2877 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2878 if ($this->status == 1) {
2879 $newStatus = 0; // Validated->Draft
2880 } elseif ($this->status == 2) {
2881 $newStatus = 0; // Approved->Draft
2882 } elseif ($this->status == 3) {
2883 $newStatus = 2; // Ordered->Approved
2884 } elseif ($this->status == 4) {
2885 $newStatus = 3;
2886 } elseif ($this->status == 5) {
2887 //$newstatus=2; // Ordered
2888 // TODO Can we set it to submitted ?
2889 //$newstatus=3; // Submitted
2890 // TODO If there is at least one reception, we can set to Received->Received partially
2891 $newStatus = 4; // Received partially
2892 } elseif ($this->status == 6) {
2893 $newStatus = 2; // Canceled->Approved
2894 } elseif ($this->status == 7) {
2895 $newStatus = 3; // Canceled->Process running
2896 } elseif ($this->status == 9) {
2897 $newStatus = 1; // Refused->Validated
2898 } else {
2899 $newStatus = 2;
2900 }
2901
2902 $this->db->begin();
2903
2904 $result = $this->setStatus($user, $newStatus);
2905 if ($result > 0) {
2906 if ($newStatus == 0) {
2907 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2908 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2909 $sql .= ' WHERE rowid = '.((int) $this->id);
2910
2911 $this->db->query($sql);
2912 }
2913
2914 $this->db->commit();
2915
2916 return 1;
2917 } else {
2918 $this->db->rollback();
2919
2920 return -1;
2921 }
2922 }
2923
2924 return 0;
2925 }
2926
2927 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2936 public function set_id_projet($user, $id_projet, $notrigger = 0)
2937 {
2938 // phpcs:enable
2939 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2940 $error = 0;
2941
2942 $this->db->begin();
2943
2944 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2945 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2946 $sql .= " WHERE rowid = ".((int) $this->id);
2947
2948 dol_syslog(__METHOD__, LOG_DEBUG);
2949 $resql = $this->db->query($sql);
2950 if (!$resql) {
2951 $this->errors[] = $this->db->error();
2952 $error++;
2953 }
2954
2955 if (!$error) {
2956 $this->oldcopy = clone $this;
2957 $this->fk_projet = $id_projet;
2958 $this->fk_project = $id_projet;
2959 }
2960
2961 if (!$notrigger && empty($error)) {
2962 // Call trigger
2963 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2964 if ($result < 0) {
2965 $error++;
2966 }
2967 // End call triggers
2968 }
2969
2970 if (!$error) {
2971 $this->db->commit();
2972 return 1;
2973 } else {
2974 foreach ($this->errors as $errmsg) {
2975 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2976 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2977 }
2978 $this->db->rollback();
2979 return -1 * $error;
2980 }
2981 } else {
2982 return -2;
2983 }
2984 }
2985
2994 public function updateFromCommandeClient($user, $idc, $comclientid)
2995 {
2996 $comclient = new Commande($this->db);
2997 $comclient->fetch($comclientid);
2998
2999 $this->id = $idc;
3000
3001 $this->lines = array();
3002
3003 $num = count($comclient->lines);
3004 for ($i = 0; $i < $num; $i++) {
3005 $prod = new Product($this->db);
3006 $label = '';
3007 $ref = '';
3008 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3009 $label = $prod->label;
3010 $ref = (string) $prod->ref;
3011 }
3012
3013 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3014 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3015 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3016 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3017 $sql .= ", ".price2num($comclient->lines[$i]->qty, 'MS').", ".price2num($comclient->lines[$i]->tva_tx, 5).", ".price2num($comclient->lines[$i]->localtax1_tx, 5).", ".price2num($comclient->lines[$i]->localtax2_tx, 5).", ".price2num($comclient->lines[$i]->remise_percent, 3);
3018 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3019 if ($this->db->query($sql)) {
3020 $this->update_price(1);
3021 }
3022 }
3023
3024 return 1;
3025 }
3026
3034 public function setStatus($user, $status)
3035 {
3036 $error = 0;
3037
3038 $this->db->begin();
3039
3040 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3041 $sql .= " SET fk_statut = ".$status;
3042 $sql .= " WHERE rowid = ".((int) $this->id);
3043
3044 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3045 $resql = $this->db->query($sql);
3046 if ($resql) {
3047 // Trigger names for each status
3048 $triggerName = array();
3049 $triggerName[0] = 'DRAFT';
3050 $triggerName[1] = 'VALIDATED';
3051 $triggerName[2] = 'APPROVED';
3052 $triggerName[3] = 'ORDERED'; // Ordered
3053 $triggerName[4] = 'RECEIVED_PARTIALLY';
3054 $triggerName[5] = 'RECEIVED_COMPLETELY';
3055 $triggerName[6] = 'CANCELED';
3056 $triggerName[7] = 'CANCELED';
3057 $triggerName[9] = 'REFUSED';
3058
3059 // Call trigger
3060 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3061 if ($result < 0) {
3062 $error++;
3063 }
3064 // End call triggers
3065 } else {
3066 $error++;
3067 $this->error = $this->db->lasterror();
3068 dol_syslog(get_class($this)."::setStatus ".$this->error);
3069 }
3070
3071 if (!$error) {
3072 $this->status = $status;
3073 $this->db->commit();
3074 return 1;
3075 } else {
3076 $this->db->rollback();
3077 return -1;
3078 }
3079 }
3080
3092 public function setCategories($categories)
3093 {
3094 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3095 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3096 }
3097
3121 public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $notrigger = 0, $date_start = 0, $date_end = 0, $array_options = [], $fk_unit = null, $pu_ht_devise = 0, $ref_supplier = '')
3122 {
3123 global $mysoc, $conf, $langs;
3124
3125 if (!$this->isLineOfObject($rowid)) {
3126 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
3127 return -1;
3128 }
3129
3130 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3131 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3132
3133 $error = 0;
3134
3135 if ($this->status == self::STATUS_DRAFT) {
3136 // Clean parameters
3137 if (empty($qty)) {
3138 $qty = 0;
3139 }
3140 if (empty($info_bits)) {
3141 $info_bits = 0;
3142 }
3143 if (empty($txtva)) {
3144 $txtva = 0;
3145 }
3146 if (empty($txlocaltax1)) {
3147 $txlocaltax1 = 0;
3148 }
3149 if (empty($txlocaltax2)) {
3150 $txlocaltax2 = 0;
3151 }
3152 if (empty($remise_percent)) {
3153 $remise_percent = 0;
3154 }
3155
3156 $remise_percent = (float) price2num($remise_percent);
3157 $qty = price2num($qty);
3158 if (!$qty) {
3159 $qty = 1;
3160 }
3161 $pu = price2num($pu);
3162 $pu_ht_devise = price2num($pu_ht_devise);
3163 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3164 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3165 }
3166 $txlocaltax1 = (float) price2num($txlocaltax1);
3167 $txlocaltax2 = (float) price2num($txlocaltax2);
3168
3169 // Check parameters
3170 if ($type < 0) {
3171 return -1;
3172 }
3173 if ($date_start && $date_end && $date_start > $date_end) {
3174 $langs->load("errors");
3175 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3176 return -1;
3177 }
3178
3179 $this->db->begin();
3180
3181 // Calcul du total TTC et de la TVA pour la ligne a partir de
3182 // qty, pu, remise_percent et txtva
3183 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3184 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3185
3186 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3187
3188 // Clean vat code
3189 $reg = array();
3190 $vat_src_code = '';
3191 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3192 $vat_src_code = $reg[1];
3193 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3194 }
3195
3196 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
3197 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
3198 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3199 $tmpline = new CommandeFournisseurLigne($this->db);
3200 if ($tmpline->fetch($rowid) > 0) {
3201 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
3202 if ($newqty != $qty) {
3203 $qty = $newqty;
3204 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
3205 }
3206 }
3207 }
3208
3209 $tabprice = calcul_price_total($qty, (float) $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, (float) $this->multicurrency_tx, (float) $pu_ht_devise);
3210 $total_ht = $tabprice[0];
3211 $total_tva = $tabprice[1];
3212 $total_ttc = $tabprice[2];
3213 $total_localtax1 = $tabprice[9];
3214 $total_localtax2 = $tabprice[10];
3215 $pu_ht = $tabprice[3];
3216 $pu_tva = $tabprice[4];
3217 $pu_ttc = $tabprice[5];
3218
3219 // MultiCurrency
3220 $multicurrency_total_ht = $tabprice[16];
3221 $multicurrency_total_tva = $tabprice[17];
3222 $multicurrency_total_ttc = $tabprice[18];
3223 $pu_ht_devise = $tabprice[19];
3224 $multicurrency_pu_ttc = $tabprice[21];
3225
3226 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3227 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3228
3229 // Fetch current line from the database and then clone the object and set it in $oldline property
3230 $this->line = new CommandeFournisseurLigne($this->db);
3231 $this->line->fetch($rowid);
3232
3233 $oldline = clone $this->line;
3234 $this->line->oldline = $oldline;
3235
3236 $this->line->context = $this->context;
3237
3238 $this->line->fk_commande = $this->id;
3239 //$this->line->label=$label;
3240 $this->line->desc = $desc;
3241
3242
3243 $this->line->qty = $qty;
3244 $this->line->ref_supplier = $ref_supplier;
3245
3246 $this->line->vat_src_code = $vat_src_code;
3247 $this->line->tva_tx = $txtva;
3248 $this->line->localtax1_tx = $txlocaltax1;
3249 $this->line->localtax2_tx = $txlocaltax2;
3250 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3251 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3252 $this->line->remise_percent = $remise_percent;
3253 $this->line->subprice = (float) $pu_ht;
3254 $this->line->subprice_ttc = (float) $pu_ttc;
3255 $this->line->info_bits = $info_bits;
3256 $this->line->total_ht = (float) $total_ht;
3257 $this->line->total_tva = (float) $total_tva;
3258 $this->line->total_localtax1 = (float) $total_localtax1;
3259 $this->line->total_localtax2 = (float) $total_localtax2;
3260 $this->line->total_ttc = (float) $total_ttc;
3261 $this->line->product_type = $type;
3262 $this->line->special_code = $oldline->special_code;
3263 $this->line->rang = $oldline->rang;
3264 $this->line->origin = $this->origin;
3265 $this->line->fk_unit = $fk_unit;
3266
3267 $this->line->date_start = $date_start;
3268 $this->line->date_end = $date_end;
3269
3270 // Multicurrency
3271 $this->line->fk_multicurrency = $this->fk_multicurrency;
3272 $this->line->multicurrency_code = $this->multicurrency_code;
3273 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3274 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3275 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3276 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3277 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3278
3279 $this->line->subprice = (float) $pu_ht;
3280 $this->line->subprice_ttc = (float) $pu_ttc;
3281 $this->line->price = $this->line->subprice;
3282
3283 $this->line->remise_percent = $remise_percent;
3284
3285 if (is_array($array_options) && count($array_options) > 0) {
3286 // We replace values in this->line->array_options only for entries defined into $array_options
3287 foreach ($array_options as $key => $value) {
3288 $this->line->array_options[$key] = $array_options[$key];
3289 }
3290 }
3291
3292 $result = $this->line->update($notrigger);
3293
3294
3295 // Mise a jour info denormalisees au niveau facture
3296 if ($result >= 0) {
3297 $this->update_price(1, 'auto');
3298 $this->db->commit();
3299 return $result;
3300 } else {
3301 $this->errors[] = $this->line->error;
3302 $this->errors = array_merge($this->errors, $this->line->errors);
3303 $this->error = $this->db->lasterror();
3304 $this->db->rollback();
3305 return -1;
3306 }
3307 } else {
3308 $this->error = "Order status makes operation forbidden";
3309 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3310 return -2;
3311 }
3312 }
3313
3314
3322 public function initAsSpecimen()
3323 {
3324 global $user, $langs, $conf;
3325
3326 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3327
3328 dol_syslog(get_class($this)."::initAsSpecimen");
3329
3330 $now = dol_now();
3331
3332 // Find first product
3333 $prodid = 0;
3334 $product = new ProductFournisseur($this->db);
3335 $sql = "SELECT rowid";
3336 $sql .= " FROM ".$this->db->prefix()."product";
3337 $sql .= " WHERE entity IN (".getEntity('product').")";
3338 $sql .= $this->db->order("rowid", "ASC");
3339 $sql .= $this->db->plimit(1);
3340 $resql = $this->db->query($sql);
3341 if ($resql && $this->db->num_rows($resql)) {
3342 $obj = $this->db->fetch_object($resql);
3343 $prodid = $obj->rowid;
3344 }
3345
3346 // Initialise parameters
3347 $this->id = 0;
3348 $this->ref = 'SPECIMEN';
3349 $this->specimen = 1;
3350 $this->socid = 1;
3351 $this->date = $now;
3352 $this->date_commande = $now;
3353 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3354 $this->cond_reglement_code = 'RECEP';
3355 $this->mode_reglement_code = 'CHQ';
3356
3357 $this->note_public = 'This is a comment (public)';
3358 $this->note_private = 'This is a comment (private)';
3359
3360 $this->multicurrency_tx = 1;
3361 $this->multicurrency_code = $conf->currency;
3362
3363 $this->statut = 0; // deprecated
3364 $this->status = 0;
3365
3366 // Lines
3367 $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)
3368 $xnbp = 0;
3369 while ($xnbp < $nbp) {
3370 $line = new CommandeFournisseurLigne($this->db);
3371 $line->desc = $langs->trans("Description")." ".$xnbp;
3372 $line->qty = 1;
3373 $line->subprice = 100;
3374 $line->tva_tx = 19.6;
3375 $line->localtax1_tx = 0;
3376 $line->localtax2_tx = 0;
3377 if ($xnbp == 2) {
3378 $line->total_ht = 50;
3379 $line->total_ttc = 59.8;
3380 $line->total_tva = 9.8;
3381 $line->remise_percent = 50;
3382 } else {
3383 $line->total_ht = 100;
3384 $line->total_ttc = 119.6;
3385 $line->total_tva = 19.6;
3386 $line->remise_percent = 00;
3387 }
3388 $line->fk_product = $prodid;
3389
3390 $this->lines[$xnbp] = $line;
3391
3392 $this->total_ht += $line->total_ht;
3393 $this->total_tva += $line->total_tva;
3394 $this->total_ttc += $line->total_ttc;
3395
3396 $xnbp++;
3397 }
3398
3399 return 1;
3400 }
3401
3408 public function info($id)
3409 {
3410 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3411 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3412 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3413 $sql .= ' WHERE c.rowid = '.((int) $id);
3414
3415 $result = $this->db->query($sql);
3416 if ($result) {
3417 if ($this->db->num_rows($result)) {
3418 $obj = $this->db->fetch_object($result);
3419
3420 $this->id = $obj->rowid;
3421
3422 $this->user_creation_id = $obj->fk_user_author;
3423 $this->user_validation_id = $obj->fk_user_valid;
3424 $this->user_modification_id = $obj->fk_user_modif;
3425 $this->user_approve_id = $obj->fk_user_approve;
3426 $this->user_approve_id2 = $obj->fk_user_approve2;
3427
3428 $this->date_creation = $this->db->jdate($obj->datec);
3429 $this->date_modification = $this->db->jdate($obj->datem);
3430 $this->date_approve = $this->db->jdate($obj->datea);
3431 $this->date_approve2 = $this->db->jdate($obj->datea2);
3432 $this->date_validation = $this->db->jdate($obj->date_validation);
3433 }
3434 $this->db->free($result);
3435 } else {
3436 dol_print_error($this->db);
3437 }
3438 }
3439
3445 public function loadStateBoard()
3446 {
3447 global $conf, $user;
3448
3449 $this->nb = array();
3450 $clause = "WHERE";
3451
3452 $sql = "SELECT count(co.rowid) as nb";
3453 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3454 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3455 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3456 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3457 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3458 $clause = "AND";
3459 }
3460 $sql .= " ".$clause." co.entity IN (".getEntity('supplier_order').")";
3461
3462 $resql = $this->db->query($sql);
3463 if ($resql) {
3464 while ($obj = $this->db->fetch_object($resql)) {
3465 $this->nb["supplier_orders"] = $obj->nb;
3466 }
3467 $this->db->free($resql);
3468 return 1;
3469 } else {
3470 dol_print_error($this->db);
3471 $this->error = $this->db->error();
3472 return -1;
3473 }
3474 }
3475
3476 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3484 public function load_board($user, $mode = 'opened')
3485 {
3486 // phpcs:enable
3487 global $conf, $langs;
3488
3489 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3490 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3491 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3492 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3493 }
3494 $sql .= " WHERE c.entity = ".$conf->entity;
3495 if ($mode === 'awaiting') {
3496 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3497 } else {
3498 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3499 }
3500 if ($user->socid) {
3501 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3502 }
3503
3504 $resql = $this->db->query($sql);
3505 if ($resql) {
3506 $commandestatic = new CommandeFournisseur($this->db);
3507
3508 $response = new WorkboardResponse();
3509 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3510 $response->label = $langs->trans("SuppliersOrdersToProcess");
3511 $response->labelShort = $langs->trans("Opened");
3512 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3513 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3514 $response->img = img_object('', "order");
3515
3516 if ($mode === 'awaiting') {
3517 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3518 $response->labelShort = $langs->trans("AwaitingReception");
3519 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3520 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3521 }
3522
3523 while ($obj = $this->db->fetch_object($resql)) {
3524 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3525 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3526 $commandestatic->statut = $obj->fk_statut; // deprecated
3527 $commandestatic->status = $obj->fk_statut;
3528
3529 $response->nbtodo++;
3530 $response->total += $obj->total_ht;
3531
3532 if ($commandestatic->hasDelay()) {
3533 $response->nbtodolate++;
3534 }
3535 }
3536
3537 return $response;
3538 } else {
3539 $this->error = $this->db->error();
3540 return -1;
3541 }
3542 }
3543
3550 public function getInputMethod()
3551 {
3552 global $langs;
3553
3554 if ($this->methode_commande_id > 0) {
3555 $sql = "SELECT rowid, code, libelle as label";
3556 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3557 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3558
3559 $resql = $this->db->query($sql);
3560 if ($resql) {
3561 if ($this->db->num_rows($resql)) {
3562 $obj = $this->db->fetch_object($resql);
3563
3564 $string = $langs->trans($obj->code);
3565 if ($string == $obj->code) {
3566 $string = $obj->label != '-' ? $obj->label : '';
3567 }
3568 return $string;
3569 }
3570 } else {
3571 dol_print_error($this->db);
3572 }
3573 }
3574
3575 return '';
3576 }
3577
3589 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3590 {
3591 global $langs;
3592
3593 if (!dol_strlen($modele)) {
3594 $modele = ''; // No doc template/generation by default
3595
3596 if (!empty($this->model_pdf)) {
3597 $modele = $this->model_pdf;
3598 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3599 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3600 }
3601 }
3602
3603 if (empty($modele)) {
3604 return 0;
3605 } else {
3606 $langs->load("suppliers");
3607 $outputlangs->load("products");
3608
3609 $modelpath = "core/modules/supplier_order/doc/";
3610 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3611 return $result;
3612 }
3613 }
3614
3621 public function getMaxDeliveryTimeDay($langs)
3622 {
3623 if (empty($this->lines)) {
3624 return '';
3625 }
3626
3627 $tmpproductfourn = new ProductFournisseur($this->db);
3628
3629 $nb = 0;
3630 foreach ($this->lines as $line) {
3631 if ($line->fk_product > 0) {
3632 // Load delivery_time_days, return id into product_fournisseur_price
3633 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3634 if ($idp > 0) {
3635 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3636 if ($tmpproductfourn->delivery_time_days > $nb) {
3637 $nb = $tmpproductfourn->delivery_time_days;
3638 }
3639 }
3640 }
3641 }
3642
3643 if ($nb === 0) {
3644 return '';
3645 } else {
3646 return $nb.' '.$langs->trans('days');
3647 }
3648 }
3649
3655 public function getRights()
3656 {
3657 global $user;
3658
3659 return $user->hasRight("fournisseur", "commande");
3660 }
3661
3662
3671 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3672 {
3673 $tables = array(
3674 'commande_fournisseur'
3675 );
3676
3677 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3678 }
3679
3688 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3689 {
3690 $tables = array(
3691 'commande_fournisseurdet'
3692 );
3693
3694 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3695 }
3696
3704 public function hasDelay()
3705 {
3706 global $conf;
3707
3708 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3709 $now = dol_now();
3710 if (!empty($this->delivery_date)) {
3711 $date_to_test = $this->delivery_date;
3712 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3713 } else {
3714 //$date_to_test = $this->date_commande;
3715 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3716 return false;
3717 }
3718 } else {
3719 $now = dol_now();
3720 $date_to_test = $this->date_commande;
3721
3722 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3723 }
3724 }
3725
3733 public function showDelay()
3734 {
3735 global $conf, $langs;
3736
3737 $langs->load('orders');
3738
3739 $text = '';
3740
3741 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3742 if (!empty($this->delivery_date)) {
3743 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3744 } else {
3745 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3746 }
3747 } else {
3748 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3749 }
3750 if ($text) {
3751 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3752 }
3753
3754 return $text;
3755 }
3756
3757
3766 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3767 {
3768 if (isModEnabled("supplier_order")) {
3769 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3770
3771 $qtydelivered = array();
3772 $qtywished = array();
3773
3774 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3775
3776 $filter = array('t.fk_element' => $this->id);
3777 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3778 $filter['t.status'] = 1; // Restrict to lines with status validated
3779 }
3780
3781 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3782 if ($ret < 0) {
3783 $this->error = $supplierorderdispatch->error;
3784 $this->errors = $supplierorderdispatch->errors;
3785 return $ret;
3786 } else {
3787 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3788 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3789 $date_liv = dol_now();
3790
3791 // Build array with quantity deliverd by product
3792 foreach ($supplierorderdispatch->lines as $line) {
3793 if (array_key_exists($line->fk_product, $qtydelivered)) {
3794 $qtydelivered[$line->fk_product] += $line->qty;
3795 } else {
3796 $qtydelivered[$line->fk_product] = $line->qty;
3797 }
3798 }
3799 foreach ($this->lines as $line) {
3800 // Free lines have no product and cannot be dispatched to stock.
3801 if (empty($line->fk_product)) {
3802 continue;
3803 }
3804 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3805 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $line->product_type > 0) {
3806 continue;
3807 }
3808 if (array_key_exists($line->fk_product, $qtywished)) {
3809 $qtywished[$line->fk_product] += $line->qty;
3810 } else {
3811 $qtywished[$line->fk_product] = $line->qty;
3812 }
3813 }
3814
3815 //Compare array
3816 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3817 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3818 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3819 //var_dump(array_keys($qtydelivered));
3820 //var_dump(array_keys($qtywished));
3821 //var_dump($diff_array);
3822 //var_dump($keysinwishednotindelivered);
3823 //var_dump($keysindeliverednotinwished);
3824 //exit;
3825
3826 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3827 if ($closeopenorder) {
3828 //$ret=$this->setStatus($user,5);
3829 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3830 if ($ret < 0) {
3831 return -1;
3832 }
3833 return 5;
3834 } else {
3835 //Diff => received partially
3836 //$ret=$this->setStatus($user,4);
3837 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3838 if ($ret < 0) {
3839 return -1;
3840 }
3841 return 4;
3842 }
3843 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3844 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3845
3846 $close = 0;
3847
3848 if (count($diff_array) > 0) {
3849 //there are some difference between the two arrays
3850
3851 //scan the array of results
3852 foreach ($diff_array as $key => $value) {
3853 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3854 if ($qtydelivered[$key] >= $qtywished[$key]) {
3855 $close++;
3856 }
3857 }
3858 }
3859
3860
3861 if ($close == count($diff_array)) {
3862 //all the products are received equal or more than the wished quantity
3863 if ($closeopenorder) {
3864 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3865 if ($ret < 0) {
3866 return -1;
3867 }
3868 return 5;
3869 } else {
3870 //Diff => received partially
3871 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3872 if ($ret < 0) {
3873 return -1;
3874 }
3875 return 4;
3876 }
3877 } else {
3878 //all the products are not received
3879 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3880 if ($ret < 0) {
3881 return -1;
3882 }
3883 return 4;
3884 }
3885 } else {
3886 //Diff => received partially
3887 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3888 if ($ret < 0) {
3889 return -1;
3890 }
3891 return 4;
3892 }
3893 }
3894 return 1;
3895 }
3896 }
3897 return 0;
3898 }
3899
3907 public function loadReceptions($filtre_statut = -1)
3908 {
3909 $this->receptions = array();
3910
3911 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3912
3913 $sql = 'SELECT cd.rowid, cd.fk_product,';
3914 $sql .= ' sum(cfd.qty) as qty';
3915 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3916 if ($filtre_statut >= 0) {
3917 $sql .= ' '.$this->db->prefix().'reception as e,';
3918 }
3919 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3920 $sql .= ' WHERE';
3921 if ($filtre_statut >= 0) {
3922 $sql .= ' cfd.fk_reception = e.rowid AND';
3923 }
3924 $sql .= ' cfd.fk_elementdet = cd.rowid';
3925 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3926 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3927 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3928 }
3929 if ($filtre_statut >= 0) {
3930 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3931 }
3932 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3933
3934 $resql = $this->db->query($sql);
3935 if ($resql) {
3936 $num = $this->db->num_rows($resql);
3937 $i = 0;
3938 while ($i < $num) {
3939 $obj = $this->db->fetch_object($resql);
3940 empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3941 $i++;
3942 }
3943 $this->db->free($resql);
3944
3945 return $num;
3946 } else {
3947 $this->error = $this->db->lasterror();
3948 return -1;
3949 }
3950 }
3951
3959 public function getKanbanView($option = '', $arraydata = null)
3960 {
3961 global $langs;
3962
3963 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3964
3965 $return = '<div class="box-flex-item box-flex-grow-zero">';
3966 $return .= '<div class="info-box info-box-sm">';
3967 $return .= '<span class="info-box-icon bg-infobox-action">';
3968 $return .= img_picto('', $this->picto);
3969 $return .= '</span>';
3970 $return .= '<div class="info-box-content">';
3971 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3972 if ($selected >= 0) {
3973 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3974 }
3975 if (!empty($arraydata['thirdparty'])) {
3976 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3977 }
3978 if (!empty($this->date)) {
3979 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3980 }
3981 if (!empty($this->total_ht)) {
3982 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
3983 $return .= ' '.$langs->trans("HT");
3984 $return .= '</span>';
3985 }
3986 if (method_exists($this, 'getLibStatut')) {
3987 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
3988 }
3989 if (property_exists($this, 'billed')) {
3990 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
3991 }
3992 $return .= '</div>';
3993 $return .= '</div>';
3994 $return .= '</div>';
3995 return $return;
3996 }
3997}
$object ref
Definition info.php:90
Class to manage table ReceptionLineBatch.
Class to manage predefined suppliers products.
const STATUS_CANCELED_AFTER_ORDER
Order canceled/never received.
const STATUS_RECEIVED_PARTIALLY
Received partially.
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set the planned delivery date.
updateFromCommandeClient($user, $idc, $comclientid)
Update a supplier order from a sales order.
getNomUrl($withpicto=0, $option='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=0)
Return clickable name (with picto eventually)
loadReceptions($filtre_statut=-1)
Load array this->receptions of lines of shipments with nb of products sent for each order line Note: ...
static replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a product id with another one.
$fields
'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortf...
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
info($id)
Charge les information d'ordre info dans l'objet facture.
const STATUS_CANCELED
Order canceled.
fetch_lines($only_product=0)
Load array lines.
getInputMethod()
Returns the translated input method of object (defined if $this->methode_commande_id > 0).
const STATUS_VALIDATED
Validated status.
const STATUS_RECEIVED_COMPLETELY
Received completely.
cancel($user, $idwarehouse=-1)
Cancel an approved order.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $fk_prod_fourn_price=0, $ref_supplier='', $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $type=0, $info_bits=0, $notrigger=0, $date_start=null, $date_end=null, $array_options=[], $fk_unit=null, $pu_ht_devise=0, $origin='', $origin_id=0, $rang=-1, $special_code=0)
Add order line.
setReopen(User $user)
Reopen supplier order.
loadStateBoard()
Load the indicators this->nb for the state board.
calcAndSetStatusDispatch(User $user, $closeopenorder=1, $comment='')
Calc status regarding to dispatched stock.
set_id_projet($user, $id_projet, $notrigger=0)
Set the id projet.
showDelay()
Show the customer delayed info.
getTooltipContentArray($params)
getTooltipContentArray
classifyUnBilled(User $user)
Classify not billed.
approve($user, $idwarehouse=0, $secondlevel=0)
Approve a supplier order.
dispatchProduct($user, $product, $qty, $entrepot, $price=0, $comment='', $eatby='', $sellby='', $batch='', $fk_commandefourndet=0, $notrigger=0, $fk_reception=0)
Save a receiving into the tracking table of receiving (receptiondet_batch) and add product into stock...
valid($user, $idwarehouse=0, $notrigger=0)
Validate an order.
create($user, $notrigger=0)
Create order with draft status.
update(User $user, $notrigger=0)
Update Supplier Order.
createFromClone(User $user, $socid=0, $notrigger=0)
Load an object from its id and create a new one in database.
updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0, $txlocaltax2=0, $price_base_type='HT', $info_bits=0, $type=0, $notrigger=0, $date_start=0, $date_end=0, $array_options=[], $fk_unit=null, $pu_ht_devise=0, $ref_supplier='')
Update line.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template model.
const STATUS_ORDERSENT
Order sent, shipment on process.
commande($user, $date, $methode, $comment='')
Submit a supplier order to supplier.
getRights()
Returns the rights used for this class.
load_board($user, $mode='opened')
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
deleteLine($idline, $notrigger=0)
Delete line.
getMaxDeliveryTimeDay($langs)
Return the max number delivery delay in day.
fetch($id, $ref='')
Get object and lines from database.
Livraison($user, $date, $type, $comment)
Set a delivery in database for this supplier order.
getDispachedLines($status=-1)
Return array of dispatched lines waiting to be approved for this order.
classifyBilled(User $user)
Class invoiced the supplier order.
initAsSpecimen()
Initialise an instance with random values.
const SOURCE_ID_REPLENISHMENT
The constant used into source field to track the order was generated by the replenishement feature.
setCategories($categories)
Sets object to given categories.
setStatus($user, $status)
Tag order with a particular status.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getLibStatut($mode=0)
Return label of the status of object.
hasDelay()
Is the supplier order delayed? We suppose a purchase ordered as late if a the purchase order has been...
LibStatut($status, $mode=0, $billed=0)
Return label of a status.
Class to manage line orders.
Class to manage customers orders.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
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.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
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.
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 Dolibarr database access.
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 predefined suppliers products.
Class to manage products or services.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
global $mysoc
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:64
dol_now($mode='gmt')
Return date for now.
getDolGlobalFloat($key, $default=0)
Return a Dolibarr global constant float value.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
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.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_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_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left 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 PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:469