dolibarr 24.0.1
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 <= ".((float) $objp->qty); // required to be qualified
736 $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((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 if (abs((float) $qty) < $prod->packaging) {
2171 $qty = (float) $prod->packaging;
2172 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2173 } else {
2174 if (!empty($prod->packaging) && (float) price2num(fmod((float) $qty, (float) $prod->packaging), 'MS')) {
2175 $coeff = intval(abs((float) $qty) / $prod->packaging) + 1;
2176 $qty = price2num((float) $prod->packaging * $coeff, 'MS');
2177 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2178 }
2179 }
2180
2181 // Enforce the supplier minimum purchase quantity on top of packaging
2182 // rounding. The packaging block above only ensures qty is a packaging
2183 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2184 // is below the supplier minimum, round qty_min itself up to the next
2185 // packaging multiple so we end up with the smallest valid order qty (#38783).
2186 if (!empty($prod->fourn_qty) && abs((float) $qty) < (float) $prod->fourn_qty) {
2187 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2188 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2189 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2190 } else {
2191 $qty = (float) $prod->fourn_qty;
2192 }
2193 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2194 }
2195 }
2196 }
2197
2198 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2199 $pu = 0;
2200 }
2201
2202 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2203
2204 // Clean vat code
2205 $reg = array();
2206 $vat_src_code = '';
2207 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2208 $vat_src_code = $reg[1];
2209 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2210 }
2211
2212 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
2213 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
2214 // and this is done at the line level, which has its own VAT rate
2215
2216 $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);
2217
2218 $total_ht = $tabprice[0];
2219 $total_tva = $tabprice[1];
2220 $total_ttc = $tabprice[2];
2221 $total_localtax1 = $tabprice[9];
2222 $total_localtax2 = $tabprice[10];
2223 $pu = $pu_ht = $tabprice[3];
2224 $pu_tva = $tabprice[4];
2225 $pu_ttc = $tabprice[5];
2226
2227 // MultiCurrency
2228 $multicurrency_total_ht = $tabprice[16];
2229 $multicurrency_total_tva = $tabprice[17];
2230 $multicurrency_total_ttc = $tabprice[18];
2231 $pu_ht_devise = $tabprice[19];
2232 $multicurrency_pu_ttc = $tabprice[21];
2233
2234 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2235 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2236
2237 if ($rang < 0) {
2238 $rangmax = $this->line_max();
2239 $rang = $rangmax + 1;
2240 }
2241
2242 // Insert line
2243 $this->line = new CommandeFournisseurLigne($this->db);
2244
2245 $this->line->context = $this->context;
2246
2247 $this->line->fk_commande = $this->id;
2248 $this->line->label = $label;
2249 $this->line->ref_fourn = $ref_supplier;
2250 $this->line->ref_supplier = $ref_supplier;
2251 $this->line->desc = $desc;
2252 $this->line->qty = $qty;
2253 $this->line->tva_tx = $txtva;
2254 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2255 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2256 $this->line->localtax1_type = $localtax1_type;
2257 $this->line->localtax2_type = $localtax2_type;
2258 $this->line->fk_product = $fk_product;
2259 $this->line->product_type = $product_type;
2260 $this->line->remise_percent = $remise_percent;
2261 $this->line->subprice = (float) $pu_ht;
2262 $this->line->subprice_ttc = (float) $pu_ttc;
2263
2264 $this->line->rang = $rang;
2265 $this->line->info_bits = $info_bits;
2266
2267 $this->line->vat_src_code = $vat_src_code;
2268 $this->line->total_ht = (float) $total_ht;
2269 $this->line->total_tva = (float) $total_tva;
2270 $this->line->total_localtax1 = (float) $total_localtax1;
2271 $this->line->total_localtax2 = (float) $total_localtax2;
2272 $this->line->total_ttc = (float) $total_ttc;
2273 $this->line->product_type = $type;
2274 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2275 $this->line->origin = $origin;
2276 $this->line->origin_type = $origin;
2277 $this->line->origin_id = $origin_id;
2278 $this->line->fk_unit = $fk_unit;
2279
2280 $this->line->date_start = $date_start;
2281 $this->line->date_end = $date_end;
2282
2283 // Multicurrency
2284 $this->line->fk_multicurrency = $this->fk_multicurrency;
2285 $this->line->multicurrency_code = $this->multicurrency_code;
2286 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2287 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2288 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2289 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2290 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2291
2292 $this->line->subprice = (float) $pu_ht;
2293 $this->line->price = $this->line->subprice;
2294
2295 $this->line->remise_percent = $remise_percent;
2296
2297 if (is_array($array_options) && count($array_options) > 0) {
2298 $this->line->array_options = $array_options;
2299 }
2300
2301 $result = $this->line->insert($notrigger);
2302 if ($result > 0) {
2303 // Update denormalized fields at the order level
2304 $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.
2305
2306 if ($result > 0) {
2307 if (!isset($this->context['createfromclone'])) {
2308 if (!empty($this->line->fk_parent_line)) {
2309 // Always reorder if child line
2310 $this->line_order(true, 'DESC');
2311 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2312 // Update all rank of all other lines starting from the same $ranktouse
2313 $linecount = count($this->lines);
2314 for ($ii = $rang; $ii <= $linecount; $ii++) {
2315 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2316 }
2317 }
2318
2319 $this->lines[] = $this->line;
2320 } else {
2321 foreach ($this->lines as $line) {
2322 if ($line->id == $origin_id) {
2323 $this->line->extraparams = $line->extraparams;
2324 $this->line->setExtraParameters();
2325 }
2326 }
2327 }
2328
2329 $this->db->commit();
2330 return $this->line->id;
2331 } else {
2332 $this->db->rollback();
2333 return -1;
2334 }
2335 } else {
2336 $this->setErrorsFromObject($this->line);
2337 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2338 $this->db->rollback();
2339 return -1;
2340 }
2341 }
2342 return -1;
2343 }
2344
2345
2363 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2364 {
2365 global $conf, $langs;
2366
2367 $error = 0;
2368 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2369
2370 // Check parameters (if test are wrong here, there is bug into caller)
2371 if ($entrepot <= 0) {
2372 $this->error = 'ErrorBadValueForParameterWarehouse';
2373 return -1;
2374 }
2375 if ($qty == 0) {
2376 $this->error = 'ErrorBadValueForParameterQty';
2377 return -1;
2378 }
2379
2380 $dispatchstatus = 1;
2381 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2382 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2383 }
2384
2385 $now = dol_now();
2386
2387 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2388
2389 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2390 $this->db->begin();
2391
2392 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2393 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2394 $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)."', ";
2395 $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");
2396 $sql .= ")";
2397
2398 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2399 $resql = $this->db->query($sql);
2400 if ($resql) {
2401 if (!$notrigger) {
2402 global $conf, $langs, $user;
2403 // Call trigger
2404 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2405 if ($result < 0) {
2406 $error++;
2407 }
2408 // End call triggers
2409 }
2410 } else {
2411 $this->error = $this->db->lasterror();
2412 $error++;
2413 }
2414
2415 // If module stock is enabled and the stock increase is done on purchase order dispatching
2416 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2417 $mouv = new MouvementStock($this->db);
2418 if ($product > 0) {
2419 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2420 $mouv->origin = &$this;
2421 $mouv->setOrigin($this->element, $this->id);
2422
2423 // Method change if qty < 0
2424 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2425 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2426 } else {
2427 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2428 }
2429
2430 if ($result < 0) {
2431 $this->error = $mouv->error;
2432 $this->errors = $mouv->errors;
2433 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2434 $error++;
2435 }
2436 }
2437 }
2438
2439 if ($error == 0) {
2440 $this->db->commit();
2441 return 1;
2442 } else {
2443 $this->db->rollback();
2444 return -1;
2445 }
2446 } else {
2447 $this->error = 'BadStatusForObject';
2448 return -2;
2449 }
2450 }
2451
2459 public function deleteLine($idline, $notrigger = 0)
2460 {
2461 global $user;
2462
2463 if ($this->status == 0) {
2464 $line = new CommandeFournisseurLigne($this->db);
2465
2466 if ($line->fetch($idline) <= 0) {
2467 return 0;
2468 }
2469
2470 // check if not yet received
2471 $dispatchedLines = $this->getDispachedLines();
2472 foreach ($dispatchedLines as $dispatchLine) {
2473 if ($dispatchLine['orderlineid'] == $idline) {
2474 $this->error = "LineAlreadyDispatched";
2475 $this->errors[] = $this->error;
2476 return -3;
2477 }
2478 }
2479
2480 if ($line->delete($user, $notrigger) > 0) {
2481 $this->update_price(1);
2482 return 1;
2483 } else {
2484 $this->setErrorsFromObject($line);
2485 return -1;
2486 }
2487 } else {
2488 return -2;
2489 }
2490 }
2491
2499 public function delete(User $user, $notrigger = 0)
2500 {
2501 global $langs, $conf;
2502 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2503
2504 $error = 0;
2505
2506 $this->db->begin();
2507
2508 if (empty($notrigger)) {
2509 // Call trigger
2510 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2511 if ($result < 0) {
2512 $this->errors[] = 'ErrorWhenRunningTrigger';
2513 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2514 $this->db->rollback();
2515 return -1;
2516 }
2517 // End call triggers
2518 }
2519
2520 // Test we can delete
2521 $this->fetchObjectLinked(null, 'order_supplier');
2522 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2523 foreach ($this->linkedObjects['reception'] as $element) {
2524 if ($element->statut >= 0) {
2525 $this->errors[] = $langs->trans('ReceptionExist');
2526 $error++;
2527 break;
2528 }
2529 }
2530 }
2531
2532 // Remove linked categories.
2533 if (!$error) {
2534 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2535 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2536
2537 $result = $this->db->query($sql);
2538 if (!$result) {
2539 $error++;
2540 $this->errors[] = $this->db->lasterror();
2541 }
2542 }
2543
2544 $main = $this->db->prefix().'commande_fournisseurdet';
2545
2546 if (!$error) {
2547 $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).")";
2548 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2549 if (!$this->db->query($sql1)) {
2550 $error++;
2551 $this->error = $this->db->lasterror();
2552 $this->errors[] = $this->db->lasterror();
2553 }
2554 }
2555
2556 if (!$error) {
2557 $ef = $main."_extrafields";
2558 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2559 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2560 if (!$this->db->query($sql)) {
2561 $this->error = $this->db->lasterror();
2562 $this->errors[] = $this->db->lasterror();
2563 $error++;
2564 }
2565 }
2566
2567 if (!$error) {
2568 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2569 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2570 if (!$this->db->query($sql)) {
2571 $this->error = $this->db->lasterror();
2572 $this->errors[] = $this->db->lasterror();
2573 $error++;
2574 }
2575 }
2576
2577 if (!$error) {
2578 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2579 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2580 if ($resql = $this->db->query($sql)) {
2581 if ($this->db->affected_rows($resql) < 1) {
2582 $this->error = $this->db->lasterror();
2583 $this->errors[] = $this->db->lasterror();
2584 $error++;
2585 }
2586 } else {
2587 $this->error = $this->db->lasterror();
2588 $this->errors[] = $this->db->lasterror();
2589 $error++;
2590 }
2591 }
2592
2593 // Remove extrafields
2594 if (!$error) {
2595 $result = $this->deleteExtraFields();
2596 if ($result < 0) {
2597 $this->error = 'FailToDeleteExtraFields';
2598 $this->errors[] = 'FailToDeleteExtraFields';
2599 $error++;
2600 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2601 }
2602 }
2603
2604 // Delete linked object
2605 $res = $this->deleteObjectLinked();
2606 if ($res < 0) {
2607 $this->error = 'FailToDeleteObjectLinked';
2608 $this->errors[] = 'FailToDeleteObjectLinked';
2609 $error++;
2610 }
2611
2612 if (!$error) {
2613 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2614 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2615 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2616
2617 // We remove directory
2618 $ref = dol_sanitizeFileName($this->ref);
2619 if ($conf->fournisseur->commande->dir_output && !empty($ref)) {
2620 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2621 $file = $dir."/".$ref.".pdf";
2622 if (file_exists($file)) {
2623 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2624 $this->error = 'ErrorFailToDeleteFile';
2625 $this->errors[] = 'ErrorFailToDeleteFile';
2626 $error++;
2627 }
2628 }
2629 if (file_exists($dir)) {
2630 $res = @dol_delete_dir_recursive($dir);
2631 if (!$res) {
2632 $this->error = 'ErrorFailToDeleteDir';
2633 $this->errors[] = 'ErrorFailToDeleteDir';
2634 $error++;
2635 }
2636 }
2637 }
2638 }
2639
2640 if (!$error) {
2641 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2642 $this->db->commit();
2643 return 1;
2644 } else {
2645 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2646 $this->db->rollback();
2647 return -$error;
2648 }
2649 }
2650
2651
2660 public function getDispachedLines($status = -1)
2661 {
2662 $ret = array();
2663
2664 // List of already dispatched lines
2665 $sql = "SELECT p.ref, p.label,";
2666 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2667 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2668 $sql .= " FROM ".$this->db->prefix()."product as p,";
2669 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2670 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2671 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2672 $sql .= " AND cfd.fk_product = p.rowid";
2673 if ($status >= 0) {
2674 $sql .= " AND cfd.status = ".((int) $status);
2675 }
2676 $sql .= " ORDER BY cfd.rowid ASC";
2677
2678 $resql = $this->db->query($sql);
2679 if ($resql) {
2680 $num = $this->db->num_rows($resql);
2681 $i = 0;
2682
2683 while ($i < $num) {
2684 $objp = $this->db->fetch_object($resql);
2685 if ($objp) {
2686 $ret[] = array(
2687 'id' => $objp->dispatchedlineid,
2688 'productid' => $objp->fk_product,
2689 'warehouseid' => $objp->warehouse_id,
2690 'qty' => $objp->qty,
2691 'orderlineid' => $objp->fk_elementdet
2692 );
2693 }
2694
2695 $i++;
2696 }
2697 } else {
2698 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2699 }
2700
2701 return $ret;
2702 }
2703
2704 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2714 public function Livraison($user, $date, $type, $comment)
2715 {
2716 // phpcs:enable
2717 global $langs;
2718
2719 $result = 0;
2720 $error = 0;
2721 $dispatchedlinearray = array();
2722
2723 dol_syslog(get_class($this)."::Livraison");
2724
2725 $usercanreceive = 0;
2726 if (!isModEnabled('reception')) {
2727 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2728 } else {
2729 $usercanreceive = $user->hasRight("reception", "creer");
2730 }
2731
2732 if ($usercanreceive) {
2733 // Define the new status
2734 if ($type == 'par') {
2736 } elseif ($type == 'tot') {
2738 } elseif ($type == 'nev') {
2740 } elseif ($type == 'can') {
2742 } else {
2743 $error++;
2744 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2745 return -2;
2746 }
2747
2748 // Some checks to accept the record
2749 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2750 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2751 if ($type == 'tot') {
2752 $dispatchedlinearray = $this->getDispachedLines(0);
2753 if (count($dispatchedlinearray) > 0) {
2754 $result = -1;
2755 $error++;
2756 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2757 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2758 }
2759 }
2760 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)
2761 $dispatcheddenied = $this->getDispachedLines(2);
2762 if (count($dispatcheddenied) > 0) {
2763 $result = -1;
2764 $error++;
2765 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2766 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2767 }
2768 }
2769 }
2770
2771 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2772
2773 if (empty($error)) {
2774 $this->db->begin();
2775
2776 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2777 $sql .= " SET fk_statut = ".((int) $statut);
2778 $sql .= " WHERE rowid = ".((int) $this->id);
2779 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2780
2781 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2782 $resql = $this->db->query($sql);
2783 if ($resql) {
2784 $result = 1;
2785 $old_statut = $this->status;
2786 $this->status = $statut;
2787 $this->context['actionmsg2'] = $comment;
2788
2789 // Call trigger
2790 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2791 if ($result_trigger < 0) {
2792 $error++;
2793 }
2794 // End call triggers
2795
2796 if (empty($error)) {
2797 $this->db->commit();
2798 } else {
2799 $this->status = $old_statut;
2800 $this->db->rollback();
2801 $this->error = $this->db->lasterror();
2802 $result = -1;
2803 }
2804 } else {
2805 $this->db->rollback();
2806 $this->error = $this->db->lasterror();
2807 $result = -1;
2808 }
2809 }
2810 } else {
2811 $this->error = $langs->trans('NotAuthorized');
2812 $this->errors[] = $langs->trans('NotAuthorized');
2813 dol_syslog(get_class($this)."::Livraison Not Authorized");
2814 $result = -3;
2815 }
2816 return $result;
2817 }
2818
2819 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2829 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2830 {
2831 // phpcs:enable
2832 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2833 }
2834
2843 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2844 {
2845 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2846 $error = 0;
2847
2848 $this->db->begin();
2849
2850 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2851 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2852 $sql .= " WHERE rowid = ".((int) $this->id);
2853
2854 dol_syslog(__METHOD__, LOG_DEBUG);
2855 $resql = $this->db->query($sql);
2856 if (!$resql) {
2857 $this->errors[] = $this->db->error();
2858 $error++;
2859 }
2860
2861 if (!$error) {
2862 $this->oldcopy = clone $this;
2863 $this->delivery_date = $delivery_date;
2864 }
2865
2866 if (!$notrigger && empty($error)) {
2867 // Call trigger
2868 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2869 if ($result < 0) {
2870 $error++;
2871 }
2872 // End call triggers
2873 }
2874
2875 if (!$error) {
2876 $this->db->commit();
2877 return 1;
2878 } else {
2879 foreach ($this->errors as $errmsg) {
2880 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2881 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2882 }
2883 $this->db->rollback();
2884 return -1 * $error;
2885 }
2886 } else {
2887 return -2;
2888 }
2889 }
2890
2897 public function setReopen(User $user): int
2898 {
2899 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2900 if ($this->status == 1) {
2901 $newStatus = 0; // Validated->Draft
2902 } elseif ($this->status == 2) {
2903 $newStatus = 0; // Approved->Draft
2904 } elseif ($this->status == 3) {
2905 $newStatus = 2; // Ordered->Approved
2906 } elseif ($this->status == 4) {
2907 $newStatus = 3;
2908 } elseif ($this->status == 5) {
2909 //$newstatus=2; // Ordered
2910 // TODO Can we set it to submitted ?
2911 //$newstatus=3; // Submitted
2912 // TODO If there is at least one reception, we can set to Received->Received partially
2913 $newStatus = 4; // Received partially
2914 } elseif ($this->status == 6) {
2915 $newStatus = 2; // Canceled->Approved
2916 } elseif ($this->status == 7) {
2917 $newStatus = 3; // Canceled->Process running
2918 } elseif ($this->status == 9) {
2919 $newStatus = 1; // Refused->Validated
2920 } else {
2921 $newStatus = 2;
2922 }
2923
2924 $this->db->begin();
2925
2926 $result = $this->setStatus($user, $newStatus);
2927 if ($result > 0) {
2928 if ($newStatus == 0) {
2929 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2930 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2931 $sql .= ' WHERE rowid = '.((int) $this->id);
2932
2933 $this->db->query($sql);
2934 }
2935
2936 $this->db->commit();
2937
2938 return 1;
2939 } else {
2940 $this->db->rollback();
2941
2942 return -1;
2943 }
2944 }
2945
2946 return 0;
2947 }
2948
2949 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2958 public function set_id_projet($user, $id_projet, $notrigger = 0)
2959 {
2960 // phpcs:enable
2961 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2962 $error = 0;
2963
2964 $this->db->begin();
2965
2966 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2967 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2968 $sql .= " WHERE rowid = ".((int) $this->id);
2969
2970 dol_syslog(__METHOD__, LOG_DEBUG);
2971 $resql = $this->db->query($sql);
2972 if (!$resql) {
2973 $this->errors[] = $this->db->error();
2974 $error++;
2975 }
2976
2977 if (!$error) {
2978 $this->oldcopy = clone $this;
2979 $this->fk_projet = $id_projet;
2980 $this->fk_project = $id_projet;
2981 }
2982
2983 if (!$notrigger && empty($error)) {
2984 // Call trigger
2985 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2986 if ($result < 0) {
2987 $error++;
2988 }
2989 // End call triggers
2990 }
2991
2992 if (!$error) {
2993 $this->db->commit();
2994 return 1;
2995 } else {
2996 foreach ($this->errors as $errmsg) {
2997 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2998 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2999 }
3000 $this->db->rollback();
3001 return -1 * $error;
3002 }
3003 } else {
3004 return -2;
3005 }
3006 }
3007
3016 public function updateFromCommandeClient($user, $idc, $comclientid)
3017 {
3018 $comclient = new Commande($this->db);
3019 $comclient->fetch($comclientid);
3020
3021 $this->id = $idc;
3022
3023 $this->lines = array();
3024
3025 $num = count($comclient->lines);
3026 for ($i = 0; $i < $num; $i++) {
3027 $prod = new Product($this->db);
3028 $label = '';
3029 $ref = '';
3030 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3031 $label = $prod->label;
3032 $ref = (string) $prod->ref;
3033 }
3034
3035 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3036 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3037 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3038 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3039 $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);
3040 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3041 if ($this->db->query($sql)) {
3042 $this->update_price(1);
3043 }
3044 }
3045
3046 return 1;
3047 }
3048
3056 public function setStatus($user, $status)
3057 {
3058 $error = 0;
3059
3060 $this->db->begin();
3061
3062 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3063 $sql .= " SET fk_statut = ".((int) $status);
3064 $sql .= " WHERE rowid = ".((int) $this->id);
3065
3066 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3067 $resql = $this->db->query($sql);
3068 if ($resql) {
3069 // Trigger names for each status
3070 $triggerName = array();
3071 $triggerName[0] = 'DRAFT';
3072 $triggerName[1] = 'VALIDATED';
3073 $triggerName[2] = 'APPROVED';
3074 $triggerName[3] = 'ORDERED'; // Ordered
3075 $triggerName[4] = 'RECEIVED_PARTIALLY';
3076 $triggerName[5] = 'RECEIVED_COMPLETELY';
3077 $triggerName[6] = 'CANCELED';
3078 $triggerName[7] = 'CANCELED';
3079 $triggerName[9] = 'REFUSED';
3080
3081 // Call trigger
3082 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3083 if ($result < 0) {
3084 $error++;
3085 }
3086 // End call triggers
3087 } else {
3088 $error++;
3089 $this->error = $this->db->lasterror();
3090 dol_syslog(get_class($this)."::setStatus ".$this->error);
3091 }
3092
3093 if (!$error) {
3094 $this->status = $status;
3095 $this->db->commit();
3096 return 1;
3097 } else {
3098 $this->db->rollback();
3099 return -1;
3100 }
3101 }
3102
3114 public function setCategories($categories)
3115 {
3116 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3117 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3118 }
3119
3143 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 = '')
3144 {
3145 global $mysoc, $conf, $langs;
3146 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3147 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3148
3149 $error = 0;
3150
3151 if ($this->status == self::STATUS_DRAFT) {
3152 // Clean parameters
3153 if (empty($qty)) {
3154 $qty = 0;
3155 }
3156 if (empty($info_bits)) {
3157 $info_bits = 0;
3158 }
3159 if (empty($txtva)) {
3160 $txtva = 0;
3161 }
3162 if (empty($txlocaltax1)) {
3163 $txlocaltax1 = 0;
3164 }
3165 if (empty($txlocaltax2)) {
3166 $txlocaltax2 = 0;
3167 }
3168 if (empty($remise_percent)) {
3169 $remise_percent = 0;
3170 }
3171
3172 $remise_percent = (float) price2num($remise_percent);
3173 $qty = price2num($qty);
3174 if (!$qty) {
3175 $qty = 1;
3176 }
3177 $pu = price2num($pu);
3178 $pu_ht_devise = price2num($pu_ht_devise);
3179 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3180 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3181 }
3182 $txlocaltax1 = (float) price2num($txlocaltax1);
3183 $txlocaltax2 = (float) price2num($txlocaltax2);
3184
3185 // Check parameters
3186 if ($type < 0) {
3187 return -1;
3188 }
3189 if ($date_start && $date_end && $date_start > $date_end) {
3190 $langs->load("errors");
3191 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3192 return -1;
3193 }
3194
3195 $this->db->begin();
3196
3197 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3198 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3199 // and this is done at the line level, which has its own VAT rate
3200
3201 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3202
3203 // Clean vat code
3204 $reg = array();
3205 $vat_src_code = '';
3206 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3207 $vat_src_code = $reg[1];
3208 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3209 }
3210
3211 $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);
3212 $total_ht = $tabprice[0];
3213 $total_tva = $tabprice[1];
3214 $total_ttc = $tabprice[2];
3215 $total_localtax1 = $tabprice[9];
3216 $total_localtax2 = $tabprice[10];
3217 $pu_ht = $tabprice[3];
3218 $pu_tva = $tabprice[4];
3219 $pu_ttc = $tabprice[5];
3220
3221 // MultiCurrency
3222 $multicurrency_total_ht = $tabprice[16];
3223 $multicurrency_total_tva = $tabprice[17];
3224 $multicurrency_total_ttc = $tabprice[18];
3225 $pu_ht_devise = $tabprice[19];
3226 $multicurrency_pu_ttc = $tabprice[21];
3227
3228 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3229 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3230
3231 // Fetch current line from the database and then clone the object and set it in $oldline property
3232 $this->line = new CommandeFournisseurLigne($this->db);
3233 $this->line->fetch($rowid);
3234
3235 $oldline = clone $this->line;
3236 $this->line->oldline = $oldline;
3237
3238 $this->line->context = $this->context;
3239
3240 $this->line->fk_commande = $this->id;
3241 //$this->line->label=$label;
3242 $this->line->desc = $desc;
3243
3244 // redefine quantity according to packaging
3245 // Mirror commande.class.php::updateline at line 3289: surface the auto-correction
3246 // to the user with a warning, float-safe the fmod / coeff arithmetic and use
3247 // abs() so negative qty is handled too (#38782 bugs 3, 4, 5, 6).
3248 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3249 if (abs((float) $qty) < $this->line->packaging) {
3250 $qty = $this->line->packaging;
3251 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3252 } else {
3253 if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0
3254 && (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
3255 $coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
3256 $qty = price2num((float) $this->line->packaging * $coeff, 'MS');
3257 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3258 }
3259 }
3260 }
3261
3262 $this->line->qty = $qty;
3263 $this->line->ref_supplier = $ref_supplier;
3264
3265 $this->line->vat_src_code = $vat_src_code;
3266 $this->line->tva_tx = $txtva;
3267 $this->line->localtax1_tx = $txlocaltax1;
3268 $this->line->localtax2_tx = $txlocaltax2;
3269 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3270 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3271 $this->line->remise_percent = $remise_percent;
3272 $this->line->subprice = (float) $pu_ht;
3273 $this->line->subprice_ttc = (float) $pu_ttc;
3274 $this->line->info_bits = $info_bits;
3275 $this->line->total_ht = (float) $total_ht;
3276 $this->line->total_tva = (float) $total_tva;
3277 $this->line->total_localtax1 = (float) $total_localtax1;
3278 $this->line->total_localtax2 = (float) $total_localtax2;
3279 $this->line->total_ttc = (float) $total_ttc;
3280 $this->line->product_type = $type;
3281 $this->line->special_code = $oldline->special_code;
3282 $this->line->rang = $oldline->rang;
3283 $this->line->origin = $this->origin;
3284 $this->line->fk_unit = $fk_unit;
3285
3286 $this->line->date_start = $date_start;
3287 $this->line->date_end = $date_end;
3288
3289 // Multicurrency
3290 $this->line->fk_multicurrency = $this->fk_multicurrency;
3291 $this->line->multicurrency_code = $this->multicurrency_code;
3292 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3293 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3294 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3295 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3296 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3297
3298 $this->line->subprice = (float) $pu_ht;
3299 $this->line->subprice_ttc = (float) $pu_ttc;
3300 $this->line->price = $this->line->subprice;
3301
3302 $this->line->remise_percent = $remise_percent;
3303
3304 if (is_array($array_options) && count($array_options) > 0) {
3305 // We replace values in this->line->array_options only for entries defined into $array_options
3306 foreach ($array_options as $key => $value) {
3307 $this->line->array_options[$key] = $array_options[$key];
3308 }
3309 }
3310
3311 $result = $this->line->update($notrigger);
3312
3313
3314 // Update denormalized information at the invoice level
3315 if ($result >= 0) {
3316 $this->update_price(1, 'auto');
3317 $this->db->commit();
3318 return $result;
3319 } else {
3320 $this->errors[] = $this->line->error;
3321 $this->errors = array_merge($this->errors, $this->line->errors);
3322 $this->error = $this->db->lasterror();
3323 $this->db->rollback();
3324 return -1;
3325 }
3326 } else {
3327 $this->error = "Order status makes operation forbidden";
3328 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3329 return -2;
3330 }
3331 }
3332
3333
3341 public function initAsSpecimen()
3342 {
3343 global $user, $langs, $conf;
3344
3345 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3346
3347 dol_syslog(get_class($this)."::initAsSpecimen");
3348
3349 $now = dol_now();
3350
3351 // Find first product
3352 $prodid = 0;
3353 $product = new ProductFournisseur($this->db);
3354 $sql = "SELECT rowid";
3355 $sql .= " FROM ".$this->db->prefix()."product";
3356 $sql .= " WHERE entity IN (".getEntity('product').")";
3357 $sql .= $this->db->order("rowid", "ASC");
3358 $sql .= $this->db->plimit(1);
3359 $resql = $this->db->query($sql);
3360 if ($resql && $this->db->num_rows($resql)) {
3361 $obj = $this->db->fetch_object($resql);
3362 $prodid = $obj->rowid;
3363 }
3364
3365 // Initialise parameters
3366 $this->id = 0;
3367 $this->ref = 'SPECIMEN';
3368 $this->specimen = 1;
3369 $this->socid = 1;
3370 $this->date = $now;
3371 $this->date_commande = $now;
3372 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3373 $this->cond_reglement_code = 'RECEP';
3374 $this->mode_reglement_code = 'CHQ';
3375
3376 $this->note_public = 'This is a comment (public)';
3377 $this->note_private = 'This is a comment (private)';
3378
3379 $this->multicurrency_tx = 1;
3380 $this->multicurrency_code = $conf->currency;
3381
3382 $this->statut = 0; // deprecated
3383 $this->status = 0;
3384
3385 // Lines
3386 $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)
3387 $xnbp = 0;
3388 while ($xnbp < $nbp) {
3389 $line = new CommandeFournisseurLigne($this->db);
3390 $line->desc = $langs->trans("Description")." ".$xnbp;
3391 $line->qty = 1;
3392 $line->subprice = 100;
3393 $line->tva_tx = 19.6;
3394 $line->localtax1_tx = 0;
3395 $line->localtax2_tx = 0;
3396 if ($xnbp == 2) {
3397 $line->total_ht = 50;
3398 $line->total_ttc = 59.8;
3399 $line->total_tva = 9.8;
3400 $line->remise_percent = 50;
3401 } else {
3402 $line->total_ht = 100;
3403 $line->total_ttc = 119.6;
3404 $line->total_tva = 19.6;
3405 $line->remise_percent = 00;
3406 }
3407 $line->fk_product = $prodid;
3408
3409 $this->lines[$xnbp] = $line;
3410
3411 $this->total_ht += $line->total_ht;
3412 $this->total_tva += $line->total_tva;
3413 $this->total_ttc += $line->total_ttc;
3414
3415 $xnbp++;
3416 }
3417
3418 return 1;
3419 }
3420
3427 public function info($id)
3428 {
3429 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3430 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3431 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3432 $sql .= ' WHERE c.rowid = '.((int) $id);
3433
3434 $result = $this->db->query($sql);
3435 if ($result) {
3436 if ($this->db->num_rows($result)) {
3437 $obj = $this->db->fetch_object($result);
3438
3439 $this->id = $obj->rowid;
3440
3441 $this->user_creation_id = $obj->fk_user_author;
3442 $this->user_validation_id = $obj->fk_user_valid;
3443 $this->user_modification_id = $obj->fk_user_modif;
3444 $this->user_approve_id = $obj->fk_user_approve;
3445 $this->user_approve_id2 = $obj->fk_user_approve2;
3446
3447 $this->date_creation = $this->db->jdate($obj->datec);
3448 $this->date_modification = $this->db->jdate($obj->datem);
3449 $this->date_approve = $this->db->jdate($obj->datea);
3450 $this->date_approve2 = $this->db->jdate($obj->datea2);
3451 $this->date_validation = $this->db->jdate($obj->date_validation);
3452 }
3453 $this->db->free($result);
3454 } else {
3455 dol_print_error($this->db);
3456 }
3457 }
3458
3464 public function loadStateBoard()
3465 {
3466 global $user;
3467
3468 $this->nb = array();
3469 $sanitizedclause = "WHERE";
3470
3471 $sql = "SELECT count(co.rowid) as nb";
3472 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3473 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3474 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3475 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3476 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3477 $sanitizedclause = "AND";
3478 }
3479 $sql .= " ".$sanitizedclause." co.entity IN (".getEntity('supplier_order').")";
3480
3481 $resql = $this->db->query($sql);
3482 if ($resql) {
3483 while ($obj = $this->db->fetch_object($resql)) {
3484 $this->nb["supplier_orders"] = $obj->nb;
3485 }
3486 $this->db->free($resql);
3487 return 1;
3488 } else {
3489 dol_print_error($this->db);
3490 $this->error = $this->db->error();
3491 return -1;
3492 }
3493 }
3494
3495 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3503 public function load_board($user, $mode = 'opened')
3504 {
3505 // phpcs:enable
3506 global $conf, $langs;
3507
3508 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3509 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3510 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3511 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3512 }
3513 $sql .= " WHERE c.entity = ".((int) $conf->entity);
3514 if ($mode === 'awaiting') {
3515 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3516 } else {
3517 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3518 }
3519 if ($user->socid) {
3520 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3521 }
3522
3523 $resql = $this->db->query($sql);
3524 if ($resql) {
3525 $commandestatic = new CommandeFournisseur($this->db);
3526
3527 $response = new WorkboardResponse();
3528 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3529 $response->label = $langs->trans("SuppliersOrdersToProcess");
3530 $response->labelShort = $langs->trans("Opened");
3531 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3532 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3533 $response->img = img_object('', "order");
3534
3535 if ($mode === 'awaiting') {
3536 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3537 $response->labelShort = $langs->trans("AwaitingReception");
3538 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3539 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3540 }
3541
3542 while ($obj = $this->db->fetch_object($resql)) {
3543 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3544 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3545 $commandestatic->statut = $obj->fk_statut; // deprecated
3546 $commandestatic->status = $obj->fk_statut;
3547
3548 $response->nbtodo++;
3549 $response->total += $obj->total_ht;
3550
3551 if ($commandestatic->hasDelay()) {
3552 $response->nbtodolate++;
3553 }
3554 }
3555
3556 return $response;
3557 } else {
3558 $this->error = $this->db->error();
3559 return -1;
3560 }
3561 }
3562
3569 public function getInputMethod()
3570 {
3571 global $langs;
3572
3573 if ($this->methode_commande_id > 0) {
3574 $sql = "SELECT rowid, code, libelle as label";
3575 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3576 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3577
3578 $resql = $this->db->query($sql);
3579 if ($resql) {
3580 if ($this->db->num_rows($resql)) {
3581 $obj = $this->db->fetch_object($resql);
3582
3583 $string = $langs->trans($obj->code);
3584 if ($string == $obj->code) {
3585 $string = $obj->label != '-' ? $obj->label : '';
3586 }
3587 return $string;
3588 }
3589 } else {
3590 dol_print_error($this->db);
3591 }
3592 }
3593
3594 return '';
3595 }
3596
3608 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3609 {
3610 global $langs;
3611
3612 if (!dol_strlen($modele)) {
3613 $modele = ''; // No doc template/generation by default
3614
3615 if (!empty($this->model_pdf)) {
3616 $modele = $this->model_pdf;
3617 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3618 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3619 }
3620 }
3621
3622 if (empty($modele)) {
3623 return 0;
3624 } else {
3625 $langs->load("suppliers");
3626 $outputlangs->load("products");
3627
3628 $modelpath = "core/modules/supplier_order/doc/";
3629 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3630 return $result;
3631 }
3632 }
3633
3640 public function getMaxDeliveryTimeDay($langs)
3641 {
3642 if (empty($this->lines)) {
3643 return '';
3644 }
3645
3646 $tmpproductfourn = new ProductFournisseur($this->db);
3647
3648 $nb = 0;
3649 foreach ($this->lines as $line) {
3650 if ($line->fk_product > 0) {
3651 // Load delivery_time_days, return id into product_fournisseur_price
3652 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3653 if ($idp > 0) {
3654 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3655 if ($tmpproductfourn->delivery_time_days > $nb) {
3656 $nb = $tmpproductfourn->delivery_time_days;
3657 }
3658 }
3659 }
3660 }
3661
3662 if ($nb === 0) {
3663 return '';
3664 } else {
3665 return $nb.' '.$langs->trans('days');
3666 }
3667 }
3668
3674 public function getRights()
3675 {
3676 global $user;
3677
3678 return $user->hasRight("fournisseur", "commande");
3679 }
3680
3681
3690 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3691 {
3692 $tables = array(
3693 'commande_fournisseur'
3694 );
3695
3696 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3697 }
3698
3707 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3708 {
3709 $tables = array(
3710 'commande_fournisseurdet'
3711 );
3712
3713 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3714 }
3715
3723 public function hasDelay()
3724 {
3725 global $conf;
3726
3727 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3728 $now = dol_now();
3729 if (!empty($this->delivery_date)) {
3730 $date_to_test = $this->delivery_date;
3731 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3732 } else {
3733 //$date_to_test = $this->date_commande;
3734 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3735 return false;
3736 }
3737 } else {
3738 $now = dol_now();
3739 $date_to_test = $this->date_commande;
3740
3741 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3742 }
3743 }
3744
3752 public function showDelay()
3753 {
3754 global $conf, $langs;
3755
3756 $langs->load('orders');
3757
3758 $text = '';
3759
3760 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3761 if (!empty($this->delivery_date)) {
3762 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3763 } else {
3764 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3765 }
3766 } else {
3767 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3768 }
3769 if ($text) {
3770 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3771 }
3772
3773 return $text;
3774 }
3775
3776
3785 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3786 {
3787 if (isModEnabled("supplier_order")) {
3788 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3789
3790 $qtydelivered = array();
3791 $qtywished = array();
3792
3793 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3794
3795 $filter = array('t.fk_element' => $this->id);
3796 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3797 $filter['t.status'] = 1; // Restrict to lines with status validated
3798 }
3799
3800 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3801 if ($ret < 0) {
3802 $this->error = $supplierorderdispatch->error;
3803 $this->errors = $supplierorderdispatch->errors;
3804 return $ret;
3805 } else {
3806 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3807 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3808 $date_liv = dol_now();
3809
3810 // Build array with quantity deliverd by product
3811 foreach ($supplierorderdispatch->lines as $line) {
3812 if (array_key_exists($line->fk_product, $qtydelivered)) {
3813 $qtydelivered[$line->fk_product] += $line->qty;
3814 } else {
3815 $qtydelivered[$line->fk_product] = $line->qty;
3816 }
3817 }
3818 foreach ($this->lines as $line) {
3819 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3820 if ($line->product_type > 0 && (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') || empty($line->stockable_product))) {
3821 continue;
3822 }
3823 if (array_key_exists($line->fk_product, $qtywished)) {
3824 $qtywished[$line->fk_product] += $line->qty;
3825 } else {
3826 $qtywished[$line->fk_product] = $line->qty;
3827 }
3828 }
3829
3830 //Compare array
3831 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3832 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3833 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3834 //var_dump(array_keys($qtydelivered));
3835 //var_dump(array_keys($qtywished));
3836 //var_dump($diff_array);
3837 //var_dump($keysinwishednotindelivered);
3838 //var_dump($keysindeliverednotinwished);
3839 //exit;
3840
3841 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3842 if ($closeopenorder) {
3843 //$ret=$this->setStatus($user,5);
3844 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3845 if ($ret < 0) {
3846 return -1;
3847 }
3848 return 5;
3849 } else {
3850 //Diff => received partially
3851 //$ret=$this->setStatus($user,4);
3852 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3853 if ($ret < 0) {
3854 return -1;
3855 }
3856 return 4;
3857 }
3858 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3859 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3860
3861 $close = 0;
3862
3863 if (count($diff_array) > 0) {
3864 //there are some difference between the two arrays
3865
3866 //scan the array of results
3867 foreach ($diff_array as $key => $value) {
3868 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3869 if ($qtydelivered[$key] >= $qtywished[$key]) {
3870 $close++;
3871 }
3872 }
3873 }
3874
3875
3876 if ($close == count($diff_array)) {
3877 //all the products are received equal or more than the wished quantity
3878 if ($closeopenorder) {
3879 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3880 if ($ret < 0) {
3881 return -1;
3882 }
3883 return 5;
3884 } else {
3885 //Diff => received partially
3886 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3887 if ($ret < 0) {
3888 return -1;
3889 }
3890 return 4;
3891 }
3892 } else {
3893 //all the products are not received
3894 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3895 if ($ret < 0) {
3896 return -1;
3897 }
3898 return 4;
3899 }
3900 } else {
3901 //Diff => received partially
3902 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3903 if ($ret < 0) {
3904 return -1;
3905 }
3906 return 4;
3907 }
3908 }
3909 return 1;
3910 }
3911 }
3912 return 0;
3913 }
3914
3922 public function loadReceptions($filtre_statut = -1)
3923 {
3924 $this->receptions = array();
3925
3926 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3927
3928 $sql = 'SELECT cd.rowid, cd.fk_product,';
3929 $sql .= ' sum(cfd.qty) as qty';
3930 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3931 if ($filtre_statut >= 0) {
3932 $sql .= ' '.$this->db->prefix().'reception as e,';
3933 }
3934 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3935 $sql .= ' WHERE';
3936 if ($filtre_statut >= 0) {
3937 $sql .= ' cfd.fk_reception = e.rowid AND';
3938 }
3939 $sql .= ' cfd.fk_elementdet = cd.rowid';
3940 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3941 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3942 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3943 }
3944 if ($filtre_statut >= 0) {
3945 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3946 }
3947 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3948
3949 $resql = $this->db->query($sql);
3950 if ($resql) {
3951 $num = $this->db->num_rows($resql);
3952 $i = 0;
3953 while ($i < $num) {
3954 $obj = $this->db->fetch_object($resql);
3955 if (empty($this->receptions[$obj->rowid])) {
3956 $this->receptions[$obj->rowid] = (float) $obj->qty;
3957 } else {
3958 $this->receptions[$obj->rowid] += (float) $obj->qty;
3959 }
3960 $i++;
3961 }
3962 $this->db->free($resql);
3963
3964 return $num;
3965 } else {
3966 $this->error = $this->db->lasterror();
3967 return -1;
3968 }
3969 }
3970
3978 public function getKanbanView($option = '', $arraydata = null)
3979 {
3980 global $langs;
3981
3982 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3983
3984 $return = '<div class="box-flex-item box-flex-grow-zero">';
3985 $return .= '<div class="info-box info-box-sm">';
3986 $return .= '<span class="info-box-icon bg-infobox-action">';
3987 $return .= img_picto('', $this->picto);
3988 $return .= '</span>';
3989 $return .= '<div class="info-box-content">';
3990 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3991 if ($selected >= 0) {
3992 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3993 }
3994 if (!empty($arraydata['thirdparty'])) {
3995 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3996 }
3997 if (!empty($this->date)) {
3998 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3999 }
4000 if (!empty($this->total_ht)) {
4001 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
4002 $return .= ' '.$langs->trans("HT");
4003 $return .= '</span>';
4004 }
4005 if (method_exists($this, 'getLibStatut')) {
4006 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
4007 }
4008 if (property_exists($this, 'billed')) {
4009 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
4010 }
4011 $return .= '</div>';
4012 $return .= '</div>';
4013 $return .= '</div>';
4014 return $return;
4015 }
4016}
$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.
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
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)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
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:487