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