dolibarr 23.0.3
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-2025 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';
46if (isModEnabled('productbatch')) {
47 require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
48}
49
50
55{
59 public $element = 'order_supplier';
60
64 public $table_element = 'commande_fournisseur';
65
69 public $table_element_line = 'commande_fournisseurdet';
70
74 public $class_element_line = 'CommandeFournisseurLigne';
75
79 public $fk_element = 'fk_commande';
80
84 public $picto = 'supplier_order';
85
90 public $restrictiononfksoc = 1;
91
95 protected $table_ref_field = 'ref';
96
100 public $id;
101
105 public $ref;
106
110 public $ref_supplier;
111
117 public $ref_fourn;
118
124 public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
125 // -> 7=Canceled/Never received -> (reopen) 3=Process running
126 // -> 6=Canceled -> (reopen) 2=Approved
127 // -> 9=Refused -> (reopen) 1=Validated
128 // Note: billed or not is on another field "billed"
129
133 public $billed;
134
138 public $socid;
139
143 public $fourn_id;
144
148 public $date;
149
153 public $date_valid;
154
158 public $date_approve;
159
164 public $date_approve2;
165
169 public $date_commande;
170
175 public $remise_percent;
179 public $methode_commande_id;
183 public $methode_commande;
184
188 public $delivery_date;
189
193 public $total_ht;
194
198 public $total_tva;
199
203 public $total_localtax1;
204
208 public $total_localtax2;
209
213 public $total_ttc;
214
218 public $source;
219
223 public $cond_reglement_id;
224
228 public $cond_reglement_code;
229
233 public $cond_reglement_label;
234
238 public $cond_reglement_doc;
239
245 public $deposit_percent;
246
250 public $fk_account;
251
255 public $mode_reglement_id;
256
260 public $mode_reglement_code;
261
265 public $mode_reglement;
266
270 public $user_author_id;
271
275 public $user_approve_id;
276
281 public $user_approve_id2;
282
286 public $refuse_note;
287
291 public $extraparams = array();
292
296 public $lines = array();
297
301 public $line;
302
306 public $origin;
307
311 public $origin_id;
312
316 public $date_lim_reglement;
317
321 public $receptions = array();
322
323 // Multicurrency
327 public $fk_multicurrency;
328
332 public $multicurrency_code;
333
337 public $multicurrency_tx;
338
342 public $multicurrency_total_ht;
343
347 public $multicurrency_total_tva;
348
352 public $multicurrency_total_ttc;
353
381 public $fields = array(
382 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
383 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
384 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 35),
385 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
386 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
387 'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
388 'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
389 'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
390 'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
391 'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'getDolGlobalInt("ORDER_DISABLE_DELIVERY_DATE") ? 0 : 1', 'visible' => 1, 'position' => 74),
392 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
393 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
394 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
395 'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
396 'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
397 'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
398 'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
399 'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
400 'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
401 'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
402 'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
403 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
404 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
405 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
406 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
407 'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
408 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
409 'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 176),
410 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
411 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
412 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
413 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
414 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
415 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
416 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
417 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
418 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
419 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
420 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
421 'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
422 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
423 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
424 'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
425 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
426 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
427 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
428 );
429
430
434 const STATUS_DRAFT = 0;
435
440
445
450
455
460
465
470
474 const STATUS_REFUSED = 9;
475
476
481
487 public function __construct($db)
488 {
489 $this->db = $db;
490
491 $this->ismultientitymanaged = 1;
492 }
493
494
502 public function fetch($id, $ref = '')
503 {
504 // Check parameters
505 if (empty($id) && empty($ref)) {
506 return -1;
507 }
508
509 $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,";
510 $sql .= " c.localtax1, c.localtax2, ";
511 $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
512 $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,";
513 $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,";
514 $sql .= " c.fk_account,";
515 $sql .= " c.note_private, c.note_public, c.model_pdf, c.last_main_doc, c.extraparams, c.billed,";
516 $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
517 $sql .= " cm.libelle as methode_commande,";
518 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc, c.deposit_percent,";
519 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
520 $sql .= ', c.fk_incoterms, c.location_incoterms';
521 $sql .= ', c.last_main_doc';
522 $sql .= ', i.libelle as label_incoterms';
523 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
524 $sql .= " LEFT JOIN ".$this->db->prefix()."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
525 $sql .= " LEFT JOIN ".$this->db->prefix()."c_paiement as p ON c.fk_mode_reglement = p.id";
526 $sql .= " LEFT JOIN ".$this->db->prefix()."c_input_method as cm ON cm.rowid = c.fk_input_method";
527 $sql .= ' LEFT JOIN '.$this->db->prefix().'c_incoterms as i ON c.fk_incoterms = i.rowid';
528
529 if (empty($id)) {
530 $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
531 } else {
532 $sql .= " WHERE c.rowid=".((int) $id);
533 }
534
535 if ($ref) {
536 $sql .= " AND c.ref='".$this->db->escape($ref)."'";
537 }
538
539 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
540 $resql = $this->db->query($sql);
541 if ($resql) {
542 $obj = $this->db->fetch_object($resql);
543 if (!$obj) {
544 $this->error = 'Bill with id '.$id.' not found';
545 dol_syslog(get_class($this).'::fetch '.$this->error);
546 return 0;
547 }
548
549 $this->id = $obj->rowid;
550 $this->entity = $obj->entity;
551
552 $this->ref = $obj->ref;
553 $this->ref_supplier = $obj->ref_supplier;
554
555 $this->socid = $obj->fk_soc;
556 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
557
558 $this->fourn_id = $obj->fk_soc;
559 $this->statut = $obj->status; // deprecated
560 $this->status = $obj->status;
561 $this->billed = $obj->billed;
562 $this->last_main_doc = $obj->last_main_doc;
563 $this->user_author_id = $obj->user_author_id;
564 $this->user_validation_id = $obj->user_validation_id;
565 $this->user_approve_id = $obj->user_approve_id;
566 $this->user_approve_id2 = $obj->user_approve_id2;
567 $this->total_ht = $obj->total_ht;
568 $this->total_tva = $obj->total_tva;
569 $this->total_localtax1 = $obj->localtax1;
570 $this->total_localtax2 = $obj->localtax2;
571 $this->total_ttc = $obj->total_ttc;
572 $this->date_creation = $this->db->jdate($obj->date_creation);
573 $this->date_valid = $this->db->jdate($obj->date_valid);
574 $this->date_approve = $this->db->jdate($obj->date_approve);
575 $this->date_approve2 = $this->db->jdate($obj->date_approve2);
576 $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
577 if (isset($obj->date_commande)) {
578 $this->date = $this->date_commande;
579 } else {
580 $this->date = $this->date_creation;
581 }
582 $this->delivery_date = $this->db->jdate($obj->delivery_date);
583 $this->remise_percent = $obj->remise_percent;
584 $this->methode_commande_id = $obj->fk_input_method;
585 $this->methode_commande = $obj->methode_commande;
586
587 $this->source = $obj->source;
588 $this->fk_project = $obj->fk_project;
589 $this->cond_reglement_id = $obj->fk_cond_reglement;
590 $this->cond_reglement_code = $obj->cond_reglement_code;
591 $this->cond_reglement_label = $obj->cond_reglement_label;
592 $this->cond_reglement_doc = $obj->cond_reglement_doc;
593 $this->deposit_percent = $obj->deposit_percent;
594 $this->fk_account = $obj->fk_account;
595 $this->mode_reglement_id = $obj->fk_mode_reglement;
596 $this->mode_reglement_code = $obj->mode_reglement_code;
597 $this->mode_reglement = $obj->mode_reglement_libelle;
598 $this->note = $obj->note_private; // deprecated
599 $this->note_private = $obj->note_private;
600 $this->note_public = $obj->note_public;
601 $this->model_pdf = $obj->model_pdf;
602 $this->last_main_doc = $obj->last_main_doc;
603
604 //Incoterms
605 $this->fk_incoterms = $obj->fk_incoterms;
606 $this->location_incoterms = $obj->location_incoterms;
607 $this->label_incoterms = $obj->label_incoterms;
608
609 // Multicurrency
610 $this->fk_multicurrency = $obj->fk_multicurrency;
611 $this->multicurrency_code = $obj->multicurrency_code;
612 $this->multicurrency_tx = $obj->multicurrency_tx;
613 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
614 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
615 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
616
617 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
618
619 $this->db->free($resql);
620
621 // Retrieve all extrafield
622 // fetch optionals attributes and labels
623 $this->fetch_optionals();
624
625 // Lines
626 $result = $this->fetch_lines();
627
628 if ($result < 0) {
629 return -1;
630 } else {
631 return 1;
632 }
633 } else {
634 $this->error = $this->db->error()." sql=".$sql;
635 return -1;
636 }
637 }
638
639 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
646 public function fetch_lines($only_product = 0)
647 {
648 // phpcs:enable
649
650 $this->lines = array();
651
652 $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
653 $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice, l.subprice_ttc,";
654 $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
655 $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.info_bits, l.special_code, l.fk_parent_line, l.rang,";
656 $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,";
657 $sql .= " l.fk_unit, l.extraparams,";
658 $sql .= " l.date_start, l.date_end,";
659 $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';
660 $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as l";
661 $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON l.fk_product = p.rowid';
662 $sql .= " WHERE l.fk_commande = ".((int) $this->id);
663 if ($only_product) {
664 $sql .= ' AND p.fk_product_type = 0';
665 }
666 $sql .= " ORDER BY l.rang, l.rowid";
667 //print $sql;
668
669 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
670
671 $result = $this->db->query($sql);
672 if ($result) {
673 $num = $this->db->num_rows($result);
674 $i = 0;
675
676 while ($i < $num) {
677 $objp = $this->db->fetch_object($result);
678
679 $line = new CommandeFournisseurLigne($this->db);
680
681 $line->id = $objp->rowid;
682 $line->fk_commande = $objp->fk_commande;
683 $line->desc = $objp->description;
684 $line->description = $objp->description;
685 $line->qty = $objp->qty;
686 $line->tva_tx = $objp->tva_tx;
687 $line->localtax1_tx = $objp->localtax1_tx;
688 $line->localtax2_tx = $objp->localtax2_tx;
689 $line->localtax1_type = $objp->localtax1_type;
690 $line->localtax2_type = $objp->localtax2_type;
691 $line->subprice = $objp->subprice;
692 $line->pu_ht = $objp->subprice; // deprecated
693 $line->subprice_ttc = $objp->subprice_ttc;
694 $line->pu_ttc = $objp->subprice_ttc; // deprecated
695 $line->remise_percent = $objp->remise_percent;
696
697 $line->vat_src_code = $objp->vat_src_code;
698 $line->total_ht = $objp->total_ht;
699 $line->total_tva = $objp->total_tva;
700 $line->total_localtax1 = $objp->total_localtax1;
701 $line->total_localtax2 = $objp->total_localtax2;
702 $line->total_ttc = $objp->total_ttc;
703 $line->product_type = $objp->product_type;
704
705 $line->fk_product = $objp->fk_product;
706
707 $line->libelle = $objp->product_label; // deprecated
708 $line->product_label = $objp->product_label;
709 $line->product_desc = $objp->product_desc;
710 $line->product_tobatch = $objp->product_tobatch;
711 $line->product_barcode = $objp->product_barcode;
712
713 $line->ref = $objp->product_ref; // Ref of product
714 $line->product_ref = $objp->product_ref; // Ref of product
715 $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
716 $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
717
718 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
719 // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
720 // Move this into another method and call it when required.
721
722 // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
723 $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
724 $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('productsupplierprice').")";
725 $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
726 $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
727 $sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty); // required to be qualified
728 $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")"; // required to be qualified
729 $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
730 $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
731 $sqlsearchpackage .= " LIMIT 1";
732
733 $resqlsearchpackage = $this->db->query($sqlsearchpackage);
734 if ($resqlsearchpackage) {
735 $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
736 if ($objsearchpackage) {
737 $line->fk_fournprice = $objsearchpackage->rowid;
738 $line->packaging = (float) $objsearchpackage->packaging;
739 }
740 } else {
741 $this->error = $this->db->lasterror();
742 return -1;
743 }
744 }
745
746 $line->date_start = $this->db->jdate($objp->date_start);
747 $line->date_end = $this->db->jdate($objp->date_end);
748 $line->fk_unit = $objp->fk_unit;
749
750 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
751
752 // Multicurrency
753 $line->fk_multicurrency = $objp->fk_multicurrency;
754 $line->multicurrency_code = $objp->multicurrency_code;
755 $line->multicurrency_subprice = $objp->multicurrency_subprice;
756 $line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
757 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
758 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
759 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
760
761 $line->info_bits = $objp->info_bits;
762 $line->special_code = $objp->special_code;
763 $line->fk_parent_line = $objp->fk_parent_line;
764
765 $line->rang = $objp->rang;
766
767 // Retrieve all extrafield
768 // fetch optionals attributes and labels
769 $line->fetch_optionals();
770
771 $this->lines[$i] = $line;
772
773 $i++;
774 }
775 $this->db->free($result);
776
777 return $num;
778 } else {
779 $this->error = $this->db->error()." sql=".$sql;
780 return -1;
781 }
782 }
783
792 public function valid($user, $idwarehouse = 0, $notrigger = 0)
793 {
794 global $conf;
795 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
796
797 $error = 0;
798
799 dol_syslog(get_class($this)."::valid");
800 $result = 0;
801 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
802 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
803 $this->db->begin();
804
805 if (!getDolGlobalBool('SUPPLIER_ORDER_NOCHECK_ONBUY_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines('onbuy')) {
806 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
807 return -1;
808 }
809 // Definition of supplier order numbering model name
810 $soc = new Societe($this->db);
811 $soc->fetch((int) $this->fourn_id);
812
813 // Check if object has a temporary ref
814 if (preg_match('/^[\‍(]?PROV/i', (string) $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
815 $num = $this->getNextNumRef($soc);
816 } else {
817 $num = (string) $this->ref;
818 }
819 $this->newref = dol_sanitizeFileName($num);
820
821 $sql = 'UPDATE '.$this->db->prefix()."commande_fournisseur";
822 $sql .= " SET ref='".$this->db->escape($num)."',";
823 $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
824 $sql .= " date_valid='".$this->db->idate(dol_now())."',";
825 $sql .= " fk_user_valid = ".((int) $user->id);
826 $sql .= " WHERE rowid = ".((int) $this->id);
827 $sql .= " AND fk_statut = ".((int) self::STATUS_DRAFT);
828
829 $resql = $this->db->query($sql);
830 if (!$resql) {
831 dol_print_error($this->db);
832 $error++;
833 }
834
835 if (!$error && !$notrigger) {
836 // Call trigger
837 $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
838 if ($result < 0) {
839 $error++;
840 }
841 // End call triggers
842 }
843
844 if (!$error) {
845 $this->oldref = $this->ref;
846
847 // Rename directory if dir was a temporary ref
848 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
849 // Now we rename also files into index
850 $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)."'";
851 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
852 $resql = $this->db->query($sql);
853 if (!$resql) {
854 $error++;
855 $this->error = $this->db->lasterror();
856 }
857 $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
858 $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
859 $resql = $this->db->query($sql);
860 if (!$resql) {
861 $error++;
862 $this->error = $this->db->lasterror();
863 }
864
865 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
866 $oldref = dol_sanitizeFileName($this->ref);
867 $newref = dol_sanitizeFileName($num);
868 $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
869 $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
870 if (!$error && file_exists($dirsource)) {
871 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
872
873 if (@rename($dirsource, $dirdest)) {
874 dol_syslog("Rename ok");
875 // Rename docs starting with $oldref with $newref
876 $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
877 foreach ($listoffiles as $fileentry) {
878 $dirsource = $fileentry['name'];
879 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
880 $dirsource = $fileentry['path'].'/'.$dirsource;
881 $dirdest = $fileentry['path'].'/'.$dirdest;
882 @rename($dirsource, $dirdest);
883 }
884 }
885 }
886 }
887 }
888
889 if (!$error) {
890 $result = 1;
892 $this->statut = self::STATUS_VALIDATED; // deprecated
893 $this->ref = $num;
894 }
895
896 if (!$error) {
897 $this->db->commit();
898 return 1;
899 } else {
900 $this->db->rollback();
901 return -1;
902 }
903 } else {
904 $this->error = 'NotAuthorized';
905 dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
906 return -1;
907 }
908 }
909
916 public function getLibStatut($mode = 0)
917 {
918 return $this->LibStatut(isset($this->status) ? $this->status : $this->statut, $mode, $this->billed);
919 }
920
921 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
930 public function LibStatut($status, $mode = 0, $billed = 0)
931 {
932 // phpcs:enable
933 global $langs, $hookmanager;
934
935 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
936 $langs->load('orders');
937
938 $this->labelStatus[0] = 'StatusSupplierOrderDraft';
939 $this->labelStatus[1] = 'StatusSupplierOrderValidated';
940 $this->labelStatus[2] = 'StatusSupplierOrderApproved';
941 if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
942 $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
943 } else {
944 $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
945 }
946 $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
947 $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
948 $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
949 $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
950 $this->labelStatus[9] = 'StatusSupplierOrderRefused';
951
952 // List of language codes for status
953 $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
954 $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
955 $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
956 $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
957 $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
958 $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
959 $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
960 $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
961 $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
962 }
963
964 $statustrans = array(
965 0 => 'status0',
966 1 => 'status1b',
967 2 => 'status1',
968 3 => 'status4',
969 4 => 'status4b',
970 5 => 'status6',
971 6 => 'status9',
972 7 => 'status9',
973 9 => 'status9',
974 );
975
976 $statusClass = 'status0';
977 if (!empty($statustrans[$status])) {
978 $statusClass = $statustrans[$status];
979 }
980
981 $billedtext = '';
982 if ($billed) {
983 $billedtext = ' - '.$langs->trans("Billed");
984 }
985 if ($status == 5 && $billed) {
986 $statusClass = 'status6';
987 }
988
989 $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]).$billedtext;
990 $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
991
992 $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
993 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
994 if ($reshook > 0) {
995 return $hookmanager->resPrint;
996 }
997
998 return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
999 }
1000
1007 public function getTooltipContentArray($params)
1008 {
1009 global $conf, $langs, $user;
1010
1011 $langs->loadLangs(['bills', 'orders']);
1012
1013 $datas = [];
1014 $nofetch = !empty($params['nofetch']);
1015
1016 if ($user->hasRight("fournisseur", "commande", "read")) {
1017 $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
1018 if ($this->status) {
1019 $datas['picto'] .= ' '.$this->getLibStatut(5);
1020 }
1021 if (!empty($this->ref)) {
1022 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1023 }
1024 if (!empty($this->ref_supplier)) {
1025 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
1026 }
1027 if (!$nofetch) {
1028 $langs->load('companies');
1029 if (empty($this->thirdparty)) {
1030 $this->fetch_thirdparty();
1031 }
1032 $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
1033 }
1034 if (!empty($this->total_ht)) {
1035 $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1036 }
1037 if (!empty($this->total_tva)) {
1038 $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1039 }
1040 if (!empty($this->total_ttc)) {
1041 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1042 }
1043 if (!empty($this->date)) {
1044 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1045 }
1046 if (!empty($this->delivery_date)) {
1047 $langs->load("sendings");
1048 $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
1049 }
1050 }
1051 return $datas;
1052 }
1053
1064 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1065 {
1066 global $langs, $user, $hookmanager;
1067
1068 $result = '';
1069 $params = [
1070 'id' => $this->id,
1071 'objecttype' => $this->element,
1072 'option' => $option,
1073 'nofetch' => 1
1074 ];
1075 $classfortooltip = 'classfortooltip';
1076 $dataparams = '';
1077 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1078 $classfortooltip = 'classforajaxtooltip';
1079 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1080 $label = '';
1081 } else {
1082 $label = implode($this->getTooltipContentArray($params));
1083 }
1084
1085 $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
1086
1087 if ($option !== 'nolink') {
1088 // Add param to save lastsearch_values or not
1089 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1090 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1091 $add_save_lastsearch_values = 1;
1092 }
1093 if ($add_save_lastsearch_values) {
1094 $url .= '&save_lastsearch_values=1';
1095 }
1096 }
1097
1098 $linkclose = '';
1099 if (empty($notooltip)) {
1100 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1101 $label = $langs->trans("ShowOrder");
1102 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
1103 }
1104 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
1105 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1106 }
1107
1108 $linkstart = '<a href="'.$url.'"';
1109 $linkstart .= $linkclose.'>';
1110 $linkend = '</a>';
1111
1112 $result .= $linkstart;
1113 if ($withpicto) {
1114 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1115 }
1116 if ($withpicto != 2) {
1117 $result .= $this->ref;
1118 }
1119 $result .= $linkend;
1120
1121 if ($addlinktonotes) {
1122 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1123 if ($txttoshow) {
1124 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1125 $result .= ' <span class="note inline-block">';
1126 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1127 $result .= img_picto('', 'note');
1128 $result .= '</a>';
1129 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1130 //$result.='</a>';
1131 $result .= '</span>';
1132 }
1133 }
1134
1135 global $action;
1136 $hookmanager->initHooks(array($this->element . 'dao'));
1137 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1138 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1139 if ($reshook > 0) {
1140 $result = $hookmanager->resPrint;
1141 } else {
1142 $result .= $hookmanager->resPrint;
1143 }
1144 return $result;
1145 }
1146
1147
1155 public function getNextNumRef($soc)
1156 {
1157 global $langs, $conf;
1158 $langs->load("orders");
1159
1160 if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1161 $mybool = false;
1162
1163 $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
1164 $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1165
1166 // Include file with class
1167 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1168
1169 foreach ($dirmodels as $reldir) {
1170 $dir = dol_buildpath($reldir."core/modules/supplier_order/");
1171
1172 // Load file with numbering class (if found)
1173 $mybool = ((bool) @include_once $dir.$file) || $mybool;
1174 }
1175
1176 if (!$mybool) {
1177 dol_print_error(null, "Failed to include file ".$file);
1178 return '';
1179 }
1180
1181 $obj = new $classname();
1182 '@phan-var-force ModeleNumRefSuppliersOrders $obj';
1184 $numref = $obj->getNextValue($soc, $this);
1185
1186 if ($numref != "") {
1187 return $numref;
1188 } else {
1189 $this->error = $obj->error;
1190 return -1;
1191 }
1192 } else {
1193 $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1194 return -2;
1195 }
1196 }
1197
1204 public function classifyBilled(User $user)
1205 {
1206 $error = 0;
1207
1208 if ($this->billed) {
1209 return 0;
1210 }
1211
1212 $this->db->begin();
1213
1214 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 1';
1215 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1216
1217 if ($this->db->query($sql)) {
1218 // Call trigger
1219 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1220 if ($result < 0) {
1221 $error++;
1222 }
1223 // End call triggers
1224
1225 if (!$error) {
1226 $this->billed = 1;
1227
1228 $this->db->commit();
1229 return 1;
1230 } else {
1231 $this->db->rollback();
1232 return -1;
1233 }
1234 } else {
1235 dol_print_error($this->db);
1236
1237 $this->db->rollback();
1238 return -1;
1239 }
1240 }
1241
1242
1249 public function classifyUnBilled(User $user)
1250 {
1251 if (empty($this->billed)) {
1252 return 0;
1253 }
1254
1255 $this->db->begin();
1256
1257 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 0';
1258 $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1259 ;
1260
1261 if (!$this->db->query($sql)) {
1262 dol_print_error($this->db);
1263 $this->db->rollback();
1264 return -1;
1265 }
1266
1267 // Call trigger
1268 $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_UNBILLED', $user);
1269 if ($result < 0) {
1270 $this->db->rollback();
1271 return -1;
1272 }
1273 // End call triggers
1274
1275 $this->billed = 1;
1276 $this->db->commit();
1277 return 1;
1278 }
1279
1280
1289 public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1290 {
1291 global $langs;
1292
1293 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1294
1295 $error = 0;
1296
1297 dol_syslog(get_class($this)."::approve");
1298
1299 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1300 $now = dol_now();
1301
1302 $this->db->begin();
1303
1304 // Definition of order numbering model name
1305 $soc = new Societe($this->db);
1306 $soc->fetch((int) $this->fourn_id);
1307
1308 // Check if object has a temporary ref
1309 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1310 $num = $this->getNextNumRef($soc);
1311 } else {
1312 $num = (string) $this->ref;
1313 }
1314 $this->newref = dol_sanitizeFileName($num);
1315
1316 // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1317 $movetoapprovestatus = true;
1318 $comment = '';
1319
1320 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1321 $sql .= " SET ref='".$this->db->escape($num)."',";
1322 if (empty($secondlevel)) { // standard or first level approval
1323 $sql .= " date_approve='".$this->db->idate($now)."',";
1324 $sql .= " fk_user_approve = ".((int) $user->id);
1325 if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= getDolGlobalFloat('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED')) {
1326 if (empty($this->user_approve_id2)) {
1327 $movetoapprovestatus = false; // second level approval not done
1328 $comment = ' (first level)';
1329 }
1330 }
1331 } else { // request a second level approval
1332 $sql .= " date_approve2='".$this->db->idate($now)."',";
1333 $sql .= " fk_user_approve2 = ".((int) $user->id);
1334 if (empty($this->user_approve_id)) {
1335 $movetoapprovestatus = false; // first level approval not done
1336 }
1337 $comment = ' (second level)';
1338 }
1339 // If double approval is required and first approval, we keep status to 1 = validated
1340 if ($movetoapprovestatus) {
1341 $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1342 } else {
1343 $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1344 }
1345 $sql .= " WHERE rowid = ".((int) $this->id);
1346 $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1347
1348 if ($this->db->query($sql)) {
1349 if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1350 $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1351 if ($result < 0 && $result != -2) { // -2 means already exists
1352 $error++;
1353 }
1354 }
1355
1356 // If stock is incremented on validate order, we must increment it
1357 if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1358 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1359 $langs->load("agenda");
1360
1361 $cpt = count($this->lines);
1362 for ($i = 0; $i < $cpt; $i++) {
1363 // Product with reference
1364 if ($this->lines[$i]->fk_product > 0) {
1365 $this->line = $this->lines[$i];
1366 $mouvP = new MouvementStock($this->db);
1367 $mouvP->origin = &$this;
1368 $mouvP->setOrigin($this->element, $this->id);
1369 // We decrement stock of product (and sub-products)
1370 $up_ht_disc = $this->lines[$i]->subprice;
1371 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1372 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1373 }
1374 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1375 if ($result < 0) {
1376 $error++;
1377 }
1378 unset($this->line);
1379 }
1380 }
1381 }
1382
1383 if (!$error) {
1384 // Call trigger
1385 $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1386 if ($result < 0) {
1387 $error++;
1388 }
1389 // End call triggers
1390 }
1391
1392 if (!$error) {
1393 $this->ref = $this->newref;
1394
1395 if ($movetoapprovestatus) {
1396 $this->statut = self::STATUS_ACCEPTED; // deprecated
1398 } else {
1399 $this->statut = self::STATUS_VALIDATED; // deprecated
1401 }
1402 if (empty($secondlevel)) { // standard or first level approval
1403 $this->date_approve = $now;
1404 $this->user_approve_id = $user->id;
1405 } else { // request a second level approval
1406 $this->date_approve2 = $now;
1407 $this->user_approve_id2 = $user->id;
1408 }
1409
1410 $this->db->commit();
1411 return 1;
1412 } else {
1413 $this->db->rollback();
1414 return -1;
1415 }
1416 } else {
1417 $this->db->rollback();
1418 $this->error = $this->db->lasterror();
1419 return -1;
1420 }
1421 } else {
1422 dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1423 }
1424 return -1;
1425 }
1426
1433 public function refuse($user)
1434 {
1435 $error = 0;
1436
1437 dol_syslog(get_class($this)."::refuse");
1438 $result = 0;
1439 if ($user->hasRight("fournisseur", "commande", "approuver")) {
1440 $this->db->begin();
1441
1442 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1443 $sql .= " WHERE rowid = ".((int) $this->id);
1444
1445 if ($this->db->query($sql)) {
1446 $result = 0;
1447
1448 if ($error == 0) {
1449 // Call trigger
1450 $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1451 if ($result < 0) {
1452 $error++;
1453 $this->db->rollback();
1454 } else {
1455 $this->db->commit();
1456 }
1457 // End call triggers
1458 }
1459 } else {
1460 $this->db->rollback();
1461 $this->error = $this->db->lasterror();
1462 dol_syslog(get_class($this)."::refuse Error -1");
1463 $result = -1;
1464 }
1465 } else {
1466 dol_syslog(get_class($this)."::refuse Not Authorized");
1467 }
1468 return $result;
1469 }
1470
1471 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1480 public function cancel($user, $idwarehouse = -1)
1481 {
1482 // phpcs:enable
1483 $error = 0;
1484
1485 //dol_syslog("CommandeFournisseur::Cancel");
1486 $result = 0;
1487 if ($user->hasRight("fournisseur", "commande", "commander")) {
1488 $statut = self::STATUS_CANCELED;
1489
1490 $this->db->begin();
1491
1492 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".((int) $statut);
1493 $sql .= " WHERE rowid = ".((int) $this->id);
1494 dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1495 if ($this->db->query($sql)) {
1496 $result = 0;
1497
1498 // Call trigger
1499 $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1500 if ($result < 0) {
1501 $error++;
1502 }
1503 // End call triggers
1504
1505 if ($error == 0) {
1506 $this->db->commit();
1507 return 1;
1508 } else {
1509 $this->db->rollback();
1510 return -1;
1511 }
1512 } else {
1513 $this->db->rollback();
1514 $this->error = $this->db->lasterror();
1515 dol_syslog(get_class($this)."::cancel ".$this->error);
1516 return -1;
1517 }
1518 } else {
1519 dol_syslog(get_class($this)."::cancel Not Authorized");
1520 return -1;
1521 }
1522 }
1523
1533 public function commande($user, $date, $methode, $comment = '')
1534 {
1535 global $langs;
1536 dol_syslog(get_class($this)."::commande");
1537 $error = 0;
1538 if ($user->hasRight("fournisseur", "commande", "commander")) {
1539 $this->db->begin();
1540
1541 $newnoteprivate = $this->note_private;
1542 if ($comment) {
1543 $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1544 }
1545
1546 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1547 $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', ";
1548 $sql .= " note_private='".$this->db->escape((string) $newnoteprivate)."'";
1549 $sql .= " WHERE rowid=".((int) $this->id);
1550
1551 dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1552 if ($this->db->query($sql)) {
1553 $this->statut = self::STATUS_ORDERSENT; // deprecated
1555 $this->methode_commande_id = $methode;
1556 $this->date_commande = $date;
1557 $this->context['comments'] = $comment;
1558
1559 // Call trigger
1560 $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1561 if ($result < 0) {
1562 $error++;
1563 }
1564 // End call triggers
1565 } else {
1566 $error++;
1567 $this->error = $this->db->lasterror();
1568 $this->errors[] = $this->db->lasterror();
1569 }
1570
1571 if (!$error) {
1572 $this->db->commit();
1573 } else {
1574 $this->db->rollback();
1575 }
1576 } else {
1577 $error++;
1578 $this->error = $langs->trans('NotAuthorized');
1579 $this->errors[] = $langs->trans('NotAuthorized');
1580 dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1581 }
1582
1583 return ($error ? -1 : 1);
1584 }
1585
1593 public function create($user, $notrigger = 0)
1594 {
1595 global $conf;
1596
1597 $this->db->begin();
1598
1599 $error = 0;
1600 $now = dol_now();
1601
1602 // set tmp vars
1603 $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1604 if (empty($date)) {
1605 $date = $now;
1606 }
1607 $delivery_date = $this->delivery_date;
1608
1609 // Clean parameters
1610 if (empty($this->source)) {
1611 $this->source = 0;
1612 }
1613
1614 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1615 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1616 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1617 } else {
1618 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1619 }
1620 if (empty($this->fk_multicurrency)) {
1621 $this->multicurrency_code = $conf->currency;
1622 $this->fk_multicurrency = 0;
1623 $this->multicurrency_tx = 1;
1624 }
1625 $this->entity = setEntity($this);
1626
1627 // We set order into draft status
1628 $this->statut = self::STATUS_DRAFT; // deprecated
1629 $this->status = self::STATUS_DRAFT;
1630
1631 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseur (";
1632 $sql .= "ref";
1633 $sql .= ", ref_supplier";
1634 $sql .= ", note_private";
1635 $sql .= ", note_public";
1636 $sql .= ", entity";
1637 $sql .= ", fk_soc";
1638 $sql .= ", fk_projet";
1639 $sql .= ", date_creation";
1640 $sql .= ", date_livraison";
1641 $sql .= ", fk_user_author";
1642 $sql .= ", fk_statut";
1643 $sql .= ", source";
1644 $sql .= ", model_pdf";
1645 $sql .= ", fk_mode_reglement";
1646 $sql .= ", deposit_percent";
1647 $sql .= ", fk_cond_reglement";
1648 $sql .= ", fk_account";
1649 $sql .= ", fk_incoterms, location_incoterms";
1650 $sql .= ", fk_multicurrency";
1651 $sql .= ", multicurrency_code";
1652 $sql .= ", multicurrency_tx";
1653 $sql .= ") ";
1654 $sql .= " VALUES (";
1655 $sql .= "'(PROV)'";
1656 $sql .= ", ".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "NULL");
1657 $sql .= ", '".$this->db->escape($this->note_private)."'";
1658 $sql .= ", '".$this->db->escape($this->note_public)."'";
1659 $sql .= ", ".((int) $this->entity);
1660 $sql .= ", ".((int) $this->socid);
1661 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1662 $sql .= ", '".$this->db->idate($date)."'";
1663 $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1664 $sql .= ", ".((int) $user->id);
1665 $sql .= ", ".self::STATUS_DRAFT;
1666 $sql .= ", ".((int) $this->source);
1667 $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
1668 $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1669 $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : "null");
1670 $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1671 $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
1672 $sql .= ", ".(int) $this->fk_incoterms;
1673 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1674 $sql .= ", ".(int) $this->fk_multicurrency;
1675 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1676 $sql .= ", ".(float) $this->multicurrency_tx;
1677 $sql .= ")";
1678
1679 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1680 if ($this->db->query($sql)) {
1681 $this->id = $this->db->last_insert_id($this->db->prefix()."commande_fournisseur");
1682
1683 if ($this->id) {
1684 $num = count($this->lines);
1685
1686 // insert products details into database
1687 for ($i = 0; $i < $num; $i++) {
1688 $line = $this->lines[$i];
1689 if (!is_object($line)) {
1690 $line = (object) $line;
1691 }
1692
1693 //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1694
1695 // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1696 $result = $this->addline(
1697 (string) $line->desc,
1698 (float) $line->subprice,
1699 (float) $line->qty,
1700 $line->tva_tx,
1701 (float) $line->localtax1_tx,
1702 (float) $line->localtax2_tx,
1703 (int) $line->fk_product,
1704 0,
1705 (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
1706 (float) $line->remise_percent,
1707 'HT',
1708 (float) $line->subprice_ttc,
1709 (int) $line->product_type,
1710 (int) $line->info_bits,
1711 0,
1712 $line->date_start,
1713 $line->date_end,
1714 $line->array_options,
1715 $line->fk_unit,
1716 (float) $line->multicurrency_subprice, // pu_ht_devise
1717 (string) $line->origin, // origin
1718 (int) $line->origin_id, // origin_id
1719 (int) $line->rang, // rang
1720 (int) $line->special_code
1721 );
1722 if ($result < 0) {
1723 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1724 $this->db->rollback();
1725 return -1;
1726 }
1727 }
1728
1729 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1730 $sql .= " SET ref='(PROV".$this->id.")'";
1731 $sql .= " WHERE rowid=".((int) $this->id);
1732
1733 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1734 if ($this->db->query($sql)) {
1735 // Add link with price request and supplier order
1736 if ($this->id) {
1737 $this->ref = "(PROV".$this->id.")";
1738
1739 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1740 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1741 }
1742
1743 // Add object linked
1744 if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
1745 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1746 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, ...))
1747 foreach ($tmp_origin_id as $origin_id) {
1748 $ret = $this->add_object_linked($origin, $origin_id);
1749 if (!$ret) {
1750 dol_print_error($this->db);
1751 $error++;
1752 }
1753 }
1754 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1755 $origin_id = $tmp_origin_id;
1756 $ret = $this->add_object_linked($origin, $origin_id);
1757 if (!$ret) {
1758 dol_print_error($this->db);
1759 $error++;
1760 }
1761 }
1762 }
1763 }
1764 }
1765
1766 if (!$error) {
1767 $result = $this->insertExtraFields();
1768 if ($result < 0) {
1769 $error++;
1770 }
1771 }
1772
1773 if (!$error && !$notrigger) {
1774 // Call trigger
1775 $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1776 if ($result < 0) {
1777 $this->db->rollback();
1778
1779 return -1;
1780 }
1781 // End call triggers
1782 }
1783
1784 $this->db->commit();
1785 return $this->id;
1786 } else {
1787 $this->error = $this->db->lasterror();
1788 $this->db->rollback();
1789
1790 return -2;
1791 }
1792 } else {
1793 $this->error = 'Failed to get ID of inserted line';
1794
1795 return -1;
1796 }
1797 } else {
1798 $this->error = $this->db->lasterror();
1799 $this->db->rollback();
1800
1801 return -1;
1802 }
1803 }
1804
1812 public function update(User $user, $notrigger = 0)
1813 {
1814 $error = 0;
1815
1816 // Clean parameters
1817 if (isset($this->ref)) {
1818 $this->ref = trim($this->ref);
1819 }
1820 if (isset($this->ref_supplier)) {
1821 $this->ref_supplier = trim($this->ref_supplier);
1822 }
1823 if (isset($this->note_private)) {
1824 $this->note_private = trim($this->note_private);
1825 }
1826 if (isset($this->note_public)) {
1827 $this->note_public = trim($this->note_public);
1828 }
1829 if (isset($this->model_pdf)) {
1830 $this->model_pdf = trim($this->model_pdf);
1831 }
1832 if (isset($this->import_key)) {
1833 $this->import_key = trim($this->import_key);
1834 }
1835
1836 // Update request
1837 $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1838
1839 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1840 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1841 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1842 $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1843 $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1844 $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1845 $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1846 $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1847 $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1848 $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1849 $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1850 $sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").",";
1851 $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1852 $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? $this->user_validation_id : "null").",";
1853 $sql .= " fk_projet=".((!empty($this->fk_project) && $this->fk_project > 0) ? $this->fk_project : "null").",";
1854 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1855 $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").",";
1856 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1857 $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1858 //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1859 $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
1860 //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1861 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1862 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1863 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1864 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1865
1866 $sql .= " WHERE rowid=".((int) $this->id);
1867
1868 $this->db->begin();
1869
1870 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1871 $resql = $this->db->query($sql);
1872 if (!$resql) {
1873 $error++;
1874 $this->errors[] = "Error ".$this->db->lasterror();
1875 }
1876
1877 if (!$error) {
1878 $result = $this->insertExtraFields();
1879 if ($result < 0) {
1880 $error++;
1881 }
1882 }
1883
1884 if (!$error && !$notrigger) {
1885 // Call trigger
1886 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1887 if ($result < 0) {
1888 $error++;
1889 }
1890 // End call triggers
1891 }
1892
1893 // Commit or rollback
1894 if ($error) {
1895 foreach ($this->errors as $errmsg) {
1896 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1897 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1898 }
1899 $this->db->rollback();
1900 return -1 * $error;
1901 } else {
1902 $this->db->commit();
1903 return 1;
1904 }
1905 }
1906
1915 public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1916 {
1917 global $conf, $user, $hookmanager;
1918
1919 $error = 0;
1920
1921 $this->db->begin();
1922
1923 // get extrafields so they will be clone
1924 foreach ($this->lines as $line) {
1925 $line->fetch_optionals();
1926 }
1927
1928 // Load source object
1929 $objFrom = clone $this;
1930
1931 // Change socid if needed
1932 if (!empty($socid) && $socid != $this->socid) {
1933 $objsoc = new Societe($this->db);
1934
1935 if ($objsoc->fetch($socid) > 0) {
1936 $this->socid = $objsoc->id;
1937 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1938 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
1939 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1940 $this->fk_project = 0;
1941 $this->fk_delivery_address = 0;
1942 }
1943
1944 // TODO Change product price if multi-prices
1945 }
1946
1947 $this->id = 0;
1948 $this->statut = self::STATUS_DRAFT; // deprecated
1949 $this->status = self::STATUS_DRAFT;
1950
1951 // Clear fields
1952 $this->user_author_id = $user->id;
1953 $this->user_validation_id = 0;
1954
1955 $this->date = dol_now();
1956 $this->date_creation = 0;
1957 $this->date_validation = 0;
1958 $this->date_commande = 0;
1959 $this->ref_supplier = '';
1960 $this->user_approve_id = 0;
1961 $this->user_approve_id2 = 0;
1962 $this->date_approve = 0;
1963 $this->date_approve2 = 0;
1964
1965 // Create clone
1966 $this->context['createfromclone'] = 'createfromclone';
1967 $result = $this->create($user, $notrigger);
1968 if ($result < 0) {
1969 $error++;
1970 }
1971
1972 if (!$error) {
1973 // Hook of thirdparty module
1974 if (is_object($hookmanager)) {
1975 $parameters = array('objFrom' => $objFrom);
1976 $action = '';
1977 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1978 if ($reshook < 0) {
1979 $this->setErrorsFromObject($hookmanager);
1980 $error++;
1981 }
1982 }
1983 }
1984
1985 unset($this->context['createfromclone']);
1986
1987 // End
1988 if (!$error) {
1989 $this->db->commit();
1990 return $this->id;
1991 } else {
1992 $this->db->rollback();
1993 return -1;
1994 }
1995 }
1996
2026 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)
2027 {
2028 global $langs, $mysoc;
2029
2030 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");
2031 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2032
2033 if ($this->status == self::STATUS_DRAFT) {
2034 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2035
2036 // Clean parameters
2037 if (empty($qty)) {
2038 $qty = 0;
2039 }
2040 if (!$info_bits) {
2041 $info_bits = 0;
2042 }
2043 if (empty($txtva)) {
2044 $txtva = 0;
2045 }
2046 if (empty($rang)) {
2047 $rang = 0;
2048 }
2049 if (empty($txlocaltax1)) {
2050 $txlocaltax1 = 0;
2051 }
2052 if (empty($txlocaltax2)) {
2053 $txlocaltax2 = 0;
2054 }
2055 if (empty($remise_percent)) {
2056 $remise_percent = 0;
2057 }
2058
2059 $remise_percent = price2num($remise_percent);
2060 $qty = price2num($qty);
2061 $pu_ht = price2num($pu_ht);
2062 $pu_ht_devise = price2num($pu_ht_devise);
2063 $pu_ttc = price2num($pu_ttc);
2064 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2065 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2066 }
2067 $txlocaltax1 = price2num($txlocaltax1);
2068 $txlocaltax2 = price2num($txlocaltax2);
2069 if ($price_base_type == 'HT') {
2070 $pu = $pu_ht;
2071 } else {
2072 $pu = $pu_ttc;
2073 }
2074 $desc = trim($desc);
2075
2076 // Check parameters
2077 if ($qty < 0 && !$fk_product) {
2078 $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
2079 return -1;
2080 }
2081 if ($type < 0) {
2082 return -1;
2083 }
2084 if ($date_start && $date_end && $date_start > $date_end) {
2085 $langs->load("errors");
2086 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2087 return -1;
2088 }
2089
2090
2091 $this->db->begin();
2092
2093 $product_type = $type;
2094 $label = ''; // deprecated
2095
2096 if ($fk_product > 0) {
2097 if (getDolGlobalInt('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2098 // Check quantity is enough
2099 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);
2100 $prod = new ProductFournisseur($this->db);
2101 if ($prod->fetch($fk_product) > 0) {
2102 $product_type = $prod->type;
2103 $label = $prod->label;
2104
2105 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2106 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2107 $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
2108
2109 // If supplier order created from sales order, we take best supplier price
2110 // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2111 if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2112 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2113 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2114 // is remise percent not keyed but present for the product we add it
2115 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2116 $remise_percent = $prod->remise_percent;
2117 }
2118 }
2119 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2120 $langs->load("errors");
2121 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2122 $this->db->rollback();
2123 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2124 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2125 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2126 return -1;
2127 }
2128 if ($result == -1) {
2129 $langs->load("errors");
2130 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2131 $this->db->rollback();
2132 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2133 return -1;
2134 }
2135 if ($result < -1) {
2136 $this->error = $prod->error;
2137 $this->db->rollback();
2138 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2139 return -1;
2140 }
2141 } else {
2142 $this->error = $prod->error;
2143 $this->db->rollback();
2144 return -1;
2145 }
2146 }
2147
2148 // Predefine quantity according to packaging
2149 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2150 $prod = new Product($this->db);
2151 $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2152
2153 // Align messaging, type and float-safety with the customer-order path at commande.class.php:1720
2154 // (#38782 bugs 1, 2, 6).
2155 if (abs((float) $qty) < $prod->packaging) {
2156 $qty = (float) $prod->packaging;
2157 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2158 } else {
2159 if (!empty($prod->packaging) && (float) price2num(fmod((float) $qty, (float) $prod->packaging), 'MS')) {
2160 $coeff = intval(abs((float) $qty) / $prod->packaging) + 1;
2161 $qty = price2num((float) $prod->packaging * $coeff, 'MS');
2162 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
2163 }
2164 }
2165
2166 // Enforce the supplier minimum purchase quantity on top of packaging
2167 // rounding. The packaging block above only ensures qty is a packaging
2168 // multiple, not that it satisfies pfp.quantity (=qty_min). If the line
2169 // is below the supplier minimum, round qty_min itself up to the next
2170 // packaging multiple so we end up with the smallest valid order qty (#38783).
2171 if (!empty($prod->fourn_qty) && abs((float) $qty) < (float) $prod->fourn_qty) {
2172 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
2173 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
2174 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
2175 } else {
2176 $qty = (float) $prod->fourn_qty;
2177 }
2178 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2179 }
2180 }
2181 }
2182
2183 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2184 $pu = 0;
2185 }
2186
2187 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2188
2189 // Clean vat code
2190 $reg = array();
2191 $vat_src_code = '';
2192 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2193 $vat_src_code = $reg[1];
2194 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2195 }
2196
2197 // Calcul du total TTC et de la TVA pour la ligne a partir de
2198 // qty, pu, remise_percent et txtva
2199 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2200 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2201
2202 $tabprice = calcul_price_total((float) $qty, $pu, $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);
2203
2204 $total_ht = $tabprice[0];
2205 $total_tva = $tabprice[1];
2206 $total_ttc = $tabprice[2];
2207 $total_localtax1 = $tabprice[9];
2208 $total_localtax2 = $tabprice[10];
2209 $pu = $pu_ht = $tabprice[3];
2210 $pu_tva = $tabprice[4];
2211 $pu_ttc = $tabprice[5];
2212
2213 // MultiCurrency
2214 $multicurrency_total_ht = $tabprice[16];
2215 $multicurrency_total_tva = $tabprice[17];
2216 $multicurrency_total_ttc = $tabprice[18];
2217 $pu_ht_devise = $tabprice[19];
2218 $multicurrency_pu_ttc = $tabprice[21];
2219
2220 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2221 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2222
2223 if ($rang < 0) {
2224 $rangmax = $this->line_max();
2225 $rang = $rangmax + 1;
2226 }
2227
2228 // Insert line
2229 $this->line = new CommandeFournisseurLigne($this->db);
2230
2231 $this->line->context = $this->context;
2232
2233 $this->line->fk_commande = $this->id;
2234 $this->line->label = $label;
2235 $this->line->ref_fourn = $ref_supplier;
2236 $this->line->ref_supplier = $ref_supplier;
2237 $this->line->desc = $desc;
2238 $this->line->qty = $qty;
2239 $this->line->tva_tx = $txtva;
2240 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2241 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2242 $this->line->localtax1_type = $localtax1_type;
2243 $this->line->localtax2_type = $localtax2_type;
2244 $this->line->fk_product = $fk_product;
2245 $this->line->product_type = $product_type;
2246 $this->line->remise_percent = $remise_percent;
2247 $this->line->subprice = (float) $pu_ht;
2248 $this->line->subprice_ttc = (float) $pu_ttc;
2249
2250 $this->line->rang = $rang;
2251 $this->line->info_bits = $info_bits;
2252
2253 $this->line->vat_src_code = $vat_src_code;
2254 $this->line->total_ht = (float) $total_ht;
2255 $this->line->total_tva = (float) $total_tva;
2256 $this->line->total_localtax1 = (float) $total_localtax1;
2257 $this->line->total_localtax2 = (float) $total_localtax2;
2258 $this->line->total_ttc = (float) $total_ttc;
2259 $this->line->product_type = $type;
2260 $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2261 $this->line->origin = $origin;
2262 $this->line->origin_type = $origin;
2263 $this->line->origin_id = $origin_id;
2264 $this->line->fk_unit = $fk_unit;
2265
2266 $this->line->date_start = $date_start;
2267 $this->line->date_end = $date_end;
2268
2269 // Multicurrency
2270 $this->line->fk_multicurrency = $this->fk_multicurrency;
2271 $this->line->multicurrency_code = $this->multicurrency_code;
2272 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
2273 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
2274 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2275 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2276 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2277
2278 $this->line->subprice = (float) $pu_ht;
2279 $this->line->price = $this->line->subprice;
2280
2281 $this->line->remise_percent = $remise_percent;
2282
2283 if (is_array($array_options) && count($array_options) > 0) {
2284 $this->line->array_options = $array_options;
2285 }
2286
2287 $result = $this->line->insert($notrigger);
2288 if ($result > 0) {
2289 // Update denormalized fields at the order level
2290 $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.
2291
2292 if ($result > 0) {
2293 if (!isset($this->context['createfromclone'])) {
2294 if (!empty($this->line->fk_parent_line)) {
2295 // Always reorder if child line
2296 $this->line_order(true, 'DESC');
2297 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2298 // Update all rank of all other lines starting from the same $ranktouse
2299 $linecount = count($this->lines);
2300 for ($ii = $rang; $ii <= $linecount; $ii++) {
2301 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2302 }
2303 }
2304
2305 $this->lines[] = $this->line;
2306 }
2307
2308 $this->db->commit();
2309 return $this->line->id;
2310 } else {
2311 $this->db->rollback();
2312 return -1;
2313 }
2314 } else {
2315 $this->setErrorsFromObject($this->line);
2316 dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2317 $this->db->rollback();
2318 return -1;
2319 }
2320 }
2321 return -1;
2322 }
2323
2324
2342 public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2343 {
2344 global $conf, $langs;
2345
2346 $error = 0;
2347 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2348
2349 // Check parameters (if test are wrong here, there is bug into caller)
2350 if ($entrepot <= 0) {
2351 $this->error = 'ErrorBadValueForParameterWarehouse';
2352 return -1;
2353 }
2354 if ($qty == 0) {
2355 $this->error = 'ErrorBadValueForParameterQty';
2356 return -1;
2357 }
2358
2359 $dispatchstatus = 1;
2360 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2361 $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2362 }
2363
2364 $now = dol_now();
2365
2366 $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2367
2368 if (($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY || $this->status == self::STATUS_RECEIVED_COMPLETELY)) {
2369 $this->db->begin();
2370
2371 $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2372 $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2373 $sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', ";
2374 $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");
2375 $sql .= ")";
2376
2377 dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2378 $resql = $this->db->query($sql);
2379 if ($resql) {
2380 if (!$notrigger) {
2381 global $conf, $langs, $user;
2382 // Call trigger
2383 $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2384 if ($result < 0) {
2385 $error++;
2386 }
2387 // End call triggers
2388 }
2389 } else {
2390 $this->error = $this->db->lasterror();
2391 $error++;
2392 }
2393
2394 // If module stock is enabled and the stock increase is done on purchase order dispatching
2395 if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2396 $mouv = new MouvementStock($this->db);
2397 if ($product > 0) {
2398 // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2399 $mouv->origin = &$this;
2400 $mouv->setOrigin($this->element, $this->id);
2401
2402 // Method change if qty < 0
2403 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2404 $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2405 } else {
2406 $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2407 }
2408
2409 if ($result < 0) {
2410 $this->error = $mouv->error;
2411 $this->errors = $mouv->errors;
2412 dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2413 $error++;
2414 }
2415 }
2416 }
2417
2418 if ($error == 0) {
2419 $this->db->commit();
2420 return 1;
2421 } else {
2422 $this->db->rollback();
2423 return -1;
2424 }
2425 } else {
2426 $this->error = 'BadStatusForObject';
2427 return -2;
2428 }
2429 }
2430
2438 public function deleteLine($idline, $notrigger = 0)
2439 {
2440 global $user;
2441
2442 if ($this->status == 0) {
2443 $line = new CommandeFournisseurLigne($this->db);
2444
2445 if ($line->fetch($idline) <= 0) {
2446 return 0;
2447 }
2448
2449 // check if not yet received
2450 $dispatchedLines = $this->getDispachedLines();
2451 foreach ($dispatchedLines as $dispatchLine) {
2452 if ($dispatchLine['orderlineid'] == $idline) {
2453 $this->error = "LineAlreadyDispatched";
2454 $this->errors[] = $this->error;
2455 return -3;
2456 }
2457 }
2458
2459 if ($line->delete($user, $notrigger) > 0) {
2460 $this->update_price(1);
2461 return 1;
2462 } else {
2463 $this->setErrorsFromObject($line);
2464 return -1;
2465 }
2466 } else {
2467 return -2;
2468 }
2469 }
2470
2478 public function delete(User $user, $notrigger = 0)
2479 {
2480 global $langs, $conf;
2481 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2482
2483 $error = 0;
2484
2485 $this->db->begin();
2486
2487 if (empty($notrigger)) {
2488 // Call trigger
2489 $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2490 if ($result < 0) {
2491 $this->errors[] = 'ErrorWhenRunningTrigger';
2492 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2493 $this->db->rollback();
2494 return -1;
2495 }
2496 // End call triggers
2497 }
2498
2499 // Test we can delete
2500 $this->fetchObjectLinked(null, 'order_supplier');
2501 if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2502 foreach ($this->linkedObjects['reception'] as $element) {
2503 if ($element->statut >= 0) {
2504 $this->errors[] = $langs->trans('ReceptionExist');
2505 $error++;
2506 break;
2507 }
2508 }
2509 }
2510
2511 // Remove linked categories.
2512 if (!$error) {
2513 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_order";
2514 $sql .= " WHERE fk_supplier_order = ".((int) $this->id);
2515
2516 $result = $this->db->query($sql);
2517 if (!$result) {
2518 $error++;
2519 $this->errors[] = $this->db->lasterror();
2520 }
2521 }
2522
2523 $main = $this->db->prefix().'commande_fournisseurdet';
2524
2525 if (!$error) {
2526 $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).")";
2527 dol_syslog(__METHOD__." linked order lines", LOG_DEBUG);
2528 if (!$this->db->query($sql1)) {
2529 $error++;
2530 $this->error = $this->db->lasterror();
2531 $this->errors[] = $this->db->lasterror();
2532 }
2533 }
2534
2535 if (!$error) {
2536 $ef = $main."_extrafields";
2537 $sql = "DELETE FROM ".$this->db->sanitize($ef)." WHERE fk_object IN (SELECT rowid FROM ".$this->db->sanitize($main)." WHERE fk_commande = ".((int) $this->id).")";
2538 dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2539 if (!$this->db->query($sql)) {
2540 $this->error = $this->db->lasterror();
2541 $this->errors[] = $this->db->lasterror();
2542 $error++;
2543 }
2544 }
2545
2546 if (!$error) {
2547 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande = ".((int) $this->id);
2548 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2549 if (!$this->db->query($sql)) {
2550 $this->error = $this->db->lasterror();
2551 $this->errors[] = $this->db->lasterror();
2552 $error++;
2553 }
2554 }
2555
2556 if (!$error) {
2557 $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid = ".((int) $this->id);
2558 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2559 if ($resql = $this->db->query($sql)) {
2560 if ($this->db->affected_rows($resql) < 1) {
2561 $this->error = $this->db->lasterror();
2562 $this->errors[] = $this->db->lasterror();
2563 $error++;
2564 }
2565 } else {
2566 $this->error = $this->db->lasterror();
2567 $this->errors[] = $this->db->lasterror();
2568 $error++;
2569 }
2570 }
2571
2572 // Remove extrafields
2573 if (!$error) {
2574 $result = $this->deleteExtraFields();
2575 if ($result < 0) {
2576 $this->error = 'FailToDeleteExtraFields';
2577 $this->errors[] = 'FailToDeleteExtraFields';
2578 $error++;
2579 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2580 }
2581 }
2582
2583 // Delete linked object
2584 $res = $this->deleteObjectLinked();
2585 if ($res < 0) {
2586 $this->error = 'FailToDeleteObjectLinked';
2587 $this->errors[] = 'FailToDeleteObjectLinked';
2588 $error++;
2589 }
2590
2591 if (!$error) {
2592 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2593 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2594 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2595
2596 // We remove directory
2597 $ref = dol_sanitizeFileName($this->ref);
2598 if ($conf->fournisseur->commande->dir_output) {
2599 $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2600 $file = $dir."/".$ref.".pdf";
2601 if (file_exists($file)) {
2602 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2603 $this->error = 'ErrorFailToDeleteFile';
2604 $this->errors[] = 'ErrorFailToDeleteFile';
2605 $error++;
2606 }
2607 }
2608 if (file_exists($dir)) {
2609 $res = @dol_delete_dir_recursive($dir);
2610 if (!$res) {
2611 $this->error = 'ErrorFailToDeleteDir';
2612 $this->errors[] = 'ErrorFailToDeleteDir';
2613 $error++;
2614 }
2615 }
2616 }
2617 }
2618
2619 if (!$error) {
2620 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2621 $this->db->commit();
2622 return 1;
2623 } else {
2624 dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2625 $this->db->rollback();
2626 return -$error;
2627 }
2628 }
2629
2630
2639 public function getDispachedLines($status = -1)
2640 {
2641 $ret = array();
2642
2643 // List of already dispatched lines
2644 $sql = "SELECT p.ref, p.label,";
2645 $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2646 $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2647 $sql .= " FROM ".$this->db->prefix()."product as p,";
2648 $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2649 $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2650 $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2651 $sql .= " AND cfd.fk_product = p.rowid";
2652 if ($status >= 0) {
2653 $sql .= " AND cfd.status = ".((int) $status);
2654 }
2655 $sql .= " ORDER BY cfd.rowid ASC";
2656
2657 $resql = $this->db->query($sql);
2658 if ($resql) {
2659 $num = $this->db->num_rows($resql);
2660 $i = 0;
2661
2662 while ($i < $num) {
2663 $objp = $this->db->fetch_object($resql);
2664 if ($objp) {
2665 $ret[] = array(
2666 'id' => $objp->dispatchedlineid,
2667 'productid' => $objp->fk_product,
2668 'warehouseid' => $objp->warehouse_id,
2669 'qty' => $objp->qty,
2670 'orderlineid' => $objp->fk_elementdet
2671 );
2672 }
2673
2674 $i++;
2675 }
2676 } else {
2677 dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2678 }
2679
2680 return $ret;
2681 }
2682
2683 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2693 public function Livraison($user, $date, $type, $comment)
2694 {
2695 // phpcs:enable
2696 global $conf, $langs;
2697
2698 $result = 0;
2699 $error = 0;
2700 $dispatchedlinearray = array();
2701
2702 dol_syslog(get_class($this)."::Livraison");
2703
2704 $usercanreceive = 0;
2705 if (!isModEnabled('reception')) {
2706 $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2707 } else {
2708 $usercanreceive = $user->hasRight("reception", "creer");
2709 }
2710
2711 if ($usercanreceive) {
2712 // Define the new status
2713 if ($type == 'par') {
2715 } elseif ($type == 'tot') {
2717 } elseif ($type == 'nev') {
2719 } elseif ($type == 'can') {
2721 } else {
2722 $error++;
2723 dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2724 return -2;
2725 }
2726
2727 // Some checks to accept the record
2728 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2729 // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2730 if ($type == 'tot') {
2731 $dispatchedlinearray = $this->getDispachedLines(0);
2732 if (count($dispatchedlinearray) > 0) {
2733 $result = -1;
2734 $error++;
2735 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2736 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2737 }
2738 }
2739 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)
2740 $dispatcheddenied = $this->getDispachedLines(2);
2741 if (count($dispatchedlinearray) > 0) {
2742 $result = -1;
2743 $error++;
2744 $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2745 dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2746 }
2747 }
2748 }
2749
2750 // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2751
2752 if (empty($error)) {
2753 $this->db->begin();
2754
2755 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2756 $sql .= " SET fk_statut = ".((int) $statut);
2757 $sql .= " WHERE rowid = ".((int) $this->id);
2758 $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2759
2760 dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2761 $resql = $this->db->query($sql);
2762 if ($resql) {
2763 $result = 1;
2764 $old_statut = $this->status;
2765 $this->status = $statut;
2766 $this->context['actionmsg2'] = $comment;
2767
2768 // Call trigger
2769 $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2770 if ($result_trigger < 0) {
2771 $error++;
2772 }
2773 // End call triggers
2774
2775 if (empty($error)) {
2776 $this->db->commit();
2777 } else {
2778 $this->status = $old_statut;
2779 $this->db->rollback();
2780 $this->error = $this->db->lasterror();
2781 $result = -1;
2782 }
2783 } else {
2784 $this->db->rollback();
2785 $this->error = $this->db->lasterror();
2786 $result = -1;
2787 }
2788 }
2789 } else {
2790 $this->error = $langs->trans('NotAuthorized');
2791 $this->errors[] = $langs->trans('NotAuthorized');
2792 dol_syslog(get_class($this)."::Livraison Not Authorized");
2793 $result = -3;
2794 }
2795 return $result;
2796 }
2797
2798 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2808 public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2809 {
2810 // phpcs:enable
2811 return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2812 }
2813
2822 public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2823 {
2824 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2825 $error = 0;
2826
2827 $this->db->begin();
2828
2829 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2830 $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2831 $sql .= " WHERE rowid = ".((int) $this->id);
2832
2833 dol_syslog(__METHOD__, LOG_DEBUG);
2834 $resql = $this->db->query($sql);
2835 if (!$resql) {
2836 $this->errors[] = $this->db->error();
2837 $error++;
2838 }
2839
2840 if (!$error) {
2841 $this->oldcopy = clone $this;
2842 $this->delivery_date = $delivery_date;
2843 }
2844
2845 if (!$notrigger && empty($error)) {
2846 // Call trigger
2847 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2848 if ($result < 0) {
2849 $error++;
2850 }
2851 // End call triggers
2852 }
2853
2854 if (!$error) {
2855 $this->db->commit();
2856 return 1;
2857 } else {
2858 foreach ($this->errors as $errmsg) {
2859 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2860 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2861 }
2862 $this->db->rollback();
2863 return -1 * $error;
2864 }
2865 } else {
2866 return -2;
2867 }
2868 }
2869
2876 public function setReopen(User $user): int
2877 {
2878 if (in_array($this->status, [1, 2, 3, 4, 5, 6, 7, 9])) {
2879 if ($this->status == 1) {
2880 $newStatus = 0; // Validated->Draft
2881 } elseif ($this->status == 2) {
2882 $newStatus = 0; // Approved->Draft
2883 } elseif ($this->status == 3) {
2884 $newStatus = 2; // Ordered->Approved
2885 } elseif ($this->status == 4) {
2886 $newStatus = 3;
2887 } elseif ($this->status == 5) {
2888 //$newstatus=2; // Ordered
2889 // TODO Can we set it to submitted ?
2890 //$newstatus=3; // Submitted
2891 // TODO If there is at least one reception, we can set to Received->Received partially
2892 $newStatus = 4; // Received partially
2893 } elseif ($this->status == 6) {
2894 $newStatus = 2; // Canceled->Approved
2895 } elseif ($this->status == 7) {
2896 $newStatus = 3; // Canceled->Process running
2897 } elseif ($this->status == 9) {
2898 $newStatus = 1; // Refused->Validated
2899 } else {
2900 $newStatus = 2;
2901 }
2902
2903 $this->db->begin();
2904
2905 $result = $this->setStatus($user, $newStatus);
2906 if ($result > 0) {
2907 if ($newStatus == 0) {
2908 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2909 $sql .= ' SET fk_user_approve = null, fk_user_approve2 = null, date_approve = null, date_approve2 = null';
2910 $sql .= ' WHERE rowid = '.((int) $this->id);
2911
2912 $this->db->query($sql);
2913 }
2914
2915 $this->db->commit();
2916
2917 return 1;
2918 } else {
2919 $this->db->rollback();
2920
2921 return -1;
2922 }
2923 }
2924
2925 return 0;
2926 }
2927
2928 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2937 public function set_id_projet($user, $id_projet, $notrigger = 0)
2938 {
2939 // phpcs:enable
2940 if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2941 $error = 0;
2942
2943 $this->db->begin();
2944
2945 $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2946 $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2947 $sql .= " WHERE rowid = ".((int) $this->id);
2948
2949 dol_syslog(__METHOD__, LOG_DEBUG);
2950 $resql = $this->db->query($sql);
2951 if (!$resql) {
2952 $this->errors[] = $this->db->error();
2953 $error++;
2954 }
2955
2956 if (!$error) {
2957 $this->oldcopy = clone $this;
2958 $this->fk_projet = $id_projet;
2959 $this->fk_project = $id_projet;
2960 }
2961
2962 if (!$notrigger && empty($error)) {
2963 // Call trigger
2964 $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2965 if ($result < 0) {
2966 $error++;
2967 }
2968 // End call triggers
2969 }
2970
2971 if (!$error) {
2972 $this->db->commit();
2973 return 1;
2974 } else {
2975 foreach ($this->errors as $errmsg) {
2976 dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2977 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2978 }
2979 $this->db->rollback();
2980 return -1 * $error;
2981 }
2982 } else {
2983 return -2;
2984 }
2985 }
2986
2995 public function updateFromCommandeClient($user, $idc, $comclientid)
2996 {
2997 $comclient = new Commande($this->db);
2998 $comclient->fetch($comclientid);
2999
3000 $this->id = $idc;
3001
3002 $this->lines = array();
3003
3004 $num = count($comclient->lines);
3005 for ($i = 0; $i < $num; $i++) {
3006 $prod = new Product($this->db);
3007 $label = '';
3008 $ref = '';
3009 if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
3010 $label = $prod->label;
3011 $ref = (string) $prod->ref;
3012 }
3013
3014 $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
3015 $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
3016 $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
3017 $sql .= ",".((int) $comclient->lines[$i]->fk_product).", ".price2num($comclient->lines[$i]->price, 'MU');
3018 $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);
3019 $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
3020 if ($this->db->query($sql)) {
3021 $this->update_price(1);
3022 }
3023 }
3024
3025 return 1;
3026 }
3027
3035 public function setStatus($user, $status)
3036 {
3037 $error = 0;
3038
3039 $this->db->begin();
3040
3041 $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
3042 $sql .= " SET fk_statut = ".$status;
3043 $sql .= " WHERE rowid = ".((int) $this->id);
3044
3045 dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
3046 $resql = $this->db->query($sql);
3047 if ($resql) {
3048 // Trigger names for each status
3049 $triggerName = array();
3050 $triggerName[0] = 'DRAFT';
3051 $triggerName[1] = 'VALIDATED';
3052 $triggerName[2] = 'APPROVED';
3053 $triggerName[3] = 'ORDERED'; // Ordered
3054 $triggerName[4] = 'RECEIVED_PARTIALLY';
3055 $triggerName[5] = 'RECEIVED_COMPLETELY';
3056 $triggerName[6] = 'CANCELED';
3057 $triggerName[7] = 'CANCELED';
3058 $triggerName[9] = 'REFUSED';
3059
3060 // Call trigger
3061 $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
3062 if ($result < 0) {
3063 $error++;
3064 }
3065 // End call triggers
3066 } else {
3067 $error++;
3068 $this->error = $this->db->lasterror();
3069 dol_syslog(get_class($this)."::setStatus ".$this->error);
3070 }
3071
3072 if (!$error) {
3073 $this->status = $status;
3074 $this->db->commit();
3075 return 1;
3076 } else {
3077 $this->db->rollback();
3078 return -1;
3079 }
3080 }
3081
3093 public function setCategories($categories)
3094 {
3095 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3096 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_ORDER);
3097 }
3098
3122 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 = '')
3123 {
3124 global $mysoc, $conf, $langs;
3125 dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
3126 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3127
3128 $error = 0;
3129
3130 if ($this->status == self::STATUS_DRAFT) {
3131 // Clean parameters
3132 if (empty($qty)) {
3133 $qty = 0;
3134 }
3135 if (empty($info_bits)) {
3136 $info_bits = 0;
3137 }
3138 if (empty($txtva)) {
3139 $txtva = 0;
3140 }
3141 if (empty($txlocaltax1)) {
3142 $txlocaltax1 = 0;
3143 }
3144 if (empty($txlocaltax2)) {
3145 $txlocaltax2 = 0;
3146 }
3147 if (empty($remise_percent)) {
3148 $remise_percent = 0;
3149 }
3150
3151 $remise_percent = (float) price2num($remise_percent);
3152 $qty = price2num($qty);
3153 if (!$qty) {
3154 $qty = 1;
3155 }
3156 $pu = price2num($pu);
3157 $pu_ht_devise = price2num($pu_ht_devise);
3158 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
3159 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3160 }
3161 $txlocaltax1 = (float) price2num($txlocaltax1);
3162 $txlocaltax2 = (float) price2num($txlocaltax2);
3163
3164 // Check parameters
3165 if ($type < 0) {
3166 return -1;
3167 }
3168 if ($date_start && $date_end && $date_start > $date_end) {
3169 $langs->load("errors");
3170 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3171 return -1;
3172 }
3173
3174 $this->db->begin();
3175
3176 // Calcul du total TTC et de la TVA pour la ligne a partir de
3177 // qty, pu, remise_percent et txtva
3178 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3179 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3180
3181 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
3182
3183 // Clean vat code
3184 $reg = array();
3185 $vat_src_code = '';
3186 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
3187 $vat_src_code = $reg[1];
3188 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
3189 }
3190
3191 $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);
3192 $total_ht = $tabprice[0];
3193 $total_tva = $tabprice[1];
3194 $total_ttc = $tabprice[2];
3195 $total_localtax1 = $tabprice[9];
3196 $total_localtax2 = $tabprice[10];
3197 $pu_ht = $tabprice[3];
3198 $pu_tva = $tabprice[4];
3199 $pu_ttc = $tabprice[5];
3200
3201 // MultiCurrency
3202 $multicurrency_total_ht = $tabprice[16];
3203 $multicurrency_total_tva = $tabprice[17];
3204 $multicurrency_total_ttc = $tabprice[18];
3205 $pu_ht_devise = $tabprice[19];
3206 $multicurrency_pu_ttc = $tabprice[21];
3207
3208 $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3209 $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3210
3211 // Fetch current line from the database and then clone the object and set it in $oldline property
3212 $this->line = new CommandeFournisseurLigne($this->db);
3213 $this->line->fetch($rowid);
3214
3215 $oldline = clone $this->line;
3216 $this->line->oldline = $oldline;
3217
3218 $this->line->context = $this->context;
3219
3220 $this->line->fk_commande = $this->id;
3221 //$this->line->label=$label;
3222 $this->line->desc = $desc;
3223
3224 // redefine quantity according to packaging
3225 // Mirror commande.class.php::updateline at line 3289: surface the auto-correction
3226 // to the user with a warning, float-safe the fmod / coeff arithmetic and use
3227 // abs() so negative qty is handled too (#38782 bugs 3, 4, 5, 6).
3228 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3229 if (abs((float) $qty) < $this->line->packaging) {
3230 $qty = $this->line->packaging;
3231 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3232 } else {
3233 if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0
3234 && (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
3235 $coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
3236 $qty = price2num((float) $this->line->packaging * $coeff, 'MS');
3237 setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
3238 }
3239 }
3240 }
3241
3242 $this->line->qty = $qty;
3243 $this->line->ref_supplier = $ref_supplier;
3244
3245 $this->line->vat_src_code = $vat_src_code;
3246 $this->line->tva_tx = $txtva;
3247 $this->line->localtax1_tx = $txlocaltax1;
3248 $this->line->localtax2_tx = $txlocaltax2;
3249 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3250 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3251 $this->line->remise_percent = $remise_percent;
3252 $this->line->subprice = (float) $pu_ht;
3253 $this->line->subprice_ttc = (float) $pu_ttc;
3254 $this->line->info_bits = $info_bits;
3255 $this->line->total_ht = (float) $total_ht;
3256 $this->line->total_tva = (float) $total_tva;
3257 $this->line->total_localtax1 = (float) $total_localtax1;
3258 $this->line->total_localtax2 = (float) $total_localtax2;
3259 $this->line->total_ttc = (float) $total_ttc;
3260 $this->line->product_type = $type;
3261 $this->line->special_code = $oldline->special_code;
3262 $this->line->rang = $oldline->rang;
3263 $this->line->origin = $this->origin;
3264 $this->line->fk_unit = $fk_unit;
3265
3266 $this->line->date_start = $date_start;
3267 $this->line->date_end = $date_end;
3268
3269 // Multicurrency
3270 $this->line->fk_multicurrency = $this->fk_multicurrency;
3271 $this->line->multicurrency_code = $this->multicurrency_code;
3272 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
3273 $this->line->multicurrency_subprice_ttc = (float) $multicurrency_pu_ttc;
3274 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
3275 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
3276 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
3277
3278 $this->line->subprice = (float) $pu_ht;
3279 $this->line->subprice_ttc = (float) $pu_ttc;
3280 $this->line->price = $this->line->subprice;
3281
3282 $this->line->remise_percent = $remise_percent;
3283
3284 if (is_array($array_options) && count($array_options) > 0) {
3285 // We replace values in this->line->array_options only for entries defined into $array_options
3286 foreach ($array_options as $key => $value) {
3287 $this->line->array_options[$key] = $array_options[$key];
3288 }
3289 }
3290
3291 $result = $this->line->update($notrigger);
3292
3293
3294 // Mise a jour info denormalisees au niveau facture
3295 if ($result >= 0) {
3296 $this->update_price(1, 'auto');
3297 $this->db->commit();
3298 return $result;
3299 } else {
3300 $this->errors[] = $this->line->error;
3301 $this->errors = array_merge($this->errors, $this->line->errors);
3302 $this->error = $this->db->lasterror();
3303 $this->db->rollback();
3304 return -1;
3305 }
3306 } else {
3307 $this->error = "Order status makes operation forbidden";
3308 dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3309 return -2;
3310 }
3311 }
3312
3313
3321 public function initAsSpecimen()
3322 {
3323 global $user, $langs, $conf;
3324
3325 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3326
3327 dol_syslog(get_class($this)."::initAsSpecimen");
3328
3329 $now = dol_now();
3330
3331 // Find first product
3332 $prodid = 0;
3333 $product = new ProductFournisseur($this->db);
3334 $sql = "SELECT rowid";
3335 $sql .= " FROM ".$this->db->prefix()."product";
3336 $sql .= " WHERE entity IN (".getEntity('product').")";
3337 $sql .= $this->db->order("rowid", "ASC");
3338 $sql .= $this->db->plimit(1);
3339 $resql = $this->db->query($sql);
3340 if ($resql && $this->db->num_rows($resql)) {
3341 $obj = $this->db->fetch_object($resql);
3342 $prodid = $obj->rowid;
3343 }
3344
3345 // Initialise parameters
3346 $this->id = 0;
3347 $this->ref = 'SPECIMEN';
3348 $this->specimen = 1;
3349 $this->socid = 1;
3350 $this->date = $now;
3351 $this->date_commande = $now;
3352 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3353 $this->cond_reglement_code = 'RECEP';
3354 $this->mode_reglement_code = 'CHQ';
3355
3356 $this->note_public = 'This is a comment (public)';
3357 $this->note_private = 'This is a comment (private)';
3358
3359 $this->multicurrency_tx = 1;
3360 $this->multicurrency_code = $conf->currency;
3361
3362 $this->statut = 0; // deprecated
3363 $this->status = 0;
3364
3365 // Lines
3366 $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)
3367 $xnbp = 0;
3368 while ($xnbp < $nbp) {
3369 $line = new CommandeFournisseurLigne($this->db);
3370 $line->desc = $langs->trans("Description")." ".$xnbp;
3371 $line->qty = 1;
3372 $line->subprice = 100;
3373 $line->tva_tx = 19.6;
3374 $line->localtax1_tx = 0;
3375 $line->localtax2_tx = 0;
3376 if ($xnbp == 2) {
3377 $line->total_ht = 50;
3378 $line->total_ttc = 59.8;
3379 $line->total_tva = 9.8;
3380 $line->remise_percent = 50;
3381 } else {
3382 $line->total_ht = 100;
3383 $line->total_ttc = 119.6;
3384 $line->total_tva = 19.6;
3385 $line->remise_percent = 00;
3386 }
3387 $line->fk_product = $prodid;
3388
3389 $this->lines[$xnbp] = $line;
3390
3391 $this->total_ht += $line->total_ht;
3392 $this->total_tva += $line->total_tva;
3393 $this->total_ttc += $line->total_ttc;
3394
3395 $xnbp++;
3396 }
3397
3398 return 1;
3399 }
3400
3407 public function info($id)
3408 {
3409 $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3410 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3411 $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3412 $sql .= ' WHERE c.rowid = '.((int) $id);
3413
3414 $result = $this->db->query($sql);
3415 if ($result) {
3416 if ($this->db->num_rows($result)) {
3417 $obj = $this->db->fetch_object($result);
3418
3419 $this->id = $obj->rowid;
3420
3421 $this->user_creation_id = $obj->fk_user_author;
3422 $this->user_validation_id = $obj->fk_user_valid;
3423 $this->user_modification_id = $obj->fk_user_modif;
3424 $this->user_approve_id = $obj->fk_user_approve;
3425 $this->user_approve_id2 = $obj->fk_user_approve2;
3426
3427 $this->date_creation = $this->db->jdate($obj->datec);
3428 $this->date_modification = $this->db->jdate($obj->datem);
3429 $this->date_approve = $this->db->jdate($obj->datea);
3430 $this->date_approve2 = $this->db->jdate($obj->datea2);
3431 $this->date_validation = $this->db->jdate($obj->date_validation);
3432 }
3433 $this->db->free($result);
3434 } else {
3435 dol_print_error($this->db);
3436 }
3437 }
3438
3444 public function loadStateBoard()
3445 {
3446 global $conf, $user;
3447
3448 $this->nb = array();
3449 $clause = "WHERE";
3450
3451 $sql = "SELECT count(co.rowid) as nb";
3452 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3453 $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3454 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3455 $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3456 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3457 $clause = "AND";
3458 }
3459 $sql .= " ".$clause." co.entity IN (".getEntity('supplier_order').")";
3460
3461 $resql = $this->db->query($sql);
3462 if ($resql) {
3463 while ($obj = $this->db->fetch_object($resql)) {
3464 $this->nb["supplier_orders"] = $obj->nb;
3465 }
3466 $this->db->free($resql);
3467 return 1;
3468 } else {
3469 dol_print_error($this->db);
3470 $this->error = $this->db->error();
3471 return -1;
3472 }
3473 }
3474
3475 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3483 public function load_board($user, $mode = 'opened')
3484 {
3485 // phpcs:enable
3486 global $conf, $langs;
3487
3488 $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3489 $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3490 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir") && !$user->socid) {
3491 $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3492 }
3493 $sql .= " WHERE c.entity = ".$conf->entity;
3494 if ($mode === 'awaiting') {
3495 $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3496 } else {
3497 $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3498 }
3499 if ($user->socid) {
3500 $sql .= " AND c.fk_soc = ".((int) $user->socid);
3501 }
3502
3503 $resql = $this->db->query($sql);
3504 if ($resql) {
3505 $commandestatic = new CommandeFournisseur($this->db);
3506
3507 $response = new WorkboardResponse();
3508 $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3509 $response->label = $langs->trans("SuppliersOrdersToProcess");
3510 $response->labelShort = $langs->trans("Opened");
3511 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3512 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=late';
3513 $response->img = img_object('', "order");
3514
3515 if ($mode === 'awaiting') {
3516 $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3517 $response->labelShort = $langs->trans("AwaitingReception");
3518 $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3519 $response->url_late = DOL_URL_ROOT.'/fourn/commande/list.php?mainmenu=commercial&leftmenu=orders_suppliers&search_option=recv_late';
3520 }
3521
3522 while ($obj = $this->db->fetch_object($resql)) {
3523 $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3524 $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3525 $commandestatic->statut = $obj->fk_statut; // deprecated
3526 $commandestatic->status = $obj->fk_statut;
3527
3528 $response->nbtodo++;
3529 $response->total += $obj->total_ht;
3530
3531 if ($commandestatic->hasDelay()) {
3532 $response->nbtodolate++;
3533 }
3534 }
3535
3536 return $response;
3537 } else {
3538 $this->error = $this->db->error();
3539 return -1;
3540 }
3541 }
3542
3549 public function getInputMethod()
3550 {
3551 global $langs;
3552
3553 if ($this->methode_commande_id > 0) {
3554 $sql = "SELECT rowid, code, libelle as label";
3555 $sql .= " FROM ".$this->db->prefix().'c_input_method';
3556 $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3557
3558 $resql = $this->db->query($sql);
3559 if ($resql) {
3560 if ($this->db->num_rows($resql)) {
3561 $obj = $this->db->fetch_object($resql);
3562
3563 $string = $langs->trans($obj->code);
3564 if ($string == $obj->code) {
3565 $string = $obj->label != '-' ? $obj->label : '';
3566 }
3567 return $string;
3568 }
3569 } else {
3570 dol_print_error($this->db);
3571 }
3572 }
3573
3574 return '';
3575 }
3576
3588 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3589 {
3590 global $langs;
3591
3592 if (!dol_strlen($modele)) {
3593 $modele = ''; // No doc template/generation by default
3594
3595 if (!empty($this->model_pdf)) {
3596 $modele = $this->model_pdf;
3597 } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3598 $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3599 }
3600 }
3601
3602 if (empty($modele)) {
3603 return 0;
3604 } else {
3605 $langs->load("suppliers");
3606 $outputlangs->load("products");
3607
3608 $modelpath = "core/modules/supplier_order/doc/";
3609 $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3610 return $result;
3611 }
3612 }
3613
3620 public function getMaxDeliveryTimeDay($langs)
3621 {
3622 if (empty($this->lines)) {
3623 return '';
3624 }
3625
3626 $tmpproductfourn = new ProductFournisseur($this->db);
3627
3628 $nb = 0;
3629 foreach ($this->lines as $line) {
3630 if ($line->fk_product > 0) {
3631 // Load delivery_time_days, return id into product_fournisseur_price
3632 $idp = $tmpproductfourn->find_min_price_product_fournisseur($line->fk_product, $line->qty, $this->thirdparty->id);
3633 if ($idp > 0) {
3634 //$tmpproductfourn->fetch_product_fournisseur_price($idp);
3635 if ($tmpproductfourn->delivery_time_days > $nb) {
3636 $nb = $tmpproductfourn->delivery_time_days;
3637 }
3638 }
3639 }
3640 }
3641
3642 if ($nb === 0) {
3643 return '';
3644 } else {
3645 return $nb.' '.$langs->trans('days');
3646 }
3647 }
3648
3654 public function getRights()
3655 {
3656 global $user;
3657
3658 return $user->hasRight("fournisseur", "commande");
3659 }
3660
3661
3670 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3671 {
3672 $tables = array(
3673 'commande_fournisseur'
3674 );
3675
3676 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3677 }
3678
3687 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3688 {
3689 $tables = array(
3690 'commande_fournisseurdet'
3691 );
3692
3693 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3694 }
3695
3703 public function hasDelay()
3704 {
3705 global $conf;
3706
3707 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3708 $now = dol_now();
3709 if (!empty($this->delivery_date)) {
3710 $date_to_test = $this->delivery_date;
3711 return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3712 } else {
3713 //$date_to_test = $this->date_commande;
3714 //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3715 return false;
3716 }
3717 } else {
3718 $now = dol_now();
3719 $date_to_test = $this->date_commande;
3720
3721 return ($this->status > 0 && $this->status < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3722 }
3723 }
3724
3732 public function showDelay()
3733 {
3734 global $conf, $langs;
3735
3736 $langs->load('orders');
3737
3738 $text = '';
3739
3740 if ($this->status == self::STATUS_ORDERSENT || $this->status == self::STATUS_RECEIVED_PARTIALLY) {
3741 if (!empty($this->delivery_date)) {
3742 $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3743 } else {
3744 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3745 }
3746 } else {
3747 $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3748 }
3749 if ($text) {
3750 $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3751 }
3752
3753 return $text;
3754 }
3755
3756
3765 public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3766 {
3767 if (isModEnabled("supplier_order")) {
3768 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3769
3770 $qtydelivered = array();
3771 $qtywished = array();
3772
3773 $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3774
3775 $filter = array('t.fk_element' => $this->id);
3776 if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3777 $filter['t.status'] = 1; // Restrict to lines with status validated
3778 }
3779
3780 $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3781 if ($ret < 0) {
3782 $this->error = $supplierorderdispatch->error;
3783 $this->errors = $supplierorderdispatch->errors;
3784 return $ret;
3785 } else {
3786 if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3787 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3788 $date_liv = dol_now();
3789
3790 // Build array with quantity deliverd by product
3791 foreach ($supplierorderdispatch->lines as $line) {
3792 if (array_key_exists($line->fk_product, $qtydelivered)) {
3793 $qtydelivered[$line->fk_product] += $line->qty;
3794 } else {
3795 $qtydelivered[$line->fk_product] = $line->qty;
3796 }
3797 }
3798 foreach ($this->lines as $line) {
3799 // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3800 if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $line->product_type > 0) {
3801 continue;
3802 }
3803 if (array_key_exists($line->fk_product, $qtywished)) {
3804 $qtywished[$line->fk_product] += $line->qty;
3805 } else {
3806 $qtywished[$line->fk_product] = $line->qty;
3807 }
3808 }
3809
3810 //Compare array
3811 $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3812 $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3813 $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3814 //var_dump(array_keys($qtydelivered));
3815 //var_dump(array_keys($qtywished));
3816 //var_dump($diff_array);
3817 //var_dump($keysinwishednotindelivered);
3818 //var_dump($keysindeliverednotinwished);
3819 //exit;
3820
3821 if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3822 if ($closeopenorder) {
3823 //$ret=$this->setStatus($user,5);
3824 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3825 if ($ret < 0) {
3826 return -1;
3827 }
3828 return 5;
3829 } else {
3830 //Diff => received partially
3831 //$ret=$this->setStatus($user,4);
3832 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3833 if ($ret < 0) {
3834 return -1;
3835 }
3836 return 4;
3837 }
3838 } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3839 //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3840
3841 $close = 0;
3842
3843 if (count($diff_array) > 0) {
3844 //there are some difference between the two arrays
3845
3846 //scan the array of results
3847 foreach ($diff_array as $key => $value) {
3848 //if the quantity delivered is greater or equal to wish quantity @phan-suppress-next-line PhanTypeInvalidDimOffset
3849 if ($qtydelivered[$key] >= $qtywished[$key]) {
3850 $close++;
3851 }
3852 }
3853 }
3854
3855
3856 if ($close == count($diff_array)) {
3857 //all the products are received equal or more than the wished quantity
3858 if ($closeopenorder) {
3859 $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3860 if ($ret < 0) {
3861 return -1;
3862 }
3863 return 5;
3864 } else {
3865 //Diff => received partially
3866 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3867 if ($ret < 0) {
3868 return -1;
3869 }
3870 return 4;
3871 }
3872 } else {
3873 //all the products are not received
3874 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3875 if ($ret < 0) {
3876 return -1;
3877 }
3878 return 4;
3879 }
3880 } else {
3881 //Diff => received partially
3882 $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3883 if ($ret < 0) {
3884 return -1;
3885 }
3886 return 4;
3887 }
3888 }
3889 return 1;
3890 }
3891 }
3892 return 0;
3893 }
3894
3902 public function loadReceptions($filtre_statut = -1)
3903 {
3904 $this->receptions = array();
3905
3906 dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3907
3908 $sql = 'SELECT cd.rowid, cd.fk_product,';
3909 $sql .= ' sum(cfd.qty) as qty';
3910 $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3911 if ($filtre_statut >= 0) {
3912 $sql .= ' '.$this->db->prefix().'reception as e,';
3913 }
3914 $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3915 $sql .= ' WHERE';
3916 if ($filtre_statut >= 0) {
3917 $sql .= ' cfd.fk_reception = e.rowid AND';
3918 }
3919 $sql .= ' cfd.fk_elementdet = cd.rowid';
3920 $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3921 if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3922 $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3923 }
3924 if ($filtre_statut >= 0) {
3925 $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3926 }
3927 $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3928
3929 $resql = $this->db->query($sql);
3930 if ($resql) {
3931 $num = $this->db->num_rows($resql);
3932 $i = 0;
3933 while ($i < $num) {
3934 $obj = $this->db->fetch_object($resql);
3935 empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3936 $i++;
3937 }
3938 $this->db->free($resql);
3939
3940 return $num;
3941 } else {
3942 $this->error = $this->db->lasterror();
3943 return -1;
3944 }
3945 }
3946
3954 public function getKanbanView($option = '', $arraydata = null)
3955 {
3956 global $langs;
3957
3958 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3959
3960 $return = '<div class="box-flex-item box-flex-grow-zero">';
3961 $return .= '<div class="info-box info-box-sm">';
3962 $return .= '<span class="info-box-icon bg-infobox-action">';
3963 $return .= img_picto('', $this->picto);
3964 $return .= '</span>';
3965 $return .= '<div class="info-box-content">';
3966 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3967 if ($selected >= 0) {
3968 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3969 }
3970 if (!empty($arraydata['thirdparty'])) {
3971 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3972 }
3973 if (!empty($this->date)) {
3974 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3975 }
3976 if (!empty($this->total_ht)) {
3977 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
3978 $return .= ' '.$langs->trans("HT");
3979 $return .= '</span>';
3980 }
3981 if (method_exists($this, 'getLibStatut')) {
3982 $return .= '<br><span class="info-box-status">'.$this->getLibStatut(3).'</span>';
3983 }
3984 if (property_exists($this, 'billed')) {
3985 $return .= ' &nbsp; <span class="opacitymedium">'.$langs->trans("Billed").': </span><span class="info-box-label">'.yn($this->billed).'</span>';
3986 }
3987 $return .= '</div>';
3988 $return .= '</div>';
3989 $return .= '</div>';
3990 return $return;
3991 }
3992}
$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.
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)
Add order line.
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.
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:171
global $mysoc
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
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.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
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...
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
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left 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 PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:464