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 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
22 * Copyright (C) 2026 Nick Fragoulis
23 *
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 3 of the License, or
27 * (at your option) any later version.
28 *
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36 */
37
44require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
45require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
46require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.orderline.class.php';
47require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
48require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
49if (isModEnabled('productbatch')) {
50 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
51}
52
53
58{
59 use CommonSubtotal;
60
64 public $element = 'order_supplier';
65
69 public $table_element = 'commande_fournisseur';
70
74 public $table_element_line = 'commande_fournisseurdet';
75
79 public $class_element_line = 'CommandeFournisseurLigne';
80
84 public $fk_element = 'fk_commande';
85
89 public $picto = 'supplier_order';
90
95 public $restrictiononfksoc = 1;
96
100 protected $table_ref_field = 'ref';
101
105 public $id;
106
110 public $ref;
111
115 public $ref_supplier;
116
122 public $ref_fourn;
123
129 public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
130 // -> 7=Canceled/Never received -> (reopen) 3=Process running
131 // -> 6=Canceled -> (reopen) 2=Approved
132 // -> 9=Refused -> (reopen) 1=Validated
133 // Note: billed or not is on another field "billed"
134
138 public $billed;
139
143 public $socid;
144
148 public $fourn_id;
149
153 public $date;
154
158 public $date_valid;
159
163 public $date_approve;
164
169 public $date_approve2;
170
174 public $date_commande;
175
180 public $remise_percent;
184 public $methode_commande_id;
188 public $methode_commande;
189
193 public $delivery_date;
194
198 public $fk_warehouse;
199
203 public $total_ht;
204
208 public $total_tva;
209
213 public $total_localtax1;
214
218 public $total_localtax2;
219
223 public $total_ttc;
224
228 public $source;
229
233 public $cond_reglement_id;
234
238 public $cond_reglement_code;
239
243 public $cond_reglement_label;
244
248 public $cond_reglement_doc;
249
254 public $deposit_percent;
255
259 public $fk_account;
260
264 public $mode_reglement_id;
265
269 public $mode_reglement_code;
270
274 public $mode_reglement;
275
279 public $user_author_id;
280
284 public $user_approve_id;
285
290 public $user_approve_id2;
291
295 public $refuse_note;
296
300 public $cancel_note;
301
305 public $extraparams = array();
306
310 public $lines = array();
311
315 public $line;
316
320 public $origin;
321
325 public $origin_id;
326
330 public $date_lim_reglement;
331
335 public $receptions = array();
336
337 // Multicurrency
341 public $fk_multicurrency;
342
346 public $multicurrency_code;
347
351 public $multicurrency_tx;
352
356 public $multicurrency_total_ht;
357
361 public $multicurrency_total_tva;
362
366 public $multicurrency_total_ttc;
367
395 public $fields = array(
396 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
397 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
398 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 35),
399 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
400 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
401 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
402 'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
403 'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
404 'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
405 'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'getDolGlobalInt("ORDER_DISABLE_DELIVERY_DATE") ? 0 : 1', 'visible' => 1, 'position' => 74),
406 'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'DefaultWarehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 75, 'nodepth' => 1),
407 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
408 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
409 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
410 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
411 'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
412 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
413 'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
414 'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
415 'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
416 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
417 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
418 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
419 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
420 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
421 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
422 'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
423 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
424 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 176),
425 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
426 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
427 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
428 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
429 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
430 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Currency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
431 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
432 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
433 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
434 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
435 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
436 'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
437 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
438 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
439 'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
440 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
441 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
442 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
443 );
444
445
449 const STATUS_DRAFT = 0;
450
455
460
465
470
475
480
485
489 const STATUS_REFUSED = 9;
490
491
496
502 public function __construct($db)
503 {
504 $this->db = $db;
505
506 $this->ismultientitymanaged = 1;
507 }
508
509
517 public function fetch($id, $ref = '')
518 {
519 // Check parameters
520 if (empty($id) && empty($ref)) {
521 return -1;
522 }
523
524 $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,";
525 $sql .= " c.localtax1, c.localtax2, ";
526 $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
527 $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,";
528 $sql .= " c.date_commande as date_commande, c.date_livraison as delivery_date, c.fk_warehouse, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
529 $sql .= " c.fk_account,";
530 $sql .= " c.note_private, c.note_public, c.model_pdf, c.last_main_doc, c.extraparams, c.billed,";
531 $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
532 $sql .= " cm.libelle as methode_commande,";
533 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc, c.deposit_percent,";
534 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
535 $sql .= ', c.fk_incoterms, c.location_incoterms';
536 $sql .= ', c.last_main_doc';
537 $sql .= ', i.libelle as label_incoterms';
538 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
539 $sql .= " LEFT JOIN ".$this->db->prefix()."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
540 $sql .= " LEFT JOIN ".$this->db->prefix()."c_paiement as p ON c.fk_mode_reglement = p.id";
541 $sql .= " LEFT JOIN ".$this->db->prefix()."c_input_method as cm ON cm.rowid = c.fk_input_method";
542 $sql .= ' LEFT JOIN '.$this->db->prefix().'c_incoterms as i ON c.fk_incoterms = i.rowid';
543
544 if (empty($id)) {
545 $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
546 } else {
547 $sql .= " WHERE c.rowid=".((int) $id);
548 }
549
550 if ($ref) {
551 $sql .= " AND c.ref='".$this->db->escape($ref)."'";
552 }
553
554 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
555 $resql = $this->db->query($sql);
556 if ($resql) {
557 $obj = $this->db->fetch_object($resql);
558 if (!$obj) {
559 $this->error = 'Bill with id '.$id.' not found';
560 dol_syslog(get_class($this).'::fetch '.$this->error);
561 return 0;
562 }
563
564 $this->id = $obj->rowid;
565 $this->entity = $obj->entity;
566
567 $this->ref = $obj->ref;
568 $this->ref_supplier = $obj->ref_supplier;
569
570 $this->socid = $obj->fk_soc;
571 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
572
573 $this->fourn_id = $obj->fk_soc;
574 $this->statut = $obj->status; // deprecated
575 $this->status = $obj->status;
576 $this->billed = $obj->billed;
577 $this->last_main_doc = $obj->last_main_doc;
578 $this->user_author_id = $obj->user_author_id;
579 $this->user_validation_id = $obj->user_validation_id;
580 $this->user_approve_id = $obj->user_approve_id;
581 $this->user_approve_id2 = $obj->user_approve_id2;
582 $this->total_ht = $obj->total_ht;
583 $this->total_tva = $obj->total_tva;
584 $this->total_localtax1 = $obj->localtax1;
585 $this->total_localtax2 = $obj->localtax2;
586 $this->total_ttc = $obj->total_ttc;
587 $this->date_creation = $this->db->jdate($obj->date_creation);
588 $this->date_valid = $this->db->jdate($obj->date_valid);
589 $this->date_approve = $this->db->jdate($obj->date_approve);
590 $this->date_approve2 = $this->db->jdate($obj->date_approve2);
591 $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
592 if (isset($obj->date_commande)) {
593 $this->date = $this->date_commande;
594 } else {
595 $this->date = $this->date_creation;
596 }
597 $this->delivery_date = $this->db->jdate($obj->delivery_date);
598 $this->fk_warehouse = $obj->fk_warehouse;
599 $this->remise_percent = $obj->remise_percent;
600 $this->methode_commande_id = $obj->fk_input_method;
601 $this->methode_commande = $obj->methode_commande;
602
603 $this->source = $obj->source;
604 $this->fk_project = $obj->fk_project;
605 $this->cond_reglement_id = $obj->fk_cond_reglement;
606 $this->cond_reglement_code = $obj->cond_reglement_code;
607 $this->cond_reglement_label = $obj->cond_reglement_label;
608 $this->cond_reglement_doc = $obj->cond_reglement_doc;
609 $this->deposit_percent = $obj->deposit_percent;
610 $this->fk_account = $obj->fk_account;
611 $this->mode_reglement_id = $obj->fk_mode_reglement;
612 $this->mode_reglement_code = $obj->mode_reglement_code;
613 $this->mode_reglement = $obj->mode_reglement_libelle;
614 $this->note = $obj->note_private; // deprecated
615 $this->note_private = $obj->note_private;
616 $this->note_public = $obj->note_public;
617 $this->model_pdf = $obj->model_pdf;
618 $this->last_main_doc = $obj->last_main_doc;
619
620 //Incoterms
621 $this->fk_incoterms = $obj->fk_incoterms;
622 $this->location_incoterms = $obj->location_incoterms;
623 $this->label_incoterms = $obj->label_incoterms;
624
625 // Multicurrency
626 $this->fk_multicurrency = $obj->fk_multicurrency;
627 $this->multicurrency_code = $obj->multicurrency_code;
628 $this->multicurrency_tx = $obj->multicurrency_tx;
629 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
630 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
631 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
632
633 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
634
635 $this->db->free($resql);
636
637 // Retrieve all extrafield
638 // fetch optionals attributes and labels
639 $this->fetch_optionals();
640
641 // Lines
642 $result = $this->fetch_lines();
643
644 if ($result < 0) {
645 return -1;
646 } else {
647 return 1;
648 }
649 } else {
650 $this->error = $this->db->error()." sql=".$sql;
651 return -1;
652 }
653 }
654
655 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
662 public function fetch_lines($only_product = 0)
663 {
664 // phpcs:enable
665
666 $this->lines = array();
667
668 $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
669 $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice, l.subprice_ttc,";
670 $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
671 $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.info_bits, l.special_code, l.fk_parent_line, l.rang,";
672 $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,";
673 $sql .= " l.fk_unit, l.extraparams,";
674 $sql .= " l.date_start, l.date_end,";
675 $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';
676 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as l";
677 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON l.fk_product = p.rowid';
678 $sql .= " WHERE l.fk_commande = ".((int) $this->id);
679 if ($only_product) {
680 $sql .= ' AND p.fk_product_type = 0';
681 }
682 $sql .= " ORDER BY l.rang, l.rowid";
683 //print $sql;
684
685 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
686
687 $result = $this->db->query($sql);
688 if ($result) {
689 $num = $this->db->num_rows($result);
690 $i = 0;
691
692 while ($i < $num) {
693 $objp = $this->db->fetch_object($result);
694
695 $line = new CommandeFournisseurLigne($this->db);
696
697 $line->id = $objp->rowid;
698 $line->fk_commande = $objp->fk_commande;
699 $line->desc = $objp->description;
700 $line->description = $objp->description;
701 $line->qty = $objp->qty;
702 $line->tva_tx = $objp->tva_tx;
703 $line->localtax1_tx = $objp->localtax1_tx;
704 $line->localtax2_tx = $objp->localtax2_tx;
705 $line->localtax1_type = $objp->localtax1_type;
706 $line->localtax2_type = $objp->localtax2_type;
707 $line->subprice = $objp->subprice;
708 $line->pu_ht = $objp->subprice; // deprecated
709 $line->subprice_ttc = $objp->subprice_ttc;
710 $line->pu_ttc = $objp->subprice_ttc; // deprecated
711 $line->remise_percent = $objp->remise_percent;
712
713 $line->vat_src_code = $objp->vat_src_code;
714 $line->total_ht = $objp->total_ht;
715 $line->total_tva = $objp->total_tva;
716 $line->total_localtax1 = $objp->total_localtax1;
717 $line->total_localtax2 = $objp->total_localtax2;
718 $line->total_ttc = $objp->total_ttc;
719 $line->product_type = $objp->product_type;
720
721 $line->fk_product = $objp->fk_product;
722
723 $line->libelle = $objp->product_label; // deprecated
724 $line->product_label = $objp->product_label;
725 $line->product_desc = $objp->product_desc;
726 $line->product_tobatch = $objp->product_tobatch;
727 $line->product_barcode = $objp->product_barcode;
728 $line->stockable_product = $objp->stockable_product;
729
730 $line->ref = $objp->product_ref; // Ref of product
731 $line->product_ref = $objp->product_ref; // Ref of product
732 $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
733 $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
734
735 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
736 // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
737 // Move this into another method and call it when required.
738
739 // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
740 $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
741 $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('productsupplierprice').")";
742 $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
743 $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
744 $sqlsearchpackage .= " AND quantity <= ".abs((float) $objp->qty); // required to be qualified
745 $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".abs((float) $objp->qty).")"; // required to be qualified
746 $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
747 $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
748 $sqlsearchpackage .= " LIMIT 1";
749
750 $resqlsearchpackage = $this->db->query($sqlsearchpackage);
751 if ($resqlsearchpackage) {
752 $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
753 if ($objsearchpackage) {
754 $line->fk_fournprice = $objsearchpackage->rowid;
755 $line->packaging = (float) $objsearchpackage->packaging;
756 }
757 } else {
758 $this->error = $this->db->lasterror();
759 return -1;
760 }
761 }
762
763 $line->date_start = $this->db->jdate($objp->date_start);
764 $line->date_end = $this->db->jdate($objp->date_end);
765 $line->fk_unit = $objp->fk_unit;
766
767 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
768
769 // Multicurrency
770 $line->fk_multicurrency = $objp->fk_multicurrency;
771 $line->multicurrency_code = $objp->multicurrency_code;
772 $line->multicurrency_subprice = $objp->multicurrency_subprice;
773 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
774 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
775 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
776 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
777
778 $line->info_bits = $objp->info_bits;
779 $line->special_code = $objp->special_code;
780 $line->fk_parent_line = $objp->fk_parent_line;
781
782 $line->rang = $objp->rang;
783
784 // Retrieve all extrafield
785 // fetch optionals attributes and labels
786 $line->fetch_optionals();
787
788 $this->lines[$i] = $line;
789
790 $i++;
791 }
792 $this->db->free($result);
793
794 return $num;
795 } else {
796 $this->error = $this->db->error()." sql=".$sql;
797 return -1;
798 }
799 }
800
809 public function valid($user, $idwarehouse = 0, $notrigger = 0)
810 {
811 global $conf;
812 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
813
814 $error = 0;
815
816 dol_syslog(get_class($this)."::valid");
817 $result = 0;
818 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
819 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
820 $this->db->begin();
821
822 if (!getDolGlobalBool('SUPPLIER_ORDER_NOCHECK_ONBUY_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines('onbuy')) {
823 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
824 return -1;
825 }
826 // Definition of supplier order numbering model name
827 $soc = new Societe($this->db);
828 $soc->fetch((int) $this->fourn_id);
829
830 // Check if object has a temporary ref
831 if (preg_match('/^[\‍(]?PROV/i', (string) $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
832 $num = $this->getNextNumRef($soc);
833 } else {
834 $num = (string) $this->ref;
835 }
836 $this->newref = dol_sanitizeFileName($num);
837
838 $sql = 'UPDATE '.$this->db->prefix()."commande_fournisseur";
839 $sql .= " SET ref='".$this->db->escape($num)."',";
840 $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
841 $sql .= " date_valid='".$this->db->idate(dol_now())."',";
842 $sql .= " fk_user_valid = ".((int) $user->id);
843 $sql .= " WHERE rowid = ".((int) $this->id);
844 $sql .= " AND fk_statut = ".((int) self::STATUS_DRAFT);
845
846 $resql = $this->db->query($sql);
847 if (!$resql) {
848 dol_print_error($this->db);
849 $error++;
850 }
851
852 if (!$error && !$notrigger) {
853 // Call trigger
854 $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
855 if ($result < 0) {
856 $error++;
857 }
858 // End call triggers
859 }
860
861 if (!$error) {
862 $this->oldref = $this->ref;
863
864 // Rename directory if dir was a temporary ref
865 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
866 // Now we rename also files into index
867 $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)."'";
868 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
869 $resql = $this->db->query($sql);
870 if (!$resql) {
871 $error++;
872 $this->error = $this->db->lasterror();
873 }
874 $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
875 $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
876 $resql = $this->db->query($sql);
877 if (!$resql) {
878 $error++;
879 $this->error = $this->db->lasterror();
880 }
881
882 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
883 $oldref = dol_sanitizeFileName($this->ref);
884 $newref = dol_sanitizeFileName($num);
885 $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
886 $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
887 if (!$error && file_exists($dirsource)) {
888 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
889
890 if (@rename($dirsource, $dirdest)) {
891 dol_syslog("Rename ok");
892 // Rename docs starting with $oldref with $newref
893 $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
894 foreach ($listoffiles as $fileentry) {
895 $dirsource = $fileentry['name'];
896 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
897 $dirsource = $fileentry['path'].'/'.$dirsource;
898 $dirdest = $fileentry['path'].'/'.$dirdest;
899 @rename($dirsource, $dirdest);
900 }
901 }
902 }
903 }
904 }
905
906 if (!$error) {
907 $result = 1;
909 $this->statut = self::STATUS_VALIDATED; // deprecated
910 $this->ref = $num;
911 }
912
913 if (!$error) {
914 $this->db->commit();
915 return 1;
916 } else {
917 $this->db->rollback();
918 return -1;
919 }
920 } else {
921 $this->error = 'NotAuthorized';
922 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
923 return -1;
924 }
925 }
926
933 public function getLibStatut($mode = 0)
934 {
935 return $this->LibStatut(isset($this->status) ? $this->status : $this->statut, $mode, $this->billed);
936 }
937
938 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
947 public function LibStatut($status, $mode = 0, $billed = 0)
948 {
949 // phpcs:enable
950 global $langs, $hookmanager;
951
952 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
953 $langs->load('orders');
954
955 $this->labelStatus[0] = 'StatusSupplierOrderDraft';
956 $this->labelStatus[1] = 'StatusSupplierOrderValidated';
957 $this->labelStatus[2] = 'StatusSupplierOrderApproved';
958 if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
959 $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
960 } else {
961 $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
962 }
963 $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
964 $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
965 $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
966 $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
967 $this->labelStatus[9] = 'StatusSupplierOrderRefused';
968
969 // List of language codes for status
970 $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
971 $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
972 $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
973 $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
974 $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
975 $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
976 $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
977 $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
978 $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
979 }
980
981 $statustrans = array(
982 0 => 'status0',
983 1 => 'status1b',
984 2 => 'status1',
985 3 => 'status4',
986 4 => 'status4b',
987 5 => 'status6',
988 6 => 'status9',
989 7 => 'status9',
990 9 => 'status9',
991 );
992
993 $statusClass = 'status0';
994 if (!empty($statustrans[$status])) {
995 $statusClass = $statustrans[$status];
996 }
997
998 $billedtext = '';
999 if ($billed) {
1000 $billedtext = ' - '.$langs->trans("Billed");
1001 }
1002 if ($status == 5 && $billed) {
1003 $statusClass = 'status6';
1004 }
1005
1006 $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]).$billedtext;
1007 $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
1008
1009 $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
1010 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
1011 if ($reshook > 0) {
1012 return $hookmanager->resPrint;
1013 }
1014
1015 return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
1016 }
1017
1024 public function getTooltipContentArray($params)
1025 {
1026 global $conf, $langs, $user;
1027
1028 $langs->loadLangs(['bills', 'orders']);
1029
1030 $datas = [];
1031 $nofetch = !empty($params['nofetch']);
1032
1033 if ($user->hasRight("fournisseur", "commande", "read")) {
1034 $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
1035 if ($this->status) {
1036 $datas['picto'] .= ' '.$this->getLibStatut(5);
1037 }
1038 if (!empty($this->ref)) {
1039 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1040 }
1041 if (!empty($this->ref_supplier)) {
1042 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
1043 }
1044 if (!$nofetch) {
1045 $langs->load('companies');
1046 if (empty($this->thirdparty)) {
1047 $this->fetch_thirdparty();
1048 }
1049 $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1050 }
1051 if (!empty($this->total_ht)) {
1052 $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1053 }
1054 if (!empty($this->total_tva)) {
1055 $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1056 }
1057 if (!empty($this->total_ttc)) {
1058 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1059 }
1060 if (!empty($this->date)) {
1061 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1062 }
1063 if (!empty($this->delivery_date)) {
1064 $langs->load("sendings");
1065 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
1066 }
1067 }
1068 return $datas;
1069 }
1070
1081 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1082 {
1083 global $langs, $user, $hookmanager;
1084
1085 $result = '';
1086 $params = [
1087 'id' => $this->id,
1088 'objecttype' => $this->element,
1089 'option' => $option,
1090 'nofetch' => 1
1091 ];
1092 $classfortooltip = 'classfortooltip';
1093 $dataparams = '';
1094 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1095 $classfortooltip = 'classforajaxtooltip';
1096 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1097 $label = '';
1098 } else {
1099 $label = $this->getTooltipContent($params);
1100 }
1101
1102 $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
1103
1104 if ($option !== 'nolink') {
1105 // Add param to save lastsearch_values or not
1106 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1107 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1108 $add_save_lastsearch_values = 1;
1109 }
1110 if ($add_save_lastsearch_values) {
1111 $url .= '&save_lastsearch_values=1';
1112 }
1113 }
1114
1115 $linkclose = '';
1116 if (empty($notooltip)) {
1117 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1118 $label = $langs->trans("ShowOrder");
1119 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1120 }
1121 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1122 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1123 }
1124
1125 $linkstart = '<a href="'.$url.'"';
1126 $linkstart .= $linkclose.'>';
1127 $linkend = '</a>';
1128
1129 $result .= $linkstart;
1130 if ($withpicto) {
1131 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1132 }
1133 if ($withpicto != 2) {
1134 $result .= $this->ref;
1135 }
1136 $result .= $linkend;
1137
1138 if ($addlinktonotes) {
1139 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1140 if ($txttoshow) {
1141 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1142 $result .= ' <span class="note inline-block">';
1143 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1144 $result .= img_picto('', 'note');
1145 $result .= '</a>';
1146 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1147 //$result.='</a>';
1148 $result .= '</span>';
1149 }
1150 }
1151
1152 global $action;
1153 $hookmanager->initHooks(array($this->element . 'dao'));
1154 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1155 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1156 if ($reshook > 0) {
1157 $result = $hookmanager->resPrint;
1158 } else {
1159 $result .= $hookmanager->resPrint;
1160 }
1161 return $result;
1162 }
1163
1164
1172 public function getNextNumRef($soc)
1173 {
1174 global $langs, $conf;
1175 $langs->load("orders");
1176
1177 if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1178 $mybool = false;
1179
1180 $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
1181 $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1182
1183 // Include file with class
1184 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1185
1186 foreach ($dirmodels as $reldir) {
1187 $dir = dol_buildpath($reldir."core/modules/supplier_order/");
1188
1189 // Load file with numbering class (if found)
1190 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1191 }
1192
1193 if (!$mybool) {
1194 dol_print_error(null, "Failed to include file ".$file);
1195 return '';
1196 }
1197
1198 $obj = new $classname();
1199 '@phan-var-force ModeleNumRefSuppliersOrders $obj';
1201 $numref = $obj->getNextValue($soc, $this);
1202
1203 if ($numref != "") {
1204 return $numref;
1205 } else {
1206 $this->error = $obj->error;
1207 return -1;
1208 }
1209 } else {
1210 $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1211 return -2;
1212 }
1213 }
1214
1221 public function classifyBilled(User $user)
1222 {
1223 $error = 0;
1224
1225 if ($this->billed) {
1226 return 0;
1227 }
1228
1229 $this->db->begin();
1230
1231 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 1';
1232 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1233
1234 if ($this->db->query($sql)) {
1235 // Call trigger
1236 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1237 if ($result < 0) {
1238 $error++;
1239 }
1240 // End call triggers
1241
1242 if (!$error) {
1243 $this->billed = 1;
1244
1245 $this->db->commit();
1246 return 1;
1247 } else {
1248 $this->db->rollback();
1249 return -1;
1250 }
1251 } else {
1252 dol_print_error($this->db);
1253
1254 $this->db->rollback();
1255 return -1;
1256 }
1257 }
1258
1259
1266 public function classifyUnBilled(User $user)
1267 {
1268 if (empty($this->billed)) {
1269 return 0;
1270 }
1271
1272 $this->db->begin();
1273
1274 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 0';
1275 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1276 ;
1277
1278 if (!$this->db->query($sql)) {
1279 dol_print_error($this->db);
1280 $this->db->rollback();
1281 return -1;
1282 }
1283
1284 // Call trigger
1285 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_UNBILLED', $user);
1286 if ($result < 0) {
1287 $this->db->rollback();
1288 return -1;
1289 }
1290 // End call triggers
1291
1292 $this->billed = 1;
1293 $this->db->commit();
1294 return 1;
1295 }
1296
1297
1306 public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1307 {
1308 global $langs;
1309
1310 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1311
1312 $error = 0;
1313
1314 dol_syslog(get_class($this)."::approve");
1315
1316 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1317 $now = dol_now();
1318
1319 $this->db->begin();
1320
1321 // Definition of order numbering model name
1322 $soc = new Societe($this->db);
1323 $soc->fetch((int) $this->fourn_id);
1324
1325 // Check if object has a temporary ref
1326 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1327 $num = $this->getNextNumRef($soc);
1328 } else {
1329 $num = (string) $this->ref;
1330 }
1331 $this->newref = dol_sanitizeFileName($num);
1332
1333 // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1334 $movetoapprovestatus = true;
1335 $comment = '';
1336
1337 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1338 $sql .= " SET ref='".$this->db->escape($num)."',";
1339 if (empty($secondlevel)) { // standard or first level approval
1340 $sql .= " date_approve='".$this->db->idate($now)."',";
1341 $sql .= " fk_user_approve = ".((int) $user->id);
1342 if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= getDolGlobalFloat('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED')) {
1343 if (empty($this->user_approve_id2)) {
1344 $movetoapprovestatus = false; // second level approval not done
1345 $comment = ' (first level)';
1346 }
1347 }
1348 } else { // request a second level approval
1349 $sql .= " date_approve2='".$this->db->idate($now)."',";
1350 $sql .= " fk_user_approve2 = ".((int) $user->id);
1351 if (empty($this->user_approve_id)) {
1352 $movetoapprovestatus = false; // first level approval not done
1353 }
1354 $comment = ' (second level)';
1355 }
1356 // If double approval is required and first approval, we keep status to 1 = validated
1357 if ($movetoapprovestatus) {
1358 $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1359 } else {
1360 $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1361 }
1362 $sql .= " WHERE rowid = ".((int) $this->id);
1363 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1364
1365 if ($this->db->query($sql)) {
1366 if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1367 $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1368 if ($result < 0 && $result != -2) { // -2 means already exists
1369 $error++;
1370 }
1371 }
1372
1373 // If stock is incremented on validate order, we must increment it
1374 if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1375 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1376 $langs->load("agenda");
1377
1378 $cpt = count($this->lines);
1379 for ($i = 0; $i < $cpt; $i++) {
1380 // Product with reference
1381 if ($this->lines[$i]->fk_product > 0) {
1382 $this->line = $this->lines[$i];
1383 $mouvP = new MouvementStock($this->db);
1384 $mouvP->origin = &$this;
1385 $mouvP->setOrigin($this->element, $this->id);
1386 // We decrement stock of product (and sub-products)
1387 $up_ht_disc = $this->lines[$i]->subprice;
1388 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1389 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1390 }
1391 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1392 if ($result < 0) {
1393 $error++;
1394 }
1395 unset($this->line);
1396 }
1397 }
1398 }
1399
1400 if (!$error) {
1401 // Call trigger
1402 $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1403 if ($result < 0) {
1404 $error++;
1405 }
1406 // End call triggers
1407 }
1408
1409 if (!$error) {
1410 $this->ref = $this->newref;
1411
1412 if ($movetoapprovestatus) {
1413 $this->statut = self::STATUS_ACCEPTED; // deprecated
1415 } else {
1416 $this->statut = self::STATUS_VALIDATED; // deprecated
1418 }
1419 if (empty($secondlevel)) { // standard or first level approval
1420 $this->date_approve = $now;
1421 $this->user_approve_id = $user->id;
1422 } else { // request a second level approval
1423 $this->date_approve2 = $now;
1424 $this->user_approve_id2 = $user->id;
1425 }
1426
1427 $this->db->commit();
1428 return 1;
1429 } else {
1430 $this->db->rollback();
1431 return -1;
1432 }
1433 } else {
1434 $this->db->rollback();
1435 $this->error = $this->db->lasterror();
1436 return -1;
1437 }
1438 } else {
1439 dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1440 }
1441 return -1;
1442 }
1443
1450 public function refuse($user)
1451 {
1452 $error = 0;
1453
1454 dol_syslog(get_class($this)."::refuse");
1455 $result = 0;
1456 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1457 $this->db->begin();
1458
1459 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1460 $sql .= " WHERE rowid = ".((int) $this->id);
1461
1462 if ($this->db->query($sql)) {
1463 $result = 0;
1464
1465 if ($error == 0) {
1466 // Call trigger
1467 $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1468 if ($result < 0) {
1469 $error++;
1470 $this->db->rollback();
1471 } else {
1472 $this->db->commit();
1473 }
1474 // End call triggers
1475 }
1476 } else {
1477 $this->db->rollback();
1478 $this->error = $this->db->lasterror();
1479 dol_syslog(get_class($this)."::refuse Error -1");
1480 $result = -1;
1481 }
1482 } else {
1483 dol_syslog(get_class($this)."::refuse Not Authorized");
1484 }
1485 return $result;
1486 }
1487
1488 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1497 public function cancel($user, $idwarehouse = -1)
1498 {
1499 // phpcs:enable
1500 $error = 0;
1501
1502 //dol_syslog("CommandeFournisseur::Cancel");
1503 $result = 0;
1504 if ($user->hasRight("fournisseur", "commande", "commander")) {
1505 $statut = self::STATUS_CANCELED;
1506
1507 $this->db->begin();
1508
1509 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".((int) $statut);
1510 $sql .= " WHERE rowid = ".((int) $this->id);
1511 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1512 if ($this->db->query($sql)) {
1513 $result = 0;
1514
1515 // Call trigger
1516 $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1517 if ($result < 0) {
1518 $error++;
1519 }
1520 // End call triggers
1521
1522 if ($error == 0) {
1523 $this->db->commit();
1524 return 1;
1525 } else {
1526 $this->db->rollback();
1527 return -1;
1528 }
1529 } else {
1530 $this->db->rollback();
1531 $this->error = $this->db->lasterror();
1532 dol_syslog(get_class($this)."::cancel ".$this->error);
1533 return -1;
1534 }
1535 } else {
1536 dol_syslog(get_class($this)."::cancel Not Authorized");
1537 return -1;
1538 }
1539 }
1540
1550 public function commande($user, $date, $methode, $comment = '')
1551 {
1552 global $langs;
1553 dol_syslog(get_class($this)."::commande");
1554 $error = 0;
1555 if ($user->hasRight("fournisseur", "commande", "commander")) {
1556 $this->db->begin();
1557
1558 $newnoteprivate = $this->note_private;
1559 if ($comment) {
1560 $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1561 }
1562
1563 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1564 $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".((int) $methode).", date_commande='".$this->db->idate($date)."', ";
1565 $sql .= " note_private='".$this->db->escape((string) $newnoteprivate)."'";
1566 $sql .= " WHERE rowid=".((int) $this->id);
1567
1568 dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1569 if ($this->db->query($sql)) {
1570 $this->statut = self::STATUS_ORDERSENT; // deprecated
1572 $this->methode_commande_id = $methode;
1573 $this->date_commande = $date;
1574 $this->context['comments'] = $comment;
1575
1576 // Call trigger
1577 $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1578 if ($result < 0) {
1579 $error++;
1580 }
1581 // End call triggers
1582 } else {
1583 $error++;
1584 $this->error = $this->db->lasterror();
1585 $this->errors[] = $this->db->lasterror();
1586 }
1587
1588 if (!$error) {
1589 $this->db->commit();
1590 } else {
1591 $this->db->rollback();
1592 }
1593 } else {
1594 $error++;
1595 $this->error = $langs->trans('NotAuthorized');
1596 $this->errors[] = $langs->trans('NotAuthorized');
1597 dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1598 }
1599
1600 return ($error ? -1 : 1);
1601 }
1602
1610 public function create($user, $notrigger = 0)
1611 {
1612 global $conf;
1613
1614 $this->db->begin();
1615
1616 $error = 0;
1617 $now = dol_now();
1618
1619 // set tmp vars
1620 $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1621 if (empty($date)) {
1622 $date = $now;
1623 }
1624 $delivery_date = $this->delivery_date;
1625
1626 // Clean parameters
1627 if (empty($this->source)) {
1628 $this->source = 0;
1629 }
1630
1631 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1632 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1633 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1634 } else {
1635 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1636 }
1637 if (empty($this->fk_multicurrency)) {
1638 $this->multicurrency_code = $conf->currency;
1639 $this->fk_multicurrency = 0;
1640 $this->multicurrency_tx = 1;
1641 }
1642 $this->entity = setEntity($this);
1643
1644 // We set order into draft status
1645 $this->statut = self::STATUS_DRAFT; // deprecated
1646 $this->status = self::STATUS_DRAFT;
1647
1648 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseur (";
1649 $sql .= "ref";
1650 $sql .= ", ref_supplier";
1651 $sql .= ", note_private";
1652 $sql .= ", note_public";
1653 $sql .= ", entity";
1654 $sql .= ", fk_soc";
1655 $sql .= ", fk_projet";
1656 $sql .= ", date_creation";
1657 $sql .= ", date_livraison";
1658 $sql .= ", fk_warehouse";
1659 $sql .= ", fk_user_author";
1660 $sql .= ", fk_statut";
1661 $sql .= ", source";
1662 $sql .= ", model_pdf";
1663 $sql .= ", fk_mode_reglement";
1664 $sql .= ", deposit_percent";
1665 $sql .= ", fk_cond_reglement";
1666 $sql .= ", fk_account";
1667 $sql .= ", fk_incoterms, location_incoterms";
1668 $sql .= ", fk_multicurrency";
1669 $sql .= ", multicurrency_code";
1670 $sql .= ", multicurrency_tx";
1671 $sql .= ") ";
1672 $sql .= " VALUES (";
1673 $sql .= "'(PROV)'";
1674 $sql .= ", ".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "NULL");
1675 $sql .= ", '".$this->db->escape($this->note_private)."'";
1676 $sql .= ", '".$this->db->escape($this->note_public)."'";
1677 $sql .= ", ".((int) $this->entity);
1678 $sql .= ", ".((int) $this->socid);
1679 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1680 $sql .= ", '".$this->db->idate($date)."'";
1681 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1682 $sql .= ", ".($this->fk_warehouse > 0 ? ((int) $this->fk_warehouse) : "null");
1683 $sql .= ", ".((int) $user->id);
1684 $sql .= ", ".self::STATUS_DRAFT;
1685 $sql .= ", ".((int) $this->source);
1686 $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
1687 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'null');
1688 $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
1689 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'null');
1690 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1691 $sql .= ", ".(int) $this->fk_incoterms;
1692 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1693 $sql .= ", ".(int) $this->fk_multicurrency;
1694 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1695 $sql .= ", ".(float) $this->multicurrency_tx;
1696 $sql .= ")";
1697
1698 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1699 if ($this->db->query($sql)) {
1700 $this->id = $this->db->last_insert_id($this->db->prefix()."commande_fournisseur");
1701
1702 if ($this->id) {
1703 $num = count($this->lines);
1704
1705 // insert products details into database
1706 for ($i = 0; $i < $num; $i++) {
1707 $line = $this->lines[$i];
1708 if (!is_object($line)) {
1709 // Build a real line object: the loop below calls methods on it
1710 // (getPriceBaseType), which a cast to stdClass cannot answer.
1711 $lineobj = new CommandeFournisseurLigne($this->db);
1712 foreach ($line as $key => $val) {
1713 $lineobj->$key = $val;
1714 }
1715 $line = $lineobj;
1716 $this->lines[$i] = $line;
1717 }
1718
1719 //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1720
1721 // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1722 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1723 // In TTC mode, do not forward the HT currency price as pu_ht_devise: under multicurrency it would
1724 // reset the local price and recompute from the HT currency amount (read as TTC) -> 0.01 drift.
1725 // Like SupplierProposal (which does not pass it), the currency price is re-derived from the local price.
1726 $line_price_base_type = $line->getPriceBaseType();
1727 $line_pu_devise = ($line_price_base_type === 'TTC') ? 0 : (float) $line->multicurrency_subprice;
1728 $result = $this->addline(
1729 (string) $line->desc,
1730 (float) $line->subprice,
1731 (float) $line->qty,
1732 $line->tva_tx,
1733 (float) $line->localtax1_tx,
1734 (float) $line->localtax2_tx,
1735 (int) $line->fk_product,
1736 0,
1737 (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
1738 (float) $line->remise_percent,
1739 $line_price_base_type,
1740 (float) $line->subprice_ttc,
1741 (int) $line->product_type,
1742 (int) $line->info_bits,
1743 0,
1744 $line->date_start ?? null,
1745 $line->date_end ?? null,
1746 $line->array_options,
1747 $line->fk_unit ?? null,
1748 $line_pu_devise, // pu_ht_devise
1749 (string) $line->origin, // origin
1750 (int) $line->origin_id, // origin_id
1751 (int) ($line->rang > 0 ? $line->rang : -1), // rang
1752 (int) $line->special_code,
1753 isset($line->product_label) ? $line->product_label : ''
1754 );
1755 if ($result < 0) {
1756 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1757 $this->db->rollback();
1758 return -1;
1759 }
1760 }
1761
1762 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1763 $sql .= " SET ref='(PROV".((int) $this->id).")'";
1764 $sql .= " WHERE rowid=".((int) $this->id);
1765
1766 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1767 if ($this->db->query($sql)) {
1768 // Add link with price request and supplier order
1769 if ($this->id) {
1770 $this->ref = "(PROV".$this->id.")";
1771
1772 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1773 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1774 }
1775
1776 // Add object linked
1777 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1778 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1779 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, ...))
1780 foreach ($tmp_origin_id as $origin_id) {
1781 $ret = $this->add_object_linked($origin, $origin_id);
1782 if (!$ret) {
1783 dol_print_error($this->db);
1784 $error++;
1785 }
1786 }
1787 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1788 $origin_id = $tmp_origin_id;
1789 $ret = $this->add_object_linked($origin, $origin_id);
1790 if (!$ret) {
1791 dol_print_error($this->db);
1792 $error++;
1793 }
1794 }
1795 }
1796 }
1797 }
1798
1799 if (!$error) {
1800 $result = $this->insertExtraFields();
1801 if ($result < 0) {
1802 $error++;
1803 }
1804 }
1805
1806 if (!$error && !$notrigger) {
1807 // Call trigger
1808 $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1809 if ($result < 0) {
1810 $this->db->rollback();
1811
1812 return -1;
1813 }
1814 // End call triggers
1815 }
1816
1817 $this->db->commit();
1818 return $this->id;
1819 } else {
1820 $this->error = $this->db->lasterror();
1821 $this->db->rollback();
1822
1823 return -2;
1824 }
1825 } else {
1826 $this->error = 'Failed to get ID of inserted line';
1827
1828 return -1;
1829 }
1830 } else {
1831 $this->error = $this->db->lasterror();
1832 $this->db->rollback();
1833
1834 return -1;
1835 }
1836 }
1837
1845 public function update(User $user, $notrigger = 0)
1846 {
1847 $error = 0;
1848
1849 // Clean parameters
1850 if (isset($this->ref)) {
1851 $this->ref = trim($this->ref);
1852 }
1853 if (isset($this->ref_supplier)) {
1854 $this->ref_supplier = trim($this->ref_supplier);
1855 }
1856 if (isset($this->note_private)) {
1857 $this->note_private = trim($this->note_private);
1858 }
1859 if (isset($this->note_public)) {
1860 $this->note_public = trim($this->note_public);
1861 }
1862 if (isset($this->model_pdf)) {
1863 $this->model_pdf = trim($this->model_pdf);
1864 }
1865 if (isset($this->import_key)) {
1866 $this->import_key = trim($this->import_key);
1867 }
1868
1869 // Update request
1870 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1871
1872 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1873 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1874 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1875 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1876 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1877 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1878 $sql .= " fk_warehouse=".($this->fk_warehouse > 0 ? ((int) $this->fk_warehouse) : "null").",";
1879 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1880 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1881 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1882 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1883 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1884 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : "null").",";
1885 $sql .= " fk_user_author=".(isset($this->user_author_id) ? ((int) $this->user_author_id) : "null").",";
1886 $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? ((int) $this->user_validation_id) : "null").",";
1887 $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? ((int) $this->fk_project) : "null").",";
1888 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1889 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1890 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
1891 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1892 //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1893 $sql .= " fk_account=".($this->fk_account > 0 ? ((int) $this->fk_account) : "null").",";
1894 //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1895 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1896 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1897 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1898 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1899
1900 $sql .= " WHERE rowid=".((int) $this->id);
1901
1902 $this->db->begin();
1903
1904 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1905 $resql = $this->db->query($sql);
1906 if (!$resql) {
1907 $error++;
1908 $this->errors[] = "Error ".$this->db->lasterror();
1909 }
1910
1911 if (!$error) {
1912 $result = $this->insertExtraFields();
1913 if ($result < 0) {
1914 $error++;
1915 }
1916 }
1917
1918 if (!$error && !$notrigger) {
1919 // Call trigger
1920 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1921 if ($result < 0) {
1922 $error++;
1923 }
1924 // End call triggers
1925 }
1926
1927 // Commit or rollback
1928 if ($error) {
1929 foreach ($this->errors as $errmsg) {
1930 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1931 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1932 }
1933 $this->db->rollback();
1934 return -1 * $error;
1935 } else {
1936 $this->db->commit();
1937 return 1;
1938 }
1939 }
1940
1949 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1950 {
1951 global $conf, $user, $hookmanager;
1952
1953 $error = 0;
1954
1955 $this->db->begin();
1956
1957 // get extrafields so they will be clone
1958 foreach ($this->lines as $line) {
1959 $line->fetch_optionals();
1960 }
1961
1962 // Load source object
1963 $objFrom = clone $this;
1964
1965 // Change socid if needed
1966 if (!empty($socid) && $socid != $this->socid) {
1967 $objsoc = new Societe($this->db);
1968
1969 if ($objsoc->fetch($socid) > 0) {
1970 $this->socid = $objsoc->id;
1971 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1972 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : '0');
1973 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1974 $this->fk_project = 0;
1975 $this->fk_delivery_address = 0;
1976 }
1977
1978 // TODO Change product price if multi-prices
1979 }
1980
1981 $this->id = 0;
1982 $this->statut = self::STATUS_DRAFT; // deprecated
1983 $this->status = self::STATUS_DRAFT;
1984
1985 // Clear fields
1986 $this->user_author_id = $user->id;
1987 $this->user_validation_id = 0;
1988
1989 $this->date = dol_now();
1990 $this->date_creation = 0;
1991 $this->date_validation = 0;
1992 $this->date_commande = null;
1993 $this->ref_supplier = '';
1994 $this->user_approve_id = 0;
1995 $this->user_approve_id2 = 0;
1996 $this->date_approve = 0;
1997 $this->date_approve2 = 0;
1998
1999 // Create clone
2000 $this->context['createfromclone'] = 'createfromclone';
2001 $result = $this->create($user, $notrigger);
2002 if ($result < 0) {
2003 $error++;
2004 }
2005
2006 if (!$error) {
2007 // Hook of thirdparty module
2008 if (is_object($hookmanager)) {
2009 $parameters = array('objFrom' => $objFrom);
2010 $action = '';
2011 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2012 if ($reshook < 0) {
2013 $this->setErrorsFromObject($hookmanager);
2014 $error++;
2015 }
2016 }
2017 }
2018
2019 unset($this->context['createfromclone']);
2020
2021 // End
2022 if (!$error) {
2023 $this->db->commit();
2024 return $this->id;
2025 } else {
2026 $this->db->rollback();
2027 return -1;
2028 }
2029 }
2030
2061 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 = '')
2062 {
2063 global $langs, $mysoc;
2064
2065 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");
2066 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2067
2068 if ($this->status == self::STATUS_DRAFT) {
2069 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2070
2071 // Clean parameters
2072 if (empty($qty)) {
2073 $qty = 0;
2074 }
2075 if (!$info_bits) {
2076 $info_bits = 0;
2077 }
2078 if (empty($txtva)) {
2079 $txtva = 0;
2080 }
2081 if (empty($rang)) {
2082 $rang = 0;
2083 }
2084 if (empty($txlocaltax1)) {
2085 $txlocaltax1 = 0;
2086 }
2087 if (empty($txlocaltax2)) {
2088 $txlocaltax2 = 0;
2089 }
2090 if (empty($remise_percent)) {
2091 $remise_percent = 0;
2092 }
2093
2094 $remise_percent = price2num($remise_percent);
2095 $qty = price2num($qty);
2096 $pu_ht = price2num($pu_ht);
2097 $pu_ht_devise = price2num($pu_ht_devise);
2098 $pu_ttc = price2num($pu_ttc);
2099 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2100 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2101 }
2102 $txlocaltax1 = price2num($txlocaltax1);
2103 $txlocaltax2 = price2num($txlocaltax2);
2104 if ($price_base_type == 'HT') {
2105 $pu = $pu_ht;
2106 } else {
2107 $pu = $pu_ttc;
2108 }
2109
2110 $label = trim((string) $label);
2111 $desc = trim((string) $desc);
2112 if ($desc === '' && $label !== '') {
2113 $desc = $label;
2114 }
2115
2116 // Check parameters
2117 if ($qty < 0 && !$fk_product) {
2118 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
2119 return -1;
2120 }
2121 if ($type < 0) {
2122 return -1;
2123 }
2124 if ($date_start && $date_end && $date_start > $date_end) {
2125 $langs->load("errors");
2126 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2127 return -1;
2128 }
2129
2130
2131 $this->db->begin();
2132
2133 $product_type = $type;
2134 $label = ''; // deprecated
2135
2136 if ($fk_product > 0) {
2137 if (getDolGlobalInt('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2138 // Check quantity is enough
2139 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);
2140 $prod = new ProductFournisseur($this->db);
2141 if ($prod->fetch($fk_product) > 0) {
2142 $product_type = $prod->type;
2143 $label = $prod->label;
2144
2145 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2146 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2147 $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
2148
2149 // If supplier order created from sales order, we take best supplier price
2150 // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2151 if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2152 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2153 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2154 // is remise percent not keyed but present for the product we add it
2155 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2156 $remise_percent = $prod->remise_percent;
2157 }
2158 }
2159 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2160 $langs->load("errors");
2161 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2162 $this->db->rollback();
2163 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2164 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2165 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2166 return -1;
2167 }
2168 if ($result == -1) {
2169 $langs->load("errors");
2170 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2171 $this->db->rollback();
2172 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2173 return -1;
2174 }
2175 if ($result < -1) {
2176 $this->error = $prod->error;
2177 $this->db->rollback();
2178 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2179 return -1;
2180 }
2181 } else {
2182 $this->error = $prod->error;
2183 $this->db->rollback();
2184 return -1;
2185 }
2186 }
2187
2188 // Predefine quantity according to packaging
2189 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2190 $prod = new Product($this->db);
2191 $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2192
2193 // Align messaging, type and float-safety with the customer-order path at commande.class.php:1720
2194 // (#38782 bugs 1, 2, 6).
2195 $newqty = $this->roundQtyToPackaging($qty, $prod->packaging);
2196 if ($newqty != $qty) {
2197 $qty = $newqty;
2198 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2199 }
2200
2201 // Enforce the supplier minimum purchase quantity on top of packaging
2202 // rounding. The packaging block above only ensures qty is a packaging
2203 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2204 // is below the supplier minimum, round qty_min itself up to the next
2205 // packaging multiple so we end up with the smallest valid order qty (#38783).
2206 if (!empty($prod->fourn_qty) && (float) $qty > 0 && (float) $qty < (float) $prod->fourn_qty) { // A negative quantity (return) is not concerned by the minimum purchase quantity
2207 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2208 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2209 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2210 } else {
2211 $qty = (float) $prod->fourn_qty;
2212 }
2213 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2214 }
2215 }
2216 }
2217
2218 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2219 $pu = 0;
2220 }
2221
2222 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2223
2224 // Clean vat code
2225 $reg = array();
2226 $vat_src_code = '';
2227 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2228 $vat_src_code = $reg[1];
2229 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2230 }
2231
2232 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
2233 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
2234 // and this is done at the line level, which has its own VAT rate
2235
2236 $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);
2237
2238 $total_ht = $tabprice[0];
2239 $total_tva = $tabprice[1];
2240 $total_ttc = $tabprice[2];
2241 $total_localtax1 = $tabprice[9];
2242 $total_localtax2 = $tabprice[10];
2243 $pu = $pu_ht = $tabprice[3];
2244 $pu_tva = $tabprice[4];
2245 $pu_ttc = $tabprice[5];
2246
2247 // MultiCurrency
2248 $multicurrency_total_ht = $tabprice[16];
2249 $multicurrency_total_tva = $tabprice[17];
2250 $multicurrency_total_ttc = $tabprice[18];
2251 $pu_ht_devise = $tabprice[19];
2252 $multicurrency_pu_ttc = $tabprice[21];
2253
2254 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2255 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2256
2257 if ($rang < 0) {
2258 $rangmax = $this->line_max();
2259 $rang = $rangmax + 1;
2260 }
2261
2262 // Insert line
2263 $this->line = new CommandeFournisseurLigne($this->db);
2264
2265 $this->line->context = $this->context;
2266
2267 $this->line->fk_commande = $this->id;
2268 $this->line->label = $label;
2269 $this->line->ref_fourn = $ref_supplier;
2270 $this->line->ref_supplier = $ref_supplier;
2271 $this->line->desc = $desc;
2272 $this->line->qty = $qty;
2273 $this->line->tva_tx = $txtva;
2274 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2275 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2276 $this->line->localtax1_type = $localtax1_type;
2277 $this->line->localtax2_type = $localtax2_type;
2278 $this->line->fk_product = $fk_product;
2279 $this->line->product_type = $product_type;
2280 $this->line->remise_percent = $remise_percent;
2281 $this->line->subprice = (float) $pu_ht;
2282 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
2283
2284 $this->line->rang = $rang;
2285 $this->line->info_bits = $info_bits;
2286
2287 $this->line->vat_src_code = $vat_src_code;
2288 $this->line->total_ht = (float) $total_ht;
2289 $this->line->total_tva = (float) $total_tva;
2290 $this->line->total_localtax1 = (float) $total_localtax1;
2291 $this->line->total_localtax2 = (float) $total_localtax2;
2292 $this->line->total_ttc = (float) $total_ttc;
2293 $this->line->product_type = $type;
2294 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2295 $this->line->origin = $origin;
2296 $this->line->origin_type = $origin;
2297 $this->line->origin_id = $origin_id;
2298 $this->line->fk_unit = $fk_unit;
2299
2300 $this->line->date_start = $date_start;
2301 $this->line->date_end = $date_end;
2302
2303 // Multicurrency
2304 $this->line->fk_multicurrency = $this->fk_multicurrency;
2305 $this->line->multicurrency_code = $this->multicurrency_code;
2306 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2307 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2308 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2309 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2310 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2311
2312 $this->line->subprice = (float) $pu_ht;
2313 $this->line->price = $this->line->subprice;
2314
2315 $this->line->remise_percent = $remise_percent;
2316
2317 if (is_array($array_options) && count($array_options) > 0) {
2318 $this->line->array_options = $array_options;
2319 }
2320
2321 $result = $this->line->insert($notrigger);
2322 if ($result > 0) {
2323 // Update denormalized fields at the order level
2324 $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.
2325
2326 if ($result > 0) {
2327 if (!isset($this->context['createfromclone'])) {
2328 if (!empty($this->line->fk_parent_line)) {
2329 // Always reorder if child line
2330 $this->line_order(true, 'DESC');
2331 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2332 // Update all rank of all other lines starting from the same $ranktouse
2333 $linecount = count($this->lines);
2334 for ($ii = $rang; $ii <= $linecount; $ii++) {
2335 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2336 }
2337 }
2338
2339 $this->lines[] = $this->line;
2340 } else {
2341 foreach ($this->lines as $line) {
2342 if ($line->id == $origin_id) {
2343 $this->line->extraparams = $line->extraparams;
2344 $this->line->setExtraParameters();
2345 }
2346 }
2347 }
2348
2349 $this->db->commit();
2350 return $this->line->id;
2351 } else {
2352 $this->db->rollback();
2353 return -1;
2354 }
2355 } else {
2356 $this->setErrorsFromObject($this->line);
2357 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2358 $this->db->rollback();
2359 return -1;
2360 }
2361 }
2362 return -1;
2363 }
2364
2365
2383 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2384 {
2385 global $conf, $langs;
2386
2387 $error = 0;
2388 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2389
2390 // Check parameters (if test are wrong here, there is bug into caller)
2391 if ($entrepot <= 0) {
2392 $this->error = 'ErrorBadValueForParameterWarehouse';
2393 return -1;
2394 }
2395 if ($qty == 0) {
2396 $this->error = 'ErrorBadValueForParameterQty';
2397 return -1;
2398 }
2399
2400 $dispatchstatus = 1;
2401 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2402 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2403 }
2404
2405 $now = dol_now();
2406
2407 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2408
2409 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2410 $this->db->begin();
2411
2412 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2413 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2414 $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)."', ";
2415 $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");
2416 $sql .= ")";
2417
2418 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2419 $resql = $this->db->query($sql);
2420 if ($resql) {
2421 if (!$notrigger) {
2422 global $conf, $langs, $user;
2423 // Call trigger
2424 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2425 if ($result < 0) {
2426 $error++;
2427 }
2428 // End call triggers
2429 }
2430 } else {
2431 $this->error = $this->db->lasterror();
2432 $error++;
2433 }
2434
2435 // If module stock is enabled and the stock increase is done on purchase order dispatching
2436 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2437 $mouv = new MouvementStock($this->db);
2438 if ($product > 0) {
2439 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2440 $mouv->origin = &$this;
2441 $mouv->setOrigin($this->element, $this->id);
2442
2443 // Method change if qty < 0
2444 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2445 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2446 } else {
2447 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2448 }
2449
2450 if ($result < 0) {
2451 $this->error = $mouv->error;
2452 $this->errors = $mouv->errors;
2453 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2454 $error++;
2455 }
2456 }
2457 }
2458
2459 if ($error == 0) {
2460 $this->db->commit();
2461 return 1;
2462 } else {
2463 $this->db->rollback();
2464 return -1;
2465 }
2466 } else {
2467 $this->error = 'BadStatusForObject';
2468 return -2;
2469 }
2470 }
2471
2479 public function deleteLine($idline, $notrigger = 0)
2480 {
2481 global $user;
2482
2483 if ($this->status == 0) {
2484 $line = new CommandeFournisseurLigne($this->db);
2485
2486 if ($line->fetch($idline) <= 0) {
2487 return 0;
2488 }
2489 if ($this->id > 0 && (int) $line->fk_commande !== (int) $this->id) {
2490 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
2491 return -1;
2492 }
2493
2494 // check if not yet received
2495 $dispatchedLines = $this->getDispachedLines();
2496 foreach ($dispatchedLines as $dispatchLine) {
2497 if ($dispatchLine['orderlineid'] == $idline) {
2498 $this->error = "LineAlreadyDispatched";
2499 $this->errors[] = $this->error;
2500 return -3;
2501 }
2502 }
2503
2504 if ($line->delete($user, $notrigger) > 0) {
2505 $this->update_price(1);
2506 return 1;
2507 } else {
2508 $this->setErrorsFromObject($line);
2509 return -1;
2510 }
2511 } else {
2512 return -2;
2513 }
2514 }
2515
2523 public function delete(User $user, $notrigger = 0)
2524 {
2525 global $langs, $conf;
2526 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2527
2528 $error = 0;
2529
2530 $this->db->begin();
2531
2532 if (empty($notrigger)) {
2533 // Call trigger
2534 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2535 if ($result < 0) {
2536 $this->errors[] = 'ErrorWhenRunningTrigger';
2537 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2538 $this->db->rollback();
2539 return -1;
2540 }
2541 // End call triggers
2542 }
2543
2544 // Test we can delete
2545 $this->fetchObjectLinked(null, 'order_supplier');
2546 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2547 foreach ($this->linkedObjects['reception'] as $element) {
2548 if ($element->statut >= 0) {
2549 $this->errors[] = $langs->trans('ReceptionExist');
2550 $error++;
2551 break;
2552 }
2553 }
2554 }
2555
2556 // Remove linked categories.
2557 if (!$error) {
2558 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2559 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2560
2561 $result = $this->db->query($sql);
2562 if (!$result) {
2563 $error++;
2564 $this->errors[] = $this->db->lasterror();
2565 }
2566 }
2567
2568 $main = $this->db->prefix().'commande_fournisseurdet';
2569
2570 if (!$error) {
2571 $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).")";
2572 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2573 if (!$this->db->query($sql1)) {
2574 $error++;
2575 $this->error = $this->db->lasterror();
2576 $this->errors[] = $this->db->lasterror();
2577 }
2578 }
2579
2580 if (!$error) {
2581 $ef = $main."_extrafields";
2582 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2583 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2584 if (!$this->db->query($sql)) {
2585 $this->error = $this->db->lasterror();
2586 $this->errors[] = $this->db->lasterror();
2587 $error++;
2588 }
2589 }
2590
2591 if (!$error) {
2592 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2593 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2594 if (!$this->db->query($sql)) {
2595 $this->error = $this->db->lasterror();
2596 $this->errors[] = $this->db->lasterror();
2597 $error++;
2598 }
2599 }
2600
2601 if (!$error) {
2602 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2603 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2604 if ($resql = $this->db->query($sql)) {
2605 if ($this->db->affected_rows($resql) < 1) {
2606 $this->error = $this->db->lasterror();
2607 $this->errors[] = $this->db->lasterror();
2608 $error++;
2609 }
2610 } else {
2611 $this->error = $this->db->lasterror();
2612 $this->errors[] = $this->db->lasterror();
2613 $error++;
2614 }
2615 }
2616
2617 // Remove extrafields
2618 if (!$error) {
2619 $result = $this->deleteExtraFields();
2620 if ($result < 0) {
2621 $this->error = 'FailToDeleteExtraFields';
2622 $this->errors[] = 'FailToDeleteExtraFields';
2623 $error++;
2624 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2625 }
2626 }
2627
2628 // Delete linked object
2629 $res = $this->deleteObjectLinked();
2630 if ($res < 0) {
2631 $this->error = 'FailToDeleteObjectLinked';
2632 $this->errors[] = 'FailToDeleteObjectLinked';
2633 $error++;
2634 }
2635
2636 if (!$error) {
2637 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2638 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2639 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2640
2641 // We remove directory
2642 $ref = dol_sanitizeFileName($this->ref);
2643 if ($conf->fournisseur->commande->dir_output && !empty($ref)) {
2644 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2645 $file = $dir."/".$ref.".pdf";
2646 if (file_exists($file)) {
2647 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2648 $this->error = 'ErrorFailToDeleteFile';
2649 $this->errors[] = 'ErrorFailToDeleteFile';
2650 $error++;
2651 }
2652 }
2653 if (file_exists($dir)) {
2654 $res = @dol_delete_dir_recursive($dir);
2655 if (!$res) {
2656 $this->error = 'ErrorFailToDeleteDir';
2657 $this->errors[] = 'ErrorFailToDeleteDir';
2658 $error++;
2659 }
2660 }
2661 }
2662 }
2663
2664 if (!$error) {
2665 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2666 $this->db->commit();
2667 return 1;
2668 } else {
2669 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2670 $this->db->rollback();
2671 return -$error;
2672 }
2673 }
2674
2675
2684 public function getDispachedLines($status = -1)
2685 {
2686 $ret = array();
2687
2688 // List of already dispatched lines
2689 $sql = "SELECT p.ref, p.label,";
2690 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2691 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2692 $sql .= " FROM ".$this->db->prefix()."product as p,";
2693 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2694 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2695 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2696 $sql .= " AND cfd.fk_product = p.rowid";
2697 if ($status >= 0) {
2698 $sql .= " AND cfd.status = ".((int) $status);
2699 }
2700 $sql .= " ORDER BY cfd.rowid ASC";
2701
2702 $resql = $this->db->query($sql);
2703 if ($resql) {
2704 $num = $this->db->num_rows($resql);
2705 $i = 0;
2706
2707 while ($i < $num) {
2708 $objp = $this->db->fetch_object($resql);
2709 if ($objp) {
2710 $ret[] = array(
2711 'id' => $objp->dispatchedlineid,
2712 'productid' => $objp->fk_product,
2713 'warehouseid' => $objp->warehouse_id,
2714 'qty' => $objp->qty,
2715 'orderlineid' => $objp->fk_elementdet
2716 );
2717 }
2718
2719 $i++;
2720 }
2721 } else {
2722 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2723 }
2724
2725 return $ret;
2726 }
2727
2728 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2738 public function Livraison($user, $date, $type, $comment)
2739 {
2740 // phpcs:enable
2741 global $langs;
2742
2743 $result = 0;
2744 $error = 0;
2745 $dispatchedlinearray = array();
2746
2747 dol_syslog(get_class($this)."::Livraison");
2748
2749 $usercanreceive = 0;
2750 if (!isModEnabled('reception')) {
2751 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2752 } else {
2753 $usercanreceive = $user->hasRight("reception", "creer");
2754 }
2755
2756 if ($usercanreceive) {
2757 // Define the new status
2758 if ($type == 'par') {
2760 } elseif ($type == 'tot') {
2762 } elseif ($type == 'nev') {
2764 } elseif ($type == 'can') {
2766 } else {
2767 $error++;
2768 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2769 return -2;
2770 }
2771
2772 // Some checks to accept the record
2773 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2774 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2775 if ($type == 'tot') {
2776 $dispatchedlinearray = $this->getDispachedLines(0);
2777 if (count($dispatchedlinearray) > 0) {
2778 $result = -1;
2779 $error++;
2780 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2781 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2782 }
2783 }
2784 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)
2785 $dispatcheddenied = $this->getDispachedLines(2);
2786 if (count($dispatcheddenied) > 0) {
2787 $result = -1;
2788 $error++;
2789 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2790 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2791 }
2792 }
2793 }
2794
2795 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2796
2797 if (empty($error)) {
2798 $this->db->begin();
2799
2800 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2801 $sql .= " SET fk_statut = ".((int) $statut);
2802 $sql .= " WHERE rowid = ".((int) $this->id);
2803 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2804
2805 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2806 $resql = $this->db->query($sql);
2807 if ($resql) {
2808 $result = 1;
2809 $old_statut = $this->status;
2810 $this->status = $statut;
2811 $this->context['actionmsg2'] = $comment;
2812
2813 // Call trigger
2814 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2815 if ($result_trigger < 0) {
2816 $error++;
2817 }
2818 // End call triggers
2819
2820 if (empty($error)) {
2821 $this->db->commit();
2822 } else {
2823 $this->status = $old_statut;
2824 $this->db->rollback();
2825 $this->error = $this->db->lasterror();
2826 $result = -1;
2827 }
2828 } else {
2829 $this->db->rollback();
2830 $this->error = $this->db->lasterror();
2831 $result = -1;
2832 }
2833 }
2834 } else {
2835 $this->error = $langs->trans('NotAuthorized');
2836 $this->errors[] = $langs->trans('NotAuthorized');
2837 dol_syslog(get_class($this)."::Livraison Not Authorized");
2838 $result = -3;
2839 }
2840 return $result;
2841 }
2842
2843 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2853 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2854 {
2855 // phpcs:enable
2856 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2857 }
2858
2867 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2868 {
2869 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2870 $error = 0;
2871
2872 $this->db->begin();
2873
2874 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2875 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2876 $sql .= " WHERE rowid = ".((int) $this->id);
2877
2878 dol_syslog(__METHOD__, LOG_DEBUG);
2879 $resql = $this->db->query($sql);
2880 if (!$resql) {
2881 $this->errors[] = $this->db->error();
2882 $error++;
2883 }
2884
2885 if (!$error) {
2886 $this->oldcopy = clone $this;
2887 $this->delivery_date = $delivery_date;
2888 }
2889
2890 if (!$notrigger && empty($error)) {
2891 // Call trigger
2892 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2893 if ($result < 0) {
2894 $error++;
2895 }
2896 // End call triggers
2897 }
2898
2899 if (!$error) {
2900 $this->db->commit();
2901 return 1;
2902 } else {
2903 foreach ($this->errors as $errmsg) {
2904 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2905 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2906 }
2907 $this->db->rollback();
2908 return -1 * $error;
2909 }
2910 } else {
2911 return -2;
2912 }
2913 }
2914
2921 public function setReopen(User $user): int
2922 {
2923 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2924 if ($this->status == 1) {
2925 $newStatus = 0; // Validated->Draft
2926 } elseif ($this->status == 2) {
2927 $newStatus = 0; // Approved->Draft
2928 } elseif ($this->status == 3) {
2929 $newStatus = 2; // Ordered->Approved
2930 } elseif ($this->status == 4) {
2931 $newStatus = 3;
2932 } elseif ($this->status == 5) {
2933 //$newstatus=2; // Ordered
2934 // TODO Can we set it to submitted ?
2935 //$newstatus=3; // Submitted
2936 // TODO If there is at least one reception, we can set to Received->Received partially
2937 $newStatus = 4; // Received partially
2938 } elseif ($this->status == 6) {
2939 $newStatus = 2; // Canceled->Approved
2940 } elseif ($this->status == 7) {
2941 $newStatus = 3; // Canceled->Process running
2942 } elseif ($this->status == 9) {
2943 $newStatus = 1; // Refused->Validated
2944 } else {
2945 $newStatus = 2;
2946 }
2947
2948 $this->db->begin();
2949
2950 $result = $this->setStatus($user, $newStatus);
2951 if ($result > 0) {
2952 if ($newStatus == 0) {
2953 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2954 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2955 $sql .= ' WHERE rowid = '.((int) $this->id);
2956
2957 $this->db->query($sql);
2958 }
2959
2960 $this->db->commit();
2961
2962 return 1;
2963 } else {
2964 $this->db->rollback();
2965
2966 return -1;
2967 }
2968 }
2969
2970 return 0;
2971 }
2972
2973 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2982 public function set_id_projet($user, $id_projet, $notrigger = 0)
2983 {
2984 // phpcs:enable
2985 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2986 $error = 0;
2987
2988 $this->db->begin();
2989
2990 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2991 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2992 $sql .= " WHERE rowid = ".((int) $this->id);
2993
2994 dol_syslog(__METHOD__, LOG_DEBUG);
2995 $resql = $this->db->query($sql);
2996 if (!$resql) {
2997 $this->errors[] = $this->db->error();
2998 $error++;
2999 }
3000
3001 if (!$error) {
3002 $this->oldcopy = clone $this;
3003 $this->fk_projet = $id_projet;
3004 $this->fk_project = $id_projet;
3005 }
3006
3007 if (!$notrigger && empty($error)) {
3008 // Call trigger
3009 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
3010 if ($result < 0) {
3011 $error++;
3012 }
3013 // End call triggers
3014 }
3015
3016 if (!$error) {
3017 $this->db->commit();
3018 return 1;
3019 } else {
3020 foreach ($this->errors as $errmsg) {
3021 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3022 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3023 }
3024 $this->db->rollback();
3025 return -1 * $error;
3026 }
3027 } else {
3028 return -2;
3029 }
3030 }
3031
3040 public function updateFromCommandeClient($user, $idc, $comclientid)
3041 {
3042 $comclient = new Commande($this->db);
3043 $comclient->fetch($comclientid);
3044
3045 $this->id = $idc;
3046
3047 $this->lines = array();
3048
3049 $num = count($comclient->lines);
3050 for ($i = 0; $i < $num; $i++) {
3051 $prod = new Product($this->db);
3052 $label = '';
3053 $ref = '';
3054 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3055 $label = $prod->label;
3056 $ref = (string) $prod->ref;
3057 }
3058
3059 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3060 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3061 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3062 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3063 $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);
3064 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3065 if ($this->db->query($sql)) {
3066 $this->update_price(1);
3067 }
3068 }
3069
3070 return 1;
3071 }
3072
3080 public function setStatus($user, $status)
3081 {
3082 $error = 0;
3083
3084 $this->db->begin();
3085
3086 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3087 $sql .= " SET fk_statut = ".((int) $status);
3088 $sql .= " WHERE rowid = ".((int) $this->id);
3089
3090 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3091 $resql = $this->db->query($sql);
3092 if ($resql) {
3093 // Trigger names for each status
3094 $triggerName = array();
3095 $triggerName[0] = 'DRAFT';
3096 $triggerName[1] = 'VALIDATED';
3097 $triggerName[2] = 'APPROVED';
3098 $triggerName[3] = 'ORDERED'; // Ordered
3099 $triggerName[4] = 'RECEIVED_PARTIALLY';
3100 $triggerName[5] = 'RECEIVED_COMPLETELY';
3101 $triggerName[6] = 'CANCELED';
3102 $triggerName[7] = 'CANCELED';
3103 $triggerName[9] = 'REFUSED';
3104
3105 // Call trigger
3106 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3107 if ($result < 0) {
3108 $error++;
3109 }
3110 // End call triggers
3111 } else {
3112 $error++;
3113 $this->error = $this->db->lasterror();
3114 dol_syslog(get_class($this)."::setStatus ".$this->error);
3115 }
3116
3117 if (!$error) {
3118 $this->status = $status;
3119 $this->db->commit();
3120 return 1;
3121 } else {
3122 $this->db->rollback();
3123 return -1;
3124 }
3125 }
3126
3138 public function setCategories($categories)
3139 {
3140 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3141 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3142 }
3143
3167 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 = '')
3168 {
3169 global $mysoc, $conf, $langs;
3170
3171 if (!$this->isLineOfObject($rowid)) {
3172 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
3173 return -1;
3174 }
3175
3176 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3177 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3178
3179 $error = 0;
3180
3181 if ($this->status == self::STATUS_DRAFT) {
3182 // Clean parameters
3183 if (empty($qty)) {
3184 $qty = 0;
3185 }
3186 if (empty($info_bits)) {
3187 $info_bits = 0;
3188 }
3189 if (empty($txtva)) {
3190 $txtva = 0;
3191 }
3192 if (empty($txlocaltax1)) {
3193 $txlocaltax1 = 0;
3194 }
3195 if (empty($txlocaltax2)) {
3196 $txlocaltax2 = 0;
3197 }
3198 if (empty($remise_percent)) {
3199 $remise_percent = 0;
3200 }
3201
3202 $remise_percent = (float) price2num($remise_percent);
3203 $qty = price2num($qty);
3204 if (!$qty) {
3205 $qty = 1;
3206 }
3207 $pu = price2num($pu);
3208 $pu_ht_devise = price2num($pu_ht_devise);
3209 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3210 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3211 }
3212 $txlocaltax1 = (float) price2num($txlocaltax1);
3213 $txlocaltax2 = (float) price2num($txlocaltax2);
3214
3215 // Check parameters
3216 if ($type < 0) {
3217 return -1;
3218 }
3219 if ($date_start && $date_end && $date_start > $date_end) {
3220 $langs->load("errors");
3221 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3222 return -1;
3223 }
3224
3225 $this->db->begin();
3226
3227 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
3228 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
3229 // and this is done at the line level, which has its own VAT rate
3230
3231 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3232
3233 // Clean vat code
3234 $reg = array();
3235 $vat_src_code = '';
3236 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3237 $vat_src_code = $reg[1];
3238 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3239 }
3240
3241 // Round the quantity to the packaging before computing the amounts of the line (and checking the stock),
3242 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
3243 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3244 $tmpline = new CommandeFournisseurLigne($this->db);
3245 if ($tmpline->fetch($rowid) > 0) {
3246 $newqty = $this->roundQtyToPackaging($qty, $tmpline->packaging);
3247 if ($newqty != $qty) {
3248 $qty = $newqty;
3249 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
3250 }
3251 }
3252 }
3253
3254 $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);
3255 $total_ht = $tabprice[0];
3256 $total_tva = $tabprice[1];
3257 $total_ttc = $tabprice[2];
3258 $total_localtax1 = $tabprice[9];
3259 $total_localtax2 = $tabprice[10];
3260 $pu_ht = $tabprice[3];
3261 $pu_tva = $tabprice[4];
3262 $pu_ttc = $tabprice[5];
3263
3264 // MultiCurrency
3265 $multicurrency_total_ht = $tabprice[16];
3266 $multicurrency_total_tva = $tabprice[17];
3267 $multicurrency_total_ttc = $tabprice[18];
3268 $pu_ht_devise = $tabprice[19];
3269 $multicurrency_pu_ttc = $tabprice[21];
3270
3271 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3272 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3273
3274 // Fetch current line from the database and then clone the object and set it in $oldline property
3275 $this->line = new CommandeFournisseurLigne($this->db);
3276 $this->line->fetch($rowid);
3277
3278 $oldline = clone $this->line;
3279 $this->line->oldline = $oldline;
3280
3281 $this->line->context = $this->context;
3282
3283 $this->line->fk_commande = $this->id;
3284 //$this->line->label=$label;
3285 $this->line->desc = $desc;
3286
3287
3288 $this->line->qty = $qty;
3289 $this->line->ref_supplier = $ref_supplier;
3290
3291 $this->line->vat_src_code = $vat_src_code;
3292 $this->line->tva_tx = $txtva;
3293 $this->line->localtax1_tx = $txlocaltax1;
3294 $this->line->localtax2_tx = $txlocaltax2;
3295 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3296 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3297 $this->line->remise_percent = $remise_percent;
3298 $this->line->subprice = (float) $pu_ht;
3299 $this->line->subprice_ttc = (float) $pu_ttc;
3300 $this->line->info_bits = $info_bits;
3301 $this->line->total_ht = (float) $total_ht;
3302 $this->line->total_tva = (float) $total_tva;
3303 $this->line->total_localtax1 = (float) $total_localtax1;
3304 $this->line->total_localtax2 = (float) $total_localtax2;
3305 $this->line->total_ttc = (float) $total_ttc;
3306 $this->line->product_type = $type;
3307 $this->line->special_code = $oldline->special_code;
3308 $this->line->rang = $oldline->rang;
3309 $this->line->origin = $this->origin;
3310 $this->line->fk_unit = $fk_unit;
3311
3312 $this->line->date_start = $date_start;
3313 $this->line->date_end = $date_end;
3314
3315 // Multicurrency
3316 $this->line->fk_multicurrency = $this->fk_multicurrency;
3317 $this->line->multicurrency_code = $this->multicurrency_code;
3318 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3319 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3320 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3321 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3322 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3323
3324 $this->line->subprice = (float) $pu_ht;
3325 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
3326 $this->line->price = $this->line->subprice;
3327
3328 $this->line->remise_percent = $remise_percent;
3329
3330 if (is_array($array_options) && count($array_options) > 0) {
3331 // We replace values in this->line->array_options only for entries defined into $array_options
3332 foreach ($array_options as $key => $value) {
3333 $this->line->array_options[$key] = $array_options[$key];
3334 }
3335 }
3336
3337 $result = $this->line->update($notrigger);
3338
3339
3340 // Update denormalized information at the invoice level
3341 if ($result >= 0) {
3342 $this->update_price(1, 'auto');
3343 $this->db->commit();
3344 return $result;
3345 } else {
3346 $this->errors[] = $this->line->error;
3347 $this->errors = array_merge($this->errors, $this->line->errors);
3348 $this->error = $this->db->lasterror();
3349 $this->db->rollback();
3350 return -1;
3351 }
3352 } else {
3353 $this->error = "Order status makes operation forbidden";
3354 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3355 return -2;
3356 }
3357 }
3358
3359
3367 public function initAsSpecimen()
3368 {
3369 global $user, $langs, $conf;
3370
3371 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3372
3373 dol_syslog(get_class($this)."::initAsSpecimen");
3374
3375 $now = dol_now();
3376
3377 // Find first product
3378 $prodid = 0;
3379 $product = new ProductFournisseur($this->db);
3380 $sql = "SELECT rowid";
3381 $sql .= " FROM ".$this->db->prefix()."product";
3382 $sql .= " WHERE entity IN (".getEntity('product').")";
3383 $sql .= $this->db->order("rowid", "ASC");
3384 $sql .= $this->db->plimit(1);
3385 $resql = $this->db->query($sql);
3386 if ($resql && $this->db->num_rows($resql)) {
3387 $obj = $this->db->fetch_object($resql);
3388 $prodid = $obj->rowid;
3389 }
3390
3391 // Initialise parameters
3392 $this->id = 0;
3393 $this->ref = 'SPECIMEN';
3394 $this->specimen = 1;
3395 $this->socid = 1;
3396 $this->date = $now;
3397 $this->date_commande = $now;
3398 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3399 $this->cond_reglement_code = 'RECEP';
3400 $this->mode_reglement_code = 'CHQ';
3401
3402 $this->note_public = 'This is a comment (public)';
3403 $this->note_private = 'This is a comment (private)';
3404
3405 $this->multicurrency_tx = 1;
3406 $this->multicurrency_code = $conf->currency;
3407
3408 $this->statut = 0; // deprecated
3409 $this->status = 0;
3410
3411 // Lines
3412 $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)
3413 $xnbp = 0;
3414 while ($xnbp < $nbp) {
3415 $line = new CommandeFournisseurLigne($this->db);
3416 $line->desc = $langs->trans("Description")." ".$xnbp;
3417 $line->qty = 1;
3418 $line->subprice = 100;
3419 $line->tva_tx = 19.6;
3420 $line->localtax1_tx = 0;
3421 $line->localtax2_tx = 0;
3422 if ($xnbp == 2) {
3423 $line->total_ht = 50;
3424 $line->total_ttc = 59.8;
3425 $line->total_tva = 9.8;
3426 $line->remise_percent = 50;
3427 } else {
3428 $line->total_ht = 100;
3429 $line->total_ttc = 119.6;
3430 $line->total_tva = 19.6;
3431 $line->remise_percent = 00;
3432 }
3433 $line->fk_product = $prodid;
3434
3435 $this->lines[$xnbp] = $line;
3436
3437 $this->total_ht += $line->total_ht;
3438 $this->total_tva += $line->total_tva;
3439 $this->total_ttc += $line->total_ttc;
3440
3441 $xnbp++;
3442 }
3443
3444 return 1;
3445 }
3446
3453 public function info($id)
3454 {
3455 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3456 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3457 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3458 $sql .= ' WHERE c.rowid = '.((int) $id);
3459
3460 $result = $this->db->query($sql);
3461 if ($result) {
3462 if ($this->db->num_rows($result)) {
3463 $obj = $this->db->fetch_object($result);
3464
3465 $this->id = $obj->rowid;
3466
3467 $this->user_creation_id = $obj->fk_user_author;
3468 $this->user_validation_id = $obj->fk_user_valid;
3469 $this->user_modification_id = $obj->fk_user_modif;
3470 $this->user_approve_id = $obj->fk_user_approve;
3471 $this->user_approve_id2 = $obj->fk_user_approve2;
3472
3473 $this->date_creation = $this->db->jdate($obj->datec);
3474 $this->date_modification = $this->db->jdate($obj->datem);
3475 $this->date_approve = $this->db->jdate($obj->datea);
3476 $this->date_approve2 = $this->db->jdate($obj->datea2);
3477 $this->date_validation = $this->db->jdate($obj->date_validation);
3478 }
3479 $this->db->free($result);
3480 } else {
3481 dol_print_error($this->db);
3482 }
3483 }
3484
3490 public function loadStateBoard()
3491 {
3492 global $user;
3493
3494 $this->nb = array();
3495 $sanitizedclause = "WHERE";
3496
3497 $sql = "SELECT count(co.rowid) as nb";
3498 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3499 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3500 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3501 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3502 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3503 $sanitizedclause = "AND";
3504 }
3505 $sql .= " ".$sanitizedclause." co.entity IN (".getEntity('supplier_order').")";
3506
3507 $resql = $this->db->query($sql);
3508 if ($resql) {
3509 while ($obj = $this->db->fetch_object($resql)) {
3510 $this->nb["supplier_orders"] = $obj->nb;
3511 }
3512 $this->db->free($resql);
3513 return 1;
3514 } else {
3515 dol_print_error($this->db);
3516 $this->error = $this->db->error();
3517 return -1;
3518 }
3519 }
3520
3521 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3529 public function load_board($user, $mode = 'opened')
3530 {
3531 // phpcs:enable
3532 global $conf, $langs;
3533
3534 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3535 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3536 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3537 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3538 }
3539 $sql .= " WHERE c.entity = ".((int) $conf->entity);
3540 if ($mode === 'awaiting') {
3541 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3542 } else {
3543 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3544 }
3545 if ($user->socid) {
3546 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3547 }
3548
3549 $resql = $this->db->query($sql);
3550 if ($resql) {
3551 $commandestatic = new CommandeFournisseur($this->db);
3552
3553 $response = new WorkboardResponse();
3554 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3555 $response->label = $langs->trans("SuppliersOrdersToProcess");
3556 $response->labelShort = $langs->trans("Opened");
3557 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3558 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3559 $response->img = img_object('', "order");
3560
3561 if ($mode === 'awaiting') {
3562 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3563 $response->labelShort = $langs->trans("AwaitingReception");
3564 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3565 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3566 }
3567
3568 while ($obj = $this->db->fetch_object($resql)) {
3569 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3570 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3571 $commandestatic->statut = $obj->fk_statut; // deprecated
3572 $commandestatic->status = $obj->fk_statut;
3573
3574 $response->nbtodo++;
3575 $response->total += $obj->total_ht;
3576
3577 if ($commandestatic->hasDelay()) {
3578 $response->nbtodolate++;
3579 }
3580 }
3581
3582 return $response;
3583 } else {
3584 $this->error = $this->db->error();
3585 return -1;
3586 }
3587 }
3588
3595 public function getInputMethod()
3596 {
3597 global $langs;
3598
3599 if ($this->methode_commande_id > 0) {
3600 $sql = "SELECT rowid, code, libelle as label";
3601 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3602 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3603
3604 $resql = $this->db->query($sql);
3605 if ($resql) {
3606 if ($this->db->num_rows($resql)) {
3607 $obj = $this->db->fetch_object($resql);
3608
3609 $string = $langs->trans($obj->code);
3610 if ($string == $obj->code) {
3611 $string = $obj->label != '-' ? $obj->label : '';
3612 }
3613 return $string;
3614 }
3615 } else {
3616 dol_print_error($this->db);
3617 }
3618 }
3619
3620 return '';
3621 }
3622
3634 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3635 {
3636 global $langs;
3637
3638 if (!dol_strlen($modele)) {
3639 $modele = ''; // No doc template/generation by default
3640
3641 if (!empty($this->model_pdf)) {
3642 $modele = $this->model_pdf;
3643 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3644 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3645 }
3646 }
3647
3648 if (empty($modele)) {
3649 return 0;
3650 } else {
3651 $langs->load("suppliers");
3652 $outputlangs->load("products");
3653
3654 $modelpath = "core/modules/supplier_order/doc/";
3655 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3656 return $result;
3657 }
3658 }
3659
3666 public function getMaxDeliveryTimeDay($langs)
3667 {
3668 if (empty($this->lines)) {
3669 return '';
3670 }
3671
3672 $tmpproductfourn = new ProductFournisseur($this->db);
3673
3674 $nb = 0;
3675 foreach ($this->lines as $line) {
3676 if ($line->fk_product > 0) {
3677 // Load delivery_time_days, return id into product_fournisseur_price
3678 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3679 if ($idp > 0) {
3680 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3681 if ($tmpproductfourn->delivery_time_days > $nb) {
3682 $nb = $tmpproductfourn->delivery_time_days;
3683 }
3684 }
3685 }
3686 }
3687
3688 if ($nb === 0) {
3689 return '';
3690 } else {
3691 return $nb.' '.$langs->trans('days');
3692 }
3693 }
3694
3700 public function getRights()
3701 {
3702 global $user;
3703
3704 return $user->hasRight("fournisseur", "commande");
3705 }
3706
3707
3716 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3717 {
3718 $tables = array(
3719 'commande_fournisseur'
3720 );
3721
3722 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3723 }
3724
3733 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3734 {
3735 $tables = array(
3736 'commande_fournisseurdet'
3737 );
3738
3739 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3740 }
3741
3749 public function hasDelay()
3750 {
3751 global $conf;
3752
3753 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3754 $now = dol_now();
3755 if (!empty($this->delivery_date)) {
3756 $date_to_test = $this->delivery_date;
3757 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3758 } else {
3759 //$date_to_test = $this->date_commande;
3760 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3761 return false;
3762 }
3763 } else {
3764 $now = dol_now();
3765 $date_to_test = $this->date_commande;
3766
3767 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3768 }
3769 }
3770
3778 public function showDelay()
3779 {
3780 global $conf, $langs;
3781
3782 $langs->load('orders');
3783
3784 $text = '';
3785
3786 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3787 if (!empty($this->delivery_date)) {
3788 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3789 } else {
3790 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3791 }
3792 } else {
3793 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3794 }
3795 if ($text) {
3796 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3797 }
3798
3799 return $text;
3800 }
3801
3802
3811 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3812 {
3813 if (isModEnabled("supplier_order")) {
3814 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3815
3816 $qtydelivered = array();
3817 $qtywished = array();
3818
3819 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3820
3821 $filter = array('t.fk_element' => $this->id);
3822 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3823 $filter['t.status'] = 1; // Restrict to lines with status validated
3824 }
3825
3826 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3827 if ($ret < 0) {
3828 $this->error = $supplierorderdispatch->error;
3829 $this->errors = $supplierorderdispatch->errors;
3830 return $ret;
3831 } else {
3832 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3833 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3834 $date_liv = dol_now();
3835
3836 // Build array with quantity deliverd by product
3837 foreach ($supplierorderdispatch->lines as $line) {
3838 if (array_key_exists($line->fk_product, $qtydelivered)) {
3839 $qtydelivered[$line->fk_product] += $line->qty;
3840 } else {
3841 $qtydelivered[$line->fk_product] = $line->qty;
3842 }
3843 }
3844 foreach ($this->lines as $line) {
3845 // Free lines have no product and cannot be dispatched to stock.
3846 if (empty($line->fk_product)) {
3847 continue;
3848 }
3849 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3850 if ($line->product_type > 0 && (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') || empty($line->stockable_product))) {
3851 continue;
3852 }
3853 if (array_key_exists($line->fk_product, $qtywished)) {
3854 $qtywished[$line->fk_product] += $line->qty;
3855 } else {
3856 $qtywished[$line->fk_product] = $line->qty;
3857 }
3858 }
3859
3860 //Compare array
3861 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3862 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3863 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3864 //var_dump(array_keys($qtydelivered));
3865 //var_dump(array_keys($qtywished));
3866 //var_dump($diff_array);
3867 //var_dump($keysinwishednotindelivered);
3868 //var_dump($keysindeliverednotinwished);
3869 //exit;
3870
3871 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3872 if ($closeopenorder) {
3873 //$ret=$this->setStatus($user,5);
3874 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3875 if ($ret < 0) {
3876 return -1;
3877 }
3878 return 5;
3879 } else {
3880 //Diff => received partially
3881 //$ret=$this->setStatus($user,4);
3882 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3883 if ($ret < 0) {
3884 return -1;
3885 }
3886 return 4;
3887 }
3888 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3889 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3890
3891 $close = 0;
3892
3893 if (count($diff_array) > 0) {
3894 //there are some difference between the two arrays
3895
3896 //scan the array of results
3897 foreach ($diff_array as $key => $value) {
3898 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3899 if ($qtydelivered[$key] >= $qtywished[$key]) {
3900 $close++;
3901 }
3902 }
3903 }
3904
3905
3906 if ($close == count($diff_array)) {
3907 //all the products are received equal or more than the wished quantity
3908 if ($closeopenorder) {
3909 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3910 if ($ret < 0) {
3911 return -1;
3912 }
3913 return 5;
3914 } else {
3915 //Diff => received partially
3916 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3917 if ($ret < 0) {
3918 return -1;
3919 }
3920 return 4;
3921 }
3922 } else {
3923 //all the products are not received
3924 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3925 if ($ret < 0) {
3926 return -1;
3927 }
3928 return 4;
3929 }
3930 } else {
3931 //Diff => received partially
3932 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3933 if ($ret < 0) {
3934 return -1;
3935 }
3936 return 4;
3937 }
3938 }
3939 return 1;
3940 }
3941 }
3942 return 0;
3943 }
3944
3952 public function loadReceptions($filtre_statut = -1)
3953 {
3954 $this->receptions = array();
3955
3956 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3957
3958 $sql = 'SELECT cd.rowid, cd.fk_product,';
3959 $sql .= ' sum(cfd.qty) as qty';
3960 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3961 if ($filtre_statut >= 0) {
3962 $sql .= ' '.$this->db->prefix().'reception as e,';
3963 }
3964 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3965 $sql .= ' WHERE';
3966 if ($filtre_statut >= 0) {
3967 $sql .= ' cfd.fk_reception = e.rowid AND';
3968 }
3969 $sql .= ' cfd.fk_elementdet = cd.rowid';
3970 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3971 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3972 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3973 }
3974 if ($filtre_statut >= 0) {
3975 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3976 }
3977 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3978
3979 $resql = $this->db->query($sql);
3980 if ($resql) {
3981 $num = $this->db->num_rows($resql);
3982 $i = 0;
3983 while ($i < $num) {
3984 $obj = $this->db->fetch_object($resql);
3985 if (empty($this->receptions[$obj->rowid])) {
3986 $this->receptions[$obj->rowid] = (float) $obj->qty;
3987 } else {
3988 $this->receptions[$obj->rowid] += (float) $obj->qty;
3989 }
3990 $i++;
3991 }
3992 $this->db->free($resql);
3993
3994 return $num;
3995 } else {
3996 $this->error = $this->db->lasterror();
3997 return -1;
3998 }
3999 }
4000
4008 public function getKanbanView($option = '', $arraydata = null)
4009 {
4010 global $langs;
4011
4012 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4013
4014 $return = '<div class="box-flex-item box-flex-grow-zero">';
4015 $return .= '<div class="info-box info-box-sm">';
4016 $return .= '<span class="info-box-icon bg-infobox-action">';
4017 $return .= img_picto('', $this->picto);
4018 $return .= '</span>';
4019 $return .= '<div class="info-box-content">';
4020 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4021 if ($selected >= 0) {
4022 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4023 }
4024 if (!empty($arraydata['thirdparty'])) {
4025 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
4026 }
4027 if (!empty($this->date)) {
4028 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
4029 }
4030 if (!empty($this->total_ht)) {
4031 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
4032 $return .= ' '.$langs->trans("HT");
4033 $return .= '</span>';
4034 }
4035 if (method_exists($this, 'getLibStatut')) {
4036 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
4037 }
4038 if (property_exists($this, 'billed')) {
4039 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
4040 }
4041 $return .= '</div>';
4042 $return .= '</div>';
4043 $return .= '</div>';
4044 return $return;
4045 }
4046}
$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.
getTooltipContent($params)
getTooltipContent
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
updateRangOfLine($rowid, $rang)
Update position of line (rang)
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
deleteExtraFields()
Delete all extra fields values for the current object.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Superclass for orders classes.
Class to manage Dolibarr database access.
Class to manage stock movements.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
global $mysoc
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php: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.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492