dolibarr  20.0.0-beta
fournisseur.commande.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2007 Franky Van Liedekerke <franky.van.liedekerke@telenet.be>
6  * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
7  * Copyright (C) 2010-2018 Philippe Grand <philippe.grand@atoo-net.com>
8  * Copyright (C) 2012-2015 Marcos García <marcosgdf@gmail.com>
9  * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
10  * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
11  * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
12  * Copyright (C) 2018-2024 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 MDW <mdeweerd@users.noreply.github.com>
18  *
19  * This program is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <https://www.gnu.org/licenses/>.
31  */
32 
39 require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
40 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
41 require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
42 if (isModEnabled('productbatch')) {
43  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
44 }
45 
46 
51 {
55  public $element = 'order_supplier';
56 
60  public $table_element = 'commande_fournisseur';
61 
65  public $table_element_line = 'commande_fournisseurdet';
66 
70  public $fk_element = 'fk_commande';
71 
75  public $picto = 'supplier_order';
76 
81  public $restrictiononfksoc = 1;
82 
86  protected $table_ref_field = 'ref';
87 
91  public $id;
92 
96  public $ref;
97 
101  public $ref_supplier;
102 
108  public $ref_fourn;
109 
113  public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process running -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
114  // -> 7=Canceled/Never received -> (reopen) 3=Process running
115  // -> 6=Canceled -> (reopen) 2=Approved
116  // -> 9=Refused -> (reopen) 1=Validated
117  // Note: billed or not is on another field "billed"
118 
119  public $billed;
120 
124  public $socid;
125 
129  public $fourn_id;
130 
134  public $date;
135 
139  public $date_creation;
140 
144  public $date_valid;
145 
149  public $date_approve;
150 
155  public $date_approve2;
156 
160  public $date_commande;
161 
162  //For backward compatibility
163  public $remise_percent;
164  public $methode_commande_id;
165  public $methode_commande;
166 
170  public $delivery_date;
171 
175  public $total_ht;
176 
180  public $total_tva;
181 
185  public $total_localtax1;
186 
190  public $total_localtax2;
191 
195  public $total_ttc;
196 
197  public $source;
198 
202  public $fk_project;
203 
207  public $cond_reglement_id;
208 
212  public $cond_reglement_code;
213 
217  public $cond_reglement_label;
218 
222  public $cond_reglement_doc;
223 
227  public $fk_account;
228 
232  public $mode_reglement_id;
233 
237  public $mode_reglement_code;
238 
242  public $mode_reglement;
243 
247  public $user_author_id;
248 
252  public $user_approve_id;
253 
258  public $user_approve_id2;
259 
260  public $refuse_note;
261 
262  public $extraparams = array();
263 
267  public $lines = array();
268 
272  public $line;
273 
274  // Add for supplier_proposal
275  public $origin;
276  public $origin_id;
277  public $linked_objects = array();
278 
282  public $date_lim_reglement;
283  public $receptions = array();
284 
285  // Multicurrency
289  public $fk_multicurrency;
290 
294  public $multicurrency_code;
295 
299  public $multicurrency_tx;
300 
304  public $multicurrency_total_ht;
305 
309  public $multicurrency_total_tva;
310 
314  public $multicurrency_total_ttc;
315 
343  public $fields = array(
344  'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 10),
345  'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
346  'ref_ext' => array('type' => 'varchar(255)', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => 0, 'position' => 35),
347  'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefOrderSupplierShort', 'enabled' => 1, 'visible' => 1, 'position' => 40, 'searchall' => 1),
348  'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 45),
349  'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 710),
350  'date_approve' => array('type' => 'datetime', 'label' => 'DateApprove', 'enabled' => 1, 'visible' => -1, 'position' => 720),
351  'date_approve2' => array('type' => 'datetime', 'label' => 'DateApprove2', 'enabled' => 1, 'visible' => 3, 'position' => 725),
352  'date_commande' => array('type' => 'date', 'label' => 'OrderDateShort', 'enabled' => 1, 'visible' => 1, 'position' => 70),
353  'date_livraison' => array('type' => 'datetime', 'label' => 'DeliveryDate', 'enabled' => 'empty($conf->global->ORDER_DISABLE_DELIVERY_DATE)', 'visible' => 1, 'position' => 74),
354  'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => 3, 'position' => 41),
355  'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => 3, 'notnull' => -1, 'position' => 80),
356  'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => 3, 'position' => 711),
357  'fk_user_approve' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval', 'enabled' => 1, 'visible' => 3, 'position' => 721),
358  'fk_user_approve2' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserApproval2', 'enabled' => 1, 'visible' => 3, 'position' => 726),
359  'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 100),
360  'billed' => array('type' => 'smallint(6)', 'label' => 'Billed', 'enabled' => 1, 'visible' => 1, 'position' => 710),
361  'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
362  'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => 1, 'position' => 135, 'isameasure' => 1),
363  'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => 3, 'position' => 140, 'isameasure' => 1),
364  'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => 3, 'position' => 145, 'isameasure' => 1),
365  'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => -1, 'position' => 150, 'isameasure' => 1),
366  'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
367  'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 760, 'searchall' => 1),
368  'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPDF', 'enabled' => 1, 'visible' => 0, 'position' => 165),
369  'fk_input_method' => array('type' => 'integer', 'label' => 'OrderMode', 'enabled' => 1, 'visible' => 3, 'position' => 170),
370  'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => 3, 'position' => 175),
371  'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => 3, 'position' => 180),
372  'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => 0, 'position' => 190),
373  'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => 3, 'position' => 200),
374  'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => 3, 'position' => 205),
375  'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => 3, 'position' => 210),
376  'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 1, 'visible' => 0, 'position' => 215),
377  'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 220),
378  'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 225),
379  'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 230),
380  'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 235),
381  'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
382  'date_creation' => array('type' => 'datetime', 'label' => 'Date creation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
383  'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => 1, 'notnull' => 1, 'position' => 50),
384  'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 1000, 'index' => 1),
385  'tms' => array('type' => 'datetime', 'label' => "DateModificationShort", 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 501),
386  'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => 0, 'position' => 700),
387  'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'position' => 701),
388  'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => 0, 'position' => 900),
389  );
390 
391 
395  const STATUS_DRAFT = 0;
396 
400  const STATUS_VALIDATED = 1;
401 
405  const STATUS_ACCEPTED = 2;
406 
410  const STATUS_ORDERSENT = 3;
411 
416 
421 
425  const STATUS_CANCELED = 6;
426 
431 
435  const STATUS_REFUSED = 9;
436 
437 
442 
448  public function __construct($db)
449  {
450  $this->db = $db;
451 
452  $this->ismultientitymanaged = 1;
453  }
454 
455 
463  public function fetch($id, $ref = '')
464  {
465  // Check parameters
466  if (empty($id) && empty($ref)) {
467  return -1;
468  }
469 
470  $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,";
471  $sql .= " c.localtax1, c.localtax2, ";
472  $sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
473  $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,";
474  $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,";
475  $sql .= " c.fk_account,";
476  $sql .= " c.note_private, c.note_public, c.model_pdf, c.extraparams, c.billed,";
477  $sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
478  $sql .= " cm.libelle as methode_commande,";
479  $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
480  $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
481  $sql .= ', c.fk_incoterms, c.location_incoterms';
482  $sql .= ', i.libelle as label_incoterms';
483  $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
484  $sql .= " LEFT JOIN ".$this->db->prefix()."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
485  $sql .= " LEFT JOIN ".$this->db->prefix()."c_paiement as p ON c.fk_mode_reglement = p.id";
486  $sql .= " LEFT JOIN ".$this->db->prefix()."c_input_method as cm ON cm.rowid = c.fk_input_method";
487  $sql .= ' LEFT JOIN '.$this->db->prefix().'c_incoterms as i ON c.fk_incoterms = i.rowid';
488 
489  if (empty($id)) {
490  $sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
491  } else {
492  $sql .= " WHERE c.rowid=".((int) $id);
493  }
494 
495  if ($ref) {
496  $sql .= " AND c.ref='".$this->db->escape($ref)."'";
497  }
498 
499  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
500  $resql = $this->db->query($sql);
501  if ($resql) {
502  $obj = $this->db->fetch_object($resql);
503  if (!$obj) {
504  $this->error = 'Bill with id '.$id.' not found';
505  dol_syslog(get_class($this).'::fetch '.$this->error);
506  return 0;
507  }
508 
509  $this->id = $obj->rowid;
510  $this->entity = $obj->entity;
511 
512  $this->ref = $obj->ref;
513  $this->ref_supplier = $obj->ref_supplier;
514  $this->socid = $obj->fk_soc;
515  $this->fourn_id = $obj->fk_soc;
516  $this->statut = $obj->status; // deprecated
517  $this->status = $obj->status;
518  $this->billed = $obj->billed;
519  $this->user_author_id = $obj->user_author_id;
520  $this->user_validation_id = $obj->user_validation_id;
521  $this->user_approve_id = $obj->user_approve_id;
522  $this->user_approve_id2 = $obj->user_approve_id2;
523  $this->total_ht = $obj->total_ht;
524  $this->total_tva = $obj->total_tva;
525  $this->total_localtax1 = $obj->localtax1;
526  $this->total_localtax2 = $obj->localtax2;
527  $this->total_ttc = $obj->total_ttc;
528  $this->date_creation = $this->db->jdate($obj->date_creation);
529  $this->date_valid = $this->db->jdate($obj->date_valid);
530  $this->date_approve = $this->db->jdate($obj->date_approve);
531  $this->date_approve2 = $this->db->jdate($obj->date_approve2);
532  $this->date_commande = $this->db->jdate($obj->date_commande); // date we make the order to supplier
533  if (isset($obj->date_commande)) {
534  $this->date = $this->date_commande;
535  } else {
536  $this->date = $this->date_creation;
537  }
538  $this->delivery_date = $this->db->jdate($obj->delivery_date);
539  $this->remise_percent = $obj->remise_percent;
540  $this->methode_commande_id = $obj->fk_input_method;
541  $this->methode_commande = $obj->methode_commande;
542 
543  $this->source = $obj->source;
544  $this->fk_project = $obj->fk_project;
545  $this->cond_reglement_id = $obj->fk_cond_reglement;
546  $this->cond_reglement_code = $obj->cond_reglement_code;
547  $this->cond_reglement = $obj->cond_reglement_label; // deprecated
548  $this->cond_reglement_label = $obj->cond_reglement_label;
549  $this->cond_reglement_doc = $obj->cond_reglement_doc;
550  $this->fk_account = $obj->fk_account;
551  $this->mode_reglement_id = $obj->fk_mode_reglement;
552  $this->mode_reglement_code = $obj->mode_reglement_code;
553  $this->mode_reglement = $obj->mode_reglement_libelle;
554  $this->note = $obj->note_private; // deprecated
555  $this->note_private = $obj->note_private;
556  $this->note_public = $obj->note_public;
557  $this->model_pdf = $obj->model_pdf;
558 
559  //Incoterms
560  $this->fk_incoterms = $obj->fk_incoterms;
561  $this->location_incoterms = $obj->location_incoterms;
562  $this->label_incoterms = $obj->label_incoterms;
563 
564  // Multicurrency
565  $this->fk_multicurrency = $obj->fk_multicurrency;
566  $this->multicurrency_code = $obj->multicurrency_code;
567  $this->multicurrency_tx = $obj->multicurrency_tx;
568  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
569  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
570  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
571 
572  $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
573 
574  $this->db->free($resql);
575 
576  // Retrieve all extrafield
577  // fetch optionals attributes and labels
578  $this->fetch_optionals();
579 
580  // Lines
581  $result = $this->fetch_lines();
582 
583  if ($result < 0) {
584  return -1;
585  } else {
586  return 1;
587  }
588  } else {
589  $this->error = $this->db->error()." sql=".$sql;
590  return -1;
591  }
592  }
593 
594  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
601  public function fetch_lines($only_product = 0)
602  {
603  // phpcs:enable
604 
605  $this->lines = array();
606 
607  $sql = "SELECT l.rowid, l.fk_commande, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
608  $sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice,";
609  $sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
610  $sql .= " l.total_ht, l.total_tva, l.total_ttc, l.special_code, l.fk_parent_line, l.rang,";
611  $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,";
612  $sql .= " l.fk_unit,";
613  $sql .= " l.date_start, l.date_end,";
614  $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc';
615  $sql .= " FROM ".$this->db->prefix()."commande_fournisseurdet as l";
616  $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON l.fk_product = p.rowid';
617  $sql .= " WHERE l.fk_commande = ".((int) $this->id);
618  if ($only_product) {
619  $sql .= ' AND p.fk_product_type = 0';
620  }
621  $sql .= " ORDER BY l.rang, l.rowid";
622  //print $sql;
623 
624  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
625 
626  $result = $this->db->query($sql);
627  if ($result) {
628  $num = $this->db->num_rows($result);
629  $i = 0;
630 
631  while ($i < $num) {
632  $objp = $this->db->fetch_object($result);
633 
634  $line = new CommandeFournisseurLigne($this->db);
635 
636  $line->id = $objp->rowid;
637  $line->fk_commande = $objp->fk_commande;
638  $line->desc = $objp->description;
639  $line->description = $objp->description;
640  $line->qty = $objp->qty;
641  $line->tva_tx = $objp->tva_tx;
642  $line->localtax1_tx = $objp->localtax1_tx;
643  $line->localtax2_tx = $objp->localtax2_tx;
644  $line->localtax1_type = $objp->localtax1_type;
645  $line->localtax2_type = $objp->localtax2_type;
646  $line->subprice = $objp->subprice;
647  $line->pu_ht = $objp->subprice;
648  $line->remise_percent = $objp->remise_percent;
649 
650  $line->vat_src_code = $objp->vat_src_code;
651  $line->total_ht = $objp->total_ht;
652  $line->total_tva = $objp->total_tva;
653  $line->total_localtax1 = $objp->total_localtax1;
654  $line->total_localtax2 = $objp->total_localtax2;
655  $line->total_ttc = $objp->total_ttc;
656  $line->product_type = $objp->product_type;
657 
658  $line->fk_product = $objp->fk_product;
659 
660  $line->libelle = $objp->product_label; // deprecated
661  $line->product_label = $objp->product_label;
662  $line->product_desc = $objp->product_desc;
663  $line->product_tobatch = $objp->product_tobatch;
664  $line->product_barcode = $objp->product_barcode;
665 
666  $line->ref = $objp->product_ref; // Ref of product
667  $line->product_ref = $objp->product_ref; // Ref of product
668  $line->ref_fourn = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
669  $line->ref_supplier = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
670 
671  if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
672  // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
673  // Move this into another method and call it when required.
674 
675  // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
676  $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
677  $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
678  $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
679  $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
680  $sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty); // required to be qualified
681  $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")"; // required to be qualified
682  $sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
683  $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
684  $sqlsearchpackage .= " LIMIT 1";
685 
686  $resqlsearchpackage = $this->db->query($sqlsearchpackage);
687  if ($resqlsearchpackage) {
688  $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
689  if ($objsearchpackage) {
690  $line->fk_fournprice = $objsearchpackage->rowid;
691  $line->packaging = $objsearchpackage->packaging;
692  }
693  } else {
694  $this->error = $this->db->lasterror();
695  return -1;
696  }
697  }
698 
699  $line->date_start = $this->db->jdate($objp->date_start);
700  $line->date_end = $this->db->jdate($objp->date_end);
701  $line->fk_unit = $objp->fk_unit;
702 
703  // Multicurrency
704  $line->fk_multicurrency = $objp->fk_multicurrency;
705  $line->multicurrency_code = $objp->multicurrency_code;
706  $line->multicurrency_subprice = $objp->multicurrency_subprice;
707  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
708  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
709  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
710 
711  $line->special_code = $objp->special_code;
712  $line->fk_parent_line = $objp->fk_parent_line;
713 
714  $line->rang = $objp->rang;
715 
716  // Retrieve all extrafield
717  // fetch optionals attributes and labels
718  $line->fetch_optionals();
719 
720  $this->lines[$i] = $line;
721 
722  $i++;
723  }
724  $this->db->free($result);
725 
726  return $num;
727  } else {
728  $this->error = $this->db->error()." sql=".$sql;
729  return -1;
730  }
731  }
732 
741  public function valid($user, $idwarehouse = 0, $notrigger = 0)
742  {
743  global $conf;
744  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
745 
746  $error = 0;
747 
748  dol_syslog(get_class($this)."::valid");
749  $result = 0;
750  if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")))
751  || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight("fournisseur", "supplier_order_advance", "validate"))) {
752  $this->db->begin();
753 
754  // Definition of supplier order numbering model name
755  $soc = new Societe($this->db);
756  $soc->fetch($this->fourn_id);
757 
758  // Check if object has a temporary ref
759  if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
760  $num = $this->getNextNumRef($soc);
761  } else {
762  $num = $this->ref;
763  }
764  $this->newref = dol_sanitizeFileName($num);
765 
766  $sql = 'UPDATE '.$this->db->prefix()."commande_fournisseur";
767  $sql .= " SET ref='".$this->db->escape($num)."',";
768  $sql .= " fk_statut = ".((int) self::STATUS_VALIDATED).",";
769  $sql .= " date_valid='".$this->db->idate(dol_now())."',";
770  $sql .= " fk_user_valid = ".((int) $user->id);
771  $sql .= " WHERE rowid = ".((int) $this->id);
772  $sql .= " AND fk_statut = ".((int) self::STATUS_DRAFT);
773 
774  $resql = $this->db->query($sql);
775  if (!$resql) {
776  dol_print_error($this->db);
777  $error++;
778  }
779 
780  if (!$error && !$notrigger) {
781  // Call trigger
782  $result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
783  if ($result < 0) {
784  $error++;
785  }
786  // End call triggers
787  }
788 
789  if (!$error) {
790  $this->oldref = $this->ref;
791 
792  // Rename directory if dir was a temporary ref
793  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
794  // Now we rename also files into index
795  $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)."'";
796  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
797  $resql = $this->db->query($sql);
798  if (!$resql) {
799  $error++;
800  $this->error = $this->db->lasterror();
801  }
802  $sql = 'UPDATE '.$this->db->prefix()."ecm_files set filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
803  $sql .= " WHERE filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
804  $resql = $this->db->query($sql);
805  if (!$resql) {
806  $error++;
807  $this->error = $this->db->lasterror();
808  }
809 
810  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
811  $oldref = dol_sanitizeFileName($this->ref);
812  $newref = dol_sanitizeFileName($num);
813  $dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
814  $dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
815  if (!$error && file_exists($dirsource)) {
816  dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
817 
818  if (@rename($dirsource, $dirdest)) {
819  dol_syslog("Rename ok");
820  // Rename docs starting with $oldref with $newref
821  $listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
822  foreach ($listoffiles as $fileentry) {
823  $dirsource = $fileentry['name'];
824  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
825  $dirsource = $fileentry['path'].'/'.$dirsource;
826  $dirdest = $fileentry['path'].'/'.$dirdest;
827  @rename($dirsource, $dirdest);
828  }
829  }
830  }
831  }
832  }
833 
834  if (!$error) {
835  $result = 1;
837  $this->statut = self::STATUS_VALIDATED; // deprecated
838  $this->ref = $num;
839  }
840 
841  if (!$error) {
842  $this->db->commit();
843  return 1;
844  } else {
845  $this->db->rollback();
846  return -1;
847  }
848  } else {
849  $this->error = 'NotAuthorized';
850  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
851  return -1;
852  }
853  }
854 
861  public function getLibStatut($mode = 0)
862  {
863  return $this->LibStatut($this->statut, $mode, $this->billed);
864  }
865 
866  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
875  public function LibStatut($status, $mode = 0, $billed = 0)
876  {
877  // phpcs:enable
878  global $langs, $hookmanager;
879 
880  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
881  $langs->load('orders');
882 
883  $this->labelStatus[0] = 'StatusSupplierOrderDraft';
884  $this->labelStatus[1] = 'StatusSupplierOrderValidated';
885  $this->labelStatus[2] = 'StatusSupplierOrderApproved';
886  if (!getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
887  $this->labelStatus[3] = 'StatusSupplierOrderOnProcess';
888  } else {
889  $this->labelStatus[3] = 'StatusSupplierOrderOnProcessWithValidation';
890  }
891  $this->labelStatus[4] = 'StatusSupplierOrderReceivedPartially';
892  $this->labelStatus[5] = 'StatusSupplierOrderReceivedAll';
893  $this->labelStatus[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
894  $this->labelStatus[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
895  $this->labelStatus[9] = 'StatusSupplierOrderRefused';
896 
897  // List of language codes for status
898  $this->labelStatusShort[0] = 'StatusSupplierOrderDraftShort';
899  $this->labelStatusShort[1] = 'StatusSupplierOrderValidatedShort';
900  $this->labelStatusShort[2] = 'StatusSupplierOrderApprovedShort';
901  $this->labelStatusShort[3] = 'StatusSupplierOrderOnProcessShort';
902  $this->labelStatusShort[4] = 'StatusSupplierOrderReceivedPartiallyShort';
903  $this->labelStatusShort[5] = 'StatusSupplierOrderReceivedAllShort';
904  $this->labelStatusShort[6] = 'StatusSupplierOrderCanceledShort';
905  $this->labelStatusShort[7] = 'StatusSupplierOrderCanceledShort';
906  $this->labelStatusShort[9] = 'StatusSupplierOrderRefusedShort';
907  }
908 
909  $statustrans = array(
910  0 => 'status0',
911  1 => 'status1b',
912  2 => 'status1',
913  3 => 'status4',
914  4 => 'status4b',
915  5 => 'status6',
916  6 => 'status9',
917  7 => 'status9',
918  9 => 'status9',
919  );
920 
921  $statusClass = 'status0';
922  if (!empty($statustrans[$status])) {
923  $statusClass = $statustrans[$status];
924  }
925 
926  $billedtext = '';
927  if ($billed) {
928  $billedtext = ' - '.$langs->trans("Billed");
929  }
930  if ($status == 5 && $billed) {
931  $statusClass = 'status6';
932  }
933 
934  $statusLong = $langs->transnoentitiesnoconv($this->labelStatus[$status]).$billedtext;
935  $statusShort = $langs->transnoentitiesnoconv($this->labelStatusShort[$status]);
936 
937  $parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
938  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
939  if ($reshook > 0) {
940  return $hookmanager->resPrint;
941  }
942 
943  return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
944  }
945 
953  public function getTooltipContentArray($params)
954  {
955  global $conf, $langs, $user;
956 
957  $langs->loadLangs(['bills', 'orders']);
958 
959  $datas = [];
960  $nofetch = !empty($params['nofetch']);
961 
962  if ($user->hasRight("fournisseur", "commande", "read")) {
963  $datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
964  if (isset($this->statut)) {
965  $datas['picto'] .= ' '.$this->getLibStatut(5);
966  }
967  if (!empty($this->ref)) {
968  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
969  }
970  if (!empty($this->ref_supplier)) {
971  $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
972  }
973  if (!$nofetch) {
974  $langs->load('companies');
975  if (empty($this->thirdparty)) {
976  $this->fetch_thirdparty();
977  }
978  $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
979  }
980  if (!empty($this->total_ht)) {
981  $datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
982  }
983  if (!empty($this->total_tva)) {
984  $datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
985  }
986  if (!empty($this->total_ttc)) {
987  $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
988  }
989  if (!empty($this->date)) {
990  $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
991  }
992  if (!empty($this->delivery_date)) {
993  $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
994  }
995  }
996  return $datas;
997  }
998 
1009  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
1010  {
1011  global $langs, $user, $hookmanager;
1012 
1013  $result = '';
1014  $params = [
1015  'id' => $this->id,
1016  'objecttype' => $this->element,
1017  'option' => $option,
1018  'nofetch' => 1
1019  ];
1020  $classfortooltip = 'classfortooltip';
1021  $dataparams = '';
1022  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1023  $classfortooltip = 'classforajaxtooltip';
1024  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
1025  $label = '';
1026  } else {
1027  $label = implode($this->getTooltipContentArray($params));
1028  }
1029 
1030  $url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
1031 
1032  if ($option !== 'nolink') {
1033  // Add param to save lastsearch_values or not
1034  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1035  if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1036  $add_save_lastsearch_values = 1;
1037  }
1038  if ($add_save_lastsearch_values) {
1039  $url .= '&save_lastsearch_values=1';
1040  }
1041  }
1042 
1043  $linkclose = '';
1044  if (empty($notooltip)) {
1045  if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1046  $label = $langs->trans("ShowOrder");
1047  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1048  }
1049  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
1050  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
1051  }
1052 
1053  $linkstart = '<a href="'.$url.'"';
1054  $linkstart .= $linkclose.'>';
1055  $linkend = '</a>';
1056 
1057  $result .= $linkstart;
1058  if ($withpicto) {
1059  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1060  }
1061  if ($withpicto != 2) {
1062  $result .= $this->ref;
1063  }
1064  $result .= $linkend;
1065 
1066  if ($addlinktonotes) {
1067  $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1068  if ($txttoshow) {
1069  $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1070  $result .= ' <span class="note inline-block">';
1071  $result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1072  $result .= img_picto('', 'note');
1073  $result .= '</a>';
1074  //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1075  //$result.='</a>';
1076  $result .= '</span>';
1077  }
1078  }
1079 
1080  global $action;
1081  $hookmanager->initHooks(array($this->element . 'dao'));
1082  $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1083  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1084  if ($reshook > 0) {
1085  $result = $hookmanager->resPrint;
1086  } else {
1087  $result .= $hookmanager->resPrint;
1088  }
1089  return $result;
1090  }
1091 
1092 
1100  public function getNextNumRef($soc)
1101  {
1102  global $langs, $conf;
1103  $langs->load("orders");
1104 
1105  if (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER')) {
1106  $mybool = false;
1107 
1108  $file = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER').'.php';
1109  $classname = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_NUMBER');
1110 
1111  // Include file with class
1112  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1113 
1114  foreach ($dirmodels as $reldir) {
1115  $dir = dol_buildpath($reldir."core/modules/supplier_order/");
1116 
1117  // Load file with numbering class (if found)
1118  $mybool = ((bool) @include_once $dir.$file) || $mybool;
1119  }
1120 
1121  if ($mybool === false) {
1122  dol_print_error(null, "Failed to include file ".$file);
1123  return '';
1124  }
1125 
1126  $obj = new $classname();
1127  $numref = $obj->getNextValue($soc, $this);
1128 
1129  if ($numref != "") {
1130  return $numref;
1131  } else {
1132  $this->error = $obj->error;
1133  return -1;
1134  }
1135  } else {
1136  $this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1137  return -2;
1138  }
1139  }
1146  public function classifyBilled(User $user)
1147  {
1148  $error = 0;
1149 
1150  if ($this->billed) {
1151  return 0;
1152  }
1153 
1154  $this->db->begin();
1155 
1156  $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur SET billed = 1';
1157  $sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1158 
1159  if ($this->db->query($sql)) {
1160  if (!$error) {
1161  // Call trigger
1162  $result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1163  if ($result < 0) {
1164  $error++;
1165  }
1166  // End call triggers
1167  }
1168 
1169  if (!$error) {
1170  $this->billed = 1;
1171 
1172  $this->db->commit();
1173  return 1;
1174  } else {
1175  $this->db->rollback();
1176  return -1;
1177  }
1178  } else {
1179  dol_print_error($this->db);
1180 
1181  $this->db->rollback();
1182  return -1;
1183  }
1184  }
1185 
1194  public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1195  {
1196  global $langs, $conf;
1197  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1198 
1199  $error = 0;
1200 
1201  dol_syslog(get_class($this)."::approve");
1202 
1203  if ($user->hasRight("fournisseur", "commande", "approuver")) {
1204  $now = dol_now();
1205 
1206  $this->db->begin();
1207 
1208  // Definition of order numbering model name
1209  $soc = new Societe($this->db);
1210  $soc->fetch($this->fourn_id);
1211 
1212  // Check if object has a temporary ref
1213  if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1214  $num = $this->getNextNumRef($soc);
1215  } else {
1216  $num = $this->ref;
1217  }
1218  $this->newref = dol_sanitizeFileName($num);
1219 
1220  // Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1221  $movetoapprovestatus = true;
1222  $comment = '';
1223 
1224  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1225  $sql .= " SET ref='".$this->db->escape($num)."',";
1226  if (empty($secondlevel)) { // standard or first level approval
1227  $sql .= " date_approve='".$this->db->idate($now)."',";
1228  $sql .= " fk_user_approve = ".$user->id;
1229  if (getDolGlobalString('SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED') && $this->total_ht >= $conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) {
1230  if (empty($this->user_approve_id2)) {
1231  $movetoapprovestatus = false; // second level approval not done
1232  $comment = ' (first level)';
1233  }
1234  }
1235  } else { // request a second level approval
1236  $sql .= " date_approve2='".$this->db->idate($now)."',";
1237  $sql .= " fk_user_approve2 = ".((int) $user->id);
1238  if (empty($this->user_approve_id)) {
1239  $movetoapprovestatus = false; // first level approval not done
1240  }
1241  $comment = ' (second level)';
1242  }
1243  // If double approval is required and first approval, we keep status to 1 = validated
1244  if ($movetoapprovestatus) {
1245  $sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1246  } else {
1247  $sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1248  }
1249  $sql .= " WHERE rowid = ".((int) $this->id);
1250  $sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1251 
1252  if ($this->db->query($sql)) {
1253  if (getDolGlobalString('SUPPLIER_ORDER_AUTOADD_USER_CONTACT')) {
1254  $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1255  if ($result < 0 && $result != -2) { // -2 means already exists
1256  $error++;
1257  }
1258  }
1259 
1260  // If stock is incremented on validate order, we must increment it
1261  if (!$error && $movetoapprovestatus && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER')) {
1262  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1263  $langs->load("agenda");
1264 
1265  $cpt = count($this->lines);
1266  for ($i = 0; $i < $cpt; $i++) {
1267  // Product with reference
1268  if ($this->lines[$i]->fk_product > 0) {
1269  $this->line = $this->lines[$i];
1270  $mouvP = new MouvementStock($this->db);
1271  $mouvP->origin = &$this;
1272  $mouvP->setOrigin($this->element, $this->id);
1273  // We decrement stock of product (and sub-products)
1274  $up_ht_disc = $this->lines[$i]->subprice;
1275  if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1276  $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1277  }
1278  $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1279  if ($result < 0) {
1280  $error++;
1281  }
1282  unset($this->line);
1283  }
1284  }
1285  }
1286 
1287  if (!$error) {
1288  // Call trigger
1289  $result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1290  if ($result < 0) {
1291  $error++;
1292  }
1293  // End call triggers
1294  }
1295 
1296  if (!$error) {
1297  $this->ref = $this->newref;
1298 
1299  if ($movetoapprovestatus) {
1300  $this->statut = self::STATUS_ACCEPTED;
1301  } else {
1302  $this->statut = self::STATUS_VALIDATED;
1303  }
1304  if (empty($secondlevel)) { // standard or first level approval
1305  $this->date_approve = $now;
1306  $this->user_approve_id = $user->id;
1307  } else { // request a second level approval
1308  $this->date_approve2 = $now;
1309  $this->user_approve_id2 = $user->id;
1310  }
1311 
1312  $this->db->commit();
1313  return 1;
1314  } else {
1315  $this->db->rollback();
1316  return -1;
1317  }
1318  } else {
1319  $this->db->rollback();
1320  $this->error = $this->db->lasterror();
1321  return -1;
1322  }
1323  } else {
1324  dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1325  }
1326  return -1;
1327  }
1328 
1335  public function refuse($user)
1336  {
1337  global $conf, $langs;
1338 
1339  $error = 0;
1340 
1341  dol_syslog(get_class($this)."::refuse");
1342  $result = 0;
1343  if ($user->hasRight("fournisseur", "commande", "approuver")) {
1344  $this->db->begin();
1345 
1346  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1347  $sql .= " WHERE rowid = ".((int) $this->id);
1348 
1349  if ($this->db->query($sql)) {
1350  $result = 0;
1351 
1352  if ($error == 0) {
1353  // Call trigger
1354  $result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1355  if ($result < 0) {
1356  $error++;
1357  $this->db->rollback();
1358  } else {
1359  $this->db->commit();
1360  }
1361  // End call triggers
1362  }
1363  } else {
1364  $this->db->rollback();
1365  $this->error = $this->db->lasterror();
1366  dol_syslog(get_class($this)."::refuse Error -1");
1367  $result = -1;
1368  }
1369  } else {
1370  dol_syslog(get_class($this)."::refuse Not Authorized");
1371  }
1372  return $result;
1373  }
1374 
1375  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1384  public function Cancel($user, $idwarehouse = -1)
1385  {
1386  // phpcs:enable
1387  global $langs, $conf;
1388 
1389  $error = 0;
1390 
1391  //dol_syslog("CommandeFournisseur::Cancel");
1392  $result = 0;
1393  if ($user->hasRight("fournisseur", "commande", "commander")) {
1394  $statut = self::STATUS_CANCELED;
1395 
1396  $this->db->begin();
1397 
1398  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur SET fk_statut = ".((int) $statut);
1399  $sql .= " WHERE rowid = ".((int) $this->id);
1400  dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1401  if ($this->db->query($sql)) {
1402  $result = 0;
1403 
1404  // Call trigger
1405  $result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1406  if ($result < 0) {
1407  $error++;
1408  }
1409  // End call triggers
1410 
1411  if ($error == 0) {
1412  $this->db->commit();
1413  return 1;
1414  } else {
1415  $this->db->rollback();
1416  return -1;
1417  }
1418  } else {
1419  $this->db->rollback();
1420  $this->error = $this->db->lasterror();
1421  dol_syslog(get_class($this)."::cancel ".$this->error);
1422  return -1;
1423  }
1424  } else {
1425  dol_syslog(get_class($this)."::cancel Not Authorized");
1426  return -1;
1427  }
1428  }
1429 
1439  public function commande($user, $date, $methode, $comment = '')
1440  {
1441  global $langs;
1442  dol_syslog(get_class($this)."::commande");
1443  $error = 0;
1444  if ($user->hasRight("fournisseur", "commande", "commander")) {
1445  $this->db->begin();
1446 
1447  $newnoteprivate = $this->note_private;
1448  if ($comment) {
1449  $newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1450  }
1451 
1452  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1453  $sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', ";
1454  $sql .= " note_private='".$this->db->escape($newnoteprivate)."'";
1455  $sql .= " WHERE rowid=".((int) $this->id);
1456 
1457  dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1458  if ($this->db->query($sql)) {
1459  $this->statut = self::STATUS_ORDERSENT;
1460  $this->methode_commande_id = $methode;
1461  $this->date_commande = $date;
1462  $this->context = array('comments' => $comment);
1463 
1464  // Call trigger
1465  $result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1466  if ($result < 0) {
1467  $error++;
1468  }
1469  // End call triggers
1470  } else {
1471  $error++;
1472  $this->error = $this->db->lasterror();
1473  $this->errors[] = $this->db->lasterror();
1474  }
1475 
1476  if (!$error) {
1477  $this->db->commit();
1478  } else {
1479  $this->db->rollback();
1480  }
1481  } else {
1482  $error++;
1483  $this->error = $langs->trans('NotAuthorized');
1484  $this->errors[] = $langs->trans('NotAuthorized');
1485  dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1486  }
1487 
1488  return ($error ? -1 : 1);
1489  }
1490 
1498  public function create($user, $notrigger = 0)
1499  {
1500  global $langs, $conf, $hookmanager;
1501 
1502  $this->db->begin();
1503 
1504  $error = 0;
1505  $now = dol_now();
1506 
1507  // set tmp vars
1508  $date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1509  if (empty($date)) {
1510  $date = $now;
1511  }
1512  $delivery_date = $this->delivery_date;
1513 
1514  // Clean parameters
1515  if (empty($this->source)) {
1516  $this->source = 0;
1517  }
1518 
1519  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1520  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1521  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1522  } else {
1523  $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1524  }
1525  if (empty($this->fk_multicurrency)) {
1526  $this->multicurrency_code = $conf->currency;
1527  $this->fk_multicurrency = 0;
1528  $this->multicurrency_tx = 1;
1529  }
1530 
1531  $this->statut = self::STATUS_DRAFT; // deprecated
1532  $this->status = self::STATUS_DRAFT;
1533 
1534  $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseur (";
1535  $sql .= "ref";
1536  $sql .= ", ref_supplier";
1537  $sql .= ", note_private";
1538  $sql .= ", note_public";
1539  $sql .= ", entity";
1540  $sql .= ", fk_soc";
1541  $sql .= ", fk_projet";
1542  $sql .= ", date_creation";
1543  $sql .= ", date_livraison";
1544  $sql .= ", fk_user_author";
1545  $sql .= ", fk_statut";
1546  $sql .= ", source";
1547  $sql .= ", model_pdf";
1548  $sql .= ", fk_mode_reglement";
1549  $sql .= ", fk_cond_reglement";
1550  $sql .= ", fk_account";
1551  $sql .= ", fk_incoterms, location_incoterms";
1552  $sql .= ", fk_multicurrency";
1553  $sql .= ", multicurrency_code";
1554  $sql .= ", multicurrency_tx";
1555  $sql .= ") ";
1556  $sql .= " VALUES (";
1557  $sql .= "'(PROV)'";
1558  $sql .= ", ".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "NULL");
1559  $sql .= ", '".$this->db->escape($this->note_private)."'";
1560  $sql .= ", '".$this->db->escape($this->note_public)."'";
1561  $sql .= ", ".setEntity($this);
1562  $sql .= ", ".((int) $this->socid);
1563  $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1564  $sql .= ", '".$this->db->idate($date)."'";
1565  $sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1566  $sql .= ", ".((int) $user->id);
1567  $sql .= ", ".self::STATUS_DRAFT;
1568  $sql .= ", ".((int) $this->source);
1569  $sql .= ", '".$this->db->escape(getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF'))."'";
1570  $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1571  $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1572  $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
1573  $sql .= ", ".(int) $this->fk_incoterms;
1574  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1575  $sql .= ", ".(int) $this->fk_multicurrency;
1576  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1577  $sql .= ", ".(float) $this->multicurrency_tx;
1578  $sql .= ")";
1579 
1580  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1581  if ($this->db->query($sql)) {
1582  $this->id = $this->db->last_insert_id($this->db->prefix()."commande_fournisseur");
1583 
1584  if ($this->id) {
1585  $num = count($this->lines);
1586 
1587  // insert products details into database
1588  for ($i = 0; $i < $num; $i++) {
1589  $line = $this->lines[$i];
1590  if (!is_object($line)) {
1591  $line = (object) $line;
1592  }
1593 
1594  //$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1595 
1596  // This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1597  $result = $this->addline(
1598  $line->desc,
1599  $line->subprice,
1600  $line->qty,
1601  $line->tva_tx,
1602  $line->localtax1_tx,
1603  $line->localtax2_tx,
1604  $line->fk_product,
1605  0,
1606  $line->ref_fourn, // $line->ref_fourn comes from field ref into table of lines. Value may ba a ref that does not exists anymore, so we first try with value of product
1607  $line->remise_percent,
1608  'HT',
1609  0,
1610  $line->product_type,
1611  $line->info_bits,
1612  false,
1613  $line->date_start,
1614  $line->date_end,
1615  $line->array_options,
1616  $line->fk_unit,
1617  $line->multicurrency_subprice, // pu_ht_devise
1618  $line->origin, // origin
1619  $line->origin_id, // origin_id
1620  $line->rang, // rang
1621  $line->special_code
1622  );
1623  if ($result < 0) {
1624  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functional error
1625  $this->db->rollback();
1626  return -1;
1627  }
1628  }
1629 
1630  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
1631  $sql .= " SET ref='(PROV".$this->id.")'";
1632  $sql .= " WHERE rowid=".((int) $this->id);
1633 
1634  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1635  if ($this->db->query($sql)) {
1636  // Add link with price request and supplier order
1637  if ($this->id) {
1638  $this->ref = "(PROV".$this->id.")";
1639 
1640  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1641  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1642  }
1643 
1644  // Add object linked
1645  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1646  foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1647  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, ...))
1648  foreach ($tmp_origin_id as $origin_id) {
1649  $ret = $this->add_object_linked($origin, $origin_id);
1650  if (!$ret) {
1651  dol_print_error($this->db);
1652  $error++;
1653  }
1654  }
1655  } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1656  $origin_id = $tmp_origin_id;
1657  $ret = $this->add_object_linked($origin, $origin_id);
1658  if (!$ret) {
1659  dol_print_error($this->db);
1660  $error++;
1661  }
1662  }
1663  }
1664  }
1665  }
1666 
1667  if (!$error) {
1668  $result = $this->insertExtraFields();
1669  if ($result < 0) {
1670  $error++;
1671  }
1672  }
1673 
1674  if (!$error && !$notrigger) {
1675  // Call trigger
1676  $result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1677  if ($result < 0) {
1678  $this->db->rollback();
1679 
1680  return -1;
1681  }
1682  // End call triggers
1683  }
1684 
1685  $this->db->commit();
1686  return $this->id;
1687  } else {
1688  $this->error = $this->db->lasterror();
1689  $this->db->rollback();
1690 
1691  return -2;
1692  }
1693  } else {
1694  $this->error = 'Failed to get ID of inserted line';
1695 
1696  return -1;
1697  }
1698  } else {
1699  $this->error = $this->db->lasterror();
1700  $this->db->rollback();
1701 
1702  return -1;
1703  }
1704  }
1705 
1713  public function update(User $user, $notrigger = 0)
1714  {
1715  global $conf;
1716 
1717  $error = 0;
1718 
1719  // Clean parameters
1720  if (isset($this->ref)) {
1721  $this->ref = trim($this->ref);
1722  }
1723  if (isset($this->ref_supplier)) {
1724  $this->ref_supplier = trim($this->ref_supplier);
1725  }
1726  if (isset($this->note_private)) {
1727  $this->note_private = trim($this->note_private);
1728  }
1729  if (isset($this->note_public)) {
1730  $this->note_public = trim($this->note_public);
1731  }
1732  if (isset($this->model_pdf)) {
1733  $this->model_pdf = trim($this->model_pdf);
1734  }
1735  if (isset($this->import_key)) {
1736  $this->import_key = trim($this->import_key);
1737  }
1738 
1739  // Update request
1740  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
1741 
1742  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1743  $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1744  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1745  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1746  $sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1747  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1748  $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1749  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1750  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1751  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1752  $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1753  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1754  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1755  $sql .= " fk_user_valid=".(isset($this->user_validation_id) && $this->user_validation_id > 0 ? $this->user_validation_id : "null").",";
1756  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1757  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1758  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1759  $sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1760  //$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1761  $sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
1762  //$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1763  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1764  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1765  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1766  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1767 
1768  $sql .= " WHERE rowid=".((int) $this->id);
1769 
1770  $this->db->begin();
1771 
1772  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1773  $resql = $this->db->query($sql);
1774  if (!$resql) {
1775  $error++;
1776  $this->errors[] = "Error ".$this->db->lasterror();
1777  }
1778 
1779  if (!$error) {
1780  $result = $this->insertExtraFields();
1781  if ($result < 0) {
1782  $error++;
1783  }
1784  }
1785 
1786  if (!$error && !$notrigger) {
1787  // Call trigger
1788  $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1789  if ($result < 0) {
1790  $error++;
1791  }
1792  // End call triggers
1793  }
1794 
1795  // Commit or rollback
1796  if ($error) {
1797  foreach ($this->errors as $errmsg) {
1798  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1799  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1800  }
1801  $this->db->rollback();
1802  return -1 * $error;
1803  } else {
1804  $this->db->commit();
1805  return 1;
1806  }
1807  }
1808 
1817  public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1818  {
1819  global $conf, $user, $hookmanager;
1820 
1821  $error = 0;
1822 
1823  $this->db->begin();
1824 
1825  // get extrafields so they will be clone
1826  foreach ($this->lines as $line) {
1827  $line->fetch_optionals();
1828  }
1829 
1830  // Load source object
1831  $objFrom = clone $this;
1832 
1833  // Change socid if needed
1834  if (!empty($socid) && $socid != $this->socid) {
1835  $objsoc = new Societe($this->db);
1836 
1837  if ($objsoc->fetch($socid) > 0) {
1838  $this->socid = $objsoc->id;
1839  $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1840  $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1841  $this->fk_project = 0;
1842  $this->fk_delivery_address = 0;
1843  }
1844 
1845  // TODO Change product price if multi-prices
1846  }
1847 
1848  $this->id = 0;
1849  $this->statut = self::STATUS_DRAFT;
1850 
1851  // Clear fields
1852  $this->user_author_id = $user->id;
1853  $this->user_validation_id = 0;
1854 
1855  $this->date = dol_now();
1856  $this->date_creation = 0;
1857  $this->date_validation = 0;
1858  $this->date_commande = 0;
1859  $this->ref_supplier = '';
1860  $this->user_approve_id = 0;
1861  $this->user_approve_id2 = 0;
1862  $this->date_approve = 0;
1863  $this->date_approve2 = 0;
1864 
1865  // Create clone
1866  $this->context['createfromclone'] = 'createfromclone';
1867  $result = $this->create($user, $notrigger);
1868  if ($result < 0) {
1869  $error++;
1870  }
1871 
1872  if (!$error) {
1873  // Hook of thirdparty module
1874  if (is_object($hookmanager)) {
1875  $parameters = array('objFrom' => $objFrom);
1876  $action = '';
1877  $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1878  if ($reshook < 0) {
1879  $this->setErrorsFromObject($hookmanager);
1880  $error++;
1881  }
1882  }
1883  }
1884 
1885  unset($this->context['createfromclone']);
1886 
1887  // End
1888  if (!$error) {
1889  $this->db->commit();
1890  return $this->id;
1891  } else {
1892  $this->db->rollback();
1893  return -1;
1894  }
1895  }
1896 
1926  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)
1927  {
1928  global $langs, $mysoc, $conf;
1929 
1930  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");
1931  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1932 
1933  if ($this->statut == self::STATUS_DRAFT) {
1934  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1935 
1936  // Clean parameters
1937  if (empty($qty)) {
1938  $qty = 0;
1939  }
1940  if (!$info_bits) {
1941  $info_bits = 0;
1942  }
1943  if (empty($txtva)) {
1944  $txtva = 0;
1945  }
1946  if (empty($rang)) {
1947  $rang = 0;
1948  }
1949  if (empty($txlocaltax1)) {
1950  $txlocaltax1 = 0;
1951  }
1952  if (empty($txlocaltax2)) {
1953  $txlocaltax2 = 0;
1954  }
1955  if (empty($remise_percent)) {
1956  $remise_percent = 0;
1957  }
1958 
1959  $remise_percent = price2num($remise_percent);
1960  $qty = price2num($qty);
1961  $pu_ht = price2num($pu_ht);
1962  $pu_ht_devise = price2num($pu_ht_devise);
1963  $pu_ttc = price2num($pu_ttc);
1964  if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
1965  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1966  }
1967  $txlocaltax1 = price2num($txlocaltax1);
1968  $txlocaltax2 = price2num($txlocaltax2);
1969  if ($price_base_type == 'HT') {
1970  $pu = $pu_ht;
1971  } else {
1972  $pu = $pu_ttc;
1973  }
1974  $desc = trim($desc);
1975 
1976  // Check parameters
1977  if ($qty < 0 && !$fk_product) {
1978  $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
1979  return -1;
1980  }
1981  if ($type < 0) {
1982  return -1;
1983  }
1984  if ($date_start && $date_end && $date_start > $date_end) {
1985  $langs->load("errors");
1986  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
1987  return -1;
1988  }
1989 
1990 
1991  $this->db->begin();
1992 
1993  $product_type = $type;
1994  $label = ''; // deprecated
1995 
1996  if ($fk_product > 0) {
1997  if (getDolGlobalString('SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY')) { // Not the common case
1998  // Check quantity is enough
1999  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);
2000  $prod = new ProductFournisseur($this->db);
2001  if ($prod->fetch($fk_product) > 0) {
2002  $product_type = $prod->type;
2003  $label = $prod->label;
2004 
2005  // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
2006  // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2007  $result = $prod->get_buyprice($fk_prod_fourn_price, $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
2008 
2009  // If supplier order created from sales order, we take best supplier price
2010  // If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
2011  if ($result > 0 && ($origin == 'commande' || $pu === '')) {
2012  $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2013  $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2014  // is remise percent not keyed but present for the product we add it
2015  if ($remise_percent == 0 && $prod->remise_percent != 0) {
2016  $remise_percent = $prod->remise_percent;
2017  }
2018  }
2019  if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2020  $langs->load("errors");
2021  $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2022  $this->db->rollback();
2023  dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2024  //$pu = $prod->fourn_pu; // We do not overwrite unit price
2025  //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2026  return -1;
2027  }
2028  if ($result == -1) {
2029  $langs->load("errors");
2030  $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2031  $this->db->rollback();
2032  dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2033  return -1;
2034  }
2035  if ($result < -1) {
2036  $this->error = $prod->error;
2037  $this->db->rollback();
2038  dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2039  return -1;
2040  }
2041  } else {
2042  $this->error = $prod->error;
2043  $this->db->rollback();
2044  return -1;
2045  }
2046  }
2047 
2048  // Predefine quantity according to packaging
2049  if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2050  $prod = new Product($this->db);
2051  $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc));
2052 
2053  if ($qty < $prod->packaging) {
2054  $qty = $prod->packaging;
2055  } else {
2056  if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) {
2057  $coeff = intval($qty / $prod->packaging) + 1;
2058  $qty = $prod->packaging * $coeff;
2059  setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
2060  }
2061  }
2062  }
2063  }
2064 
2065  if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
2066  $pu = 0;
2067  }
2068 
2069  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2070 
2071  // Clean vat code
2072  $reg = array();
2073  $vat_src_code = '';
2074  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2075  $vat_src_code = $reg[1];
2076  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2077  }
2078 
2079  // Calcul du total TTC et de la TVA pour la ligne a partir de
2080  // qty, pu, remise_percent et txtva
2081  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2082  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2083 
2084  $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
2085 
2086  $total_ht = $tabprice[0];
2087  $total_tva = $tabprice[1];
2088  $total_ttc = $tabprice[2];
2089  $total_localtax1 = $tabprice[9];
2090  $total_localtax2 = $tabprice[10];
2091  $pu = $pu_ht = $tabprice[3];
2092 
2093  // MultiCurrency
2094  $multicurrency_total_ht = $tabprice[16];
2095  $multicurrency_total_tva = $tabprice[17];
2096  $multicurrency_total_ttc = $tabprice[18];
2097  $pu_ht_devise = $tabprice[19];
2098 
2099  $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2100  $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2101 
2102  if ($rang < 0) {
2103  $rangmax = $this->line_max();
2104  $rang = $rangmax + 1;
2105  }
2106 
2107  // Insert line
2108  $this->line = new CommandeFournisseurLigne($this->db);
2109 
2110  $this->line->context = $this->context;
2111 
2112  $this->line->fk_commande = $this->id;
2113  $this->line->label = $label;
2114  $this->line->ref_fourn = $ref_supplier;
2115  $this->line->ref_supplier = $ref_supplier;
2116  $this->line->desc = $desc;
2117  $this->line->qty = $qty;
2118  $this->line->tva_tx = $txtva;
2119  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2120  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2121  $this->line->localtax1_type = $localtax1_type;
2122  $this->line->localtax2_type = $localtax2_type;
2123  $this->line->fk_product = $fk_product;
2124  $this->line->product_type = $product_type;
2125  $this->line->remise_percent = $remise_percent;
2126  $this->line->subprice = $pu_ht;
2127  $this->line->rang = $rang;
2128  $this->line->info_bits = $info_bits;
2129 
2130  $this->line->vat_src_code = $vat_src_code;
2131  $this->line->total_ht = $total_ht;
2132  $this->line->total_tva = $total_tva;
2133  $this->line->total_localtax1 = $total_localtax1;
2134  $this->line->total_localtax2 = $total_localtax2;
2135  $this->line->total_ttc = $total_ttc;
2136  $this->line->product_type = $type;
2137  $this->line->special_code = (!empty($special_code) ? $special_code : 0);
2138  $this->line->origin = $origin;
2139  $this->line->origin_id = $origin_id;
2140  $this->line->fk_unit = $fk_unit;
2141 
2142  $this->line->date_start = $date_start;
2143  $this->line->date_end = $date_end;
2144 
2145  // Multicurrency
2146  $this->line->fk_multicurrency = $this->fk_multicurrency;
2147  $this->line->multicurrency_code = $this->multicurrency_code;
2148  $this->line->multicurrency_subprice = $pu_ht_devise;
2149  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
2150  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
2151  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
2152 
2153  $this->line->subprice = $pu_ht;
2154  $this->line->price = $this->line->subprice;
2155 
2156  $this->line->remise_percent = $remise_percent;
2157 
2158  if (is_array($array_options) && count($array_options) > 0) {
2159  $this->line->array_options = $array_options;
2160  }
2161 
2162  $result = $this->line->insert($notrigger);
2163  if ($result > 0) {
2164  // Reorder if child line
2165  if (!empty($this->line->fk_parent_line)) {
2166  $this->line_order(true, 'DESC');
2167  } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
2168  $linecount = count($this->lines);
2169  for ($ii = $rang; $ii <= $linecount; $ii++) {
2170  $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2171  }
2172  }
2173 
2174  // Mise a jour information denormalisees au niveau de la commande meme
2175  $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.
2176  if ($result > 0) {
2177  $this->db->commit();
2178  return $this->line->id;
2179  } else {
2180  $this->db->rollback();
2181  return -1;
2182  }
2183  } else {
2184  $this->error = $this->line->error;
2185  $this->errors = $this->line->errors;
2186  dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2187  $this->db->rollback();
2188  return -1;
2189  }
2190  }
2191  return -1;
2192  }
2193 
2194 
2212  public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0, $fk_reception = 0)
2213  {
2214  global $conf, $langs;
2215 
2216  $error = 0;
2217  require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2218 
2219  // Check parameters (if test are wrong here, there is bug into caller)
2220  if ($entrepot <= 0) {
2221  $this->error = 'ErrorBadValueForParameterWarehouse';
2222  return -1;
2223  }
2224  if ($qty == 0) {
2225  $this->error = 'ErrorBadValueForParameterQty';
2226  return -1;
2227  }
2228 
2229  $dispatchstatus = 1;
2230  if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2231  $dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2232  }
2233 
2234  $now = dol_now();
2235 
2236  $inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2237 
2238  if (($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY || $this->statut == self::STATUS_RECEIVED_COMPLETELY)) {
2239  $this->db->begin();
2240 
2241  $sql = "INSERT INTO ".$this->db->prefix()."receptiondet_batch";
2242  $sql .= " (fk_element, fk_product, qty, fk_entrepot, fk_user, datec, fk_elementdet, status, comment, eatby, sellby, batch, fk_reception) VALUES";
2243  $sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', ";
2244  $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($fk_reception)."'" : "null");
2245  $sql .= ")";
2246 
2247  dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2248  $resql = $this->db->query($sql);
2249  if ($resql) {
2250  if (!$notrigger) {
2251  global $conf, $langs, $user;
2252  // Call trigger
2253  $result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2254  if ($result < 0) {
2255  $error++;
2256  }
2257  // End call triggers
2258  }
2259  } else {
2260  $this->error = $this->db->lasterror();
2261  $error++;
2262  }
2263 
2264  // If module stock is enabled and the stock increase is done on purchase order dispatching
2265  if (!$error && $entrepot > 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER')) {
2266  $mouv = new MouvementStock($this->db);
2267  if ($product > 0) {
2268  // $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2269  $mouv->origin = &$this;
2270  $mouv->setOrigin($this->element, $this->id);
2271 
2272  // Method change if qty < 0
2273  if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN') && $qty < 0) {
2274  $result = $mouv->livraison($user, $product, $entrepot, $qty * (-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2275  } else {
2276  $result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2277  }
2278 
2279  if ($result < 0) {
2280  $this->error = $mouv->error;
2281  $this->errors = $mouv->errors;
2282  dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".implode(',', $this->errors), LOG_ERR);
2283  $error++;
2284  }
2285  }
2286  }
2287 
2288  if ($error == 0) {
2289  $this->db->commit();
2290  return 1;
2291  } else {
2292  $this->db->rollback();
2293  return -1;
2294  }
2295  } else {
2296  $this->error = 'BadStatusForObject';
2297  return -2;
2298  }
2299  }
2300 
2308  public function deleteLine($idline, $notrigger = 0)
2309  {
2310  global $user;
2311 
2312  if ($this->statut == 0) {
2313  $line = new CommandeFournisseurLigne($this->db);
2314 
2315  if ($line->fetch($idline) <= 0) {
2316  return 0;
2317  }
2318 
2319  // check if not yet received
2320  $dispatchedLines = $this->getDispachedLines();
2321  foreach ($dispatchedLines as $dispatchLine) {
2322  if ($dispatchLine['orderlineid'] == $idline) {
2323  $this->error = "LineAlreadyDispatched";
2324  $this->errors[] = $this->error;
2325  return -3;
2326  }
2327  }
2328 
2329  if ($line->delete($user, $notrigger) > 0) {
2330  $this->update_price(1);
2331  return 1;
2332  } else {
2333  $this->setErrorsFromObject($line);
2334  return -1;
2335  }
2336  } else {
2337  return -2;
2338  }
2339  }
2340 
2348  public function delete(User $user, $notrigger = 0)
2349  {
2350  global $langs, $conf;
2351  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2352 
2353  $error = 0;
2354 
2355  $this->db->begin();
2356 
2357  if (empty($notrigger)) {
2358  // Call trigger
2359  $result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2360  if ($result < 0) {
2361  $this->errors[] = 'ErrorWhenRunningTrigger';
2362  dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2363  $this->db->rollback();
2364  return -1;
2365  }
2366  // End call triggers
2367  }
2368 
2369  // Test we can delete
2370  $this->fetchObjectLinked(null, 'order_supplier');
2371  if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2372  foreach ($this->linkedObjects['reception'] as $element) {
2373  if ($element->statut >= 0) {
2374  $this->errors[] = $langs->trans('ReceptionExist');
2375  $error++;
2376  break;
2377  }
2378  }
2379  }
2380 
2381  $main = $this->db->prefix().'commande_fournisseurdet';
2382  $ef = $main."_extrafields";
2383  $sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = ".((int) $this->id).")";
2384  dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2385  if (!$this->db->query($sql)) {
2386  $this->error = $this->db->lasterror();
2387  $this->errors[] = $this->db->lasterror();
2388  $error++;
2389  }
2390 
2391  $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseurdet WHERE fk_commande =".((int) $this->id);
2392  dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2393  if (!$this->db->query($sql)) {
2394  $this->error = $this->db->lasterror();
2395  $this->errors[] = $this->db->lasterror();
2396  $error++;
2397  }
2398 
2399  $sql = "DELETE FROM ".$this->db->prefix()."commande_fournisseur WHERE rowid =".((int) $this->id);
2400  dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2401  if ($resql = $this->db->query($sql)) {
2402  if ($this->db->affected_rows($resql) < 1) {
2403  $this->error = $this->db->lasterror();
2404  $this->errors[] = $this->db->lasterror();
2405  $error++;
2406  }
2407  } else {
2408  $this->error = $this->db->lasterror();
2409  $this->errors[] = $this->db->lasterror();
2410  $error++;
2411  }
2412 
2413  // Remove extrafields
2414  if (!$error) {
2415  $result = $this->deleteExtraFields();
2416  if ($result < 0) {
2417  $this->error = 'FailToDeleteExtraFields';
2418  $this->errors[] = 'FailToDeleteExtraFields';
2419  $error++;
2420  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2421  }
2422  }
2423 
2424  // Delete linked object
2425  $res = $this->deleteObjectLinked();
2426  if ($res < 0) {
2427  $this->error = 'FailToDeleteObjectLinked';
2428  $this->errors[] = 'FailToDeleteObjectLinked';
2429  $error++;
2430  }
2431 
2432  if (!$error) {
2433  // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2434  $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2435  $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2436 
2437  // We remove directory
2438  $ref = dol_sanitizeFileName($this->ref);
2439  if ($conf->fournisseur->commande->dir_output) {
2440  $dir = $conf->fournisseur->commande->dir_output."/".$ref;
2441  $file = $dir."/".$ref.".pdf";
2442  if (file_exists($file)) {
2443  if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2444  $this->error = 'ErrorFailToDeleteFile';
2445  $this->errors[] = 'ErrorFailToDeleteFile';
2446  $error++;
2447  }
2448  }
2449  if (file_exists($dir)) {
2450  $res = @dol_delete_dir_recursive($dir);
2451  if (!$res) {
2452  $this->error = 'ErrorFailToDeleteDir';
2453  $this->errors[] = 'ErrorFailToDeleteDir';
2454  $error++;
2455  }
2456  }
2457  }
2458  }
2459 
2460  if (!$error) {
2461  dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2462  $this->db->commit();
2463  return 1;
2464  } else {
2465  dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2466  $this->db->rollback();
2467  return -$error;
2468  }
2469  }
2470 
2471 
2480  public function getDispachedLines($status = -1)
2481  {
2482  $ret = array();
2483 
2484  // List of already dispatched lines
2485  $sql = "SELECT p.ref, p.label,";
2486  $sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2487  $sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_elementdet";
2488  $sql .= " FROM ".$this->db->prefix()."product as p,";
2489  $sql .= " ".$this->db->prefix()."receptiondet_batch as cfd";
2490  $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e ON cfd.fk_entrepot = e.rowid";
2491  $sql .= " WHERE cfd.fk_element = ".((int) $this->id);
2492  $sql .= " AND cfd.fk_product = p.rowid";
2493  if ($status >= 0) {
2494  $sql .= " AND cfd.status = ".((int) $status);
2495  }
2496  $sql .= " ORDER BY cfd.rowid ASC";
2497 
2498  $resql = $this->db->query($sql);
2499  if ($resql) {
2500  $num = $this->db->num_rows($resql);
2501  $i = 0;
2502 
2503  while ($i < $num) {
2504  $objp = $this->db->fetch_object($resql);
2505  if ($objp) {
2506  $ret[] = array(
2507  'id' => $objp->dispatchedlineid,
2508  'productid' => $objp->fk_product,
2509  'warehouseid' => $objp->warehouse_id,
2510  'qty' => $objp->qty,
2511  'orderlineid' => $objp->fk_elementdet
2512  );
2513  }
2514 
2515  $i++;
2516  }
2517  } else {
2518  dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2519  }
2520 
2521  return $ret;
2522  }
2523 
2524  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2534  public function Livraison($user, $date, $type, $comment)
2535  {
2536  // phpcs:enable
2537  global $conf, $langs;
2538 
2539  $result = 0;
2540  $error = 0;
2541 
2542  dol_syslog(get_class($this)."::Livraison");
2543 
2544  $usercanreceive = 0;
2545  if (!isModEnabled('reception')) {
2546  $usercanreceive = $user->hasRight("fournisseur", "commande", "receptionner");
2547  } else {
2548  $usercanreceive = $user->hasRight("reception", "creer");
2549  }
2550 
2551  if ($usercanreceive) {
2552  // Define the new status
2553  if ($type == 'par') {
2555  } elseif ($type == 'tot') {
2557  } elseif ($type == 'nev') {
2559  } elseif ($type == 'can') {
2561  } else {
2562  $error++;
2563  dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2564  return -2;
2565  }
2566 
2567  // Some checks to accept the record
2568  if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
2569  // If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2570  if (!$error && ($type == 'tot')) {
2571  $dispatchedlinearray = $this->getDispachedLines(0);
2572  if (count($dispatchedlinearray) > 0) {
2573  $result = -1;
2574  $error++;
2575  $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2576  dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2577  }
2578  }
2579  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)
2580  $dispatcheddenied = $this->getDispachedLines(2);
2581  if (count($dispatchedlinearray) > 0) {
2582  $result = -1;
2583  $error++;
2584  $this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2585  dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2586  }
2587  }
2588  }
2589 
2590  // TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2591 
2592  if (empty($error)) {
2593  $this->db->begin();
2594 
2595  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2596  $sql .= " SET fk_statut = ".((int) $statut);
2597  $sql .= " WHERE rowid = ".((int) $this->id);
2598  $sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2599 
2600  dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2601  $resql = $this->db->query($sql);
2602  if ($resql) {
2603  $result = 1;
2604  $old_statut = $this->statut;
2605  $this->statut = $statut;
2606  $this->context['actionmsg2'] = $comment;
2607 
2608  // Call trigger
2609  $result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2610  if ($result_trigger < 0) {
2611  $error++;
2612  }
2613  // End call triggers
2614 
2615  if (empty($error)) {
2616  $this->db->commit();
2617  } else {
2618  $this->statut = $old_statut;
2619  $this->db->rollback();
2620  $this->error = $this->db->lasterror();
2621  $result = -1;
2622  }
2623  } else {
2624  $this->db->rollback();
2625  $this->error = $this->db->lasterror();
2626  $result = -1;
2627  }
2628  }
2629  } else {
2630  $this->error = $langs->trans('NotAuthorized');
2631  $this->errors[] = $langs->trans('NotAuthorized');
2632  dol_syslog(get_class($this)."::Livraison Not Authorized");
2633  $result = -3;
2634  }
2635  return $result;
2636  }
2637 
2638  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2648  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2649  {
2650  // phpcs:enable
2651  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2652  }
2653 
2662  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2663  {
2664  if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2665  $error = 0;
2666 
2667  $this->db->begin();
2668 
2669  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2670  $sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2671  $sql .= " WHERE rowid = ".((int) $this->id);
2672 
2673  dol_syslog(__METHOD__, LOG_DEBUG);
2674  $resql = $this->db->query($sql);
2675  if (!$resql) {
2676  $this->errors[] = $this->db->error();
2677  $error++;
2678  }
2679 
2680  if (!$error) {
2681  $this->oldcopy = clone $this;
2682  $this->delivery_date = $delivery_date;
2683  }
2684 
2685  if (!$notrigger && empty($error)) {
2686  // Call trigger
2687  $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2688  if ($result < 0) {
2689  $error++;
2690  }
2691  // End call triggers
2692  }
2693 
2694  if (!$error) {
2695  $this->db->commit();
2696  return 1;
2697  } else {
2698  foreach ($this->errors as $errmsg) {
2699  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2700  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2701  }
2702  $this->db->rollback();
2703  return -1 * $error;
2704  }
2705  } else {
2706  return -2;
2707  }
2708  }
2709 
2710  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2719  public function set_id_projet($user, $id_projet, $notrigger = 0)
2720  {
2721  // phpcs:enable
2722  if ($user->hasRight("fournisseur", "commande", "creer") || $user->hasRight("supplier_order", "creer")) {
2723  $error = 0;
2724 
2725  $this->db->begin();
2726 
2727  $sql = "UPDATE ".$this->db->prefix()."commande_fournisseur";
2728  $sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2729  $sql .= " WHERE rowid = ".((int) $this->id);
2730 
2731  dol_syslog(__METHOD__, LOG_DEBUG);
2732  $resql = $this->db->query($sql);
2733  if (!$resql) {
2734  $this->errors[] = $this->db->error();
2735  $error++;
2736  }
2737 
2738  if (!$error) {
2739  $this->oldcopy = clone $this;
2740  $this->fk_projet = $id_projet;
2741  $this->fk_project = $id_projet;
2742  }
2743 
2744  if (!$notrigger && empty($error)) {
2745  // Call trigger
2746  $result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2747  if ($result < 0) {
2748  $error++;
2749  }
2750  // End call triggers
2751  }
2752 
2753  if (!$error) {
2754  $this->db->commit();
2755  return 1;
2756  } else {
2757  foreach ($this->errors as $errmsg) {
2758  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2759  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2760  }
2761  $this->db->rollback();
2762  return -1 * $error;
2763  }
2764  } else {
2765  return -2;
2766  }
2767  }
2768 
2777  public function updateFromCommandeClient($user, $idc, $comclientid)
2778  {
2779  $comclient = new Commande($this->db);
2780  $comclient->fetch($comclientid);
2781 
2782  $this->id = $idc;
2783 
2784  $this->lines = array();
2785 
2786  $num = count($comclient->lines);
2787  for ($i = 0; $i < $num; $i++) {
2788  $prod = new Product($this->db);
2789  $label = '';
2790  $ref = '';
2791  if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
2792  $label = $prod->label;
2793  $ref = $prod->ref;
2794  }
2795 
2796  $sql = "INSERT INTO ".$this->db->prefix()."commande_fournisseurdet";
2797  $sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
2798  $sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
2799  $sql .= ",".$comclient->lines[$i]->fk_product.", ".price2num($comclient->lines[$i]->price, 'MU');
2800  $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);
2801  $sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
2802  if ($this->db->query($sql)) {
2803  $this->update_price(1);
2804  }
2805  }
2806 
2807  return 1;
2808  }
2809 
2817  public function setStatus($user, $status)
2818  {
2819  global $conf, $langs;
2820  $error = 0;
2821 
2822  $this->db->begin();
2823 
2824  $sql = 'UPDATE '.$this->db->prefix().'commande_fournisseur';
2825  $sql .= " SET fk_statut = ".$status;
2826  $sql .= " WHERE rowid = ".((int) $this->id);
2827 
2828  dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
2829  $resql = $this->db->query($sql);
2830  if ($resql) {
2831  // Trigger names for each status
2832  $triggerName = array();
2833  $triggerName[0] = 'DRAFT';
2834  $triggerName[1] = 'VALIDATED';
2835  $triggerName[2] = 'APPROVED';
2836  $triggerName[3] = 'ORDERED'; // Ordered
2837  $triggerName[4] = 'RECEIVED_PARTIALLY';
2838  $triggerName[5] = 'RECEIVED_COMPLETELY';
2839  $triggerName[6] = 'CANCELED';
2840  $triggerName[7] = 'CANCELED';
2841  $triggerName[9] = 'REFUSED';
2842 
2843  // Call trigger
2844  $result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
2845  if ($result < 0) {
2846  $error++;
2847  }
2848  // End call triggers
2849  } else {
2850  $error++;
2851  $this->error = $this->db->lasterror();
2852  dol_syslog(get_class($this)."::setStatus ".$this->error);
2853  }
2854 
2855  if (!$error) {
2856  $this->statut = $status;
2857  $this->db->commit();
2858  return 1;
2859  } else {
2860  $this->db->rollback();
2861  return -1;
2862  }
2863  }
2864 
2888  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 = '')
2889  {
2890  global $mysoc, $conf, $langs;
2891  dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
2892  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2893 
2894  $error = 0;
2895 
2896  if ($this->statut == self::STATUS_DRAFT) {
2897  // Clean parameters
2898  if (empty($qty)) {
2899  $qty = 0;
2900  }
2901  if (empty($info_bits)) {
2902  $info_bits = 0;
2903  }
2904  if (empty($txtva)) {
2905  $txtva = 0;
2906  }
2907  if (empty($txlocaltax1)) {
2908  $txlocaltax1 = 0;
2909  }
2910  if (empty($txlocaltax2)) {
2911  $txlocaltax2 = 0;
2912  }
2913  if (empty($remise_percent)) {
2914  $remise_percent = 0;
2915  }
2916 
2917  $remise_percent = (float) price2num($remise_percent);
2918  $qty = price2num($qty);
2919  if (!$qty) {
2920  $qty = 1;
2921  }
2922  $pu = price2num($pu);
2923  $pu_ht_devise = price2num($pu_ht_devise);
2924  if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2925  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2926  }
2927  $txlocaltax1 = (float) price2num($txlocaltax1);
2928  $txlocaltax2 = (float) price2num($txlocaltax2);
2929 
2930  // Check parameters
2931  if ($type < 0) {
2932  return -1;
2933  }
2934  if ($date_start && $date_end && $date_start > $date_end) {
2935  $langs->load("errors");
2936  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2937  return -1;
2938  }
2939 
2940  $this->db->begin();
2941 
2942  // Calcul du total TTC et de la TVA pour la ligne a partir de
2943  // qty, pu, remise_percent et txtva
2944  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2945  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2946 
2947  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2948 
2949  // Clean vat code
2950  $reg = array();
2951  $vat_src_code = '';
2952  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2953  $vat_src_code = $reg[1];
2954  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2955  }
2956 
2957  $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
2958  $total_ht = $tabprice[0];
2959  $total_tva = $tabprice[1];
2960  $total_ttc = $tabprice[2];
2961  $total_localtax1 = $tabprice[9];
2962  $total_localtax2 = $tabprice[10];
2963  $pu_ht = $tabprice[3];
2964  $pu_tva = $tabprice[4];
2965  $pu_ttc = $tabprice[5];
2966 
2967  // MultiCurrency
2968  $multicurrency_total_ht = $tabprice[16];
2969  $multicurrency_total_tva = $tabprice[17];
2970  $multicurrency_total_ttc = $tabprice[18];
2971  $pu_ht_devise = $tabprice[19];
2972 
2973  $localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2974  $localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2975 
2976  //Fetch current line from the database and then clone the object and set it in $oldline property
2977  $this->line = new CommandeFournisseurLigne($this->db);
2978  $this->line->fetch($rowid);
2979 
2980  $oldline = clone $this->line;
2981  $this->line->oldline = $oldline;
2982 
2983  $this->line->context = $this->context;
2984 
2985  $this->line->fk_commande = $this->id;
2986  //$this->line->label=$label;
2987  $this->line->desc = $desc;
2988 
2989  // redefine quantity according to packaging
2990  if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
2991  if ($qty < $this->line->packaging) {
2992  $qty = $this->line->packaging;
2993  } else {
2994  if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) {
2995  $coeff = intval($qty / $this->line->packaging) + 1;
2996  $qty = $this->line->packaging * $coeff;
2997  setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
2998  }
2999  }
3000  }
3001 
3002  $this->line->qty = $qty;
3003  $this->line->ref_supplier = $ref_supplier;
3004 
3005  $this->line->vat_src_code = $vat_src_code;
3006  $this->line->tva_tx = $txtva;
3007  $this->line->localtax1_tx = $txlocaltax1;
3008  $this->line->localtax2_tx = $txlocaltax2;
3009  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3010  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3011  $this->line->remise_percent = $remise_percent;
3012  $this->line->subprice = $pu_ht;
3013  $this->line->info_bits = $info_bits;
3014  $this->line->total_ht = $total_ht;
3015  $this->line->total_tva = $total_tva;
3016  $this->line->total_localtax1 = $total_localtax1;
3017  $this->line->total_localtax2 = $total_localtax2;
3018  $this->line->total_ttc = $total_ttc;
3019  $this->line->product_type = $type;
3020  $this->line->special_code = $oldline->special_code;
3021  $this->line->rang = $oldline->rang;
3022  $this->line->origin = $this->origin;
3023  $this->line->fk_unit = $fk_unit;
3024 
3025  $this->line->date_start = $date_start;
3026  $this->line->date_end = $date_end;
3027 
3028  // Multicurrency
3029  $this->line->fk_multicurrency = $this->fk_multicurrency;
3030  $this->line->multicurrency_code = $this->multicurrency_code;
3031  $this->line->multicurrency_subprice = $pu_ht_devise;
3032  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
3033  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
3034  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
3035 
3036  $this->line->subprice = $pu_ht;
3037  $this->line->price = $this->line->subprice;
3038 
3039  $this->line->remise_percent = $remise_percent;
3040 
3041  if (is_array($array_options) && count($array_options) > 0) {
3042  // We replace values in this->line->array_options only for entries defined into $array_options
3043  foreach ($array_options as $key => $value) {
3044  $this->line->array_options[$key] = $array_options[$key];
3045  }
3046  }
3047 
3048  $result = $this->line->update($notrigger);
3049 
3050 
3051  // Mise a jour info denormalisees au niveau facture
3052  if ($result >= 0) {
3053  $this->update_price('1', 'auto');
3054  $this->db->commit();
3055  return $result;
3056  } else {
3057  $this->error = $this->db->lasterror();
3058  $this->db->rollback();
3059  return -1;
3060  }
3061  } else {
3062  $this->error = "Order status makes operation forbidden";
3063  dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
3064  return -2;
3065  }
3066  }
3067 
3068 
3076  public function initAsSpecimen()
3077  {
3078  global $user, $langs, $conf;
3079 
3080  include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
3081 
3082  dol_syslog(get_class($this)."::initAsSpecimen");
3083 
3084  $now = dol_now();
3085 
3086  // Find first product
3087  $prodid = 0;
3088  $product = new ProductFournisseur($this->db);
3089  $sql = "SELECT rowid";
3090  $sql .= " FROM ".$this->db->prefix()."product";
3091  $sql .= " WHERE entity IN (".getEntity('product').")";
3092  $sql .= $this->db->order("rowid", "ASC");
3093  $sql .= $this->db->plimit(1);
3094  $resql = $this->db->query($sql);
3095  if ($resql) {
3096  $obj = $this->db->fetch_object($resql);
3097  $prodid = $obj->rowid;
3098  }
3099 
3100  // Initialise parameters
3101  $this->id = 0;
3102  $this->ref = 'SPECIMEN';
3103  $this->specimen = 1;
3104  $this->socid = 1;
3105  $this->date = $now;
3106  $this->date_commande = $now;
3107  $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3108  $this->cond_reglement_code = 'RECEP';
3109  $this->mode_reglement_code = 'CHQ';
3110 
3111  $this->note_public = 'This is a comment (public)';
3112  $this->note_private = 'This is a comment (private)';
3113 
3114  $this->multicurrency_tx = 1;
3115  $this->multicurrency_code = $conf->currency;
3116 
3117  $this->statut = 0;
3118 
3119  // Lines
3120  $nbp = 5;
3121  $xnbp = 0;
3122  while ($xnbp < $nbp) {
3123  $line = new CommandeFournisseurLigne($this->db);
3124  $line->desc = $langs->trans("Description")." ".$xnbp;
3125  $line->qty = 1;
3126  $line->subprice = 100;
3127  $line->tva_tx = 19.6;
3128  $line->localtax1_tx = 0;
3129  $line->localtax2_tx = 0;
3130  if ($xnbp == 2) {
3131  $line->total_ht = 50;
3132  $line->total_ttc = 59.8;
3133  $line->total_tva = 9.8;
3134  $line->remise_percent = 50;
3135  } else {
3136  $line->total_ht = 100;
3137  $line->total_ttc = 119.6;
3138  $line->total_tva = 19.6;
3139  $line->remise_percent = 00;
3140  }
3141  $line->fk_product = $prodid;
3142 
3143  $this->lines[$xnbp] = $line;
3144 
3145  $this->total_ht += $line->total_ht;
3146  $this->total_tva += $line->total_tva;
3147  $this->total_ttc += $line->total_ttc;
3148 
3149  $xnbp++;
3150  }
3151 
3152  return 1;
3153  }
3154 
3161  public function info($id)
3162  {
3163  $sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3164  $sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3165  $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c';
3166  $sql .= ' WHERE c.rowid = '.((int) $id);
3167 
3168  $result = $this->db->query($sql);
3169  if ($result) {
3170  if ($this->db->num_rows($result)) {
3171  $obj = $this->db->fetch_object($result);
3172 
3173  $this->id = $obj->rowid;
3174 
3175  $this->user_creation_id = $obj->fk_user_author;
3176  $this->user_validation_id = $obj->fk_user_valid;
3177  $this->user_modification_id = $obj->fk_user_modif;
3178  $this->user_approve_id = $obj->fk_user_approve;
3179  $this->user_approve_id2 = $obj->fk_user_approve2;
3180 
3181  $this->date_creation = $this->db->jdate($obj->datec);
3182  $this->date_modification = $this->db->jdate($obj->datem);
3183  $this->date_approve = $this->db->jdate($obj->datea);
3184  $this->date_approve2 = $this->db->jdate($obj->datea2);
3185  $this->date_validation = $this->db->jdate($obj->date_validation);
3186  }
3187  $this->db->free($result);
3188  } else {
3189  dol_print_error($this->db);
3190  }
3191  }
3192 
3198  public function loadStateBoard()
3199  {
3200  global $conf, $user;
3201 
3202  $this->nb = array();
3203  $clause = "WHERE";
3204 
3205  $sql = "SELECT count(co.rowid) as nb";
3206  $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as co";
3207  $sql .= " LEFT JOIN ".$this->db->prefix()."societe as s ON co.fk_soc = s.rowid";
3208  if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3209  $sql .= " LEFT JOIN ".$this->db->prefix()."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3210  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3211  $clause = "AND";
3212  }
3213  $sql .= " ".$clause." co.entity IN (".getEntity('supplier_order').")";
3214 
3215  $resql = $this->db->query($sql);
3216  if ($resql) {
3217  while ($obj = $this->db->fetch_object($resql)) {
3218  $this->nb["supplier_orders"] = $obj->nb;
3219  }
3220  $this->db->free($resql);
3221  return 1;
3222  } else {
3223  dol_print_error($this->db);
3224  $this->error = $this->db->error();
3225  return -1;
3226  }
3227  }
3228 
3229  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3237  public function load_board($user, $mode = 'opened')
3238  {
3239  // phpcs:enable
3240  global $conf, $langs;
3241 
3242  $sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3243  $sql .= " FROM ".$this->db->prefix()."commande_fournisseur as c";
3244  if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3245  $sql .= " JOIN ".$this->db->prefix()."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3246  }
3247  $sql .= " WHERE c.entity = ".$conf->entity;
3248  if ($mode === 'awaiting') {
3249  $sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3250  } else {
3251  $sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3252  }
3253  if ($user->socid) {
3254  $sql .= " AND c.fk_soc = ".((int) $user->socid);
3255  }
3256 
3257  $resql = $this->db->query($sql);
3258  if ($resql) {
3259  $commandestatic = new CommandeFournisseur($this->db);
3260 
3261  $response = new WorkboardResponse();
3262  $response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3263  $response->label = $langs->trans("SuppliersOrdersToProcess");
3264  $response->labelShort = $langs->trans("Opened");
3265  $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3266  $response->img = img_object('', "order");
3267 
3268  if ($mode === 'awaiting') {
3269  $response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3270  $response->labelShort = $langs->trans("AwaitingReception");
3271  $response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3272  }
3273 
3274  while ($obj = $this->db->fetch_object($resql)) {
3275  $commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3276  $commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3277  $commandestatic->statut = $obj->fk_statut;
3278 
3279  $response->nbtodo++;
3280  $response->total += $obj->total_ht;
3281 
3282  if ($commandestatic->hasDelay()) {
3283  $response->nbtodolate++;
3284  }
3285  }
3286 
3287  return $response;
3288  } else {
3289  $this->error = $this->db->error();
3290  return -1;
3291  }
3292  }
3293 
3300  public function getInputMethod()
3301  {
3302  global $langs;
3303 
3304  if ($this->methode_commande_id > 0) {
3305  $sql = "SELECT rowid, code, libelle as label";
3306  $sql .= " FROM ".$this->db->prefix().'c_input_method';
3307  $sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3308 
3309  $resql = $this->db->query($sql);
3310  if ($resql) {
3311  if ($this->db->num_rows($resql)) {
3312  $obj = $this->db->fetch_object($resql);
3313 
3314  $string = $langs->trans($obj->code);
3315  if ($string == $obj->code) {
3316  $string = $obj->label != '-' ? $obj->label : '';
3317  }
3318  return $string;
3319  }
3320  } else {
3321  dol_print_error($this->db);
3322  }
3323  }
3324 
3325  return '';
3326  }
3327 
3339  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3340  {
3341  global $conf, $langs;
3342 
3343  if (!dol_strlen($modele)) {
3344  $modele = ''; // No doc template/generation by default
3345 
3346  if (!empty($this->model_pdf)) {
3347  $modele = $this->model_pdf;
3348  } elseif (getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF')) {
3349  $modele = getDolGlobalString('COMMANDE_SUPPLIER_ADDON_PDF');
3350  }
3351  }
3352 
3353  if (empty($modele)) {
3354  return 0;
3355  } else {
3356  $langs->load("suppliers");
3357  $outputlangs->load("products");
3358 
3359  $modelpath = "core/modules/supplier_order/doc/";
3360  $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3361  return $result;
3362  }
3363  }
3364 
3371  public function getMaxDeliveryTimeDay($langs)
3372  {
3373  if (empty($this->lines)) {
3374  return '';
3375  }
3376 
3377  $obj = new ProductFournisseur($this->db);
3378 
3379  $nb = 0;
3380  foreach ($this->lines as $line) {
3381  if ($line->fk_product > 0) {
3382  $idp = $obj->find_min_price_product_fournisseur($line->fk_product, $line->qty);
3383  if ($idp) {
3384  $obj->fetch($idp);
3385  if ($obj->delivery_time_days > $nb) {
3386  $nb = $obj->delivery_time_days;
3387  }
3388  }
3389  }
3390  }
3391 
3392  if ($nb === 0) {
3393  return '';
3394  } else {
3395  return $nb.' '.$langs->trans('Days');
3396  }
3397  }
3398 
3403  public function getRights()
3404  {
3405  global $user;
3406 
3407  return $user->hasRight("fournisseur", "commande");
3408  }
3409 
3410 
3419  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3420  {
3421  $tables = array(
3422  'commande_fournisseur'
3423  );
3424 
3425  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3426  }
3427 
3436  public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
3437  {
3438  $tables = array(
3439  'commande_fournisseurdet'
3440  );
3441 
3442  return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
3443  }
3444 
3452  public function hasDelay()
3453  {
3454  global $conf;
3455 
3456  if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3457  $now = dol_now();
3458  if (!empty($this->delivery_date)) {
3459  $date_to_test = $this->delivery_date;
3460  return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3461  } else {
3462  //$date_to_test = $this->date_commande;
3463  //return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3464  return false;
3465  }
3466  } else {
3467  $now = dol_now();
3468  $date_to_test = $this->date_commande;
3469 
3470  return ($this->statut > 0 && $this->statut < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3471  }
3472  }
3473 
3481  public function showDelay()
3482  {
3483  global $conf, $langs;
3484 
3485  $langs->load('orders');
3486 
3487  $text = '';
3488 
3489  if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3490  if (!empty($this->delivery_date)) {
3491  $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3492  } else {
3493  $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3494  }
3495  } else {
3496  $text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3497  }
3498  if ($text) {
3499  $text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3500  }
3501 
3502  return $text;
3503  }
3504 
3505 
3514  public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3515  {
3516  if (isModEnabled("supplier_order")) {
3517  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3518 
3519  $qtydelivered = array();
3520  $qtywished = array();
3521 
3522  $supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3523 
3524  $filter = array('t.fk_element' => $this->id);
3525  if (getDolGlobalString('SUPPLIER_ORDER_USE_DISPATCH_STATUS')) {
3526  $filter['t.status'] = 1; // Restrict to lines with status validated
3527  }
3528 
3529  $ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3530  if ($ret < 0) {
3531  $this->error = $supplierorderdispatch->error;
3532  $this->errors = $supplierorderdispatch->errors;
3533  return $ret;
3534  } else {
3535  if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3536  require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3537  $date_liv = dol_now();
3538 
3539  // Build array with quantity deliverd by product
3540  foreach ($supplierorderdispatch->lines as $line) {
3541  $qtydelivered[$line->fk_product] += $line->qty;
3542  }
3543  foreach ($this->lines as $line) {
3544  // Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3545  if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $line->product_type > 0) {
3546  continue;
3547  }
3548  $qtywished[$line->fk_product] += $line->qty;
3549  }
3550 
3551  //Compare array
3552  $diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3553  $keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3554  $keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3555  //var_dump(array_keys($qtydelivered));
3556  //var_dump(array_keys($qtywished));
3557  //var_dump($diff_array);
3558  //var_dump($keysinwishednotindelivered);
3559  //var_dump($keysindeliverednotinwished);
3560  //exit;
3561 
3562  if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everything is received
3563  if ($closeopenorder) {
3564  //$ret=$this->setStatus($user,5);
3565  $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3566  if ($ret < 0) {
3567  return -1;
3568  }
3569  return 5;
3570  } else {
3571  //Diff => received partially
3572  //$ret=$this->setStatus($user,4);
3573  $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3574  if ($ret < 0) {
3575  return -1;
3576  }
3577  return 4;
3578  }
3579  } elseif (getDolGlobalString('SUPPLIER_ORDER_MORE_THAN_WISHED')) {
3580  //set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3581 
3582  $close = 0;
3583 
3584  if (count($diff_array) > 0) {
3585  //there are some difference between the two arrays
3586 
3587  //scan the array of results
3588  foreach ($diff_array as $key => $value) {
3589  //if the quantity delivered is greater or equal to wish quantity
3590  if ($qtydelivered[$key] >= $qtywished[$key]) {
3591  $close++;
3592  }
3593  }
3594  }
3595 
3596 
3597  if ($close == count($diff_array)) {
3598  //all the products are received equal or more than the wished quantity
3599  if ($closeopenorder) {
3600  $ret = $this->Livraison($user, $date_liv, 'tot', $comment); // $type is 'tot', 'par', 'nev', 'can'
3601  if ($ret < 0) {
3602  return -1;
3603  }
3604  return 5;
3605  } else {
3606  //Diff => received partially
3607  $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3608  if ($ret < 0) {
3609  return -1;
3610  }
3611  return 4;
3612  }
3613  } else {
3614  //all the products are not received
3615  $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3616  if ($ret < 0) {
3617  return -1;
3618  }
3619  return 4;
3620  }
3621  } else {
3622  //Diff => received partially
3623  $ret = $this->Livraison($user, $date_liv, 'par', $comment); // $type is 'tot', 'par', 'nev', 'can'
3624  if ($ret < 0) {
3625  return -1;
3626  }
3627  return 4;
3628  }
3629  }
3630  return 1;
3631  }
3632  }
3633  return 0;
3634  }
3635 
3643  public function loadReceptions($filtre_statut = -1)
3644  {
3645  $this->receptions = array();
3646 
3647  dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3648 
3649  $sql = 'SELECT cd.rowid, cd.fk_product,';
3650  $sql .= ' sum(cfd.qty) as qty';
3651  $sql .= ' FROM '.$this->db->prefix().'receptiondet_batch as cfd,';
3652  if ($filtre_statut >= 0) {
3653  $sql .= ' '.$this->db->prefix().'reception as e,';
3654  }
3655  $sql .= ' '.$this->db->prefix().'commande_fournisseurdet as cd';
3656  $sql .= ' WHERE';
3657  if ($filtre_statut >= 0) {
3658  $sql .= ' cfd.fk_reception = e.rowid AND';
3659  }
3660  $sql .= ' cfd.fk_elementdet = cd.rowid';
3661  $sql .= ' AND cd.fk_commande ='.((int) $this->id);
3662  if (isset($this->fk_product) && !empty($this->fk_product) > 0) {
3663  $sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3664  }
3665  if ($filtre_statut >= 0) {
3666  $sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3667  }
3668  $sql .= ' GROUP BY cd.rowid, cd.fk_product';
3669 
3670  $resql = $this->db->query($sql);
3671  if ($resql) {
3672  $num = $this->db->num_rows($resql);
3673  $i = 0;
3674  while ($i < $num) {
3675  $obj = $this->db->fetch_object($resql);
3676  empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3677  $i++;
3678  }
3679  $this->db->free($resql);
3680 
3681  return $num;
3682  } else {
3683  $this->error = $this->db->lasterror();
3684  return -1;
3685  }
3686  }
3687 
3695  public function getKanbanView($option = '', $arraydata = null)
3696  {
3697  global $langs;
3698 
3699  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3700 
3701  $return = '<div class="box-flex-item box-flex-grow-zero">';
3702  $return .= '<div class="info-box info-box-sm">';
3703  $return .= '<span class="info-box-icon bg-infobox-action">';
3704  $return .= img_picto('', $this->picto);
3705  $return .= '</span>';
3706  $return .= '<div class="info-box-content">';
3707  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3708  if ($selected >= 0) {
3709  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3710  }
3711  if (property_exists($this, 'socid') || property_exists($this, 'total_tva')) {
3712  $return .= '<br><span class="info-box-label amount">'.$this->socid.'</span>';
3713  }
3714  if (property_exists($this, 'billed')) {
3715  $return .= '<br><span class="opacitymedium">'.$langs->trans("Billed").' : </span><span class="info-box-label">'.yn($this->billed).'</span>';
3716  }
3717  if (method_exists($this, 'getLibStatut')) {
3718  $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
3719  }
3720  $return .= '</div>';
3721  $return .= '</div>';
3722  $return .= '</div>';
3723  return $return;
3724  }
3725 }
3726 
3727 
3728 
3733 {
3737  public $element = 'commande_fournisseurdet';
3738 
3742  public $table_element = 'commande_fournisseurdet';
3743 
3744  public $oldline;
3745 
3750  public $fk_commande;
3751 
3752  // From llx_commande_fournisseurdet
3756  public $fk_parent_line;
3757 
3761  public $fk_facture;
3762 
3763  public $rang = 0;
3764 
3768  public $special_code = 0;
3769 
3774  public $pu_ht;
3775 
3776  public $date_start;
3777  public $date_end;
3778  public $fk_fournprice;
3779  public $packaging;
3780  public $pa_ht;
3781 
3782  // From llx_product_fournisseur_price
3783 
3788  public $ref_supplier;
3789 
3795  public $ref_fourn;
3796 
3797  public $remise;
3798 
3799 
3805  public function __construct($db)
3806  {
3807  $this->db = $db;
3808  }
3809 
3816  public function fetch($rowid)
3817  {
3818  $sql = 'SELECT cd.rowid, cd.fk_commande, cd.fk_product, cd.product_type, cd.description, cd.qty, cd.tva_tx, cd.special_code,';
3819  $sql .= ' cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.ref as ref_supplier,';
3820  $sql .= ' cd.remise, cd.remise_percent, cd.subprice,';
3821  $sql .= ' cd.info_bits, cd.total_ht, cd.total_tva, cd.total_ttc,';
3822  $sql .= ' cd.total_localtax1, cd.total_localtax2,';
3823  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3824  $sql .= ' cd.date_start, cd.date_end, cd.fk_unit,';
3825  $sql .= ' cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,';
3826  $sql .= ' c.fk_soc as socid';
3827  $sql .= ' FROM '.$this->db->prefix().'commande_fournisseur as c, '.$this->db->prefix().'commande_fournisseurdet as cd';
3828  $sql .= ' LEFT JOIN '.$this->db->prefix().'product as p ON cd.fk_product = p.rowid';
3829  $sql .= ' WHERE cd.fk_commande = c.rowid AND cd.rowid = '.((int) $rowid);
3830 
3831  $result = $this->db->query($sql);
3832  if ($result) {
3833  $objp = $this->db->fetch_object($result);
3834 
3835  if (!empty($objp)) {
3836  $this->rowid = $objp->rowid;
3837  $this->id = $objp->rowid;
3838  $this->fk_commande = $objp->fk_commande;
3839  $this->desc = $objp->description;
3840  $this->qty = $objp->qty;
3841  $this->ref_fourn = $objp->ref_supplier;
3842  $this->ref_supplier = $objp->ref_supplier;
3843  $this->subprice = $objp->subprice;
3844  $this->tva_tx = $objp->tva_tx;
3845  $this->localtax1_tx = $objp->localtax1_tx;
3846  $this->localtax2_tx = $objp->localtax2_tx;
3847  $this->localtax1_type = $objp->localtax1_type;
3848  $this->localtax2_type = $objp->localtax2_type;
3849  $this->remise = $objp->remise;
3850  $this->remise_percent = $objp->remise_percent;
3851  $this->fk_product = $objp->fk_product;
3852  $this->info_bits = $objp->info_bits;
3853  $this->total_ht = $objp->total_ht;
3854  $this->total_tva = $objp->total_tva;
3855  $this->total_localtax1 = $objp->total_localtax1;
3856  $this->total_localtax2 = $objp->total_localtax2;
3857  $this->total_ttc = $objp->total_ttc;
3858  $this->product_type = $objp->product_type;
3859  $this->special_code = $objp->special_code;
3860 
3861  $this->ref = $objp->product_ref;
3862 
3863  $this->product_ref = $objp->product_ref;
3864  $this->product_label = $objp->product_label;
3865  $this->product_desc = $objp->product_desc;
3866 
3867  if (getDolGlobalInt('PRODUCT_USE_SUPPLIER_PACKAGING')) {
3868  // TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
3869  // Move this into another method and call it when required.
3870 
3871  // Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
3872  $sqlsearchpackage = 'SELECT rowid, packaging FROM '.$this->db->prefix()."product_fournisseur_price";
3873  $sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
3874  $sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
3875  $sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
3876  $sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty); // required to be qualified
3877  $sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")"; // required to be qualified
3878  $sqlsearchpackage .= " AND fk_soc = ".((int) $objp->socid);
3879  $sqlsearchpackage .= " ORDER BY packaging ASC"; // Take the smaller package first
3880  $sqlsearchpackage .= " LIMIT 1";
3881 
3882  $resqlsearchpackage = $this->db->query($sqlsearchpackage);
3883  if ($resqlsearchpackage) {
3884  $objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
3885  if ($objsearchpackage) {
3886  $this->fk_fournprice = $objsearchpackage->rowid;
3887  $this->packaging = $objsearchpackage->packaging;
3888  }
3889  } else {
3890  $this->error = $this->db->lasterror();
3891  return -1;
3892  }
3893  }
3894 
3895  $this->date_start = $this->db->jdate($objp->date_start);
3896  $this->date_end = $this->db->jdate($objp->date_end);
3897  $this->fk_unit = $objp->fk_unit;
3898 
3899  $this->multicurrency_subprice = $objp->multicurrency_subprice;
3900  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
3901  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
3902  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
3903 
3904  $this->fetch_optionals();
3905 
3906  $this->db->free($result);
3907  return 1;
3908  } else {
3909  $this->error = 'Supplier order line with id='.$rowid.' not found';
3910  dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
3911  return 0;
3912  }
3913  } else {
3914  dol_print_error($this->db);
3915  return -1;
3916  }
3917  }
3918 
3925  public function insert($notrigger = 0)
3926  {
3927  global $conf, $user;
3928 
3929  $error = 0;
3930 
3931  dol_syslog(get_class($this)."::insert rang=".$this->rang);
3932 
3933  // Clean parameters
3934  if (empty($this->tva_tx)) {
3935  $this->tva_tx = 0;
3936  }
3937  if (empty($this->localtax1_tx)) {
3938  $this->localtax1_tx = 0;
3939  }
3940  if (empty($this->localtax2_tx)) {
3941  $this->localtax2_tx = 0;
3942  }
3943  if (empty($this->localtax1_type)) {
3944  $this->localtax1_type = '0';
3945  }
3946  if (empty($this->localtax2_type)) {
3947  $this->localtax2_type = '0';
3948  }
3949  if (empty($this->total_localtax1)) {
3950  $this->total_localtax1 = 0;
3951  }
3952  if (empty($this->total_localtax2)) {
3953  $this->total_localtax2 = 0;
3954  }
3955  if (empty($this->rang)) {
3956  $this->rang = 0;
3957  }
3958  if (empty($this->remise_percent)) {
3959  $this->remise_percent = 0;
3960  }
3961  if (empty($this->info_bits)) {
3962  $this->info_bits = 0;
3963  }
3964  if (empty($this->special_code)) {
3965  $this->special_code = 0;
3966  }
3967  if (empty($this->fk_parent_line)) {
3968  $this->fk_parent_line = 0;
3969  }
3970  if (empty($this->pa_ht)) {
3971  $this->pa_ht = 0;
3972  }
3973 
3974  // Multicurrency
3975  if (!empty($this->multicurrency_code)) {
3976  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
3977  }
3978  if (empty($this->fk_multicurrency)) {
3979  $this->multicurrency_code = $conf->currency;
3980  $this->fk_multicurrency = 0;
3981  $this->multicurrency_tx = 1;
3982  }
3983 
3984  // Check parameters
3985  if ($this->product_type < 0) {
3986  return -1;
3987  }
3988 
3989  $this->db->begin();
3990 
3991  // Insertion dans base de la ligne
3992  $sql = 'INSERT INTO '.$this->db->prefix().$this->table_element;
3993  $sql .= " (fk_commande, label, description, date_start, date_end,";
3994  $sql .= " fk_product, product_type, special_code, rang,";
3995  $sql .= " qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice, ref,";
3996  $sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_unit,";
3997  $sql .= " fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc,";
3998  $sql .= " fk_parent_line)";
3999  $sql .= " VALUES (".$this->fk_commande.", '".$this->db->escape($this->label)."','".$this->db->escape($this->desc)."',";
4000  $sql .= " ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null").",";
4001  $sql .= " ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null").",";
4002  if ($this->fk_product) {
4003  $sql .= $this->fk_product.",";
4004  } else {
4005  $sql .= "null,";
4006  }
4007  $sql .= "'".$this->db->escape($this->product_type)."',";
4008  $sql .= (int) $this->special_code . ",";
4009  $sql .= "'".$this->db->escape($this->rang)."',";
4010  $sql .= "'".$this->db->escape($this->qty)."', ";
4011  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4012  $sql .= " ".price2num($this->tva_tx).", ";
4013  $sql .= " ".price2num($this->localtax1_tx).",";
4014  $sql .= " ".price2num($this->localtax2_tx).",";
4015  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4016  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4017  $sql .= " ".((float) $this->remise_percent).", ".price2num($this->subprice, 'MU').", '".$this->db->escape($this->ref_supplier)."',";
4018  $sql .= " ".price2num($this->total_ht).",";
4019  $sql .= " ".price2num($this->total_tva).",";
4020  $sql .= " ".price2num($this->total_localtax1).",";
4021  $sql .= " ".price2num($this->total_localtax2).",";
4022  $sql .= " ".price2num($this->total_ttc).",";
4023  $sql .= ($this->fk_unit ? "'".$this->db->escape($this->fk_unit)."'" : "null");
4024  $sql .= ", ".($this->fk_multicurrency ? ((int) $this->fk_multicurrency) : "null");
4025  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4026  $sql .= ", ".($this->multicurrency_subprice ? price2num($this->multicurrency_subprice) : '0');
4027  $sql .= ", ".($this->multicurrency_total_ht ? price2num($this->multicurrency_total_ht) : '0');
4028  $sql .= ", ".($this->multicurrency_total_tva ? price2num($this->multicurrency_total_tva) : '0');
4029  $sql .= ", ".($this->multicurrency_total_ttc ? price2num($this->multicurrency_total_ttc) : '0');
4030  $sql .= ", ".((!empty($this->fk_parent_line) && $this->fk_parent_line > 0) ? $this->fk_parent_line : 'null');
4031  $sql .= ")";
4032 
4033  dol_syslog(get_class($this)."::insert", LOG_DEBUG);
4034  $resql = $this->db->query($sql);
4035  if ($resql) {
4036  $this->id = $this->db->last_insert_id($this->db->prefix().$this->table_element);
4037  $this->rowid = $this->id;
4038 
4039  if (!$error) {
4040  $result = $this->insertExtraFields();
4041  if ($result < 0) {
4042  $error++;
4043  }
4044  }
4045 
4046  if (!$error && !$notrigger) {
4047  // Call trigger
4048  $result = $this->call_trigger('LINEORDER_SUPPLIER_CREATE', $user);
4049  if ($result < 0) {
4050  $error++;
4051  }
4052  // End call triggers
4053  }
4054 
4055  if (!$error) {
4056  $this->db->commit();
4057  return 1;
4058  }
4059 
4060  foreach ($this->errors as $errmsg) {
4061  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
4062  $this->errors[] = ($this->errors ? ', '.$errmsg : $errmsg);
4063  }
4064  $this->db->rollback();
4065  return -1 * $error;
4066  } else {
4067  $this->errors[] = $this->db->error();
4068  $this->db->rollback();
4069  return -2;
4070  }
4071  }
4078  public function update($notrigger = 0)
4079  {
4080  global $user;
4081 
4082  $error = 0;
4083 
4084  $this->db->begin();
4085 
4086  $sql = "UPDATE ".$this->db->prefix().$this->table_element." SET";
4087  $sql .= " description='".$this->db->escape($this->desc)."'";
4088  $sql .= ", ref='".$this->db->escape($this->ref_supplier)."'";
4089  $sql .= ", subprice='".price2num($this->subprice)."'";
4090  //$sql.= ",remise='".price2num($remise)."'";
4091  $sql .= ", remise_percent='".price2num($this->remise_percent)."'";
4092 
4093  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4094  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4095  $sql .= ", localtax1_tx='".price2num($this->localtax1_tx)."'";
4096  $sql .= ", localtax2_tx='".price2num($this->localtax2_tx)."'";
4097  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4098  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4099  $sql .= ", qty='".price2num($this->qty)."'";
4100  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4101  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4102  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4103  $sql .= ", total_ht='".price2num($this->total_ht)."'";
4104  $sql .= ", total_tva='".price2num($this->total_tva)."'";
4105  $sql .= ", total_localtax1='".price2num($this->total_localtax1)."'";
4106  $sql .= ", total_localtax2='".price2num($this->total_localtax2)."'";
4107  $sql .= ", total_ttc='".price2num($this->total_ttc)."'";
4108  $sql .= ", product_type=".$this->product_type;
4109  $sql .= ", special_code=".(!empty($this->special_code) ? $this->special_code : 0);
4110  $sql .= ($this->fk_unit ? ", fk_unit='".$this->db->escape($this->fk_unit)."'" : ", fk_unit=null");
4111 
4112  // Multicurrency
4113  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4114  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4115  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4116  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4117 
4118  $sql .= " WHERE rowid = ".((int) $this->id);
4119 
4120  dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
4121  $resql = $this->db->query($sql);
4122  if ($resql) {
4123  if (!$error) {
4124  $result = $this->insertExtraFields();
4125  if ($result < 0) {
4126  $error++;
4127  }
4128  }
4129 
4130  if (!$error && !$notrigger) {
4131  // Call trigger
4132  $result = $this->call_trigger('LINEORDER_SUPPLIER_MODIFY', $user);
4133  if ($result < 0) {
4134  $this->db->rollback();
4135  return -1;
4136  }
4137  // End call triggers
4138  }
4139 
4140  if (!$error) {
4141  $this->db->commit();
4142  return 1;
4143  } else {
4144  $this->db->rollback();
4145  return -1;
4146  }
4147  } else {
4148  $this->error = $this->db->lasterror();
4149  $this->db->rollback();
4150  return -1;
4151  }
4152  }
4153 
4161  public function delete($user, $notrigger = 0)
4162  {
4163  if (empty($user)) {
4164  global $user;
4165  }
4166 
4167  $error = 0;
4168 
4169  $this->db->begin();
4170 
4171  // extrafields
4172  $result = $this->deleteExtraFields();
4173  if ($result < 0) {
4174  $this->db->rollback();
4175  return -1;
4176  }
4177 
4178  $sql1 = 'UPDATE '.$this->db->prefix()."commandedet SET fk_commandefourndet = NULL WHERE rowid=".((int) $this->id);
4179  $resql = $this->db->query($sql1);
4180  if (!$resql) {
4181  $this->db->rollback();
4182  return -1;
4183  }
4184 
4185  $sql2 = 'DELETE FROM '.$this->db->prefix()."commande_fournisseurdet WHERE rowid=".((int) $this->id);
4186 
4187  dol_syslog(__METHOD__, LOG_DEBUG);
4188  $resql = $this->db->query($sql2);
4189  if ($resql) {
4190  if (!$notrigger) {
4191  // Call trigger
4192  $result = $this->call_trigger('LINEORDER_SUPPLIER_DELETE', $user);
4193  if ($result < 0) {
4194  $error++;
4195  }
4196  // End call triggers
4197  }
4198 
4199  if (!$error) {
4200  $this->db->commit();
4201  return 1;
4202  }
4203 
4204  foreach ($this->errors as $errmsg) {
4205  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
4206  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4207  }
4208  $this->db->rollback();
4209  return -1 * $error;
4210  } else {
4211  $this->error = $this->db->lasterror();
4212  return -1;
4213  }
4214  }
4215 }
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:607
$object ref
Definition: info.php:79
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 clicable 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...
refuse($user)
Refuse an order.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
getKanbanView($option='', $arraydata=null)
Return clicable link of object (with eventually picto)
info($id)
Charge les information d'ordre info dans l'objet facture.
const STATUS_CANCELED
Order canceled.
getNextNumRef($soc)
Returns the next order reference not used, based on the numbering model defined within COMMANDE_SUPPL...
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.
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.
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
approve($user, $idwarehouse=0, $secondlevel=0)
Approve a supplier order.
Cancel($user, $idwarehouse=-1)
Cancel an approved 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.
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.
update($notrigger=0)
Update the line object into db.
insert($notrigger=0)
Insert line into database.
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)
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
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 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.
call_trigger($triggerName, $user)
Call trigger based on this instance.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Superclass for orders classes.
Superclass for orders classes.
Class to manage Dolibarr database access.
Class to manage stock movements.
static getIdFromCode($dbs, $code)
Get id of currency from code.
static getIdAndTxFromCode($dbs, $code, $date_document='')
Get id and rate 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.
Definition: user.class.php:50
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:745
print *****$script_file(".$version.") pid c cd cd cd description as p label as s rowid
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
Definition: files.lib.php:1609
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.
Definition: files.lib.php:1458
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:63
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
yn($yesno, $case=1, $color=0)
Return yes or no in current language.
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 '.
setEventMessage($mesgs, $style='mesgs', $noduplicate=0)
Set event message in dol_events session object.
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.
dol_now($mode='auto')
Return date for now.
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_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0)
Set event messages in dol_events session object.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
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 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...
div float
Buy price without taxes.
Definition: style.css.php:960
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller='', $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition: price.lib.php:88