dolibarr 25.0.0-alpha
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 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1705 // In TTC mode, do not forward the HT currency price as pu_ht_devise: under multicurrency it would
1706 // reset the local price and recompute from the HT currency amount (read as TTC) -> 0.01 drift.
1707 // Like SupplierProposal (which does not pass it), the currency price is re-derived from the local price.
1708 $line_price_base_type = $line->getPriceBaseType();
1709 $line_pu_devise = ($line_price_base_type === 'TTC') ? 0 : (float) $line->multicurrency_subprice;
1710 $result = $this->addline(
1711 (string) $line->desc,
1712 (float) $line->subprice,
1713 (float) $line->qty,
1714 $line->tva_tx,
1715 (float) $line->localtax1_tx,
1716 (float) $line->localtax2_tx,
1717 (int) $line->fk_product,
1718 0,
1719 (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
1720 (float) $line->remise_percent,
1721 $line_price_base_type,
1722 (float) $line->subprice_ttc,
1723 (int) $line->product_type,
1724 (int) $line->info_bits,
1725 0,
1726 $line->date_start ?? null,
1727 $line->date_end ?? null,
1728 $line->array_options ?? [],
1729 $line->fk_unit ?? null,
1730 $line_pu_devise, // pu_ht_devise
1731 (string) $line->origin, // origin
1732 (int) $line->origin_id, // origin_id
1733 (int) ($line->rang ?? -1), // rang
1734 (int) $line->special_code,
1735 isset($line->product_label) ? $line->product_label : ''
1736 );
1737 if ($result < 0) {
1738 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1739 $this->db->rollback();
1740 return -1;
1741 }
1742 }
1743
1744 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1745 $sql .= " SET ref='(PROV".((int) $this->id).")'";
1746 $sql .= " WHERE rowid=".((int) $this->id);
1747
1748 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1749 if ($this->db->query($sql)) {
1750 // Add link with price request and supplier order
1751 if ($this->id) {
1752 $this->ref = "(PROV".$this->id.")";
1753
1754 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1755 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1756 }
1757
1758 // Add object linked
1759 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1760 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1761 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, ...))
1762 foreach ($tmp_origin_id as $origin_id) {
1763 $ret = $this->add_object_linked($origin, $origin_id);
1764 if (!$ret) {
1765 dol_print_error($this->db);
1766 $error++;
1767 }
1768 }
1769 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1770 $origin_id = $tmp_origin_id;
1771 $ret = $this->add_object_linked($origin, $origin_id);
1772 if (!$ret) {
1773 dol_print_error($this->db);
1774 $error++;
1775 }
1776 }
1777 }
1778 }
1779 }
1780
1781 if (!$error) {
1782 $result = $this->insertExtraFields();
1783 if ($result < 0) {
1784 $error++;
1785 }
1786 }
1787
1788 if (!$error && !$notrigger) {
1789 // Call trigger
1790 $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1791 if ($result < 0) {
1792 $this->db->rollback();
1793
1794 return -1;
1795 }
1796 // End call triggers
1797 }
1798
1799 $this->db->commit();
1800 return $this->id;
1801 } else {
1802 $this->error = $this->db->lasterror();
1803 $this->db->rollback();
1804
1805 return -2;
1806 }
1807 } else {
1808 $this->error = 'Failed to get ID of inserted line';
1809
1810 return -1;
1811 }
1812 } else {
1813 $this->error = $this->db->lasterror();
1814 $this->db->rollback();
1815
1816 return -1;
1817 }
1818 }
1819
1827 public function update(User $user, $notrigger = 0)
1828 {
1829 $error = 0;
1830
1831 // Clean parameters
1832 if (isset($this->ref)) {
1833 $this->ref = trim($this->ref);
1834 }
1835 if (isset($this->ref_supplier)) {
1836 $this->ref_supplier = trim($this->ref_supplier);
1837 }
1838 if (isset($this->note_private)) {
1839 $this->note_private = trim($this->note_private);
1840 }
1841 if (isset($this->note_public)) {
1842 $this->note_public = trim($this->note_public);
1843 }
1844 if (isset($this->model_pdf)) {
1845 $this->model_pdf = trim($this->model_pdf);
1846 }
1847 if (isset($this->import_key)) {
1848 $this->import_key = trim($this->import_key);
1849 }
1850
1851 // Update request
1852 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1853
1854 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1855 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1856 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1857 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1858 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1859 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1860 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1861 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1862 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1863 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1864 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1865 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
1866 $sql .= " fk_user_author=".(isset($this->user_author_id) ? ((int) $this->user_author_id) : "null").",";
1867 $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? ((int) $this->user_validation_id) : "null").",";
1868 $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").",";
1869 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1870 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1871 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
1872 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1873 //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1874 $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").",";
1875 //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1876 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1877 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1878 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1879 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1880
1881 $sql .= " WHERE rowid=".((int) $this->id);
1882
1883 $this->db->begin();
1884
1885 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1886 $resql = $this->db->query($sql);
1887 if (!$resql) {
1888 $error++;
1889 $this->errors[] = "Error ".$this->db->lasterror();
1890 }
1891
1892 if (!$error) {
1893 $result = $this->insertExtraFields();
1894 if ($result < 0) {
1895 $error++;
1896 }
1897 }
1898
1899 if (!$error && !$notrigger) {
1900 // Call trigger
1901 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1902 if ($result < 0) {
1903 $error++;
1904 }
1905 // End call triggers
1906 }
1907
1908 // Commit or rollback
1909 if ($error) {
1910 foreach ($this->errors as $errmsg) {
1911 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1912 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1913 }
1914 $this->db->rollback();
1915 return -1 * $error;
1916 } else {
1917 $this->db->commit();
1918 return 1;
1919 }
1920 }
1921
1930 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1931 {
1932 global $conf, $user, $hookmanager;
1933
1934 $error = 0;
1935
1936 $this->db->begin();
1937
1938 // get extrafields so they will be clone
1939 foreach ($this->lines as $line) {
1940 $line->fetch_optionals();
1941 }
1942
1943 // Load source object
1944 $objFrom = clone $this;
1945
1946 // Change socid if needed
1947 if (!empty($socid) && $socid != $this->socid) {
1948 $objsoc = new Societe($this->db);
1949
1950 if ($objsoc->fetch($socid) > 0) {
1951 $this->socid = $objsoc->id;
1952 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1953 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1954 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1955 $this->fk_project = 0;
1956 $this->fk_delivery_address = 0;
1957 }
1958
1959 // TODO Change product price if multi-prices
1960 }
1961
1962 $this->id = 0;
1963 $this->statut = self::STATUS_DRAFT; // deprecated
1964 $this->status = self::STATUS_DRAFT;
1965
1966 // Clear fields
1967 $this->user_author_id = $user->id;
1968 $this->user_validation_id = 0;
1969
1970 $this->date = dol_now();
1971 $this->date_creation = 0;
1972 $this->date_validation = 0;
1973 $this->date_commande = null;
1974 $this->ref_supplier = '';
1975 $this->user_approve_id = 0;
1976 $this->user_approve_id2 = 0;
1977 $this->date_approve = 0;
1978 $this->date_approve2 = 0;
1979
1980 // Create clone
1981 $this->context['createfromclone'] = 'createfromclone';
1982 $result = $this->create($user, $notrigger);
1983 if ($result < 0) {
1984 $error++;
1985 }
1986
1987 if (!$error) {
1988 // Hook of thirdparty module
1989 if (is_object($hookmanager)) {
1990 $parameters = array('objFrom' => $objFrom);
1991 $action = '';
1992 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1993 if ($reshook < 0) {
1994 $this->setErrorsFromObject($hookmanager);
1995 $error++;
1996 }
1997 }
1998 }
1999
2000 unset($this->context['createfromclone']);
2001
2002 // End
2003 if (!$error) {
2004 $this->db->commit();
2005 return $this->id;
2006 } else {
2007 $this->db->rollback();
2008 return -1;
2009 }
2010 }
2011
2042 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 = '')
2043 {
2044 global $langs, $mysoc;
2045
2046 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");
2047 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2048
2049 if ($this->status == self::STATUS_DRAFT) {
2050 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2051
2052 // Clean parameters
2053 if (empty($qty)) {
2054 $qty = 0;
2055 }
2056 if (!$info_bits) {
2057 $info_bits = 0;
2058 }
2059 if (empty($txtva)) {
2060 $txtva = 0;
2061 }
2062 if (empty($rang)) {
2063 $rang = 0;
2064 }
2065 if (empty($txlocaltax1)) {
2066 $txlocaltax1 = 0;
2067 }
2068 if (empty($txlocaltax2)) {
2069 $txlocaltax2 = 0;
2070 }
2071 if (empty($remise_percent)) {
2072 $remise_percent = 0;
2073 }
2074
2075 $remise_percent = price2num($remise_percent);
2076 $qty = price2num($qty);
2077 $pu_ht = price2num($pu_ht);
2078 $pu_ht_devise = price2num($pu_ht_devise);
2079 $pu_ttc = price2num($pu_ttc);
2080 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2081 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2082 }
2083 $txlocaltax1 = price2num($txlocaltax1);
2084 $txlocaltax2 = price2num($txlocaltax2);
2085 if ($price_base_type == 'HT') {
2086 $pu = $pu_ht;
2087 } else {
2088 $pu = $pu_ttc;
2089 }
2090 $label = trim((string) $label);
2091 $desc = trim($desc);
2092 if ($desc === '' && $label !== '') {
2093 $desc = $label;
2094 }
2095
2096 // Check parameters
2097 if ($qty < 0 && !$fk_product) {
2098 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
2099 return -1;
2100 }
2101 if ($type < 0) {
2102 return -1;
2103 }
2104 if ($date_start && $date_end && $date_start > $date_end) {
2105 $langs->load("errors");
2106 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2107 return -1;
2108 }
2109
2110
2111 $this->db->begin();
2112
2113 $product_type = $type;
2114 $label = ''; // deprecated
2115
2116 if ($fk_product > 0) {
2117 if (getDolGlobalInt('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2118 // Check quantity is enough
2119 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);
2120 $prod = new ProductFournisseur($this->db);
2121 if ($prod->fetch($fk_product) > 0) {
2122 $product_type = $prod->type;
2123 $label = $prod->label;
2124
2125 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2126 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2127 $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
2128
2129 // If supplier order created from sales order, we take best supplier price
2130 // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2131 if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2132 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2133 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2134 // is remise percent not keyed but present for the product we add it
2135 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2136 $remise_percent = $prod->remise_percent;
2137 }
2138 }
2139 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2140 $langs->load("errors");
2141 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2142 $this->db->rollback();
2143 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2144 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2145 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2146 return -1;
2147 }
2148 if ($result == -1) {
2149 $langs->load("errors");
2150 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2151 $this->db->rollback();
2152 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2153 return -1;
2154 }
2155 if ($result < -1) {
2156 $this->error = $prod->error;
2157 $this->db->rollback();
2158 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2159 return -1;
2160 }
2161 } else {
2162 $this->error = $prod->error;
2163 $this->db->rollback();
2164 return -1;
2165 }
2166 }
2167
2168 // Predefine quantity according to packaging
2169 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2170 $prod = new Product($this->db);
2171 $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2172
2173 // Align messaging, type and float-safety with the customer-order path at commande.class.php:1720
2174 // (#38782 bugs 1, 2, 6).
2175 if (abs((float) $qty) < $prod->packaging) {
2176 $qty = (float) $prod->packaging;
2177 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2178 } else {
2179 if (!empty($prod->packaging) && (float) price2num(fmod((float) $qty, (float) $prod->packaging), 'MS')) {
2180 $coeff = intval(abs((float) $qty) / $prod->packaging) + 1;
2181 $qty = price2num((float) $prod->packaging * $coeff, 'MS');
2182 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2183 }
2184 }
2185
2186 // Enforce the supplier minimum purchase quantity on top of packaging
2187 // rounding. The packaging block above only ensures qty is a packaging
2188 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2189 // is below the supplier minimum, round qty_min itself up to the next
2190 // packaging multiple so we end up with the smallest valid order qty (#38783).
2191 if (!empty($prod->fourn_qty) && abs((float) $qty) < (float) $prod->fourn_qty) {
2192 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2193 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2194 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2195 } else {
2196 $qty = (float) $prod->fourn_qty;
2197 }
2198 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2199 }
2200 }
2201 }
2202
2203 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2204 $pu = 0;
2205 }
2206
2207 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2208
2209 // Clean vat code
2210 $reg = array();
2211 $vat_src_code = '';
2212 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2213 $vat_src_code = $reg[1];
2214 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2215 }
2216
2217 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
2218 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
2219 // and this is done at the line level, which has its own VAT rate
2220
2221 $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);
2222
2223 $total_ht = $tabprice[0];
2224 $total_tva = $tabprice[1];
2225 $total_ttc = $tabprice[2];
2226 $total_localtax1 = $tabprice[9];
2227 $total_localtax2 = $tabprice[10];
2228 $pu = $pu_ht = $tabprice[3];
2229 $pu_tva = $tabprice[4];
2230 $pu_ttc = $tabprice[5];
2231
2232 // MultiCurrency
2233 $multicurrency_total_ht = $tabprice[16];
2234 $multicurrency_total_tva = $tabprice[17];
2235 $multicurrency_total_ttc = $tabprice[18];
2236 $pu_ht_devise = $tabprice[19];
2237 $multicurrency_pu_ttc = $tabprice[21];
2238
2239 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2240 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2241
2242 if ($rang < 0) {
2243 $rangmax = $this->line_max();
2244 $rang = $rangmax + 1;
2245 }
2246
2247 // Insert line
2248 $this->line = new CommandeFournisseurLigne($this->db);
2249
2250 $this->line->context = $this->context;
2251
2252 $this->line->fk_commande = $this->id;
2253 $this->line->label = $label;
2254 $this->line->ref_fourn = $ref_supplier;
2255 $this->line->ref_supplier = $ref_supplier;
2256 $this->line->desc = $desc;
2257 $this->line->qty = $qty;
2258 $this->line->tva_tx = $txtva;
2259 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2260 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2261 $this->line->localtax1_type = $localtax1_type;
2262 $this->line->localtax2_type = $localtax2_type;
2263 $this->line->fk_product = $fk_product;
2264 $this->line->product_type = $product_type;
2265 $this->line->remise_percent = $remise_percent;
2266 $this->line->subprice = (float) $pu_ht;
2267 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
2268
2269 $this->line->rang = $rang;
2270 $this->line->info_bits = $info_bits;
2271
2272 $this->line->vat_src_code = $vat_src_code;
2273 $this->line->total_ht = (float) $total_ht;
2274 $this->line->total_tva = (float) $total_tva;
2275 $this->line->total_localtax1 = (float) $total_localtax1;
2276 $this->line->total_localtax2 = (float) $total_localtax2;
2277 $this->line->total_ttc = (float) $total_ttc;
2278 $this->line->product_type = $type;
2279 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2280 $this->line->origin = $origin;
2281 $this->line->origin_type = $origin;
2282 $this->line->origin_id = $origin_id;
2283 $this->line->fk_unit = $fk_unit;
2284
2285 $this->line->date_start = $date_start;
2286 $this->line->date_end = $date_end;
2287
2288 // Multicurrency
2289 $this->line->fk_multicurrency = $this->fk_multicurrency;
2290 $this->line->multicurrency_code = $this->multicurrency_code;
2291 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2292 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2293 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2294 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2295 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2296
2297 $this->line->subprice = (float) $pu_ht;
2298 $this->line->price = $this->line->subprice;
2299
2300 $this->line->remise_percent = $remise_percent;
2301
2302 if (is_array($array_options) && count($array_options) > 0) {
2303 $this->line->array_options = $array_options;
2304 }
2305
2306 $result = $this->line->insert($notrigger);
2307 if ($result > 0) {
2308 // Update denormalized fields at the order level
2309 $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.
2310
2311 if ($result > 0) {
2312 if (!isset($this->context['createfromclone'])) {
2313 if (!empty($this->line->fk_parent_line)) {
2314 // Always reorder if child line
2315 $this->line_order(true, 'DESC');
2316 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2317 // Update all rank of all other lines starting from the same $ranktouse
2318 $linecount = count($this->lines);
2319 for ($ii = $rang; $ii <= $linecount; $ii++) {
2320 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2321 }
2322 }
2323
2324 $this->lines[] = $this->line;
2325 } else {
2326 foreach ($this->lines as $line) {
2327 if ($line->id == $origin_id) {
2328 $this->line->extraparams = $line->extraparams;
2329 $this->line->setExtraParameters();
2330 }
2331 }
2332 }
2333
2334 $this->db->commit();
2335 return $this->line->id;
2336 } else {
2337 $this->db->rollback();
2338 return -1;
2339 }
2340 } else {
2341 $this->setErrorsFromObject($this->line);
2342 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2343 $this->db->rollback();
2344 return -1;
2345 }
2346 }
2347 return -1;
2348 }
2349
2350
2368 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2369 {
2370 global $conf, $langs;
2371
2372 $error = 0;
2373 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2374
2375 // Check parameters (if test are wrong here, there is bug into caller)
2376 if ($entrepot <= 0) {
2377 $this->error = 'ErrorBadValueForParameterWarehouse';
2378 return -1;
2379 }
2380 if ($qty == 0) {
2381 $this->error = 'ErrorBadValueForParameterQty';
2382 return -1;
2383 }
2384
2385 $dispatchstatus = 1;
2386 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2387 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2388 }
2389
2390 $now = dol_now();
2391
2392 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2393
2394 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2395 $this->db->begin();
2396
2397 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2398 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2399 $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)."', ";
2400 $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");
2401 $sql .= ")";
2402
2403 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2404 $resql = $this->db->query($sql);
2405 if ($resql) {
2406 if (!$notrigger) {
2407 global $conf, $langs, $user;
2408 // Call trigger
2409 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2410 if ($result < 0) {
2411 $error++;
2412 }
2413 // End call triggers
2414 }
2415 } else {
2416 $this->error = $this->db->lasterror();
2417 $error++;
2418 }
2419
2420 // If module stock is enabled and the stock increase is done on purchase order dispatching
2421 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2422 $mouv = new MouvementStock($this->db);
2423 if ($product > 0) {
2424 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2425 $mouv->origin = &$this;
2426 $mouv->setOrigin($this->element, $this->id);
2427
2428 // Method change if qty < 0
2429 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2430 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2431 } else {
2432 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2433 }
2434
2435 if ($result < 0) {
2436 $this->error = $mouv->error;
2437 $this->errors = $mouv->errors;
2438 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2439 $error++;
2440 }
2441 }
2442 }
2443
2444 if ($error == 0) {
2445 $this->db->commit();
2446 return 1;
2447 } else {
2448 $this->db->rollback();
2449 return -1;
2450 }
2451 } else {
2452 $this->error = 'BadStatusForObject';
2453 return -2;
2454 }
2455 }
2456
2464 public function deleteLine($idline, $notrigger = 0)
2465 {
2466 global $user;
2467
2468 if ($this->status == 0) {
2469 $line = new CommandeFournisseurLigne($this->db);
2470
2471 if ($line->fetch($idline) <= 0) {
2472 return 0;
2473 }
2474
2475 // check if not yet received
2476 $dispatchedLines = $this->getDispachedLines();
2477 foreach ($dispatchedLines as $dispatchLine) {
2478 if ($dispatchLine['orderlineid'] == $idline) {
2479 $this->error = "LineAlreadyDispatched";
2480 $this->errors[] = $this->error;
2481 return -3;
2482 }
2483 }
2484
2485 if ($line->delete($user, $notrigger) > 0) {
2486 $this->update_price(1);
2487 return 1;
2488 } else {
2489 $this->setErrorsFromObject($line);
2490 return -1;
2491 }
2492 } else {
2493 return -2;
2494 }
2495 }
2496
2504 public function delete(User $user, $notrigger = 0)
2505 {
2506 global $langs, $conf;
2507 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2508
2509 $error = 0;
2510
2511 $this->db->begin();
2512
2513 if (empty($notrigger)) {
2514 // Call trigger
2515 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2516 if ($result < 0) {
2517 $this->errors[] = 'ErrorWhenRunningTrigger';
2518 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2519 $this->db->rollback();
2520 return -1;
2521 }
2522 // End call triggers
2523 }
2524
2525 // Test we can delete
2526 $this->fetchObjectLinked(null, 'order_supplier');
2527 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2528 foreach ($this->linkedObjects['reception'] as $element) {
2529 if ($element->statut >= 0) {
2530 $this->errors[] = $langs->trans('ReceptionExist');
2531 $error++;
2532 break;
2533 }
2534 }
2535 }
2536
2537 // Remove linked categories.
2538 if (!$error) {
2539 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2540 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2541
2542 $result = $this->db->query($sql);
2543 if (!$result) {
2544 $error++;
2545 $this->errors[] = $this->db->lasterror();
2546 }
2547 }
2548
2549 $main = $this->db->prefix().'commande_fournisseurdet';
2550
2551 if (!$error) {
2552 $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).")";
2553 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2554 if (!$this->db->query($sql1)) {
2555 $error++;
2556 $this->error = $this->db->lasterror();
2557 $this->errors[] = $this->db->lasterror();
2558 }
2559 }
2560
2561 if (!$error) {
2562 $ef = $main."_extrafields";
2563 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2564 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2565 if (!$this->db->query($sql)) {
2566 $this->error = $this->db->lasterror();
2567 $this->errors[] = $this->db->lasterror();
2568 $error++;
2569 }
2570 }
2571
2572 if (!$error) {
2573 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2574 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2575 if (!$this->db->query($sql)) {
2576 $this->error = $this->db->lasterror();
2577 $this->errors[] = $this->db->lasterror();
2578 $error++;
2579 }
2580 }
2581
2582 if (!$error) {
2583 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2584 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2585 if ($resql = $this->db->query($sql)) {
2586 if ($this->db->affected_rows($resql) < 1) {
2587 $this->error = $this->db->lasterror();
2588 $this->errors[] = $this->db->lasterror();
2589 $error++;
2590 }
2591 } else {
2592 $this->error = $this->db->lasterror();
2593 $this->errors[] = $this->db->lasterror();
2594 $error++;
2595 }
2596 }
2597
2598 // Remove extrafields
2599 if (!$error) {
2600 $result = $this->deleteExtraFields();
2601 if ($result < 0) {
2602 $this->error = 'FailToDeleteExtraFields';
2603 $this->errors[] = 'FailToDeleteExtraFields';
2604 $error++;
2605 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2606 }
2607 }
2608
2609 // Delete linked object
2610 $res = $this->deleteObjectLinked();
2611 if ($res < 0) {
2612 $this->error = 'FailToDeleteObjectLinked';
2613 $this->errors[] = 'FailToDeleteObjectLinked';
2614 $error++;
2615 }
2616
2617 if (!$error) {
2618 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2619 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2620 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2621
2622 // We remove directory
2623 $ref = dol_sanitizeFileName($this->ref);
2624 if ($conf->fournisseur->commande->dir_output && !empty($ref)) {
2625 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2626 $file = $dir."/".$ref.".pdf";
2627 if (file_exists($file)) {
2628 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2629 $this->error = 'ErrorFailToDeleteFile';
2630 $this->errors[] = 'ErrorFailToDeleteFile';
2631 $error++;
2632 }
2633 }
2634 if (file_exists($dir)) {
2635 $res = @dol_delete_dir_recursive($dir);
2636 if (!$res) {
2637 $this->error = 'ErrorFailToDeleteDir';
2638 $this->errors[] = 'ErrorFailToDeleteDir';
2639 $error++;
2640 }
2641 }
2642 }
2643 }
2644
2645 if (!$error) {
2646 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2647 $this->db->commit();
2648 return 1;
2649 } else {
2650 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2651 $this->db->rollback();
2652 return -$error;
2653 }
2654 }
2655
2656
2665 public function getDispachedLines($status = -1)
2666 {
2667 $ret = array();
2668
2669 // List of already dispatched lines
2670 $sql = "SELECT p.ref, p.label,";
2671 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2672 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2673 $sql .= " FROM ".$this->db->prefix()."product as p,";
2674 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2675 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2676 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2677 $sql .= " AND cfd.fk_product = p.rowid";
2678 if ($status >= 0) {
2679 $sql .= " AND cfd.status = ".((int) $status);
2680 }
2681 $sql .= " ORDER BY cfd.rowid ASC";
2682
2683 $resql = $this->db->query($sql);
2684 if ($resql) {
2685 $num = $this->db->num_rows($resql);
2686 $i = 0;
2687
2688 while ($i < $num) {
2689 $objp = $this->db->fetch_object($resql);
2690 if ($objp) {
2691 $ret[] = array(
2692 'id' => $objp->dispatchedlineid,
2693 'productid' => $objp->fk_product,
2694 'warehouseid' => $objp->warehouse_id,
2695 'qty' => $objp->qty,
2696 'orderlineid' => $objp->fk_elementdet
2697 );
2698 }
2699
2700 $i++;
2701 }
2702 } else {
2703 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2704 }
2705
2706 return $ret;
2707 }
2708
2709 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2719 public function Livraison($user, $date, $type, $comment)
2720 {
2721 // phpcs:enable
2722 global $langs;
2723
2724 $result = 0;
2725 $error = 0;
2726 $dispatchedlinearray = array();
2727
2728 dol_syslog(get_class($this)."::Livraison");
2729
2730 $usercanreceive = 0;
2731 if (!isModEnabled('reception')) {
2732 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2733 } else {
2734 $usercanreceive = $user->hasRight("reception", "creer");
2735 }
2736
2737 if ($usercanreceive) {
2738 // Define the new status
2739 if ($type == 'par') {
2741 } elseif ($type == 'tot') {
2743 } elseif ($type == 'nev') {
2745 } elseif ($type == 'can') {
2747 } else {
2748 $error++;
2749 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2750 return -2;
2751 }
2752
2753 // Some checks to accept the record
2754 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2755 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2756 if ($type == 'tot') {
2757 $dispatchedlinearray = $this->getDispachedLines(0);
2758 if (count($dispatchedlinearray) > 0) {
2759 $result = -1;
2760 $error++;
2761 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2762 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2763 }
2764 }
2765 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)
2766 $dispatcheddenied = $this->getDispachedLines(2);
2767 if (count($dispatcheddenied) > 0) {
2768 $result = -1;
2769 $error++;
2770 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2771 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2772 }
2773 }
2774 }
2775
2776 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2777
2778 if (empty($error)) {
2779 $this->db->begin();
2780
2781 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2782 $sql .= " SET fk_statut = ".((int) $statut);
2783 $sql .= " WHERE rowid = ".((int) $this->id);
2784 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2785
2786 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2787 $resql = $this->db->query($sql);
2788 if ($resql) {
2789 $result = 1;
2790 $old_statut = $this->status;
2791 $this->status = $statut;
2792 $this->context['actionmsg2'] = $comment;
2793
2794 // Call trigger
2795 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2796 if ($result_trigger < 0) {
2797 $error++;
2798 }
2799 // End call triggers
2800
2801 if (empty($error)) {
2802 $this->db->commit();
2803 } else {
2804 $this->status = $old_statut;
2805 $this->db->rollback();
2806 $this->error = $this->db->lasterror();
2807 $result = -1;
2808 }
2809 } else {
2810 $this->db->rollback();
2811 $this->error = $this->db->lasterror();
2812 $result = -1;
2813 }
2814 }
2815 } else {
2816 $this->error = $langs->trans('NotAuthorized');
2817 $this->errors[] = $langs->trans('NotAuthorized');
2818 dol_syslog(get_class($this)."::Livraison Not Authorized");
2819 $result = -3;
2820 }
2821 return $result;
2822 }
2823
2824 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2834 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2835 {
2836 // phpcs:enable
2837 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2838 }
2839
2848 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2849 {
2850 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2851 $error = 0;
2852
2853 $this->db->begin();
2854
2855 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2856 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2857 $sql .= " WHERE rowid = ".((int) $this->id);
2858
2859 dol_syslog(__METHOD__, LOG_DEBUG);
2860 $resql = $this->db->query($sql);
2861 if (!$resql) {
2862 $this->errors[] = $this->db->error();
2863 $error++;
2864 }
2865
2866 if (!$error) {
2867 $this->oldcopy = clone $this;
2868 $this->delivery_date = $delivery_date;
2869 }
2870
2871 if (!$notrigger && empty($error)) {
2872 // Call trigger
2873 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2874 if ($result < 0) {
2875 $error++;
2876 }
2877 // End call triggers
2878 }
2879
2880 if (!$error) {
2881 $this->db->commit();
2882 return 1;
2883 } else {
2884 foreach ($this->errors as $errmsg) {
2885 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2886 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2887 }
2888 $this->db->rollback();
2889 return -1 * $error;
2890 }
2891 } else {
2892 return -2;
2893 }
2894 }
2895
2902 public function setReopen(User $user): int
2903 {
2904 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2905 if ($this->status == 1) {
2906 $newStatus = 0; // Validated->Draft
2907 } elseif ($this->status == 2) {
2908 $newStatus = 0; // Approved->Draft
2909 } elseif ($this->status == 3) {
2910 $newStatus = 2; // Ordered->Approved
2911 } elseif ($this->status == 4) {
2912 $newStatus = 3;
2913 } elseif ($this->status == 5) {
2914 //$newstatus=2; // Ordered
2915 // TODO Can we set it to submitted ?
2916 //$newstatus=3; // Submitted
2917 // TODO If there is at least one reception, we can set to Received->Received partially
2918 $newStatus = 4; // Received partially
2919 } elseif ($this->status == 6) {
2920 $newStatus = 2; // Canceled->Approved
2921 } elseif ($this->status == 7) {
2922 $newStatus = 3; // Canceled->Process running
2923 } elseif ($this->status == 9) {
2924 $newStatus = 1; // Refused->Validated
2925 } else {
2926 $newStatus = 2;
2927 }
2928
2929 $this->db->begin();
2930
2931 $result = $this->setStatus($user, $newStatus);
2932 if ($result > 0) {
2933 if ($newStatus == 0) {
2934 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2935 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2936 $sql .= ' WHERE rowid = '.((int) $this->id);
2937
2938 $this->db->query($sql);
2939 }
2940
2941 $this->db->commit();
2942
2943 return 1;
2944 } else {
2945 $this->db->rollback();
2946
2947 return -1;
2948 }
2949 }
2950
2951 return 0;
2952 }
2953
2954 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2963 public function set_id_projet($user, $id_projet, $notrigger = 0)
2964 {
2965 // phpcs:enable
2966 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2967 $error = 0;
2968
2969 $this->db->begin();
2970
2971 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2972 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2973 $sql .= " WHERE rowid = ".((int) $this->id);
2974
2975 dol_syslog(__METHOD__, LOG_DEBUG);
2976 $resql = $this->db->query($sql);
2977 if (!$resql) {
2978 $this->errors[] = $this->db->error();
2979 $error++;
2980 }
2981
2982 if (!$error) {
2983 $this->oldcopy = clone $this;
2984 $this->fk_projet = $id_projet;
2985 $this->fk_project = $id_projet;
2986 }
2987
2988 if (!$notrigger && empty($error)) {
2989 // Call trigger
2990 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2991 if ($result < 0) {
2992 $error++;
2993 }
2994 // End call triggers
2995 }
2996
2997 if (!$error) {
2998 $this->db->commit();
2999 return 1;
3000 } else {
3001 foreach ($this->errors as $errmsg) {
3002 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3003 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3004 }
3005 $this->db->rollback();
3006 return -1 * $error;
3007 }
3008 } else {
3009 return -2;
3010 }
3011 }
3012
3021 public function updateFromCommandeClient($user, $idc, $comclientid)
3022 {
3023 $comclient = new Commande($this->db);
3024 $comclient->fetch($comclientid);
3025
3026 $this->id = $idc;
3027
3028 $this->lines = array();
3029
3030 $num = count($comclient->lines);
3031 for ($i = 0; $i < $num; $i++) {
3032 $prod = new Product($this->db);
3033 $label = '';
3034 $ref = '';
3035 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3036 $label = $prod->label;
3037 $ref = (string) $prod->ref;
3038 }
3039
3040 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3041 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3042 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3043 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3044 $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);
3045 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3046 if ($this->db->query($sql)) {
3047 $this->update_price(1);
3048 }
3049 }
3050
3051 return 1;
3052 }
3053
3061 public function setStatus($user, $status)
3062 {
3063 $error = 0;
3064
3065 $this->db->begin();
3066
3067 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3068 $sql .= " SET fk_statut = ".((int) $status);
3069 $sql .= " WHERE rowid = ".((int) $this->id);
3070
3071 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3072 $resql = $this->db->query($sql);
3073 if ($resql) {
3074 // Trigger names for each status
3075 $triggerName = array();
3076 $triggerName[0] = 'DRAFT';
3077 $triggerName[1] = 'VALIDATED';
3078 $triggerName[2] = 'APPROVED';
3079 $triggerName[3] = 'ORDERED'; // Ordered
3080 $triggerName[4] = 'RECEIVED_PARTIALLY';
3081 $triggerName[5] = 'RECEIVED_COMPLETELY';
3082 $triggerName[6] = 'CANCELED';
3083 $triggerName[7] = 'CANCELED';
3084 $triggerName[9] = 'REFUSED';
3085
3086 // Call trigger
3087 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3088 if ($result < 0) {
3089 $error++;
3090 }
3091 // End call triggers
3092 } else {
3093 $error++;
3094 $this->error = $this->db->lasterror();
3095 dol_syslog(get_class($this)."::setStatus ".$this->error);
3096 }
3097
3098 if (!$error) {
3099 $this->status = $status;
3100 $this->db->commit();
3101 return 1;
3102 } else {
3103 $this->db->rollback();
3104 return -1;
3105 }
3106 }
3107
3119 public function setCategories($categories)
3120 {
3121 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3122 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3123 }
3124
3148 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 = '')
3149 {
3150 global $mysoc, $conf, $langs;
3151 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3152 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3153
3154 $error = 0;
3155
3156 if ($this->status == self::STATUS_DRAFT) {
3157 // Clean parameters
3158 if (empty($qty)) {
3159 $qty = 0;
3160 }
3161 if (empty($info_bits)) {
3162 $info_bits = 0;
3163 }
3164 if (empty($txtva)) {
3165 $txtva = 0;
3166 }
3167 if (empty($txlocaltax1)) {
3168 $txlocaltax1 = 0;
3169 }
3170 if (empty($txlocaltax2)) {
3171 $txlocaltax2 = 0;
3172 }
3173 if (empty($remise_percent)) {
3174 $remise_percent = 0;
3175 }
3176
3177 $remise_percent = (float) price2num($remise_percent);
3178 $qty = price2num($qty);
3179 if (!$qty) {
3180 $qty = 1;
3181 }
3182 $pu = price2num($pu);
3183 $pu_ht_devise = price2num($pu_ht_devise);
3184 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3185 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3186 }
3187 $txlocaltax1 = (float) price2num($txlocaltax1);
3188 $txlocaltax2 = (float) price2num($txlocaltax2);
3189
3190 // Check parameters
3191 if ($type < 0) {
3192 return -1;
3193 }
3194 if ($date_start && $date_end && $date_start > $date_end) {
3195 $langs->load("errors");
3196 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3197 return -1;
3198 }
3199
3200 $this->db->begin();
3201
3202 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3203 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3204 // and this is done at the line level, which has its own VAT rate
3205
3206 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3207
3208 // Clean vat code
3209 $reg = array();
3210 $vat_src_code = '';
3211 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3212 $vat_src_code = $reg[1];
3213 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3214 }
3215
3216 $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);
3217 $total_ht = $tabprice[0];
3218 $total_tva = $tabprice[1];
3219 $total_ttc = $tabprice[2];
3220 $total_localtax1 = $tabprice[9];
3221 $total_localtax2 = $tabprice[10];
3222 $pu_ht = $tabprice[3];
3223 $pu_tva = $tabprice[4];
3224 $pu_ttc = $tabprice[5];
3225
3226 // MultiCurrency
3227 $multicurrency_total_ht = $tabprice[16];
3228 $multicurrency_total_tva = $tabprice[17];
3229 $multicurrency_total_ttc = $tabprice[18];
3230 $pu_ht_devise = $tabprice[19];
3231 $multicurrency_pu_ttc = $tabprice[21];
3232
3233 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3234 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3235
3236 // Fetch current line from the database and then clone the object and set it in $oldline property
3237 $this->line = new CommandeFournisseurLigne($this->db);
3238 $this->line->fetch($rowid);
3239
3240 $oldline = clone $this->line;
3241 $this->line->oldline = $oldline;
3242
3243 $this->line->context = $this->context;
3244
3245 $this->line->fk_commande = $this->id;
3246 //$this->line->label=$label;
3247 $this->line->desc = $desc;
3248
3249 // redefine quantity according to packaging
3250 // Mirror commande.class.php::updateline at line 3289: surface the auto-correction
3251 // to the user with a warning, float-safe the fmod / coeff arithmetic and use
3252 // abs() so negative qty is handled too (#38782 bugs 3, 4, 5, 6).
3253 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3254 if (abs((float) $qty) < $this->line->packaging) {
3255 $qty = $this->line->packaging;
3256 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3257 } else {
3258 if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0
3259 && (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
3260 $coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
3261 $qty = price2num((float) $this->line->packaging * $coeff, 'MS');
3262 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3263 }
3264 }
3265 }
3266
3267 $this->line->qty = $qty;
3268 $this->line->ref_supplier = $ref_supplier;
3269
3270 $this->line->vat_src_code = $vat_src_code;
3271 $this->line->tva_tx = $txtva;
3272 $this->line->localtax1_tx = $txlocaltax1;
3273 $this->line->localtax2_tx = $txlocaltax2;
3274 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3275 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3276 $this->line->remise_percent = $remise_percent;
3277 $this->line->subprice = (float) $pu_ht;
3278 $this->line->subprice_ttc = (float) $pu_ttc;
3279 $this->line->info_bits = $info_bits;
3280 $this->line->total_ht = (float) $total_ht;
3281 $this->line->total_tva = (float) $total_tva;
3282 $this->line->total_localtax1 = (float) $total_localtax1;
3283 $this->line->total_localtax2 = (float) $total_localtax2;
3284 $this->line->total_ttc = (float) $total_ttc;
3285 $this->line->product_type = $type;
3286 $this->line->special_code = $oldline->special_code;
3287 $this->line->rang = $oldline->rang;
3288 $this->line->origin = $this->origin;
3289 $this->line->fk_unit = $fk_unit;
3290
3291 $this->line->date_start = $date_start;
3292 $this->line->date_end = $date_end;
3293
3294 // Multicurrency
3295 $this->line->fk_multicurrency = $this->fk_multicurrency;
3296 $this->line->multicurrency_code = $this->multicurrency_code;
3297 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3298 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3299 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3300 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3301 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3302
3303 $this->line->subprice = (float) $pu_ht;
3304 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
3305 $this->line->price = $this->line->subprice;
3306
3307 $this->line->remise_percent = $remise_percent;
3308
3309 if (is_array($array_options) && count($array_options) > 0) {
3310 // We replace values in this->line->array_options only for entries defined into $array_options
3311 foreach ($array_options as $key => $value) {
3312 $this->line->array_options[$key] = $array_options[$key];
3313 }
3314 }
3315
3316 $result = $this->line->update($notrigger);
3317
3318
3319 // Update denormalized information at the invoice level
3320 if ($result >= 0) {
3321 $this->update_price(1, 'auto');
3322 $this->db->commit();
3323 return $result;
3324 } else {
3325 $this->errors[] = $this->line->error;
3326 $this->errors = array_merge($this->errors, $this->line->errors);
3327 $this->error = $this->db->lasterror();
3328 $this->db->rollback();
3329 return -1;
3330 }
3331 } else {
3332 $this->error = "Order status makes operation forbidden";
3333 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3334 return -2;
3335 }
3336 }
3337
3338
3346 public function initAsSpecimen()
3347 {
3348 global $user, $langs, $conf;
3349
3350 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3351
3352 dol_syslog(get_class($this)."::initAsSpecimen");
3353
3354 $now = dol_now();
3355
3356 // Find first product
3357 $prodid = 0;
3358 $product = new ProductFournisseur($this->db);
3359 $sql = "SELECT rowid";
3360 $sql .= " FROM ".$this->db->prefix()."product";
3361 $sql .= " WHERE entity IN (".getEntity('product').")";
3362 $sql .= $this->db->order("rowid", "ASC");
3363 $sql .= $this->db->plimit(1);
3364 $resql = $this->db->query($sql);
3365 if ($resql && $this->db->num_rows($resql)) {
3366 $obj = $this->db->fetch_object($resql);
3367 $prodid = $obj->rowid;
3368 }
3369
3370 // Initialise parameters
3371 $this->id = 0;
3372 $this->ref = 'SPECIMEN';
3373 $this->specimen = 1;
3374 $this->socid = 1;
3375 $this->date = $now;
3376 $this->date_commande = $now;
3377 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3378 $this->cond_reglement_code = 'RECEP';
3379 $this->mode_reglement_code = 'CHQ';
3380
3381 $this->note_public = 'This is a comment (public)';
3382 $this->note_private = 'This is a comment (private)';
3383
3384 $this->multicurrency_tx = 1;
3385 $this->multicurrency_code = $conf->currency;
3386
3387 $this->statut = 0; // deprecated
3388 $this->status = 0;
3389
3390 // Lines
3391 $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)
3392 $xnbp = 0;
3393 while ($xnbp < $nbp) {
3394 $line = new CommandeFournisseurLigne($this->db);
3395 $line->desc = $langs->trans("Description")." ".$xnbp;
3396 $line->qty = 1;
3397 $line->subprice = 100;
3398 $line->tva_tx = 19.6;
3399 $line->localtax1_tx = 0;
3400 $line->localtax2_tx = 0;
3401 if ($xnbp == 2) {
3402 $line->total_ht = 50;
3403 $line->total_ttc = 59.8;
3404 $line->total_tva = 9.8;
3405 $line->remise_percent = 50;
3406 } else {
3407 $line->total_ht = 100;
3408 $line->total_ttc = 119.6;
3409 $line->total_tva = 19.6;
3410 $line->remise_percent = 00;
3411 }
3412 $line->fk_product = $prodid;
3413
3414 $this->lines[$xnbp] = $line;
3415
3416 $this->total_ht += $line->total_ht;
3417 $this->total_tva += $line->total_tva;
3418 $this->total_ttc += $line->total_ttc;
3419
3420 $xnbp++;
3421 }
3422
3423 return 1;
3424 }
3425
3432 public function info($id)
3433 {
3434 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3435 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3436 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3437 $sql .= ' WHERE c.rowid = '.((int) $id);
3438
3439 $result = $this->db->query($sql);
3440 if ($result) {
3441 if ($this->db->num_rows($result)) {
3442 $obj = $this->db->fetch_object($result);
3443
3444 $this->id = $obj->rowid;
3445
3446 $this->user_creation_id = $obj->fk_user_author;
3447 $this->user_validation_id = $obj->fk_user_valid;
3448 $this->user_modification_id = $obj->fk_user_modif;
3449 $this->user_approve_id = $obj->fk_user_approve;
3450 $this->user_approve_id2 = $obj->fk_user_approve2;
3451
3452 $this->date_creation = $this->db->jdate($obj->datec);
3453 $this->date_modification = $this->db->jdate($obj->datem);
3454 $this->date_approve = $this->db->jdate($obj->datea);
3455 $this->date_approve2 = $this->db->jdate($obj->datea2);
3456 $this->date_validation = $this->db->jdate($obj->date_validation);
3457 }
3458 $this->db->free($result);
3459 } else {
3460 dol_print_error($this->db);
3461 }
3462 }
3463
3469 public function loadStateBoard()
3470 {
3471 global $user;
3472
3473 $this->nb = array();
3474 $sanitizedclause = "WHERE";
3475
3476 $sql = "SELECT count(co.rowid) as nb";
3477 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3478 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3479 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3480 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3481 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3482 $sanitizedclause = "AND";
3483 }
3484 $sql .= " ".$sanitizedclause." co.entity IN (".getEntity('supplier_order').")";
3485
3486 $resql = $this->db->query($sql);
3487 if ($resql) {
3488 while ($obj = $this->db->fetch_object($resql)) {
3489 $this->nb["supplier_orders"] = $obj->nb;
3490 }
3491 $this->db->free($resql);
3492 return 1;
3493 } else {
3494 dol_print_error($this->db);
3495 $this->error = $this->db->error();
3496 return -1;
3497 }
3498 }
3499
3500 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3508 public function load_board($user, $mode = 'opened')
3509 {
3510 // phpcs:enable
3511 global $conf, $langs;
3512
3513 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3514 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3515 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3516 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3517 }
3518 $sql .= " WHERE c.entity = ".((int) $conf->entity);
3519 if ($mode === 'awaiting') {
3520 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3521 } else {
3522 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3523 }
3524 if ($user->socid) {
3525 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3526 }
3527
3528 $resql = $this->db->query($sql);
3529 if ($resql) {
3530 $commandestatic = new CommandeFournisseur($this->db);
3531
3532 $response = new WorkboardResponse();
3533 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3534 $response->label = $langs->trans("SuppliersOrdersToProcess");
3535 $response->labelShort = $langs->trans("Opened");
3536 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3537 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3538 $response->img = img_object('', "order");
3539
3540 if ($mode === 'awaiting') {
3541 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3542 $response->labelShort = $langs->trans("AwaitingReception");
3543 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3544 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3545 }
3546
3547 while ($obj = $this->db->fetch_object($resql)) {
3548 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3549 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3550 $commandestatic->statut = $obj->fk_statut; // deprecated
3551 $commandestatic->status = $obj->fk_statut;
3552
3553 $response->nbtodo++;
3554 $response->total += $obj->total_ht;
3555
3556 if ($commandestatic->hasDelay()) {
3557 $response->nbtodolate++;
3558 }
3559 }
3560
3561 return $response;
3562 } else {
3563 $this->error = $this->db->error();
3564 return -1;
3565 }
3566 }
3567
3574 public function getInputMethod()
3575 {
3576 global $langs;
3577
3578 if ($this->methode_commande_id > 0) {
3579 $sql = "SELECT rowid, code, libelle as label";
3580 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3581 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3582
3583 $resql = $this->db->query($sql);
3584 if ($resql) {
3585 if ($this->db->num_rows($resql)) {
3586 $obj = $this->db->fetch_object($resql);
3587
3588 $string = $langs->trans($obj->code);
3589 if ($string == $obj->code) {
3590 $string = $obj->label != '-' ? $obj->label : '';
3591 }
3592 return $string;
3593 }
3594 } else {
3595 dol_print_error($this->db);
3596 }
3597 }
3598
3599 return '';
3600 }
3601
3613 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3614 {
3615 global $langs;
3616
3617 if (!dol_strlen($modele)) {
3618 $modele = ''; // No doc template/generation by default
3619
3620 if (!empty($this->model_pdf)) {
3621 $modele = $this->model_pdf;
3622 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3623 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3624 }
3625 }
3626
3627 if (empty($modele)) {
3628 return 0;
3629 } else {
3630 $langs->load("suppliers");
3631 $outputlangs->load("products");
3632
3633 $modelpath = "core/modules/supplier_order/doc/";
3634 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3635 return $result;
3636 }
3637 }
3638
3645 public function getMaxDeliveryTimeDay($langs)
3646 {
3647 if (empty($this->lines)) {
3648 return '';
3649 }
3650
3651 $tmpproductfourn = new ProductFournisseur($this->db);
3652
3653 $nb = 0;
3654 foreach ($this->lines as $line) {
3655 if ($line->fk_product > 0) {
3656 // Load delivery_time_days, return id into product_fournisseur_price
3657 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3658 if ($idp > 0) {
3659 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3660 if ($tmpproductfourn->delivery_time_days > $nb) {
3661 $nb = $tmpproductfourn->delivery_time_days;
3662 }
3663 }
3664 }
3665 }
3666
3667 if ($nb === 0) {
3668 return '';
3669 } else {
3670 return $nb.' '.$langs->trans('days');
3671 }
3672 }
3673
3679 public function getRights()
3680 {
3681 global $user;
3682
3683 return $user->hasRight("fournisseur", "commande");
3684 }
3685
3686
3695 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3696 {
3697 $tables = array(
3698 'commande_fournisseur'
3699 );
3700
3701 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3702 }
3703
3712 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3713 {
3714 $tables = array(
3715 'commande_fournisseurdet'
3716 );
3717
3718 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3719 }
3720
3728 public function hasDelay()
3729 {
3730 global $conf;
3731
3732 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3733 $now = dol_now();
3734 if (!empty($this->delivery_date)) {
3735 $date_to_test = $this->delivery_date;
3736 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3737 } else {
3738 //$date_to_test = $this->date_commande;
3739 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3740 return false;
3741 }
3742 } else {
3743 $now = dol_now();
3744 $date_to_test = $this->date_commande;
3745
3746 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3747 }
3748 }
3749
3757 public function showDelay()
3758 {
3759 global $conf, $langs;
3760
3761 $langs->load('orders');
3762
3763 $text = '';
3764
3765 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3766 if (!empty($this->delivery_date)) {
3767 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3768 } else {
3769 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3770 }
3771 } else {
3772 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3773 }
3774 if ($text) {
3775 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3776 }
3777
3778 return $text;
3779 }
3780
3781
3790 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3791 {
3792 if (isModEnabled("supplier_order")) {
3793 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3794
3795 $qtydelivered = array();
3796 $qtywished = array();
3797
3798 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3799
3800 $filter = array('t.fk_element' => $this->id);
3801 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3802 $filter['t.status'] = 1; // Restrict to lines with status validated
3803 }
3804
3805 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3806 if ($ret < 0) {
3807 $this->error = $supplierorderdispatch->error;
3808 $this->errors = $supplierorderdispatch->errors;
3809 return $ret;
3810 } else {
3811 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3812 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3813 $date_liv = dol_now();
3814
3815 // Build array with quantity deliverd by product
3816 foreach ($supplierorderdispatch->lines as $line) {
3817 if (array_key_exists($line->fk_product, $qtydelivered)) {
3818 $qtydelivered[$line->fk_product] += $line->qty;
3819 } else {
3820 $qtydelivered[$line->fk_product] = $line->qty;
3821 }
3822 }
3823 foreach ($this->lines as $line) {
3824 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3825 if ($line->product_type > 0 && (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') || empty($line->stockable_product))) {
3826 continue;
3827 }
3828 if (array_key_exists($line->fk_product, $qtywished)) {
3829 $qtywished[$line->fk_product] += $line->qty;
3830 } else {
3831 $qtywished[$line->fk_product] = $line->qty;
3832 }
3833 }
3834
3835 //Compare array
3836 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3837 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3838 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3839 //var_dump(array_keys($qtydelivered));
3840 //var_dump(array_keys($qtywished));
3841 //var_dump($diff_array);
3842 //var_dump($keysinwishednotindelivered);
3843 //var_dump($keysindeliverednotinwished);
3844 //exit;
3845
3846 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3847 if ($closeopenorder) {
3848 //$ret=$this->setStatus($user,5);
3849 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3850 if ($ret < 0) {
3851 return -1;
3852 }
3853 return 5;
3854 } else {
3855 //Diff => received partially
3856 //$ret=$this->setStatus($user,4);
3857 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3858 if ($ret < 0) {
3859 return -1;
3860 }
3861 return 4;
3862 }
3863 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3864 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3865
3866 $close = 0;
3867
3868 if (count($diff_array) > 0) {
3869 //there are some difference between the two arrays
3870
3871 //scan the array of results
3872 foreach ($diff_array as $key => $value) {
3873 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3874 if ($qtydelivered[$key] >= $qtywished[$key]) {
3875 $close++;
3876 }
3877 }
3878 }
3879
3880
3881 if ($close == count($diff_array)) {
3882 //all the products are received equal or more than the wished quantity
3883 if ($closeopenorder) {
3884 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3885 if ($ret < 0) {
3886 return -1;
3887 }
3888 return 5;
3889 } else {
3890 //Diff => received partially
3891 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3892 if ($ret < 0) {
3893 return -1;
3894 }
3895 return 4;
3896 }
3897 } else {
3898 //all the products are not received
3899 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3900 if ($ret < 0) {
3901 return -1;
3902 }
3903 return 4;
3904 }
3905 } else {
3906 //Diff => received partially
3907 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3908 if ($ret < 0) {
3909 return -1;
3910 }
3911 return 4;
3912 }
3913 }
3914 return 1;
3915 }
3916 }
3917 return 0;
3918 }
3919
3927 public function loadReceptions($filtre_statut = -1)
3928 {
3929 $this->receptions = array();
3930
3931 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3932
3933 $sql = 'SELECT cd.rowid, cd.fk_product,';
3934 $sql .= ' sum(cfd.qty) as qty';
3935 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3936 if ($filtre_statut >= 0) {
3937 $sql .= ' '.$this->db->prefix().'reception as e,';
3938 }
3939 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3940 $sql .= ' WHERE';
3941 if ($filtre_statut >= 0) {
3942 $sql .= ' cfd.fk_reception = e.rowid AND';
3943 }
3944 $sql .= ' cfd.fk_elementdet = cd.rowid';
3945 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3946 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3947 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3948 }
3949 if ($filtre_statut >= 0) {
3950 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3951 }
3952 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3953
3954 $resql = $this->db->query($sql);
3955 if ($resql) {
3956 $num = $this->db->num_rows($resql);
3957 $i = 0;
3958 while ($i < $num) {
3959 $obj = $this->db->fetch_object($resql);
3960 if (empty($this->receptions[$obj->rowid])) {
3961 $this->receptions[$obj->rowid] = (float) $obj->qty;
3962 } else {
3963 $this->receptions[$obj->rowid] += (float) $obj->qty;
3964 }
3965 $i++;
3966 }
3967 $this->db->free($resql);
3968
3969 return $num;
3970 } else {
3971 $this->error = $this->db->lasterror();
3972 return -1;
3973 }
3974 }
3975
3983 public function getKanbanView($option = '', $arraydata = null)
3984 {
3985 global $langs;
3986
3987 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3988
3989 $return = '<div class="box-flex-item box-flex-grow-zero">';
3990 $return .= '<div class="info-box info-box-sm">';
3991 $return .= '<span class="info-box-icon bg-infobox-action">';
3992 $return .= img_picto('', $this->picto);
3993 $return .= '</span>';
3994 $return .= '<div class="info-box-content">';
3995 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3996 if ($selected >= 0) {
3997 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3998 }
3999 if (!empty($arraydata['thirdparty'])) {
4000 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
4001 }
4002 if (!empty($this->date)) {
4003 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
4004 }
4005 if (!empty($this->total_ht)) {
4006 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
4007 $return .= ' '.$langs->trans("HT");
4008 $return .= '</span>';
4009 }
4010 if (method_exists($this, 'getLibStatut')) {
4011 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
4012 }
4013 if (property_exists($this, 'billed')) {
4014 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
4015 }
4016 $return .= '</div>';
4017 $return .= '</div>';
4018 $return .= '</div>';
4019 return $return;
4020 }
4021}
$object ref
Definition info.php:90
Class to manage table ReceptionLineBatch.
Class to manage predefined suppliers products.
const STATUS_CANCELED_AFTER_ORDER
Order canceled/never received.
const STATUS_RECEIVED_PARTIALLY
Received partially.
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set the planned delivery date.
updateFromCommandeClient($user, $idc, $comclientid)
Update a supplier order from a sales order.
getNomUrl($withpicto=0, $option='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=0)
Return clickable name (with picto eventually)
loadReceptions($filtre_statut=-1)
Load array this->receptions of lines of shipments with nb of products sent for each order line Note: ...
static replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a product id with another one.
$fields
'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortf...
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
info($id)
Charge les information d'ordre info dans l'objet facture.
const STATUS_CANCELED
Order canceled.
fetch_lines($only_product=0)
Load array lines.
getInputMethod()
Returns the translated input method of object (defined if $this->methode_commande_id > 0).
const STATUS_VALIDATED
Validated status.
const STATUS_RECEIVED_COMPLETELY
Received completely.
cancel($user, $idwarehouse=-1)
Cancel an approved order.
setReopen(User $user)
Reopen supplier order.
loadStateBoard()
Load the indicators this->nb for the state board.
calcAndSetStatusDispatch(User $user, $closeopenorder=1, $comment='')
Calc status regarding to dispatched stock.
set_id_projet($user, $id_projet, $notrigger=0)
Set the id projet.
showDelay()
Show the customer delayed info.
getTooltipContentArray($params)
getTooltipContentArray
classifyUnBilled(User $user)
Classify not billed.
approve($user, $idwarehouse=0, $secondlevel=0)
Approve a supplier order.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $fk_prod_fourn_price=0, $ref_supplier='', $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $type=0, $info_bits=0, $notrigger=0, $date_start=null, $date_end=null, $array_options=[], $fk_unit=null, $pu_ht_devise=0, $origin='', $origin_id=0, $rang=-1, $special_code=0, $label='')
Add order line.
dispatchProduct($user, $product, $qty, $entrepot, $price=0, $comment='', $eatby='', $sellby='', $batch='', $fk_commandefourndet=0, $notrigger=0, $fk_reception=0)
Save a receiving into the tracking table of receiving (receptiondet_batch) and add product into stock...
valid($user, $idwarehouse=0, $notrigger=0)
Validate an order.
create($user, $notrigger=0)
Create order with draft status.
update(User $user, $notrigger=0)
Update Supplier Order.
createFromClone(User $user, $socid=0, $notrigger=0)
Load an object from its id and create a new one in database.
updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0, $txlocaltax2=0, $price_base_type='HT', $info_bits=0, $type=0, $notrigger=0, $date_start=0, $date_end=0, $array_options=[], $fk_unit=null, $pu_ht_devise=0, $ref_supplier='')
Update line.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template model.
const STATUS_ORDERSENT
Order sent, shipment on process.
commande($user, $date, $methode, $comment='')
Submit a supplier order to supplier.
getRights()
Returns the rights used for this class.
load_board($user, $mode='opened')
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
deleteLine($idline, $notrigger=0)
Delete line.
getMaxDeliveryTimeDay($langs)
Return the max number delivery delay in day.
fetch($id, $ref='')
Get object and lines from database.
Livraison($user, $date, $type, $comment)
Set a delivery in database for this supplier order.
getDispachedLines($status=-1)
Return array of dispatched lines waiting to be approved for this order.
classifyBilled(User $user)
Class invoiced the supplier order.
initAsSpecimen()
Initialise an instance with random values.
const SOURCE_ID_REPLENISHMENT
The constant used into source field to track the order was generated by the replenishement feature.
setCategories($categories)
Sets object to given categories.
setStatus($user, $status)
Tag order with a particular status.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getLibStatut($mode=0)
Return label of the status of object.
hasDelay()
Is the supplier order delayed? We suppose a purchase ordered as late if a the purchase order has been...
LibStatut($status, $mode=0, $billed=0)
Return label of a status.
Class to manage line orders.
Class to manage customers orders.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
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:65
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
getDolGlobalFloat($key, $default=0)
Return a Dolibarr global constant float value.
setEntity($currentobject)
Set entity id to use when to create an object.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
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...
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.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
setEventMessage($mesgs, $style='mesgs', $noduplicate=0, $attop=0)
Set event message in dol_events session object.
yn($yesno, $format=1, $color=0)
Return yes or no in current language.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
Definition html.lib.php:172
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
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