dolibarr 24.0.2
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-2026 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';
46require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
47if (isModEnabled('productbatch')) {
48 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
49}
50
51
56{
57 use CommonSubtotal;
58
62 public $element = 'order_supplier';
63
67 public $table_element = 'commande_fournisseur';
68
72 public $table_element_line = 'commande_fournisseurdet';
73
77 public $class_element_line = 'CommandeFournisseurLigne';
78
82 public $fk_element = 'fk_commande';
83
87 public $picto = 'supplier_order';
88
93 public $restrictiononfksoc = 1;
94
98 protected $table_ref_field = 'ref';
99
103 public $id;
104
108 public $ref;
109
113 public $ref_supplier;
114
120 public $ref_fourn;
121
127 public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
128 // -> 7=Canceled/Never received -> (reopen) 3=Process running
129 // -> 6=Canceled -> (reopen) 2=Approved
130 // -> 9=Refused -> (reopen) 1=Validated
131 // Note: billed or not is on another field "billed"
132
136 public $billed;
137
141 public $socid;
142
146 public $fourn_id;
147
151 public $date;
152
156 public $date_valid;
157
161 public $date_approve;
162
167 public $date_approve2;
168
172 public $date_commande;
173
178 public $remise_percent;
182 public $methode_commande_id;
186 public $methode_commande;
187
191 public $delivery_date;
192
196 public $total_ht;
197
201 public $total_tva;
202
206 public $total_localtax1;
207
211 public $total_localtax2;
212
216 public $total_ttc;
217
221 public $source;
222
226 public $cond_reglement_id;
227
231 public $cond_reglement_code;
232
236 public $cond_reglement_label;
237
241 public $cond_reglement_doc;
242
247 public $deposit_percent;
248
252 public $fk_account;
253
257 public $mode_reglement_id;
258
262 public $mode_reglement_code;
263
267 public $mode_reglement;
268
272 public $user_author_id;
273
277 public $user_approve_id;
278
283 public $user_approve_id2;
284
288 public $refuse_note;
289
293 public $cancel_note;
294
298 public $extraparams = array();
299
303 public $lines = array();
304
308 public $line;
309
313 public $origin;
314
318 public $origin_id;
319
323 public $date_lim_reglement;
324
328 public $receptions = array();
329
330 // Multicurrency
334 public $fk_multicurrency;
335
339 public $multicurrency_code;
340
344 public $multicurrency_tx;
345
349 public $multicurrency_total_ht;
350
354 public $multicurrency_total_tva;
355
359 public $multicurrency_total_ttc;
360
388 public $fields = array(
389 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
390 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
391 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 35),
392 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
393 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
394 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
395 'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
396 'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
397 'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
398 'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'getDolGlobalInt("ORDER_DISABLE_DELIVERY_DATE") ? 0 : 1', 'visible' => 1, 'position' => 74),
399 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
400 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
401 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
402 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
403 'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
404 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
405 'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
406 'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
407 'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
408 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
409 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
410 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
411 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
412 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
413 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
414 'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
415 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
416 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 176),
417 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
418 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
419 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
420 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
421 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
422 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
423 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
424 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
425 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
426 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
427 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
428 'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
429 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
430 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
431 'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
432 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
433 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
434 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
435 );
436
437
441 const STATUS_DRAFT = 0;
442
447
452
457
462
467
472
477
481 const STATUS_REFUSED = 9;
482
483
488
494 public function __construct($db)
495 {
496 $this->db = $db;
497
498 $this->ismultientitymanaged = 1;
499 }
500
501
509 public function fetch($id, $ref = '')
510 {
511 // Check parameters
512 if (empty($id) && empty($ref)) {
513 return -1;
514 }
515
516 $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,";
517 $sql .= " c.localtax1, c.localtax2, ";
518 $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
519 $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,";
520 $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,";
521 $sql .= " c.fk_account,";
522 $sql .= " c.note_private, c.note_public, c.model_pdf, c.last_main_doc, c.extraparams, c.billed,";
523 $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
524 $sql .= " cm.libelle as methode_commande,";
525 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc, c.deposit_percent,";
526 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
527 $sql .= ', c.fk_incoterms, c.location_incoterms';
528 $sql .= ', c.last_main_doc';
529 $sql .= ', i.libelle as label_incoterms';
530 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
531 $sql .= " LEFT JOIN ".$this->db->prefix()."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
532 $sql .= " LEFT JOIN ".$this->db->prefix()."c_paiement as p ON c.fk_mode_reglement = p.id";
533 $sql .= " LEFT JOIN ".$this->db->prefix()."c_input_method as cm ON cm.rowid = c.fk_input_method";
534 $sql .= ' LEFT JOIN '.$this->db->prefix().'c_incoterms as i ON c.fk_incoterms = i.rowid';
535
536 if (empty($id)) {
537 $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
538 } else {
539 $sql .= " WHERE c.rowid=".((int) $id);
540 }
541
542 if ($ref) {
543 $sql .= " AND c.ref='".$this->db->escape($ref)."'";
544 }
545
546 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
547 $resql = $this->db->query($sql);
548 if ($resql) {
549 $obj = $this->db->fetch_object($resql);
550 if (!$obj) {
551 $this->error = 'Bill with id '.$id.' not found';
552 dol_syslog(get_class($this).'::fetch '.$this->error);
553 return 0;
554 }
555
556 $this->id = $obj->rowid;
557 $this->entity = $obj->entity;
558
559 $this->ref = $obj->ref;
560 $this->ref_supplier = $obj->ref_supplier;
561
562 $this->socid = $obj->fk_soc;
563 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
564
565 $this->fourn_id = $obj->fk_soc;
566 $this->statut = $obj->status; // deprecated
567 $this->status = $obj->status;
568 $this->billed = $obj->billed;
569 $this->last_main_doc = $obj->last_main_doc;
570 $this->user_author_id = $obj->user_author_id;
571 $this->user_validation_id = $obj->user_validation_id;
572 $this->user_approve_id = $obj->user_approve_id;
573 $this->user_approve_id2 = $obj->user_approve_id2;
574 $this->total_ht = $obj->total_ht;
575 $this->total_tva = $obj->total_tva;
576 $this->total_localtax1 = $obj->localtax1;
577 $this->total_localtax2 = $obj->localtax2;
578 $this->total_ttc = $obj->total_ttc;
579 $this->date_creation = $this->db->jdate($obj->date_creation);
580 $this->date_valid = $this->db->jdate($obj->date_valid);
581 $this->date_approve = $this->db->jdate($obj->date_approve);
582 $this->date_approve2 = $this->db->jdate($obj->date_approve2);
583 $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
584 if (isset($obj->date_commande)) {
585 $this->date = $this->date_commande;
586 } else {
587 $this->date = $this->date_creation;
588 }
589 $this->delivery_date = $this->db->jdate($obj->delivery_date);
590 $this->remise_percent = $obj->remise_percent;
591 $this->methode_commande_id = $obj->fk_input_method;
592 $this->methode_commande = $obj->methode_commande;
593
594 $this->source = $obj->source;
595 $this->fk_project = $obj->fk_project;
596 $this->cond_reglement_id = $obj->fk_cond_reglement;
597 $this->cond_reglement_code = $obj->cond_reglement_code;
598 $this->cond_reglement_label = $obj->cond_reglement_label;
599 $this->cond_reglement_doc = $obj->cond_reglement_doc;
600 $this->deposit_percent = $obj->deposit_percent;
601 $this->fk_account = $obj->fk_account;
602 $this->mode_reglement_id = $obj->fk_mode_reglement;
603 $this->mode_reglement_code = $obj->mode_reglement_code;
604 $this->mode_reglement = $obj->mode_reglement_libelle;
605 $this->note = $obj->note_private; // deprecated
606 $this->note_private = $obj->note_private;
607 $this->note_public = $obj->note_public;
608 $this->model_pdf = $obj->model_pdf;
609 $this->last_main_doc = $obj->last_main_doc;
610
611 //Incoterms
612 $this->fk_incoterms = $obj->fk_incoterms;
613 $this->location_incoterms = $obj->location_incoterms;
614 $this->label_incoterms = $obj->label_incoterms;
615
616 // Multicurrency
617 $this->fk_multicurrency = $obj->fk_multicurrency;
618 $this->multicurrency_code = $obj->multicurrency_code;
619 $this->multicurrency_tx = $obj->multicurrency_tx;
620 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
621 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
622 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
623
624 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
625
626 $this->db->free($resql);
627
628 // Retrieve all extrafield
629 // fetch optionals attributes and labels
630 $this->fetch_optionals();
631
632 // Lines
633 $result = $this->fetch_lines();
634
635 if ($result < 0) {
636 return -1;
637 } else {
638 return 1;
639 }
640 } else {
641 $this->error = $this->db->error()." sql=".$sql;
642 return -1;
643 }
644 }
645
646 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
653 public function fetch_lines($only_product = 0)
654 {
655 // phpcs:enable
656
657 $this->lines = array();
658
659 $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
660 $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice, l.subprice_ttc,";
661 $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
662 $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.info_bits, l.special_code, l.fk_parent_line, l.rang,";
663 $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, p.stockable_product,";
664 $sql .= " l.fk_unit, l.extraparams,";
665 $sql .= " l.date_start, l.date_end,";
666 $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';
667 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as l";
668 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON l.fk_product = p.rowid';
669 $sql .= " WHERE l.fk_commande = ".((int) $this->id);
670 if ($only_product) {
671 $sql .= ' AND p.fk_product_type = 0';
672 }
673 $sql .= " ORDER BY l.rang, l.rowid";
674 //print $sql;
675
676 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
677
678 $result = $this->db->query($sql);
679 if ($result) {
680 $num = $this->db->num_rows($result);
681 $i = 0;
682
683 while ($i < $num) {
684 $objp = $this->db->fetch_object($result);
685
686 $line = new CommandeFournisseurLigne($this->db);
687
688 $line->id = $objp->rowid;
689 $line->fk_commande = $objp->fk_commande;
690 $line->desc = $objp->description;
691 $line->description = $objp->description;
692 $line->qty = $objp->qty;
693 $line->tva_tx = $objp->tva_tx;
694 $line->localtax1_tx = $objp->localtax1_tx;
695 $line->localtax2_tx = $objp->localtax2_tx;
696 $line->localtax1_type = $objp->localtax1_type;
697 $line->localtax2_type = $objp->localtax2_type;
698 $line->subprice = $objp->subprice;
699 $line->pu_ht = $objp->subprice; // deprecated
700 $line->subprice_ttc = $objp->subprice_ttc;
701 $line->pu_ttc = $objp->subprice_ttc; // deprecated
702 $line->remise_percent = $objp->remise_percent;
703
704 $line->vat_src_code = $objp->vat_src_code;
705 $line->total_ht = $objp->total_ht;
706 $line->total_tva = $objp->total_tva;
707 $line->total_localtax1 = $objp->total_localtax1;
708 $line->total_localtax2 = $objp->total_localtax2;
709 $line->total_ttc = $objp->total_ttc;
710 $line->product_type = $objp->product_type;
711
712 $line->fk_product = $objp->fk_product;
713
714 $line->libelle = $objp->product_label; // deprecated
715 $line->product_label = $objp->product_label;
716 $line->product_desc = $objp->product_desc;
717 $line->product_tobatch = $objp->product_tobatch;
718 $line->product_barcode = $objp->product_barcode;
719 $line->stockable_product = $objp->stockable_product;
720
721 $line->ref = $objp->product_ref; // Ref of product
722 $line->product_ref = $objp->product_ref; // Ref of product
723 $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
724 $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
725
726 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
727 // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
728 // Move this into another method and call it when required.
729
730 // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
731 $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
732 $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('productsupplierprice').")";
733 $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
734 $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
735 $sqlsearchpackage .= " AND quantity <= ".abs((float) $objp->qty); // required to be qualified
736 $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".abs((float) $objp->qty).")"; // required to be qualified
737 $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
738 $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
739 $sqlsearchpackage .= " LIMIT 1";
740
741 $resqlsearchpackage = $this->db->query($sqlsearchpackage);
742 if ($resqlsearchpackage) {
743 $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
744 if ($objsearchpackage) {
745 $line->fk_fournprice = $objsearchpackage->rowid;
746 $line->packaging = (float) $objsearchpackage->packaging;
747 }
748 } else {
749 $this->error = $this->db->lasterror();
750 return -1;
751 }
752 }
753
754 $line->date_start = $this->db->jdate($objp->date_start);
755 $line->date_end = $this->db->jdate($objp->date_end);
756 $line->fk_unit = $objp->fk_unit;
757
758 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
759
760 // Multicurrency
761 $line->fk_multicurrency = $objp->fk_multicurrency;
762 $line->multicurrency_code = $objp->multicurrency_code;
763 $line->multicurrency_subprice = $objp->multicurrency_subprice;
764 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
765 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
766 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
767 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
768
769 $line->info_bits = $objp->info_bits;
770 $line->special_code = $objp->special_code;
771 $line->fk_parent_line = $objp->fk_parent_line;
772
773 $line->rang = $objp->rang;
774
775 // Retrieve all extrafield
776 // fetch optionals attributes and labels
777 $line->fetch_optionals();
778
779 $this->lines[$i] = $line;
780
781 $i++;
782 }
783 $this->db->free($result);
784
785 return $num;
786 } else {
787 $this->error = $this->db->error()." sql=".$sql;
788 return -1;
789 }
790 }
791
800 public function valid($user, $idwarehouse = 0, $notrigger = 0)
801 {
802 global $conf;
803 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
804
805 $error = 0;
806
807 dol_syslog(get_class($this)."::valid");
808 $result = 0;
809 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
810 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
811 $this->db->begin();
812
813 if (!getDolGlobalBool('SUPPLIER_ORDER_NOCHECK_ONBUY_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines('onbuy')) {
814 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
815 return -1;
816 }
817 // Definition of supplier order numbering model name
818 $soc = new Societe($this->db);
819 $soc->fetch((int) $this->fourn_id);
820
821 // Check if object has a temporary ref
822 if (preg_match('/^[\‍(]?PROV/i', (string) $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
823 $num = $this->getNextNumRef($soc);
824 } else {
825 $num = (string) $this->ref;
826 }
827 $this->newref = dol_sanitizeFileName($num);
828
829 $sql = 'UPDATE '.$this->db->prefix()."commande_fournisseur";
830 $sql .= " SET ref='".$this->db->escape($num)."',";
831 $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
832 $sql .= " date_valid='".$this->db->idate(dol_now())."',";
833 $sql .= " fk_user_valid = ".((int) $user->id);
834 $sql .= " WHERE rowid = ".((int) $this->id);
835 $sql .= " AND fk_statut = ".((int) self::STATUS_DRAFT);
836
837 $resql = $this->db->query($sql);
838 if (!$resql) {
839 dol_print_error($this->db);
840 $error++;
841 }
842
843 if (!$error && !$notrigger) {
844 // Call trigger
845 $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
846 if ($result < 0) {
847 $error++;
848 }
849 // End call triggers
850 }
851
852 if (!$error) {
853 $this->oldref = $this->ref;
854
855 // Rename directory if dir was a temporary ref
856 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
857 // Now we rename also files into index
858 $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)."'";
859 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
860 $resql = $this->db->query($sql);
861 if (!$resql) {
862 $error++;
863 $this->error = $this->db->lasterror();
864 }
865 $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
866 $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
867 $resql = $this->db->query($sql);
868 if (!$resql) {
869 $error++;
870 $this->error = $this->db->lasterror();
871 }
872
873 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
874 $oldref = dol_sanitizeFileName($this->ref);
875 $newref = dol_sanitizeFileName($num);
876 $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
877 $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
878 if (!$error && file_exists($dirsource)) {
879 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
880
881 if (@rename($dirsource, $dirdest)) {
882 dol_syslog("Rename ok");
883 // Rename docs starting with $oldref with $newref
884 $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
885 foreach ($listoffiles as $fileentry) {
886 $dirsource = $fileentry['name'];
887 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
888 $dirsource = $fileentry['path'].'/'.$dirsource;
889 $dirdest = $fileentry['path'].'/'.$dirdest;
890 @rename($dirsource, $dirdest);
891 }
892 }
893 }
894 }
895 }
896
897 if (!$error) {
898 $result = 1;
900 $this->statut = self::STATUS_VALIDATED; // deprecated
901 $this->ref = $num;
902 }
903
904 if (!$error) {
905 $this->db->commit();
906 return 1;
907 } else {
908 $this->db->rollback();
909 return -1;
910 }
911 } else {
912 $this->error = 'NotAuthorized';
913 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
914 return -1;
915 }
916 }
917
924 public function getLibStatut($mode = 0)
925 {
926 return $this->LibStatut(isset($this->status) ? $this->status : $this->statut, $mode, $this->billed);
927 }
928
929 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
938 public function LibStatut($status, $mode = 0, $billed = 0)
939 {
940 // phpcs:enable
941 global $langs, $hookmanager;
942
943 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
944 $langs->load('orders');
945
946 $this->labelStatus[0] = 'StatusSupplierOrderDraft';
947 $this->labelStatus[1] = 'StatusSupplierOrderValidated';
948 $this->labelStatus[2] = 'StatusSupplierOrderApproved';
949 if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
950 $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
951 } else {
952 $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
953 }
954 $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
955 $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
956 $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
957 $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
958 $this->labelStatus[9] = 'StatusSupplierOrderRefused';
959
960 // List of language codes for status
961 $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
962 $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
963 $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
964 $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
965 $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
966 $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
967 $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
968 $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
969 $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
970 }
971
972 $statustrans = array(
973 0 => 'status0',
974 1 => 'status1b',
975 2 => 'status1',
976 3 => 'status4',
977 4 => 'status4b',
978 5 => 'status6',
979 6 => 'status9',
980 7 => 'status9',
981 9 => 'status9',
982 );
983
984 $statusClass = 'status0';
985 if (!empty($statustrans[$status])) {
986 $statusClass = $statustrans[$status];
987 }
988
989 $billedtext = '';
990 if ($billed) {
991 $billedtext = ' - '.$langs->trans("Billed");
992 }
993 if ($status == 5 && $billed) {
994 $statusClass = 'status6';
995 }
996
997 $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]).$billedtext;
998 $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
999
1000 $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
1001 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
1002 if ($reshook > 0) {
1003 return $hookmanager->resPrint;
1004 }
1005
1006 return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
1007 }
1008
1015 public function getTooltipContentArray($params)
1016 {
1017 global $conf, $langs, $user;
1018
1019 $langs->loadLangs(['bills', 'orders']);
1020
1021 $datas = [];
1022 $nofetch = !empty($params['nofetch']);
1023
1024 if ($user->hasRight("fournisseur", "commande", "read")) {
1025 $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
1026 if ($this->status) {
1027 $datas['picto'] .= ' '.$this->getLibStatut(5);
1028 }
1029 if (!empty($this->ref)) {
1030 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1031 }
1032 if (!empty($this->ref_supplier)) {
1033 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
1034 }
1035 if (!$nofetch) {
1036 $langs->load('companies');
1037 if (empty($this->thirdparty)) {
1038 $this->fetch_thirdparty();
1039 }
1040 $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1041 }
1042 if (!empty($this->total_ht)) {
1043 $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1044 }
1045 if (!empty($this->total_tva)) {
1046 $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1047 }
1048 if (!empty($this->total_ttc)) {
1049 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1050 }
1051 if (!empty($this->date)) {
1052 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1053 }
1054 if (!empty($this->delivery_date)) {
1055 $langs->load("sendings");
1056 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
1057 }
1058 }
1059 return $datas;
1060 }
1061
1072 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1073 {
1074 global $langs, $user, $hookmanager;
1075
1076 $result = '';
1077 $params = [
1078 'id' => $this->id,
1079 'objecttype' => $this->element,
1080 'option' => $option,
1081 'nofetch' => 1
1082 ];
1083 $classfortooltip = 'classfortooltip';
1084 $dataparams = '';
1085 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1086 $classfortooltip = 'classforajaxtooltip';
1087 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1088 $label = '';
1089 } else {
1090 $label = implode($this->getTooltipContentArray($params));
1091 }
1092
1093 $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
1094
1095 if ($option !== 'nolink') {
1096 // Add param to save lastsearch_values or not
1097 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1098 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1099 $add_save_lastsearch_values = 1;
1100 }
1101 if ($add_save_lastsearch_values) {
1102 $url .= '&save_lastsearch_values=1';
1103 }
1104 }
1105
1106 $linkclose = '';
1107 if (empty($notooltip)) {
1108 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1109 $label = $langs->trans("ShowOrder");
1110 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1111 }
1112 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1113 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1114 }
1115
1116 $linkstart = '<a href="'.$url.'"';
1117 $linkstart .= $linkclose.'>';
1118 $linkend = '</a>';
1119
1120 $result .= $linkstart;
1121 if ($withpicto) {
1122 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1123 }
1124 if ($withpicto != 2) {
1125 $result .= $this->ref;
1126 }
1127 $result .= $linkend;
1128
1129 if ($addlinktonotes) {
1130 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1131 if ($txttoshow) {
1132 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1133 $result .= ' <span class="note inline-block">';
1134 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1135 $result .= img_picto('', 'note');
1136 $result .= '</a>';
1137 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1138 //$result.='</a>';
1139 $result .= '</span>';
1140 }
1141 }
1142
1143 global $action;
1144 $hookmanager->initHooks(array($this->element . 'dao'));
1145 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1146 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1147 if ($reshook > 0) {
1148 $result = $hookmanager->resPrint;
1149 } else {
1150 $result .= $hookmanager->resPrint;
1151 }
1152 return $result;
1153 }
1154
1155
1163 public function getNextNumRef($soc)
1164 {
1165 global $langs, $conf;
1166 $langs->load("orders");
1167
1168 if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1169 $mybool = false;
1170
1171 $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
1172 $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1173
1174 // Include file with class
1175 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1176
1177 foreach ($dirmodels as $reldir) {
1178 $dir = dol_buildpath($reldir."core/modules/supplier_order/");
1179
1180 // Load file with numbering class (if found)
1181 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1182 }
1183
1184 if (!$mybool) {
1185 dol_print_error(null, "Failed to include file ".$file);
1186 return '';
1187 }
1188
1189 $obj = new $classname();
1190 '@phan-var-force ModeleNumRefSuppliersOrders $obj';
1192 $numref = $obj->getNextValue($soc, $this);
1193
1194 if ($numref != "") {
1195 return $numref;
1196 } else {
1197 $this->error = $obj->error;
1198 return -1;
1199 }
1200 } else {
1201 $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1202 return -2;
1203 }
1204 }
1205
1212 public function classifyBilled(User $user)
1213 {
1214 $error = 0;
1215
1216 if ($this->billed) {
1217 return 0;
1218 }
1219
1220 $this->db->begin();
1221
1222 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 1';
1223 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1224
1225 if ($this->db->query($sql)) {
1226 // Call trigger
1227 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1228 if ($result < 0) {
1229 $error++;
1230 }
1231 // End call triggers
1232
1233 if (!$error) {
1234 $this->billed = 1;
1235
1236 $this->db->commit();
1237 return 1;
1238 } else {
1239 $this->db->rollback();
1240 return -1;
1241 }
1242 } else {
1243 dol_print_error($this->db);
1244
1245 $this->db->rollback();
1246 return -1;
1247 }
1248 }
1249
1250
1257 public function classifyUnBilled(User $user)
1258 {
1259 if (empty($this->billed)) {
1260 return 0;
1261 }
1262
1263 $this->db->begin();
1264
1265 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 0';
1266 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1267 ;
1268
1269 if (!$this->db->query($sql)) {
1270 dol_print_error($this->db);
1271 $this->db->rollback();
1272 return -1;
1273 }
1274
1275 // Call trigger
1276 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_UNBILLED', $user);
1277 if ($result < 0) {
1278 $this->db->rollback();
1279 return -1;
1280 }
1281 // End call triggers
1282
1283 $this->billed = 1;
1284 $this->db->commit();
1285 return 1;
1286 }
1287
1288
1297 public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1298 {
1299 global $langs;
1300
1301 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1302
1303 $error = 0;
1304
1305 dol_syslog(get_class($this)."::approve");
1306
1307 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1308 $now = dol_now();
1309
1310 $this->db->begin();
1311
1312 // Definition of order numbering model name
1313 $soc = new Societe($this->db);
1314 $soc->fetch((int) $this->fourn_id);
1315
1316 // Check if object has a temporary ref
1317 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1318 $num = $this->getNextNumRef($soc);
1319 } else {
1320 $num = (string) $this->ref;
1321 }
1322 $this->newref = dol_sanitizeFileName($num);
1323
1324 // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1325 $movetoapprovestatus = true;
1326 $comment = '';
1327
1328 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1329 $sql .= " SET ref='".$this->db->escape($num)."',";
1330 if (empty($secondlevel)) { // standard or first level approval
1331 $sql .= " date_approve='".$this->db->idate($now)."',";
1332 $sql .= " fk_user_approve = ".((int) $user->id);
1333 if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= getDolGlobalFloat('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED')) {
1334 if (empty($this->user_approve_id2)) {
1335 $movetoapprovestatus = false; // second level approval not done
1336 $comment = ' (first level)';
1337 }
1338 }
1339 } else { // request a second level approval
1340 $sql .= " date_approve2='".$this->db->idate($now)."',";
1341 $sql .= " fk_user_approve2 = ".((int) $user->id);
1342 if (empty($this->user_approve_id)) {
1343 $movetoapprovestatus = false; // first level approval not done
1344 }
1345 $comment = ' (second level)';
1346 }
1347 // If double approval is required and first approval, we keep status to 1 = validated
1348 if ($movetoapprovestatus) {
1349 $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1350 } else {
1351 $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1352 }
1353 $sql .= " WHERE rowid = ".((int) $this->id);
1354 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1355
1356 if ($this->db->query($sql)) {
1357 if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1358 $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1359 if ($result < 0 && $result != -2) { // -2 means already exists
1360 $error++;
1361 }
1362 }
1363
1364 // If stock is incremented on validate order, we must increment it
1365 if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1366 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1367 $langs->load("agenda");
1368
1369 $cpt = count($this->lines);
1370 for ($i = 0; $i < $cpt; $i++) {
1371 // Product with reference
1372 if ($this->lines[$i]->fk_product > 0) {
1373 $this->line = $this->lines[$i];
1374 $mouvP = new MouvementStock($this->db);
1375 $mouvP->origin = &$this;
1376 $mouvP->setOrigin($this->element, $this->id);
1377 // We decrement stock of product (and sub-products)
1378 $up_ht_disc = $this->lines[$i]->subprice;
1379 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1380 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1381 }
1382 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1383 if ($result < 0) {
1384 $error++;
1385 }
1386 unset($this->line);
1387 }
1388 }
1389 }
1390
1391 if (!$error) {
1392 // Call trigger
1393 $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1394 if ($result < 0) {
1395 $error++;
1396 }
1397 // End call triggers
1398 }
1399
1400 if (!$error) {
1401 $this->ref = $this->newref;
1402
1403 if ($movetoapprovestatus) {
1404 $this->statut = self::STATUS_ACCEPTED; // deprecated
1406 } else {
1407 $this->statut = self::STATUS_VALIDATED; // deprecated
1409 }
1410 if (empty($secondlevel)) { // standard or first level approval
1411 $this->date_approve = $now;
1412 $this->user_approve_id = $user->id;
1413 } else { // request a second level approval
1414 $this->date_approve2 = $now;
1415 $this->user_approve_id2 = $user->id;
1416 }
1417
1418 $this->db->commit();
1419 return 1;
1420 } else {
1421 $this->db->rollback();
1422 return -1;
1423 }
1424 } else {
1425 $this->db->rollback();
1426 $this->error = $this->db->lasterror();
1427 return -1;
1428 }
1429 } else {
1430 dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1431 }
1432 return -1;
1433 }
1434
1441 public function refuse($user)
1442 {
1443 $error = 0;
1444
1445 dol_syslog(get_class($this)."::refuse");
1446 $result = 0;
1447 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1448 $this->db->begin();
1449
1450 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1451 $sql .= " WHERE rowid = ".((int) $this->id);
1452
1453 if ($this->db->query($sql)) {
1454 $result = 0;
1455
1456 if ($error == 0) {
1457 // Call trigger
1458 $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1459 if ($result < 0) {
1460 $error++;
1461 $this->db->rollback();
1462 } else {
1463 $this->db->commit();
1464 }
1465 // End call triggers
1466 }
1467 } else {
1468 $this->db->rollback();
1469 $this->error = $this->db->lasterror();
1470 dol_syslog(get_class($this)."::refuse Error -1");
1471 $result = -1;
1472 }
1473 } else {
1474 dol_syslog(get_class($this)."::refuse Not Authorized");
1475 }
1476 return $result;
1477 }
1478
1479 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1488 public function cancel($user, $idwarehouse = -1)
1489 {
1490 // phpcs:enable
1491 $error = 0;
1492
1493 //dol_syslog("CommandeFournisseur::Cancel");
1494 $result = 0;
1495 if ($user->hasRight("fournisseur", "commande", "commander")) {
1496 $statut = self::STATUS_CANCELED;
1497
1498 $this->db->begin();
1499
1500 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".((int) $statut);
1501 $sql .= " WHERE rowid = ".((int) $this->id);
1502 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1503 if ($this->db->query($sql)) {
1504 $result = 0;
1505
1506 // Call trigger
1507 $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1508 if ($result < 0) {
1509 $error++;
1510 }
1511 // End call triggers
1512
1513 if ($error == 0) {
1514 $this->db->commit();
1515 return 1;
1516 } else {
1517 $this->db->rollback();
1518 return -1;
1519 }
1520 } else {
1521 $this->db->rollback();
1522 $this->error = $this->db->lasterror();
1523 dol_syslog(get_class($this)."::cancel ".$this->error);
1524 return -1;
1525 }
1526 } else {
1527 dol_syslog(get_class($this)."::cancel Not Authorized");
1528 return -1;
1529 }
1530 }
1531
1541 public function commande($user, $date, $methode, $comment = '')
1542 {
1543 global $langs;
1544 dol_syslog(get_class($this)."::commande");
1545 $error = 0;
1546 if ($user->hasRight("fournisseur", "commande", "commander")) {
1547 $this->db->begin();
1548
1549 $newnoteprivate = $this->note_private;
1550 if ($comment) {
1551 $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1552 }
1553
1554 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1555 $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".((int) $methode).", date_commande='".$this->db->idate($date)."', ";
1556 $sql .= " note_private='".$this->db->escape((string) $newnoteprivate)."'";
1557 $sql .= " WHERE rowid=".((int) $this->id);
1558
1559 dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1560 if ($this->db->query($sql)) {
1561 $this->statut = self::STATUS_ORDERSENT; // deprecated
1563 $this->methode_commande_id = $methode;
1564 $this->date_commande = $date;
1565 $this->context['comments'] = $comment;
1566
1567 // Call trigger
1568 $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1569 if ($result < 0) {
1570 $error++;
1571 }
1572 // End call triggers
1573 } else {
1574 $error++;
1575 $this->error = $this->db->lasterror();
1576 $this->errors[] = $this->db->lasterror();
1577 }
1578
1579 if (!$error) {
1580 $this->db->commit();
1581 } else {
1582 $this->db->rollback();
1583 }
1584 } else {
1585 $error++;
1586 $this->error = $langs->trans('NotAuthorized');
1587 $this->errors[] = $langs->trans('NotAuthorized');
1588 dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1589 }
1590
1591 return ($error ? -1 : 1);
1592 }
1593
1601 public function create($user, $notrigger = 0)
1602 {
1603 global $conf;
1604
1605 $this->db->begin();
1606
1607 $error = 0;
1608 $now = dol_now();
1609
1610 // set tmp vars
1611 $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1612 if (empty($date)) {
1613 $date = $now;
1614 }
1615 $delivery_date = $this->delivery_date;
1616
1617 // Clean parameters
1618 if (empty($this->source)) {
1619 $this->source = 0;
1620 }
1621
1622 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1623 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1624 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1625 } else {
1626 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1627 }
1628 if (empty($this->fk_multicurrency)) {
1629 $this->multicurrency_code = $conf->currency;
1630 $this->fk_multicurrency = 0;
1631 $this->multicurrency_tx = 1;
1632 }
1633 $this->entity = setEntity($this);
1634
1635 // We set order into draft status
1636 $this->statut = self::STATUS_DRAFT; // deprecated
1637 $this->status = self::STATUS_DRAFT;
1638
1639 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseur (";
1640 $sql .= "ref";
1641 $sql .= ", ref_supplier";
1642 $sql .= ", note_private";
1643 $sql .= ", note_public";
1644 $sql .= ", entity";
1645 $sql .= ", fk_soc";
1646 $sql .= ", fk_projet";
1647 $sql .= ", date_creation";
1648 $sql .= ", date_livraison";
1649 $sql .= ", fk_user_author";
1650 $sql .= ", fk_statut";
1651 $sql .= ", source";
1652 $sql .= ", model_pdf";
1653 $sql .= ", fk_mode_reglement";
1654 $sql .= ", deposit_percent";
1655 $sql .= ", fk_cond_reglement";
1656 $sql .= ", fk_account";
1657 $sql .= ", fk_incoterms, location_incoterms";
1658 $sql .= ", fk_multicurrency";
1659 $sql .= ", multicurrency_code";
1660 $sql .= ", multicurrency_tx";
1661 $sql .= ") ";
1662 $sql .= " VALUES (";
1663 $sql .= "'(PROV)'";
1664 $sql .= ", ".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "NULL");
1665 $sql .= ", '".$this->db->escape($this->note_private)."'";
1666 $sql .= ", '".$this->db->escape($this->note_public)."'";
1667 $sql .= ", ".((int) $this->entity);
1668 $sql .= ", ".((int) $this->socid);
1669 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1670 $sql .= ", '".$this->db->idate($date)."'";
1671 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1672 $sql .= ", ".((int) $user->id);
1673 $sql .= ", ".self::STATUS_DRAFT;
1674 $sql .= ", ".((int) $this->source);
1675 $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
1676 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'null');
1677 $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
1678 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'null');
1679 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1680 $sql .= ", ".(int) $this->fk_incoterms;
1681 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1682 $sql .= ", ".(int) $this->fk_multicurrency;
1683 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1684 $sql .= ", ".(float) $this->multicurrency_tx;
1685 $sql .= ")";
1686
1687 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1688 if ($this->db->query($sql)) {
1689 $this->id = $this->db->last_insert_id($this->db->prefix()."commande_fournisseur");
1690
1691 if ($this->id) {
1692 $num = count($this->lines);
1693
1694 // insert products details into database
1695 for ($i = 0; $i < $num; $i++) {
1696 $line = $this->lines[$i];
1697 if (!is_object($line)) {
1698 $line = (object) $line;
1699 }
1700
1701 //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1702
1703 // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1704 $result = $this->addline(
1705 (string) $line->desc,
1706 (float) $line->subprice,
1707 (float) $line->qty,
1708 $line->tva_tx,
1709 (float) $line->localtax1_tx,
1710 (float) $line->localtax2_tx,
1711 (int) $line->fk_product,
1712 0,
1713 (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
1714 (float) $line->remise_percent,
1715 'HT',
1716 (float) $line->subprice_ttc,
1717 (int) $line->product_type,
1718 (int) $line->info_bits,
1719 0,
1720 $line->date_start ?? null,
1721 $line->date_end ?? null,
1722 $line->array_options ?? [],
1723 $line->fk_unit ?? null,
1724 (float) $line->multicurrency_subprice, // pu_ht_devise
1725 (string) $line->origin, // origin
1726 (int) $line->origin_id, // origin_id
1727 (int) ($line->rang ?? -1), // rang
1728 (int) $line->special_code,
1729 isset($line->product_label) ? $line->product_label : ''
1730 );
1731 if ($result < 0) {
1732 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1733 $this->db->rollback();
1734 return -1;
1735 }
1736 }
1737
1738 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1739 $sql .= " SET ref='(PROV".((int) $this->id).")'";
1740 $sql .= " WHERE rowid=".((int) $this->id);
1741
1742 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1743 if ($this->db->query($sql)) {
1744 // Add link with price request and supplier order
1745 if ($this->id) {
1746 $this->ref = "(PROV".$this->id.")";
1747
1748 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1749 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1750 }
1751
1752 // Add object linked
1753 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1754 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1755 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, ...))
1756 foreach ($tmp_origin_id as $origin_id) {
1757 $ret = $this->add_object_linked($origin, $origin_id);
1758 if (!$ret) {
1759 dol_print_error($this->db);
1760 $error++;
1761 }
1762 }
1763 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1764 $origin_id = $tmp_origin_id;
1765 $ret = $this->add_object_linked($origin, $origin_id);
1766 if (!$ret) {
1767 dol_print_error($this->db);
1768 $error++;
1769 }
1770 }
1771 }
1772 }
1773 }
1774
1775 if (!$error) {
1776 $result = $this->insertExtraFields();
1777 if ($result < 0) {
1778 $error++;
1779 }
1780 }
1781
1782 if (!$error && !$notrigger) {
1783 // Call trigger
1784 $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1785 if ($result < 0) {
1786 $this->db->rollback();
1787
1788 return -1;
1789 }
1790 // End call triggers
1791 }
1792
1793 $this->db->commit();
1794 return $this->id;
1795 } else {
1796 $this->error = $this->db->lasterror();
1797 $this->db->rollback();
1798
1799 return -2;
1800 }
1801 } else {
1802 $this->error = 'Failed to get ID of inserted line';
1803
1804 return -1;
1805 }
1806 } else {
1807 $this->error = $this->db->lasterror();
1808 $this->db->rollback();
1809
1810 return -1;
1811 }
1812 }
1813
1821 public function update(User $user, $notrigger = 0)
1822 {
1823 $error = 0;
1824
1825 // Clean parameters
1826 if (isset($this->ref)) {
1827 $this->ref = trim($this->ref);
1828 }
1829 if (isset($this->ref_supplier)) {
1830 $this->ref_supplier = trim($this->ref_supplier);
1831 }
1832 if (isset($this->note_private)) {
1833 $this->note_private = trim($this->note_private);
1834 }
1835 if (isset($this->note_public)) {
1836 $this->note_public = trim($this->note_public);
1837 }
1838 if (isset($this->model_pdf)) {
1839 $this->model_pdf = trim($this->model_pdf);
1840 }
1841 if (isset($this->import_key)) {
1842 $this->import_key = trim($this->import_key);
1843 }
1844
1845 // Update request
1846 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1847
1848 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1849 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1850 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1851 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1852 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1853 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1854 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1855 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1856 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1857 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1858 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1859 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
1860 $sql .= " fk_user_author=".(isset($this->user_author_id) ? ((int) $this->user_author_id) : "null").",";
1861 $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? ((int) $this->user_validation_id) : "null").",";
1862 $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").",";
1863 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1864 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1865 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
1866 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1867 //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1868 $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").",";
1869 //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1870 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1871 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1872 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1873 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1874
1875 $sql .= " WHERE rowid=".((int) $this->id);
1876
1877 $this->db->begin();
1878
1879 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1880 $resql = $this->db->query($sql);
1881 if (!$resql) {
1882 $error++;
1883 $this->errors[] = "Error ".$this->db->lasterror();
1884 }
1885
1886 if (!$error) {
1887 $result = $this->insertExtraFields();
1888 if ($result < 0) {
1889 $error++;
1890 }
1891 }
1892
1893 if (!$error && !$notrigger) {
1894 // Call trigger
1895 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1896 if ($result < 0) {
1897 $error++;
1898 }
1899 // End call triggers
1900 }
1901
1902 // Commit or rollback
1903 if ($error) {
1904 foreach ($this->errors as $errmsg) {
1905 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1906 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1907 }
1908 $this->db->rollback();
1909 return -1 * $error;
1910 } else {
1911 $this->db->commit();
1912 return 1;
1913 }
1914 }
1915
1924 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1925 {
1926 global $conf, $user, $hookmanager;
1927
1928 $error = 0;
1929
1930 $this->db->begin();
1931
1932 // get extrafields so they will be clone
1933 foreach ($this->lines as $line) {
1934 $line->fetch_optionals();
1935 }
1936
1937 // Load source object
1938 $objFrom = clone $this;
1939
1940 // Change socid if needed
1941 if (!empty($socid) && $socid != $this->socid) {
1942 $objsoc = new Societe($this->db);
1943
1944 if ($objsoc->fetch($socid) > 0) {
1945 $this->socid = $objsoc->id;
1946 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1947 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1948 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1949 $this->fk_project = 0;
1950 $this->fk_delivery_address = 0;
1951 }
1952
1953 // TODO Change product price if multi-prices
1954 }
1955
1956 $this->id = 0;
1957 $this->statut = self::STATUS_DRAFT; // deprecated
1958 $this->status = self::STATUS_DRAFT;
1959
1960 // Clear fields
1961 $this->user_author_id = $user->id;
1962 $this->user_validation_id = 0;
1963
1964 $this->date = dol_now();
1965 $this->date_creation = 0;
1966 $this->date_validation = 0;
1967 $this->date_commande = null;
1968 $this->ref_supplier = '';
1969 $this->user_approve_id = 0;
1970 $this->user_approve_id2 = 0;
1971 $this->date_approve = 0;
1972 $this->date_approve2 = 0;
1973
1974 // Create clone
1975 $this->context['createfromclone'] = 'createfromclone';
1976 $result = $this->create($user, $notrigger);
1977 if ($result < 0) {
1978 $error++;
1979 }
1980
1981 if (!$error) {
1982 // Hook of thirdparty module
1983 if (is_object($hookmanager)) {
1984 $parameters = array('objFrom' => $objFrom);
1985 $action = '';
1986 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1987 if ($reshook < 0) {
1988 $this->setErrorsFromObject($hookmanager);
1989 $error++;
1990 }
1991 }
1992 }
1993
1994 unset($this->context['createfromclone']);
1995
1996 // End
1997 if (!$error) {
1998 $this->db->commit();
1999 return $this->id;
2000 } else {
2001 $this->db->rollback();
2002 return -1;
2003 }
2004 }
2005
2036 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, $label = '')
2037 {
2038 global $langs, $mysoc;
2039
2040 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");
2041 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2042
2043 if ($this->status == self::STATUS_DRAFT) {
2044 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2045
2046 // Clean parameters
2047 if (empty($qty)) {
2048 $qty = 0;
2049 }
2050 if (!$info_bits) {
2051 $info_bits = 0;
2052 }
2053 if (empty($txtva)) {
2054 $txtva = 0;
2055 }
2056 if (empty($rang)) {
2057 $rang = 0;
2058 }
2059 if (empty($txlocaltax1)) {
2060 $txlocaltax1 = 0;
2061 }
2062 if (empty($txlocaltax2)) {
2063 $txlocaltax2 = 0;
2064 }
2065 if (empty($remise_percent)) {
2066 $remise_percent = 0;
2067 }
2068
2069 $remise_percent = price2num($remise_percent);
2070 $qty = price2num($qty);
2071 $pu_ht = price2num($pu_ht);
2072 $pu_ht_devise = price2num($pu_ht_devise);
2073 $pu_ttc = price2num($pu_ttc);
2074 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2075 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2076 }
2077 $txlocaltax1 = price2num($txlocaltax1);
2078 $txlocaltax2 = price2num($txlocaltax2);
2079 if ($price_base_type == 'HT') {
2080 $pu = $pu_ht;
2081 } else {
2082 $pu = $pu_ttc;
2083 }
2084
2085 $label = trim((string) $label);
2086 $desc = trim((string) $desc);
2087 if ($desc === '' && $label !== '') {
2088 $desc = $label;
2089 }
2090
2091 // Check parameters
2092 if ($qty < 0 && !$fk_product) {
2093 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
2094 return -1;
2095 }
2096 if ($type < 0) {
2097 return -1;
2098 }
2099 if ($date_start && $date_end && $date_start > $date_end) {
2100 $langs->load("errors");
2101 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2102 return -1;
2103 }
2104
2105
2106 $this->db->begin();
2107
2108 $product_type = $type;
2109 $label = ''; // deprecated
2110
2111 if ($fk_product > 0) {
2112 if (getDolGlobalInt('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2113 // Check quantity is enough
2114 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);
2115 $prod = new ProductFournisseur($this->db);
2116 if ($prod->fetch($fk_product) > 0) {
2117 $product_type = $prod->type;
2118 $label = $prod->label;
2119
2120 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2121 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2122 $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
2123
2124 // If supplier order created from sales order, we take best supplier price
2125 // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2126 if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2127 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2128 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2129 // is remise percent not keyed but present for the product we add it
2130 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2131 $remise_percent = $prod->remise_percent;
2132 }
2133 }
2134 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2135 $langs->load("errors");
2136 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2137 $this->db->rollback();
2138 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2139 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2140 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2141 return -1;
2142 }
2143 if ($result == -1) {
2144 $langs->load("errors");
2145 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2146 $this->db->rollback();
2147 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2148 return -1;
2149 }
2150 if ($result < -1) {
2151 $this->error = $prod->error;
2152 $this->db->rollback();
2153 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2154 return -1;
2155 }
2156 } else {
2157 $this->error = $prod->error;
2158 $this->db->rollback();
2159 return -1;
2160 }
2161 }
2162
2163 // Predefine quantity according to packaging
2164 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2165 $prod = new Product($this->db);
2166 $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2167
2168 // Align messaging, type and float-safety with the customer-order path at commande.class.php:1720
2169 // (#38782 bugs 1, 2, 6).
2170 $newqty = $this->roundQtyToPackaging($qty, $prod->packaging);
2171 if ($newqty != $qty) {
2172 $qty = $newqty;
2173 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2174 }
2175
2176 // Enforce the supplier minimum purchase quantity on top of packaging
2177 // rounding. The packaging block above only ensures qty is a packaging
2178 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2179 // is below the supplier minimum, round qty_min itself up to the next
2180 // packaging multiple so we end up with the smallest valid order qty (#38783).
2181 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
2182 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2183 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2184 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2185 } else {
2186 $qty = (float) $prod->fourn_qty;
2187 }
2188 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2189 }
2190 }
2191 }
2192
2193 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2194 $pu = 0;
2195 }
2196
2197 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2198
2199 // Clean vat code
2200 $reg = array();
2201 $vat_src_code = '';
2202 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2203 $vat_src_code = $reg[1];
2204 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2205 }
2206
2207 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
2208 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
2209 // and this is done at the line level, which has its own VAT rate
2210
2211 $tabprice = calcul_price_total((float) $qty, $pu, (float) $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);
2212
2213 $total_ht = $tabprice[0];
2214 $total_tva = $tabprice[1];
2215 $total_ttc = $tabprice[2];
2216 $total_localtax1 = $tabprice[9];
2217 $total_localtax2 = $tabprice[10];
2218 $pu = $pu_ht = $tabprice[3];
2219 $pu_tva = $tabprice[4];
2220 $pu_ttc = $tabprice[5];
2221
2222 // MultiCurrency
2223 $multicurrency_total_ht = $tabprice[16];
2224 $multicurrency_total_tva = $tabprice[17];
2225 $multicurrency_total_ttc = $tabprice[18];
2226 $pu_ht_devise = $tabprice[19];
2227 $multicurrency_pu_ttc = $tabprice[21];
2228
2229 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2230 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2231
2232 if ($rang < 0) {
2233 $rangmax = $this->line_max();
2234 $rang = $rangmax + 1;
2235 }
2236
2237 // Insert line
2238 $this->line = new CommandeFournisseurLigne($this->db);
2239
2240 $this->line->context = $this->context;
2241
2242 $this->line->fk_commande = $this->id;
2243 $this->line->label = $label;
2244 $this->line->ref_fourn = $ref_supplier;
2245 $this->line->ref_supplier = $ref_supplier;
2246 $this->line->desc = $desc;
2247 $this->line->qty = $qty;
2248 $this->line->tva_tx = $txtva;
2249 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2250 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2251 $this->line->localtax1_type = $localtax1_type;
2252 $this->line->localtax2_type = $localtax2_type;
2253 $this->line->fk_product = $fk_product;
2254 $this->line->product_type = $product_type;
2255 $this->line->remise_percent = $remise_percent;
2256 $this->line->subprice = (float) $pu_ht;
2257 $this->line->subprice_ttc = (float) $pu_ttc;
2258
2259 $this->line->rang = $rang;
2260 $this->line->info_bits = $info_bits;
2261
2262 $this->line->vat_src_code = $vat_src_code;
2263 $this->line->total_ht = (float) $total_ht;
2264 $this->line->total_tva = (float) $total_tva;
2265 $this->line->total_localtax1 = (float) $total_localtax1;
2266 $this->line->total_localtax2 = (float) $total_localtax2;
2267 $this->line->total_ttc = (float) $total_ttc;
2268 $this->line->product_type = $type;
2269 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2270 $this->line->origin = $origin;
2271 $this->line->origin_type = $origin;
2272 $this->line->origin_id = $origin_id;
2273 $this->line->fk_unit = $fk_unit;
2274
2275 $this->line->date_start = $date_start;
2276 $this->line->date_end = $date_end;
2277
2278 // Multicurrency
2279 $this->line->fk_multicurrency = $this->fk_multicurrency;
2280 $this->line->multicurrency_code = $this->multicurrency_code;
2281 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2282 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2283 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2284 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2285 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2286
2287 $this->line->subprice = (float) $pu_ht;
2288 $this->line->price = $this->line->subprice;
2289
2290 $this->line->remise_percent = $remise_percent;
2291
2292 if (is_array($array_options) && count($array_options) > 0) {
2293 $this->line->array_options = $array_options;
2294 }
2295
2296 $result = $this->line->insert($notrigger);
2297 if ($result > 0) {
2298 // Update denormalized fields at the order level
2299 $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.
2300
2301 if ($result > 0) {
2302 if (!isset($this->context['createfromclone'])) {
2303 if (!empty($this->line->fk_parent_line)) {
2304 // Always reorder if child line
2305 $this->line_order(true, 'DESC');
2306 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2307 // Update all rank of all other lines starting from the same $ranktouse
2308 $linecount = count($this->lines);
2309 for ($ii = $rang; $ii <= $linecount; $ii++) {
2310 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2311 }
2312 }
2313
2314 $this->lines[] = $this->line;
2315 } else {
2316 foreach ($this->lines as $line) {
2317 if ($line->id == $origin_id) {
2318 $this->line->extraparams = $line->extraparams;
2319 $this->line->setExtraParameters();
2320 }
2321 }
2322 }
2323
2324 $this->db->commit();
2325 return $this->line->id;
2326 } else {
2327 $this->db->rollback();
2328 return -1;
2329 }
2330 } else {
2331 $this->setErrorsFromObject($this->line);
2332 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2333 $this->db->rollback();
2334 return -1;
2335 }
2336 }
2337 return -1;
2338 }
2339
2340
2358 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2359 {
2360 global $conf, $langs;
2361
2362 $error = 0;
2363 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2364
2365 // Check parameters (if test are wrong here, there is bug into caller)
2366 if ($entrepot <= 0) {
2367 $this->error = 'ErrorBadValueForParameterWarehouse';
2368 return -1;
2369 }
2370 if ($qty == 0) {
2371 $this->error = 'ErrorBadValueForParameterQty';
2372 return -1;
2373 }
2374
2375 $dispatchstatus = 1;
2376 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2377 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2378 }
2379
2380 $now = dol_now();
2381
2382 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2383
2384 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2385 $this->db->begin();
2386
2387 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2388 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2389 $sql .= " ('".((int) $this->id)."','".((int) $product)."','".((float) $qty)."',".($entrepot > 0 ? "'".((int) $entrepot)."'" : "null").",'".((int) $user->id)."','".$this->db->idate($now)."','".((int) $fk_commandefourndet)."', ".((int) $dispatchstatus).", '".$this->db->escape($comment)."', ";
2390 $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");
2391 $sql .= ")";
2392
2393 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2394 $resql = $this->db->query($sql);
2395 if ($resql) {
2396 if (!$notrigger) {
2397 global $conf, $langs, $user;
2398 // Call trigger
2399 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2400 if ($result < 0) {
2401 $error++;
2402 }
2403 // End call triggers
2404 }
2405 } else {
2406 $this->error = $this->db->lasterror();
2407 $error++;
2408 }
2409
2410 // If module stock is enabled and the stock increase is done on purchase order dispatching
2411 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2412 $mouv = new MouvementStock($this->db);
2413 if ($product > 0) {
2414 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2415 $mouv->origin = &$this;
2416 $mouv->setOrigin($this->element, $this->id);
2417
2418 // Method change if qty < 0
2419 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2420 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2421 } else {
2422 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2423 }
2424
2425 if ($result < 0) {
2426 $this->error = $mouv->error;
2427 $this->errors = $mouv->errors;
2428 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2429 $error++;
2430 }
2431 }
2432 }
2433
2434 if ($error == 0) {
2435 $this->db->commit();
2436 return 1;
2437 } else {
2438 $this->db->rollback();
2439 return -1;
2440 }
2441 } else {
2442 $this->error = 'BadStatusForObject';
2443 return -2;
2444 }
2445 }
2446
2454 public function deleteLine($idline, $notrigger = 0)
2455 {
2456 global $user;
2457
2458 if ($this->status == 0) {
2459 $line = new CommandeFournisseurLigne($this->db);
2460
2461 if ($line->fetch($idline) <= 0) {
2462 return 0;
2463 }
2464 if ($this->id > 0 && (int) $line->fk_commande !== (int) $this->id) {
2465 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2466 return -1;
2467 }
2468
2469 // check if not yet received
2470 $dispatchedLines = $this->getDispachedLines();
2471 foreach ($dispatchedLines as $dispatchLine) {
2472 if ($dispatchLine['orderlineid'] == $idline) {
2473 $this->error = "LineAlreadyDispatched";
2474 $this->errors[] = $this->error;
2475 return -3;
2476 }
2477 }
2478
2479 if ($line->delete($user, $notrigger) > 0) {
2480 $this->update_price(1);
2481 return 1;
2482 } else {
2483 $this->setErrorsFromObject($line);
2484 return -1;
2485 }
2486 } else {
2487 return -2;
2488 }
2489 }
2490
2498 public function delete(User $user, $notrigger = 0)
2499 {
2500 global $langs, $conf;
2501 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2502
2503 $error = 0;
2504
2505 $this->db->begin();
2506
2507 if (empty($notrigger)) {
2508 // Call trigger
2509 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2510 if ($result < 0) {
2511 $this->errors[] = 'ErrorWhenRunningTrigger';
2512 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2513 $this->db->rollback();
2514 return -1;
2515 }
2516 // End call triggers
2517 }
2518
2519 // Test we can delete
2520 $this->fetchObjectLinked(null, 'order_supplier');
2521 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2522 foreach ($this->linkedObjects['reception'] as $element) {
2523 if ($element->statut >= 0) {
2524 $this->errors[] = $langs->trans('ReceptionExist');
2525 $error++;
2526 break;
2527 }
2528 }
2529 }
2530
2531 // Remove linked categories.
2532 if (!$error) {
2533 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2534 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2535
2536 $result = $this->db->query($sql);
2537 if (!$result) {
2538 $error++;
2539 $this->errors[] = $this->db->lasterror();
2540 }
2541 }
2542
2543 $main = $this->db->prefix().'commande_fournisseurdet';
2544
2545 if (!$error) {
2546 $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).")";
2547 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2548 if (!$this->db->query($sql1)) {
2549 $error++;
2550 $this->error = $this->db->lasterror();
2551 $this->errors[] = $this->db->lasterror();
2552 }
2553 }
2554
2555 if (!$error) {
2556 $ef = $main."_extrafields";
2557 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2558 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2559 if (!$this->db->query($sql)) {
2560 $this->error = $this->db->lasterror();
2561 $this->errors[] = $this->db->lasterror();
2562 $error++;
2563 }
2564 }
2565
2566 if (!$error) {
2567 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2568 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2569 if (!$this->db->query($sql)) {
2570 $this->error = $this->db->lasterror();
2571 $this->errors[] = $this->db->lasterror();
2572 $error++;
2573 }
2574 }
2575
2576 if (!$error) {
2577 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2578 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2579 if ($resql = $this->db->query($sql)) {
2580 if ($this->db->affected_rows($resql) < 1) {
2581 $this->error = $this->db->lasterror();
2582 $this->errors[] = $this->db->lasterror();
2583 $error++;
2584 }
2585 } else {
2586 $this->error = $this->db->lasterror();
2587 $this->errors[] = $this->db->lasterror();
2588 $error++;
2589 }
2590 }
2591
2592 // Remove extrafields
2593 if (!$error) {
2594 $result = $this->deleteExtraFields();
2595 if ($result < 0) {
2596 $this->error = 'FailToDeleteExtraFields';
2597 $this->errors[] = 'FailToDeleteExtraFields';
2598 $error++;
2599 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2600 }
2601 }
2602
2603 // Delete linked object
2604 $res = $this->deleteObjectLinked();
2605 if ($res < 0) {
2606 $this->error = 'FailToDeleteObjectLinked';
2607 $this->errors[] = 'FailToDeleteObjectLinked';
2608 $error++;
2609 }
2610
2611 if (!$error) {
2612 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2613 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2614 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2615
2616 // We remove directory
2617 $ref = dol_sanitizeFileName($this->ref);
2618 if ($conf->fournisseur->commande->dir_output && !empty($ref)) {
2619 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2620 $file = $dir."/".$ref.".pdf";
2621 if (file_exists($file)) {
2622 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2623 $this->error = 'ErrorFailToDeleteFile';
2624 $this->errors[] = 'ErrorFailToDeleteFile';
2625 $error++;
2626 }
2627 }
2628 if (file_exists($dir)) {
2629 $res = @dol_delete_dir_recursive($dir);
2630 if (!$res) {
2631 $this->error = 'ErrorFailToDeleteDir';
2632 $this->errors[] = 'ErrorFailToDeleteDir';
2633 $error++;
2634 }
2635 }
2636 }
2637 }
2638
2639 if (!$error) {
2640 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2641 $this->db->commit();
2642 return 1;
2643 } else {
2644 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2645 $this->db->rollback();
2646 return -$error;
2647 }
2648 }
2649
2650
2659 public function getDispachedLines($status = -1)
2660 {
2661 $ret = array();
2662
2663 // List of already dispatched lines
2664 $sql = "SELECT p.ref, p.label,";
2665 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2666 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2667 $sql .= " FROM ".$this->db->prefix()."product as p,";
2668 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2669 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2670 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2671 $sql .= " AND cfd.fk_product = p.rowid";
2672 if ($status >= 0) {
2673 $sql .= " AND cfd.status = ".((int) $status);
2674 }
2675 $sql .= " ORDER BY cfd.rowid ASC";
2676
2677 $resql = $this->db->query($sql);
2678 if ($resql) {
2679 $num = $this->db->num_rows($resql);
2680 $i = 0;
2681
2682 while ($i < $num) {
2683 $objp = $this->db->fetch_object($resql);
2684 if ($objp) {
2685 $ret[] = array(
2686 'id' => $objp->dispatchedlineid,
2687 'productid' => $objp->fk_product,
2688 'warehouseid' => $objp->warehouse_id,
2689 'qty' => $objp->qty,
2690 'orderlineid' => $objp->fk_elementdet
2691 );
2692 }
2693
2694 $i++;
2695 }
2696 } else {
2697 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2698 }
2699
2700 return $ret;
2701 }
2702
2703 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2713 public function Livraison($user, $date, $type, $comment)
2714 {
2715 // phpcs:enable
2716 global $langs;
2717
2718 $result = 0;
2719 $error = 0;
2720 $dispatchedlinearray = array();
2721
2722 dol_syslog(get_class($this)."::Livraison");
2723
2724 $usercanreceive = 0;
2725 if (!isModEnabled('reception')) {
2726 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2727 } else {
2728 $usercanreceive = $user->hasRight("reception", "creer");
2729 }
2730
2731 if ($usercanreceive) {
2732 // Define the new status
2733 if ($type == 'par') {
2735 } elseif ($type == 'tot') {
2737 } elseif ($type == 'nev') {
2739 } elseif ($type == 'can') {
2741 } else {
2742 $error++;
2743 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2744 return -2;
2745 }
2746
2747 // Some checks to accept the record
2748 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2749 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2750 if ($type == 'tot') {
2751 $dispatchedlinearray = $this->getDispachedLines(0);
2752 if (count($dispatchedlinearray) > 0) {
2753 $result = -1;
2754 $error++;
2755 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2756 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2757 }
2758 }
2759 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)
2760 $dispatcheddenied = $this->getDispachedLines(2);
2761 if (count($dispatcheddenied) > 0) {
2762 $result = -1;
2763 $error++;
2764 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2765 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2766 }
2767 }
2768 }
2769
2770 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2771
2772 if (empty($error)) {
2773 $this->db->begin();
2774
2775 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2776 $sql .= " SET fk_statut = ".((int) $statut);
2777 $sql .= " WHERE rowid = ".((int) $this->id);
2778 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2779
2780 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2781 $resql = $this->db->query($sql);
2782 if ($resql) {
2783 $result = 1;
2784 $old_statut = $this->status;
2785 $this->status = $statut;
2786 $this->context['actionmsg2'] = $comment;
2787
2788 // Call trigger
2789 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2790 if ($result_trigger < 0) {
2791 $error++;
2792 }
2793 // End call triggers
2794
2795 if (empty($error)) {
2796 $this->db->commit();
2797 } else {
2798 $this->status = $old_statut;
2799 $this->db->rollback();
2800 $this->error = $this->db->lasterror();
2801 $result = -1;
2802 }
2803 } else {
2804 $this->db->rollback();
2805 $this->error = $this->db->lasterror();
2806 $result = -1;
2807 }
2808 }
2809 } else {
2810 $this->error = $langs->trans('NotAuthorized');
2811 $this->errors[] = $langs->trans('NotAuthorized');
2812 dol_syslog(get_class($this)."::Livraison Not Authorized");
2813 $result = -3;
2814 }
2815 return $result;
2816 }
2817
2818 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2828 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2829 {
2830 // phpcs:enable
2831 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2832 }
2833
2842 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2843 {
2844 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2845 $error = 0;
2846
2847 $this->db->begin();
2848
2849 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2850 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2851 $sql .= " WHERE rowid = ".((int) $this->id);
2852
2853 dol_syslog(__METHOD__, LOG_DEBUG);
2854 $resql = $this->db->query($sql);
2855 if (!$resql) {
2856 $this->errors[] = $this->db->error();
2857 $error++;
2858 }
2859
2860 if (!$error) {
2861 $this->oldcopy = clone $this;
2862 $this->delivery_date = $delivery_date;
2863 }
2864
2865 if (!$notrigger && empty($error)) {
2866 // Call trigger
2867 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2868 if ($result < 0) {
2869 $error++;
2870 }
2871 // End call triggers
2872 }
2873
2874 if (!$error) {
2875 $this->db->commit();
2876 return 1;
2877 } else {
2878 foreach ($this->errors as $errmsg) {
2879 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2880 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2881 }
2882 $this->db->rollback();
2883 return -1 * $error;
2884 }
2885 } else {
2886 return -2;
2887 }
2888 }
2889
2896 public function setReopen(User $user): int
2897 {
2898 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2899 if ($this->status == 1) {
2900 $newStatus = 0; // Validated->Draft
2901 } elseif ($this->status == 2) {
2902 $newStatus = 0; // Approved->Draft
2903 } elseif ($this->status == 3) {
2904 $newStatus = 2; // Ordered->Approved
2905 } elseif ($this->status == 4) {
2906 $newStatus = 3;
2907 } elseif ($this->status == 5) {
2908 //$newstatus=2; // Ordered
2909 // TODO Can we set it to submitted ?
2910 //$newstatus=3; // Submitted
2911 // TODO If there is at least one reception, we can set to Received->Received partially
2912 $newStatus = 4; // Received partially
2913 } elseif ($this->status == 6) {
2914 $newStatus = 2; // Canceled->Approved
2915 } elseif ($this->status == 7) {
2916 $newStatus = 3; // Canceled->Process running
2917 } elseif ($this->status == 9) {
2918 $newStatus = 1; // Refused->Validated
2919 } else {
2920 $newStatus = 2;
2921 }
2922
2923 $this->db->begin();
2924
2925 $result = $this->setStatus($user, $newStatus);
2926 if ($result > 0) {
2927 if ($newStatus == 0) {
2928 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2929 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2930 $sql .= ' WHERE rowid = '.((int) $this->id);
2931
2932 $this->db->query($sql);
2933 }
2934
2935 $this->db->commit();
2936
2937 return 1;
2938 } else {
2939 $this->db->rollback();
2940
2941 return -1;
2942 }
2943 }
2944
2945 return 0;
2946 }
2947
2948 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2957 public function set_id_projet($user, $id_projet, $notrigger = 0)
2958 {
2959 // phpcs:enable
2960 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2961 $error = 0;
2962
2963 $this->db->begin();
2964
2965 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2966 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2967 $sql .= " WHERE rowid = ".((int) $this->id);
2968
2969 dol_syslog(__METHOD__, LOG_DEBUG);
2970 $resql = $this->db->query($sql);
2971 if (!$resql) {
2972 $this->errors[] = $this->db->error();
2973 $error++;
2974 }
2975
2976 if (!$error) {
2977 $this->oldcopy = clone $this;
2978 $this->fk_projet = $id_projet;
2979 $this->fk_project = $id_projet;
2980 }
2981
2982 if (!$notrigger && empty($error)) {
2983 // Call trigger
2984 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2985 if ($result < 0) {
2986 $error++;
2987 }
2988 // End call triggers
2989 }
2990
2991 if (!$error) {
2992 $this->db->commit();
2993 return 1;
2994 } else {
2995 foreach ($this->errors as $errmsg) {
2996 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2997 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2998 }
2999 $this->db->rollback();
3000 return -1 * $error;
3001 }
3002 } else {
3003 return -2;
3004 }
3005 }
3006
3015 public function updateFromCommandeClient($user, $idc, $comclientid)
3016 {
3017 $comclient = new Commande($this->db);
3018 $comclient->fetch($comclientid);
3019
3020 $this->id = $idc;
3021
3022 $this->lines = array();
3023
3024 $num = count($comclient->lines);
3025 for ($i = 0; $i < $num; $i++) {
3026 $prod = new Product($this->db);
3027 $label = '';
3028 $ref = '';
3029 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3030 $label = $prod->label;
3031 $ref = (string) $prod->ref;
3032 }
3033
3034 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3035 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3036 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3037 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3038 $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);
3039 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3040 if ($this->db->query($sql)) {
3041 $this->update_price(1);
3042 }
3043 }
3044
3045 return 1;
3046 }
3047
3055 public function setStatus($user, $status)
3056 {
3057 $error = 0;
3058
3059 $this->db->begin();
3060
3061 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3062 $sql .= " SET fk_statut = ".((int) $status);
3063 $sql .= " WHERE rowid = ".((int) $this->id);
3064
3065 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3066 $resql = $this->db->query($sql);
3067 if ($resql) {
3068 // Trigger names for each status
3069 $triggerName = array();
3070 $triggerName[0] = 'DRAFT';
3071 $triggerName[1] = 'VALIDATED';
3072 $triggerName[2] = 'APPROVED';
3073 $triggerName[3] = 'ORDERED'; // Ordered
3074 $triggerName[4] = 'RECEIVED_PARTIALLY';
3075 $triggerName[5] = 'RECEIVED_COMPLETELY';
3076 $triggerName[6] = 'CANCELED';
3077 $triggerName[7] = 'CANCELED';
3078 $triggerName[9] = 'REFUSED';
3079
3080 // Call trigger
3081 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3082 if ($result < 0) {
3083 $error++;
3084 }
3085 // End call triggers
3086 } else {
3087 $error++;
3088 $this->error = $this->db->lasterror();
3089 dol_syslog(get_class($this)."::setStatus ".$this->error);
3090 }
3091
3092 if (!$error) {
3093 $this->status = $status;
3094 $this->db->commit();
3095 return 1;
3096 } else {
3097 $this->db->rollback();
3098 return -1;
3099 }
3100 }
3101
3113 public function setCategories($categories)
3114 {
3115 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3116 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3117 }
3118
3142 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 = '')
3143 {
3144 global $mysoc, $conf, $langs;
3145
3146 if (!$this->isLineOfObject($rowid)) {
3147 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
3148 return -1;
3149 }
3150
3151 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3152 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3153
3154 $error = 0;
3155
3156 if ($this->status == self::STATUS_DRAFT) {
3157 // Clean parameters
3158 if (empty($qty)) {
3159 $qty = 0;
3160 }
3161 if (empty($info_bits)) {
3162 $info_bits = 0;
3163 }
3164 if (empty($txtva)) {
3165 $txtva = 0;
3166 }
3167 if (empty($txlocaltax1)) {
3168 $txlocaltax1 = 0;
3169 }
3170 if (empty($txlocaltax2)) {
3171 $txlocaltax2 = 0;
3172 }
3173 if (empty($remise_percent)) {
3174 $remise_percent = 0;
3175 }
3176
3177 $remise_percent = (float) price2num($remise_percent);
3178 $qty = price2num($qty);
3179 if (!$qty) {
3180 $qty = 1;
3181 }
3182 $pu = price2num($pu);
3183 $pu_ht_devise = price2num($pu_ht_devise);
3184 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3185 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3186 }
3187 $txlocaltax1 = (float) price2num($txlocaltax1);
3188 $txlocaltax2 = (float) price2num($txlocaltax2);
3189
3190 // Check parameters
3191 if ($type < 0) {
3192 return -1;
3193 }
3194 if ($date_start && $date_end && $date_start > $date_end) {
3195 $langs->load("errors");
3196 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3197 return -1;
3198 }
3199
3200 $this->db->begin();
3201
3202 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3203 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3204 // and this is done at the line level, which has its own VAT rate
3205
3206 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3207
3208 // Clean vat code
3209 $reg = array();
3210 $vat_src_code = '';
3211 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3212 $vat_src_code = $reg[1];
3213 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3214 }
3215
3216 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
3217 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
3218 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3219 $tmpline = new CommandeFournisseurLigne($this->db);
3220 if ($tmpline->fetch($rowid) > 0) {
3221 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
3222 if ($newqty != $qty) {
3223 $qty = $newqty;
3224 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
3225 }
3226 }
3227 }
3228
3229 $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);
3230 $total_ht = $tabprice[0];
3231 $total_tva = $tabprice[1];
3232 $total_ttc = $tabprice[2];
3233 $total_localtax1 = $tabprice[9];
3234 $total_localtax2 = $tabprice[10];
3235 $pu_ht = $tabprice[3];
3236 $pu_tva = $tabprice[4];
3237 $pu_ttc = $tabprice[5];
3238
3239 // MultiCurrency
3240 $multicurrency_total_ht = $tabprice[16];
3241 $multicurrency_total_tva = $tabprice[17];
3242 $multicurrency_total_ttc = $tabprice[18];
3243 $pu_ht_devise = $tabprice[19];
3244 $multicurrency_pu_ttc = $tabprice[21];
3245
3246 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3247 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3248
3249 // Fetch current line from the database and then clone the object and set it in $oldline property
3250 $this->line = new CommandeFournisseurLigne($this->db);
3251 $this->line->fetch($rowid);
3252
3253 $oldline = clone $this->line;
3254 $this->line->oldline = $oldline;
3255
3256 $this->line->context = $this->context;
3257
3258 $this->line->fk_commande = $this->id;
3259 //$this->line->label=$label;
3260 $this->line->desc = $desc;
3261
3262
3263 $this->line->qty = $qty;
3264 $this->line->ref_supplier = $ref_supplier;
3265
3266 $this->line->vat_src_code = $vat_src_code;
3267 $this->line->tva_tx = $txtva;
3268 $this->line->localtax1_tx = $txlocaltax1;
3269 $this->line->localtax2_tx = $txlocaltax2;
3270 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3271 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3272 $this->line->remise_percent = $remise_percent;
3273 $this->line->subprice = (float) $pu_ht;
3274 $this->line->subprice_ttc = (float) $pu_ttc;
3275 $this->line->info_bits = $info_bits;
3276 $this->line->total_ht = (float) $total_ht;
3277 $this->line->total_tva = (float) $total_tva;
3278 $this->line->total_localtax1 = (float) $total_localtax1;
3279 $this->line->total_localtax2 = (float) $total_localtax2;
3280 $this->line->total_ttc = (float) $total_ttc;
3281 $this->line->product_type = $type;
3282 $this->line->special_code = $oldline->special_code;
3283 $this->line->rang = $oldline->rang;
3284 $this->line->origin = $this->origin;
3285 $this->line->fk_unit = $fk_unit;
3286
3287 $this->line->date_start = $date_start;
3288 $this->line->date_end = $date_end;
3289
3290 // Multicurrency
3291 $this->line->fk_multicurrency = $this->fk_multicurrency;
3292 $this->line->multicurrency_code = $this->multicurrency_code;
3293 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3294 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3295 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3296 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3297 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3298
3299 $this->line->subprice = (float) $pu_ht;
3300 $this->line->subprice_ttc = (float) $pu_ttc;
3301 $this->line->price = $this->line->subprice;
3302
3303 $this->line->remise_percent = $remise_percent;
3304
3305 if (is_array($array_options) && count($array_options) > 0) {
3306 // We replace values in this->line->array_options only for entries defined into $array_options
3307 foreach ($array_options as $key => $value) {
3308 $this->line->array_options[$key] = $array_options[$key];
3309 }
3310 }
3311
3312 $result = $this->line->update($notrigger);
3313
3314
3315 // Update denormalized information at the invoice level
3316 if ($result >= 0) {
3317 $this->update_price(1, 'auto');
3318 $this->db->commit();
3319 return $result;
3320 } else {
3321 $this->errors[] = $this->line->error;
3322 $this->errors = array_merge($this->errors, $this->line->errors);
3323 $this->error = $this->db->lasterror();
3324 $this->db->rollback();
3325 return -1;
3326 }
3327 } else {
3328 $this->error = "Order status makes operation forbidden";
3329 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3330 return -2;
3331 }
3332 }
3333
3334
3342 public function initAsSpecimen()
3343 {
3344 global $user, $langs, $conf;
3345
3346 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3347
3348 dol_syslog(get_class($this)."::initAsSpecimen");
3349
3350 $now = dol_now();
3351
3352 // Find first product
3353 $prodid = 0;
3354 $product = new ProductFournisseur($this->db);
3355 $sql = "SELECT rowid";
3356 $sql .= " FROM ".$this->db->prefix()."product";
3357 $sql .= " WHERE entity IN (".getEntity('product').")";
3358 $sql .= $this->db->order("rowid", "ASC");
3359 $sql .= $this->db->plimit(1);
3360 $resql = $this->db->query($sql);
3361 if ($resql && $this->db->num_rows($resql)) {
3362 $obj = $this->db->fetch_object($resql);
3363 $prodid = $obj->rowid;
3364 }
3365
3366 // Initialise parameters
3367 $this->id = 0;
3368 $this->ref = 'SPECIMEN';
3369 $this->specimen = 1;
3370 $this->socid = 1;
3371 $this->date = $now;
3372 $this->date_commande = $now;
3373 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3374 $this->cond_reglement_code = 'RECEP';
3375 $this->mode_reglement_code = 'CHQ';
3376
3377 $this->note_public = 'This is a comment (public)';
3378 $this->note_private = 'This is a comment (private)';
3379
3380 $this->multicurrency_tx = 1;
3381 $this->multicurrency_code = $conf->currency;
3382
3383 $this->statut = 0; // deprecated
3384 $this->status = 0;
3385
3386 // Lines
3387 $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)
3388 $xnbp = 0;
3389 while ($xnbp < $nbp) {
3390 $line = new CommandeFournisseurLigne($this->db);
3391 $line->desc = $langs->trans("Description")." ".$xnbp;
3392 $line->qty = 1;
3393 $line->subprice = 100;
3394 $line->tva_tx = 19.6;
3395 $line->localtax1_tx = 0;
3396 $line->localtax2_tx = 0;
3397 if ($xnbp == 2) {
3398 $line->total_ht = 50;
3399 $line->total_ttc = 59.8;
3400 $line->total_tva = 9.8;
3401 $line->remise_percent = 50;
3402 } else {
3403 $line->total_ht = 100;
3404 $line->total_ttc = 119.6;
3405 $line->total_tva = 19.6;
3406 $line->remise_percent = 00;
3407 }
3408 $line->fk_product = $prodid;
3409
3410 $this->lines[$xnbp] = $line;
3411
3412 $this->total_ht += $line->total_ht;
3413 $this->total_tva += $line->total_tva;
3414 $this->total_ttc += $line->total_ttc;
3415
3416 $xnbp++;
3417 }
3418
3419 return 1;
3420 }
3421
3428 public function info($id)
3429 {
3430 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3431 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3432 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3433 $sql .= ' WHERE c.rowid = '.((int) $id);
3434
3435 $result = $this->db->query($sql);
3436 if ($result) {
3437 if ($this->db->num_rows($result)) {
3438 $obj = $this->db->fetch_object($result);
3439
3440 $this->id = $obj->rowid;
3441
3442 $this->user_creation_id = $obj->fk_user_author;
3443 $this->user_validation_id = $obj->fk_user_valid;
3444 $this->user_modification_id = $obj->fk_user_modif;
3445 $this->user_approve_id = $obj->fk_user_approve;
3446 $this->user_approve_id2 = $obj->fk_user_approve2;
3447
3448 $this->date_creation = $this->db->jdate($obj->datec);
3449 $this->date_modification = $this->db->jdate($obj->datem);
3450 $this->date_approve = $this->db->jdate($obj->datea);
3451 $this->date_approve2 = $this->db->jdate($obj->datea2);
3452 $this->date_validation = $this->db->jdate($obj->date_validation);
3453 }
3454 $this->db->free($result);
3455 } else {
3456 dol_print_error($this->db);
3457 }
3458 }
3459
3465 public function loadStateBoard()
3466 {
3467 global $user;
3468
3469 $this->nb = array();
3470 $sanitizedclause = "WHERE";
3471
3472 $sql = "SELECT count(co.rowid) as nb";
3473 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3474 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3475 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3476 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3477 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3478 $sanitizedclause = "AND";
3479 }
3480 $sql .= " ".$sanitizedclause." co.entity IN (".getEntity('supplier_order').")";
3481
3482 $resql = $this->db->query($sql);
3483 if ($resql) {
3484 while ($obj = $this->db->fetch_object($resql)) {
3485 $this->nb["supplier_orders"] = $obj->nb;
3486 }
3487 $this->db->free($resql);
3488 return 1;
3489 } else {
3490 dol_print_error($this->db);
3491 $this->error = $this->db->error();
3492 return -1;
3493 }
3494 }
3495
3496 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3504 public function load_board($user, $mode = 'opened')
3505 {
3506 // phpcs:enable
3507 global $conf, $langs;
3508
3509 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3510 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3511 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3512 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3513 }
3514 $sql .= " WHERE c.entity = ".((int) $conf->entity);
3515 if ($mode === 'awaiting') {
3516 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3517 } else {
3518 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3519 }
3520 if ($user->socid) {
3521 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3522 }
3523
3524 $resql = $this->db->query($sql);
3525 if ($resql) {
3526 $commandestatic = new CommandeFournisseur($this->db);
3527
3528 $response = new WorkboardResponse();
3529 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3530 $response->label = $langs->trans("SuppliersOrdersToProcess");
3531 $response->labelShort = $langs->trans("Opened");
3532 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3533 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3534 $response->img = img_object('', "order");
3535
3536 if ($mode === 'awaiting') {
3537 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3538 $response->labelShort = $langs->trans("AwaitingReception");
3539 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3540 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3541 }
3542
3543 while ($obj = $this->db->fetch_object($resql)) {
3544 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3545 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3546 $commandestatic->statut = $obj->fk_statut; // deprecated
3547 $commandestatic->status = $obj->fk_statut;
3548
3549 $response->nbtodo++;
3550 $response->total += $obj->total_ht;
3551
3552 if ($commandestatic->hasDelay()) {
3553 $response->nbtodolate++;
3554 }
3555 }
3556
3557 return $response;
3558 } else {
3559 $this->error = $this->db->error();
3560 return -1;
3561 }
3562 }
3563
3570 public function getInputMethod()
3571 {
3572 global $langs;
3573
3574 if ($this->methode_commande_id > 0) {
3575 $sql = "SELECT rowid, code, libelle as label";
3576 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3577 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3578
3579 $resql = $this->db->query($sql);
3580 if ($resql) {
3581 if ($this->db->num_rows($resql)) {
3582 $obj = $this->db->fetch_object($resql);
3583
3584 $string = $langs->trans($obj->code);
3585 if ($string == $obj->code) {
3586 $string = $obj->label != '-' ? $obj->label : '';
3587 }
3588 return $string;
3589 }
3590 } else {
3591 dol_print_error($this->db);
3592 }
3593 }
3594
3595 return '';
3596 }
3597
3609 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3610 {
3611 global $langs;
3612
3613 if (!dol_strlen($modele)) {
3614 $modele = ''; // No doc template/generation by default
3615
3616 if (!empty($this->model_pdf)) {
3617 $modele = $this->model_pdf;
3618 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3619 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3620 }
3621 }
3622
3623 if (empty($modele)) {
3624 return 0;
3625 } else {
3626 $langs->load("suppliers");
3627 $outputlangs->load("products");
3628
3629 $modelpath = "core/modules/supplier_order/doc/";
3630 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3631 return $result;
3632 }
3633 }
3634
3641 public function getMaxDeliveryTimeDay($langs)
3642 {
3643 if (empty($this->lines)) {
3644 return '';
3645 }
3646
3647 $tmpproductfourn = new ProductFournisseur($this->db);
3648
3649 $nb = 0;
3650 foreach ($this->lines as $line) {
3651 if ($line->fk_product > 0) {
3652 // Load delivery_time_days, return id into product_fournisseur_price
3653 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3654 if ($idp > 0) {
3655 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3656 if ($tmpproductfourn->delivery_time_days > $nb) {
3657 $nb = $tmpproductfourn->delivery_time_days;
3658 }
3659 }
3660 }
3661 }
3662
3663 if ($nb === 0) {
3664 return '';
3665 } else {
3666 return $nb.' '.$langs->trans('days');
3667 }
3668 }
3669
3675 public function getRights()
3676 {
3677 global $user;
3678
3679 return $user->hasRight("fournisseur", "commande");
3680 }
3681
3682
3691 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3692 {
3693 $tables = array(
3694 'commande_fournisseur'
3695 );
3696
3697 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3698 }
3699
3708 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3709 {
3710 $tables = array(
3711 'commande_fournisseurdet'
3712 );
3713
3714 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3715 }
3716
3724 public function hasDelay()
3725 {
3726 global $conf;
3727
3728 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3729 $now = dol_now();
3730 if (!empty($this->delivery_date)) {
3731 $date_to_test = $this->delivery_date;
3732 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3733 } else {
3734 //$date_to_test = $this->date_commande;
3735 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3736 return false;
3737 }
3738 } else {
3739 $now = dol_now();
3740 $date_to_test = $this->date_commande;
3741
3742 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3743 }
3744 }
3745
3753 public function showDelay()
3754 {
3755 global $conf, $langs;
3756
3757 $langs->load('orders');
3758
3759 $text = '';
3760
3761 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3762 if (!empty($this->delivery_date)) {
3763 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3764 } else {
3765 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3766 }
3767 } else {
3768 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3769 }
3770 if ($text) {
3771 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3772 }
3773
3774 return $text;
3775 }
3776
3777
3786 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3787 {
3788 if (isModEnabled("supplier_order")) {
3789 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3790
3791 $qtydelivered = array();
3792 $qtywished = array();
3793
3794 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3795
3796 $filter = array('t.fk_element' => $this->id);
3797 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3798 $filter['t.status'] = 1; // Restrict to lines with status validated
3799 }
3800
3801 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3802 if ($ret < 0) {
3803 $this->error = $supplierorderdispatch->error;
3804 $this->errors = $supplierorderdispatch->errors;
3805 return $ret;
3806 } else {
3807 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3808 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3809 $date_liv = dol_now();
3810
3811 // Build array with quantity deliverd by product
3812 foreach ($supplierorderdispatch->lines as $line) {
3813 if (array_key_exists($line->fk_product, $qtydelivered)) {
3814 $qtydelivered[$line->fk_product] += $line->qty;
3815 } else {
3816 $qtydelivered[$line->fk_product] = $line->qty;
3817 }
3818 }
3819 foreach ($this->lines as $line) {
3820 // Free lines have no product and cannot be dispatched to stock.
3821 if (empty($line->fk_product)) {
3822 continue;
3823 }
3824 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3825 if ($line->product_type > 0 && (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') || empty($line->stockable_product))) {
3826 continue;
3827 }
3828 if (array_key_exists($line->fk_product, $qtywished)) {
3829 $qtywished[$line->fk_product] += $line->qty;
3830 } else {
3831 $qtywished[$line->fk_product] = $line->qty;
3832 }
3833 }
3834
3835 //Compare array
3836 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3837 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3838 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3839 //var_dump(array_keys($qtydelivered));
3840 //var_dump(array_keys($qtywished));
3841 //var_dump($diff_array);
3842 //var_dump($keysinwishednotindelivered);
3843 //var_dump($keysindeliverednotinwished);
3844 //exit;
3845
3846 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3847 if ($closeopenorder) {
3848 //$ret=$this->setStatus($user,5);
3849 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3850 if ($ret < 0) {
3851 return -1;
3852 }
3853 return 5;
3854 } else {
3855 //Diff => received partially
3856 //$ret=$this->setStatus($user,4);
3857 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3858 if ($ret < 0) {
3859 return -1;
3860 }
3861 return 4;
3862 }
3863 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3864 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3865
3866 $close = 0;
3867
3868 if (count($diff_array) > 0) {
3869 //there are some difference between the two arrays
3870
3871 //scan the array of results
3872 foreach ($diff_array as $key => $value) {
3873 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3874 if ($qtydelivered[$key] >= $qtywished[$key]) {
3875 $close++;
3876 }
3877 }
3878 }
3879
3880
3881 if ($close == count($diff_array)) {
3882 //all the products are received equal or more than the wished quantity
3883 if ($closeopenorder) {
3884 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3885 if ($ret < 0) {
3886 return -1;
3887 }
3888 return 5;
3889 } else {
3890 //Diff => received partially
3891 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3892 if ($ret < 0) {
3893 return -1;
3894 }
3895 return 4;
3896 }
3897 } else {
3898 //all the products are not received
3899 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3900 if ($ret < 0) {
3901 return -1;
3902 }
3903 return 4;
3904 }
3905 } else {
3906 //Diff => received partially
3907 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3908 if ($ret < 0) {
3909 return -1;
3910 }
3911 return 4;
3912 }
3913 }
3914 return 1;
3915 }
3916 }
3917 return 0;
3918 }
3919
3927 public function loadReceptions($filtre_statut = -1)
3928 {
3929 $this->receptions = array();
3930
3931 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3932
3933 $sql = 'SELECT cd.rowid, cd.fk_product,';
3934 $sql .= ' sum(cfd.qty) as qty';
3935 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3936 if ($filtre_statut >= 0) {
3937 $sql .= ' '.$this->db->prefix().'reception as e,';
3938 }
3939 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3940 $sql .= ' WHERE';
3941 if ($filtre_statut >= 0) {
3942 $sql .= ' cfd.fk_reception = e.rowid AND';
3943 }
3944 $sql .= ' cfd.fk_elementdet = cd.rowid';
3945 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3946 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3947 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3948 }
3949 if ($filtre_statut >= 0) {
3950 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3951 }
3952 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3953
3954 $resql = $this->db->query($sql);
3955 if ($resql) {
3956 $num = $this->db->num_rows($resql);
3957 $i = 0;
3958 while ($i < $num) {
3959 $obj = $this->db->fetch_object($resql);
3960 if (empty($this->receptions[$obj->rowid])) {
3961 $this->receptions[$obj->rowid] = (float) $obj->qty;
3962 } else {
3963 $this->receptions[$obj->rowid] += (float) $obj->qty;
3964 }
3965 $i++;
3966 }
3967 $this->db->free($resql);
3968
3969 return $num;
3970 } else {
3971 $this->error = $this->db->lasterror();
3972 return -1;
3973 }
3974 }
3975
3983 public function getKanbanView($option = '', $arraydata = null)
3984 {
3985 global $langs;
3986
3987 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3988
3989 $return = '<div class="box-flex-item box-flex-grow-zero">';
3990 $return .= '<div class="info-box info-box-sm">';
3991 $return .= '<span class="info-box-icon bg-infobox-action">';
3992 $return .= img_picto('', $this->picto);
3993 $return .= '</span>';
3994 $return .= '<div class="info-box-content">';
3995 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3996 if ($selected >= 0) {
3997 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3998 }
3999 if (!empty($arraydata['thirdparty'])) {
4000 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
4001 }
4002 if (!empty($this->date)) {
4003 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
4004 }
4005 if (!empty($this->total_ht)) {
4006 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
4007 $return .= ' '.$langs->trans("HT");
4008 $return .= '</span>';
4009 }
4010 if (method_exists($this, 'getLibStatut')) {
4011 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
4012 }
4013 if (property_exists($this, 'billed')) {
4014 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
4015 }
4016 $return .= '</div>';
4017 $return .= '</div>';
4018 $return .= '</div>';
4019 return $return;
4020 }
4021}
$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.
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.
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, $label='')
Add order line.
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:169
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
$date_start
Variables from include:
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)
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.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
dol_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...
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492