dolibarr  17.0.4
propal.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
4  * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
5  * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6  * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
7  * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
8  * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
9  * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
10  * Copyright (C) 2010-2022 Philippe Grand <philippe.grand@atoo-net.com>
11  * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12  * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
13  * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
14  * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
15  * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
16  * Copyright (C) 2018-2021 Frédéric France <frederic.france@netlogic.fr>
17  * Copyright (C) 2018 Ferran Marcet <fmarcet@2byte.es>
18  * Copyright (C) 2022 ATM Consulting <contact@atm-consulting.fr>
19  * Copyright (C) 2022 OpenDSI <support@open-dsi.fr>
20  * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
21  *
22  * This program is free software; you can redistribute it and/or modify
23  * it under the terms of the GNU General Public License as published by
24  * the Free Software Foundation; either version 3 of the License, or
25  * (at your option) any later version.
26  *
27  * This program is distributed in the hope that it will be useful,
28  * but WITHOUT ANY WARRANTY; without even the implied warranty of
29  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30  * GNU General Public License for more details.
31  *
32  * You should have received a copy of the GNU General Public License
33  * along with this program. If not, see <https://www.gnu.org/licenses/>.
34  */
35 
41 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
42 require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
43 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
44 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
45 require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
46 require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
47 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
48 
52 class Propal extends CommonObject
53 {
54  use CommonIncoterm;
55 
59  public $code = "";
60 
64  public $element = 'propal';
65 
69  public $table_element = 'propal';
70 
74  public $table_element_line = 'propaldet';
75 
79  public $fk_element = 'fk_propal';
80 
84  public $picto = 'propal';
85 
90  public $ismultientitymanaged = 1;
91 
96  public $restrictiononfksoc = 1;
97 
101  protected $table_ref_field = 'ref';
102 
107  public $socid;
108 
113  public $contactid;
114  public $author;
115 
120  public $ref_client;
121 
127  public $statut;
128 
134  public $status;
135 
140  public $datec;
141 
145  public $date_creation;
146 
151  public $datev;
152 
156  public $date_validation;
157 
161  public $date_signature;
162 
166  public $user_signature;
167 
171  public $date;
172 
177  public $datep;
178 
183  public $date_livraison; // deprecated; Use delivery_date instead.
184 
188  public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
189 
190 
191  public $fin_validite;
192 
193  public $user_author_id;
194  public $user_valid_id;
195  public $user_close_id;
196 
201  public $price;
206  public $tva;
211  public $total;
212 
213  public $cond_reglement_code;
214  public $deposit_percent;
215  public $mode_reglement_code;
216  public $remise_percent;
217 
221  public $remise;
226 
231  public $fk_address;
232 
233  public $address_type;
234  public $address;
235 
236  public $availability_id;
237  public $availability_code;
238 
239  public $duree_validite;
240 
241  public $demand_reason_id;
242  public $demand_reason_code;
243 
244  public $warehouse_id;
245 
246  public $extraparams = array();
247 
251  public $lines = array();
252  public $line;
253 
254  public $labelStatus = array();
255  public $labelStatusShort = array();
256 
257  // Multicurrency
261  public $fk_multicurrency;
262 
263  public $multicurrency_code;
264  public $multicurrency_tx;
265  public $multicurrency_total_ht;
266  public $multicurrency_total_tva;
267  public $multicurrency_total_ttc;
268 
269 
294  // BEGIN MODULEBUILDER PROPERTIES
298  public $fields = array(
299  'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
300  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
301  'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
302  'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
303  'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
304  'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'$conf->societe->enabled', 'visible'=>-1, 'position'=>23),
305  'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Fk projet', 'enabled'=>"isModEnabled('project')", 'visible'=>-1, 'position'=>24),
306  'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
307  'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
308  'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
309  'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
310  'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
311  'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
312  'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
313  'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
314  'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
315  'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
316  'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
317  'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
318  //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
319  //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
320  'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
321  'total_tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
322  'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
323  'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
324  'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
325  'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'$conf->banque->enabled', 'visible'=>-1, 'position'=>150),
326  'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
327  'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
328  'deposit_percent' =>array('type'=>'varchar(63)', 'label'=>'DepositPercent', 'enabled'=>1, 'visible'=>-1, 'position'=>161),
329  'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
330  'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>170),
331  'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>175),
332  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
333  'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
334  'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
335  'fk_warehouse' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Fk warehouse', 'enabled'=>'$conf->stock->enabled', 'visible'=>-1, 'position'=>191),
336  'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
337  'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
338  'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
339  'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
340  'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
341  'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
342  'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
343  'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
344  'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
345  'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
346  'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
347  'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
348  'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
349  'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
350  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
351  );
352  // END MODULEBUILDER PROPERTIES
353 
357  const STATUS_DRAFT = 0;
361  const STATUS_VALIDATED = 1;
365  const STATUS_SIGNED = 2;
369  const STATUS_NOTSIGNED = 3;
373  const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
374 
375 
383  public function __construct($db, $socid = 0, $propalid = 0)
384  {
385  global $conf, $langs;
386 
387  $this->db = $db;
388 
389  $this->socid = $socid;
390  $this->id = $propalid;
391 
392  $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
393  }
394 
395 
396  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
408  public function add_product($idproduct, $qty, $remise_percent = 0)
409  {
410  // phpcs:enable
411  global $conf, $mysoc;
412 
413  if (!$qty) {
414  $qty = 1;
415  }
416 
417  dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
418  if ($idproduct > 0) {
419  $prod = new Product($this->db);
420  $prod->fetch($idproduct);
421 
422  $productdesc = $prod->description;
423 
424  $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
425  $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
426  if (empty($tva_tx)) {
427  $tva_npr = 0;
428  }
429  $vat_src_code = ''; // May be defined into tva_tx
430 
431  $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
432  $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
433 
434  // multiprices
435  if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
436  $price = $prod->multiprices[$this->thirdparty->price_level];
437  } else {
438  $price = $prod->price;
439  }
440 
441  $line = new PropaleLigne($this->db);
442 
443  $line->fk_product = $idproduct;
444  $line->desc = $productdesc;
445  $line->qty = $qty;
446  $line->subprice = $price;
447  $line->remise_percent = $remise_percent;
448  $line->vat_src_code = $vat_src_code;
449  $line->tva_tx = $tva_tx;
450  $line->fk_unit = $prod->fk_unit;
451  if ($tva_npr) {
452  $line->info_bits = 1;
453  }
454 
455  $this->lines[] = $line;
456  }
457  }
458 
459  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
466  public function insert_discount($idremise)
467  {
468  // phpcs:enable
469  global $langs;
470 
471  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
472  include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
473 
474  $this->db->begin();
475 
476  $remise = new DiscountAbsolute($this->db);
477  $result = $remise->fetch($idremise);
478 
479  if ($result > 0) {
480  if ($remise->fk_facture) { // Protection against multiple submission
481  $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
482  $this->db->rollback();
483  return -5;
484  }
485 
486  $line = new PropaleLigne($this->db);
487 
488  $this->line->context = $this->context;
489 
490  $line->fk_propal = $this->id;
491  $line->fk_remise_except = $remise->id;
492  $line->desc = $remise->description; // Description ligne
493  $line->vat_src_code = $remise->vat_src_code;
494  $line->tva_tx = $remise->tva_tx;
495  $line->subprice = -$remise->amount_ht;
496  $line->fk_product = 0; // Id produit predefined
497  $line->qty = 1;
498  $line->remise_percent = 0;
499  $line->rang = -1;
500  $line->info_bits = 2;
501 
502  // TODO deprecated
503  $line->price = -$remise->amount_ht;
504 
505  $line->total_ht = -$remise->amount_ht;
506  $line->total_tva = -$remise->amount_tva;
507  $line->total_ttc = -$remise->amount_ttc;
508 
509  $result = $line->insert();
510  if ($result > 0) {
511  $result = $this->update_price(1);
512  if ($result > 0) {
513  $this->db->commit();
514  return 1;
515  } else {
516  $this->db->rollback();
517  return -1;
518  }
519  } else {
520  $this->error = $line->error;
521  $this->errors = $line->errors;
522  $this->db->rollback();
523  return -2;
524  }
525  } else {
526  $this->db->rollback();
527  return -2;
528  }
529  }
530 
568  public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $fk_remise_except = 0, $noupdateafterinsertline = 0)
569  {
570  global $mysoc, $conf, $langs;
571 
572  dol_syslog(get_class($this)."::addline propalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type, fk_remise_except=".$fk_remise_except);
573 
574  if ($this->statut == self::STATUS_DRAFT) {
575  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
576 
577  // Clean parameters
578  if (empty($remise_percent)) {
579  $remise_percent = 0;
580  }
581  if (empty($qty)) {
582  $qty = 0;
583  }
584  if (empty($info_bits)) {
585  $info_bits = 0;
586  }
587  if (empty($rang)) {
588  $rang = 0;
589  }
590  if (empty($fk_parent_line) || $fk_parent_line < 0) {
591  $fk_parent_line = 0;
592  }
593 
594  $remise_percent = price2num($remise_percent);
595  $qty = price2num($qty);
596  $pu_ht = price2num($pu_ht);
597  $pu_ht_devise = price2num($pu_ht_devise);
598  $pu_ttc = price2num($pu_ttc);
599  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
600  $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
601  }
602  $txlocaltax1 = price2num($txlocaltax1);
603  $txlocaltax2 = price2num($txlocaltax2);
604  $pa_ht = price2num($pa_ht);
605  if ($price_base_type == 'HT') {
606  $pu = $pu_ht;
607  } else {
608  $pu = $pu_ttc;
609  }
610 
611  // Check parameters
612  if ($type < 0) {
613  return -1;
614  }
615 
616  if ($date_start && $date_end && $date_start > $date_end) {
617  $langs->load("errors");
618  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
619  return -1;
620  }
621 
622  $this->db->begin();
623 
624  $product_type = $type;
625  if (!empty($fk_product) && $fk_product > 0) {
626  $product = new Product($this->db);
627  $result = $product->fetch($fk_product);
628  $product_type = $product->type;
629 
630  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
631  $langs->load("errors");
632  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
633  $this->db->rollback();
634  return -3;
635  }
636  }
637 
638  // Calcul du total TTC et de la TVA pour la ligne a partir de
639  // qty, pu, remise_percent et txtva
640  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
641  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
642 
643  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
644 
645  // Clean vat code
646  $reg = array();
647  $vat_src_code = '';
648  $reg = array();
649  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
650  $vat_src_code = $reg[1];
651  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
652  }
653 
654  $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
655 
656  $total_ht = $tabprice[0];
657  $total_tva = $tabprice[1];
658  $total_ttc = $tabprice[2];
659  $total_localtax1 = $tabprice[9];
660  $total_localtax2 = $tabprice[10];
661  $pu_ht = $tabprice[3];
662  $pu_tva = $tabprice[4];
663  $pu_ttc = $tabprice[5];
664 
665  // MultiCurrency
666  $multicurrency_total_ht = $tabprice[16];
667  $multicurrency_total_tva = $tabprice[17];
668  $multicurrency_total_ttc = $tabprice[18];
669  $pu_ht_devise = $tabprice[19];
670 
671  // Rang to use
672  $ranktouse = $rang;
673  if ($ranktouse == -1) {
674  $rangmax = $this->line_max($fk_parent_line);
675  $ranktouse = $rangmax + 1;
676  }
677 
678  // TODO A virer
679  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
680  $price = $pu;
681  $remise = 0;
682  if ($remise_percent > 0) {
683  $remise = round(($pu * $remise_percent / 100), 2);
684  $price = $pu - $remise;
685  }
686 
687  // Insert line
688  $this->line = new PropaleLigne($this->db);
689 
690  $this->line->context = $this->context;
691 
692  $this->line->fk_propal = $this->id;
693  $this->line->label = $label;
694  $this->line->desc = $desc;
695  $this->line->qty = $qty;
696 
697  $this->line->vat_src_code = $vat_src_code;
698  $this->line->tva_tx = $txtva;
699  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
700  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
701  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
702  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
703  $this->line->fk_product = $fk_product;
704  $this->line->product_type = $type;
705  $this->line->fk_remise_except = $fk_remise_except;
706  $this->line->remise_percent = $remise_percent;
707  $this->line->subprice = $pu_ht;
708  $this->line->rang = $ranktouse;
709  $this->line->info_bits = $info_bits;
710  $this->line->total_ht = $total_ht;
711  $this->line->total_tva = $total_tva;
712  $this->line->total_localtax1 = $total_localtax1;
713  $this->line->total_localtax2 = $total_localtax2;
714  $this->line->total_ttc = $total_ttc;
715  $this->line->special_code = $special_code;
716  $this->line->fk_parent_line = $fk_parent_line;
717  $this->line->fk_unit = $fk_unit;
718 
719  $this->line->date_start = $date_start;
720  $this->line->date_end = $date_end;
721 
722  $this->line->fk_fournprice = $fk_fournprice;
723  $this->line->pa_ht = $pa_ht;
724 
725  $this->line->origin_id = $origin_id;
726  $this->line->origin = $origin;
727 
728  // Multicurrency
729  $this->line->fk_multicurrency = $this->fk_multicurrency;
730  $this->line->multicurrency_code = $this->multicurrency_code;
731  $this->line->multicurrency_subprice = $pu_ht_devise;
732  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
733  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
734  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
735 
736  // Mise en option de la ligne
737  if (empty($qty) && empty($special_code)) {
738  $this->line->special_code = 3;
739  }
740 
741  // TODO deprecated
742  $this->line->price = $price;
743 
744  if (is_array($array_options) && count($array_options) > 0) {
745  $this->line->array_options = $array_options;
746  }
747 
748  $result = $this->line->insert();
749  if ($result > 0) {
750  // Reorder if child line
751  if (!empty($fk_parent_line)) {
752  $this->line_order(true, 'DESC');
753  } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
754  $linecount = count($this->lines);
755  for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
756  $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
757  }
758  }
759 
760  // Mise a jour informations denormalisees au niveau de la propale meme
761  if (empty($noupdateafterinsertline)) {
762  $result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
763  }
764 
765  if ($result > 0) {
766  $this->db->commit();
767  return $this->line->id;
768  } else {
769  $this->error = $this->db->error();
770  $this->db->rollback();
771  return -1;
772  }
773  } else {
774  $this->error = $this->line->error;
775  $this->errors = $this->line->errors;
776  $this->db->rollback();
777  return -2;
778  }
779  } else {
780  dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
781  return -3;
782  }
783  }
784 
785 
815  public function updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $desc = '', $price_base_type = 'HT', $info_bits = 0, $special_code = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $type = 0, $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $rang = 0)
816  {
817  global $mysoc, $langs;
818 
819  dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
820  txtva=$txtva, desc=$desc, price_base_type=$price_base_type, info_bits=$info_bits, special_code=$special_code, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, type=$type, date_start=$date_start, date_end=$date_end");
821  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
822 
823  // Clean parameters
824  $remise_percent = price2num($remise_percent);
825  $qty = price2num($qty);
826  $pu = price2num($pu);
827  $pu_ht_devise = price2num($pu_ht_devise);
828  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
829  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
830  }
831  $txlocaltax1 = price2num($txlocaltax1);
832  $txlocaltax2 = price2num($txlocaltax2);
833  $pa_ht = price2num($pa_ht);
834  if (empty($qty) && empty($special_code)) {
835  $special_code = 3; // Set option tag
836  }
837  if (!empty($qty) && $special_code == 3) {
838  $special_code = 0; // Remove option tag
839  }
840  if (empty($type)) {
841  $type = 0;
842  }
843 
844  if ($date_start && $date_end && $date_start > $date_end) {
845  $langs->load("errors");
846  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
847  return -1;
848  }
849 
850  if ($this->statut == self::STATUS_DRAFT) {
851  $this->db->begin();
852 
853  // Calcul du total TTC et de la TVA pour la ligne a partir de
854  // qty, pu, remise_percent et txtva
855  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
856  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
857 
858  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
859 
860  // Clean vat code
861  $reg = array();
862  $vat_src_code = '';
863  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
864  $vat_src_code = $reg[1];
865  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
866  }
867 
868  // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
869 
870  $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
871  $total_ht = $tabprice[0];
872  $total_tva = $tabprice[1];
873  $total_ttc = $tabprice[2];
874  $total_localtax1 = $tabprice[9];
875  $total_localtax2 = $tabprice[10];
876  $pu_ht = $tabprice[3];
877  $pu_tva = $tabprice[4];
878  $pu_ttc = $tabprice[5];
879 
880  // MultiCurrency
881  $multicurrency_total_ht = $tabprice[16];
882  $multicurrency_total_tva = $tabprice[17];
883  $multicurrency_total_ttc = $tabprice[18];
884  $pu_ht_devise = $tabprice[19];
885 
886  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
887  $price = $pu;
888  $remise = 0;
889  if ($remise_percent > 0) {
890  $remise = round(($pu * $remise_percent / 100), 2);
891  $price = $pu - $remise;
892  }
893 
894  //Fetch current line from the database and then clone the object and set it in $oldline property
895  $line = new PropaleLigne($this->db);
896  $line->fetch($rowid);
897 
898  $staticline = clone $line;
899 
900  $line->oldline = $staticline;
901  $this->line = $line;
902  $this->line->context = $this->context;
903  $this->line->rang = $rang;
904 
905  // Reorder if fk_parent_line change
906  if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
907  $rangmax = $this->line_max($fk_parent_line);
908  $this->line->rang = $rangmax + 1;
909  }
910 
911  $this->line->id = $rowid;
912  $this->line->label = $label;
913  $this->line->desc = $desc;
914  $this->line->qty = $qty;
915  $this->line->product_type = $type;
916  $this->line->vat_src_code = $vat_src_code;
917  $this->line->tva_tx = $txtva;
918  $this->line->localtax1_tx = $txlocaltax1;
919  $this->line->localtax2_tx = $txlocaltax2;
920  $this->line->localtax1_type = $localtaxes_type[0];
921  $this->line->localtax2_type = $localtaxes_type[2];
922  $this->line->remise_percent = $remise_percent;
923  $this->line->subprice = $pu_ht;
924  $this->line->info_bits = $info_bits;
925 
926  $this->line->total_ht = $total_ht;
927  $this->line->total_tva = $total_tva;
928  $this->line->total_localtax1 = $total_localtax1;
929  $this->line->total_localtax2 = $total_localtax2;
930  $this->line->total_ttc = $total_ttc;
931  $this->line->special_code = $special_code;
932  $this->line->fk_parent_line = $fk_parent_line;
933  $this->line->skip_update_total = $skip_update_total;
934  $this->line->fk_unit = $fk_unit;
935 
936  $this->line->fk_fournprice = $fk_fournprice;
937  $this->line->pa_ht = $pa_ht;
938 
939  $this->line->date_start = $date_start;
940  $this->line->date_end = $date_end;
941 
942  if (is_array($array_options) && count($array_options) > 0) {
943  // We replace values in this->line->array_options only for entries defined into $array_options
944  foreach ($array_options as $key => $value) {
945  $this->line->array_options[$key] = $array_options[$key];
946  }
947  }
948 
949  // Multicurrency
950  $this->line->multicurrency_subprice = $pu_ht_devise;
951  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
952  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
953  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
954 
955  $result = $this->line->update($notrigger);
956  if ($result > 0) {
957  // Reorder if child line
958  if (!empty($fk_parent_line)) {
959  $this->line_order(true, 'DESC');
960  }
961 
962  $this->update_price(1, 'auto');
963 
964  $this->fk_propal = $this->id;
965  $this->rowid = $rowid;
966 
967  $this->db->commit();
968  return $result;
969  } else {
970  $this->error = $this->line->error;
971  $this->errors = $this->line->errors;
972  $this->db->rollback();
973  return -1;
974  }
975  } else {
976  dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
977  return -2;
978  }
979  }
980 
981 
988  public function deleteline($lineid)
989  {
990  global $user;
991 
992  if ($this->statut == self::STATUS_DRAFT) {
993  $this->db->begin();
994 
995  $line = new PropaleLigne($this->db);
996 
997  // For triggers
998  $line->fetch($lineid);
999 
1000  if ($line->delete($user) > 0) {
1001  $this->update_price(1);
1002 
1003  $this->db->commit();
1004  return 1;
1005  } else {
1006  $this->error = $line->error;
1007  $this->errors = $line->errors;
1008  $this->db->rollback();
1009  return -1;
1010  }
1011  } else {
1012  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1013  return -2;
1014  }
1015  }
1016 
1017 
1026  public function create($user, $notrigger = 0)
1027  {
1028  global $conf, $hookmanager, $mysoc;
1029  $error = 0;
1030 
1031  $now = dol_now();
1032 
1033  // Clean parameters
1034  if (empty($this->date)) {
1035  $this->date = $this->datep;
1036  }
1037  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1038  if (empty($this->availability_id)) {
1039  $this->availability_id = 0;
1040  }
1041  if (empty($this->demand_reason_id)) {
1042  $this->demand_reason_id = 0;
1043  }
1044 
1045  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1046  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1047  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1048  } else {
1049  $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1050  }
1051  if (empty($this->fk_multicurrency)) {
1052  $this->multicurrency_code = $conf->currency;
1053  $this->fk_multicurrency = 0;
1054  $this->multicurrency_tx = 1;
1055  }
1056 
1057  // Set tmp vars
1058  $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1059 
1060  dol_syslog(get_class($this)."::create");
1061 
1062  // Check parameters
1063  $result = $this->fetch_thirdparty();
1064  if ($result < 0) {
1065  $this->error = "Failed to fetch company";
1066  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1067  return -3;
1068  }
1069 
1070  // Check parameters
1071  if (!empty($this->ref)) { // We check that ref is not already used
1072  $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1073  if ($result > 0) {
1074  $this->error = 'ErrorRefAlreadyExists';
1075  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1076  $this->db->rollback();
1077  return -1;
1078  }
1079  }
1080 
1081  if (empty($this->date)) {
1082  $this->error = "Date of proposal is required";
1083  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1084  return -4;
1085  }
1086 
1087 
1088  $this->db->begin();
1089 
1090  // Insert into database
1091  $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1092  $sql .= "fk_soc";
1093  $sql .= ", price";
1094  $sql .= ", remise";
1095  $sql .= ", remise_percent";
1096  $sql .= ", remise_absolue";
1097  $sql .= ", total_tva";
1098  $sql .= ", total_ttc";
1099  $sql .= ", datep";
1100  $sql .= ", datec";
1101  $sql .= ", ref";
1102  $sql .= ", fk_user_author";
1103  $sql .= ", note_private";
1104  $sql .= ", note_public";
1105  $sql .= ", model_pdf";
1106  $sql .= ", fin_validite";
1107  $sql .= ", fk_cond_reglement";
1108  $sql .= ", deposit_percent";
1109  $sql .= ", fk_mode_reglement";
1110  $sql .= ", fk_account";
1111  $sql .= ", ref_client";
1112  $sql .= ", ref_ext";
1113  $sql .= ", date_livraison";
1114  $sql .= ", fk_shipping_method";
1115  $sql .= ", fk_warehouse";
1116  $sql .= ", fk_availability";
1117  $sql .= ", fk_input_reason";
1118  $sql .= ", fk_projet";
1119  $sql .= ", fk_incoterms";
1120  $sql .= ", location_incoterms";
1121  $sql .= ", entity";
1122  $sql .= ", fk_multicurrency";
1123  $sql .= ", multicurrency_code";
1124  $sql .= ", multicurrency_tx";
1125  $sql .= ") ";
1126  $sql .= " VALUES (";
1127  $sql .= $this->socid;
1128  $sql .= ", 0";
1129  $sql .= ", ".((float) $this->remise); // deprecated
1130  $sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL');
1131  $sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL'); // deprecated
1132  $sql .= ", 0";
1133  $sql .= ", 0";
1134  $sql .= ", '".$this->db->idate($this->date)."'";
1135  $sql .= ", '".$this->db->idate($now)."'";
1136  $sql .= ", '(PROV)'";
1137  $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1138  $sql .= ", '".$this->db->escape($this->note_private)."'";
1139  $sql .= ", '".$this->db->escape($this->note_public)."'";
1140  $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1141  $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1142  $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1143  $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1144  $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1145  $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1146  $sql .= ", '".$this->db->escape($this->ref_client)."'";
1147  $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1148  $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1149  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1150  $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1151  $sql .= ", ".$this->availability_id;
1152  $sql .= ", ".$this->demand_reason_id;
1153  $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1154  $sql .= ", ".(int) $this->fk_incoterms;
1155  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1156  $sql .= ", ".setEntity($this);
1157  $sql .= ", ".(int) $this->fk_multicurrency;
1158  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1159  $sql .= ", ".(double) $this->multicurrency_tx;
1160  $sql .= ")";
1161 
1162  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1163  $resql = $this->db->query($sql);
1164  if ($resql) {
1165  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1166 
1167  if ($this->id) {
1168  $this->ref = '(PROV'.$this->id.')';
1169  $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1170 
1171  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1172  $resql = $this->db->query($sql);
1173  if (!$resql) {
1174  $error++;
1175  }
1176 
1177  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1178  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1179  }
1180 
1181  // Add object linked
1182  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1183  foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1184  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, ...))
1185  foreach ($tmp_origin_id as $origin_id) {
1186  $ret = $this->add_object_linked($origin, $origin_id);
1187  if (!$ret) {
1188  $this->error = $this->db->lasterror();
1189  $error++;
1190  }
1191  }
1192  } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1193  {
1194  $origin_id = $tmp_origin_id;
1195  $ret = $this->add_object_linked($origin, $origin_id);
1196  if (!$ret) {
1197  $this->error = $this->db->lasterror();
1198  $error++;
1199  }
1200  }
1201  }
1202  }
1203 
1204  /*
1205  * Insertion du detail des produits dans la base
1206  * Insert products detail in database
1207  */
1208  if (!$error) {
1209  $fk_parent_line = 0;
1210  $num = count($this->lines);
1211 
1212  for ($i = 0; $i < $num; $i++) {
1213  if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1214  // Convert into object this->lines[$i].
1215  $line = (object) $this->lines[$i];
1216  } else {
1217  $line = $this->lines[$i];
1218  }
1219  // Reset fk_parent_line for line that are not child lines or special product
1220  if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1221  $fk_parent_line = 0;
1222  }
1223  // Complete vat rate with code
1224  $vatrate = $line->tva_tx;
1225  if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1226  $vatrate .= ' ('.$line->vat_src_code.')';
1227  }
1228 
1229  if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1230  $originid = $line->origin_id;
1231  $origintype = $line->origin;
1232  } else {
1233  $originid = $line->id;
1234  $origintype = $this->element;
1235  }
1236 
1237  $result = $this->addline(
1238  $line->desc,
1239  $line->subprice,
1240  $line->qty,
1241  $vatrate,
1242  $line->localtax1_tx,
1243  $line->localtax2_tx,
1244  $line->fk_product,
1245  $line->remise_percent,
1246  'HT',
1247  0,
1248  $line->info_bits,
1249  $line->product_type,
1250  $line->rang,
1251  $line->special_code,
1252  $fk_parent_line,
1253  $line->fk_fournprice,
1254  $line->pa_ht,
1255  $line->label,
1256  $line->date_start,
1257  $line->date_end,
1258  $line->array_options,
1259  $line->fk_unit,
1260  $origintype,
1261  $originid,
1262  0,
1263  0,
1264  1
1265  );
1266 
1267  if ($result < 0) {
1268  $error++;
1269  $this->error = $this->db->error;
1270  dol_print_error($this->db);
1271  break;
1272  }
1273 
1274  // Set the id on created row
1275  $line->id = $result;
1276 
1277  // Defined the new fk_parent_line
1278  if ($result > 0 && $line->product_type == 9) {
1279  $fk_parent_line = $result;
1280  }
1281  }
1282  }
1283 
1284  // Set delivery address
1285  /*if (! $error && $this->fk_delivery_address)
1286  {
1287  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1288  $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1289  $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1290  $sql.= " AND entity = ".setEntity($this);
1291 
1292  $result=$this->db->query($sql);
1293  }*/
1294 
1295  if (!$error) {
1296  // Mise a jour infos denormalisees
1297  $resql = $this->update_price(1, 'auto', 0, $mysoc);
1298  if ($resql) {
1299  $action = 'update';
1300 
1301  // Actions on extra fields
1302  if (!$error) {
1303  $result = $this->insertExtraFields();
1304  if ($result < 0) {
1305  $error++;
1306  }
1307  }
1308 
1309  if (!$error && !$notrigger) {
1310  // Call trigger
1311  $result = $this->call_trigger('PROPAL_CREATE', $user);
1312  if ($result < 0) {
1313  $error++;
1314  }
1315  // End call triggers
1316  }
1317  } else {
1318  $this->error = $this->db->lasterror();
1319  $error++;
1320  }
1321  }
1322  } else {
1323  $this->error = $this->db->lasterror();
1324  $error++;
1325  }
1326 
1327  if (!$error) {
1328  $this->db->commit();
1329  dol_syslog(get_class($this)."::create done id=".$this->id);
1330  return $this->id;
1331  } else {
1332  $this->db->rollback();
1333  return -2;
1334  }
1335  } else {
1336  $this->error = $this->db->lasterror();
1337  $this->db->rollback();
1338  return -1;
1339  }
1340  }
1341 
1351  public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false)
1352  {
1353  global $conf, $hookmanager, $mysoc;
1354 
1355  dol_include_once('/projet/class/project.class.php');
1356 
1357  $error = 0;
1358  $now = dol_now();
1359 
1360  dol_syslog(__METHOD__, LOG_DEBUG);
1361 
1362  $object = new self($this->db);
1363 
1364  $this->db->begin();
1365 
1366  // Load source object
1367  $object->fetch($this->id);
1368 
1369  $objsoc = new Societe($this->db);
1370 
1371  // Change socid if needed
1372  if (!empty($socid) && $socid != $object->socid) {
1373  if ($objsoc->fetch($socid) > 0) {
1374  $object->socid = $objsoc->id;
1375  $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1376  $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1377  $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1378  $object->fk_delivery_address = '';
1379 
1380  /*if (isModEnabled('project'))
1381  {
1382  $project = new Project($db);
1383  if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1384  if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1385  else $clonedObj->fk_project = '';
1386  } else {
1387  $clonedObj->fk_project = '';
1388  }
1389  }*/
1390  $object->fk_project = ''; // A cloned proposal is set by default to no project.
1391  }
1392 
1393  // reset ref_client
1394  $object->ref_client = '';
1395 
1396  // TODO Change product price if multi-prices
1397  } else {
1398  $objsoc->fetch($object->socid);
1399  }
1400 
1401  // update prices
1402  if ($update_prices === true) {
1403  if ($objsoc->id > 0 && !empty($object->lines)) {
1404  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1405  // If price per customer
1406  require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1407  }
1408 
1409  foreach ($object->lines as $line) {
1410  $line->id = 0;
1411 
1412  if ($line->fk_product > 0) {
1413  $prod = new Product($this->db);
1414  $res = $prod->fetch($line->fk_product);
1415  if ($res > 0) {
1416  $pu_ht = $prod->price;
1417  $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1418  $remise_percent = $objsoc->remise_percent;
1419 
1420  if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
1421  $pu_ht = $prod->multiprices[$objsoc->price_level];
1422  if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
1423  if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1424  $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1425  }
1426  }
1427  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1428  $prodcustprice = new Productcustomerprice($this->db);
1429  $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1430  $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1431  if ($result) {
1432  // If there is some prices specific to the customer
1433  if (count($prodcustprice->lines) > 0) {
1434  $pu_ht = price($prodcustprice->lines[0]->price);
1435  $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1436  if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1437  $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1438  }
1439  }
1440  }
1441  }
1442 
1443  $line->subprice = $pu_ht;
1444  $line->tva_tx = $tva_tx;
1445  $line->remise_percent = $remise_percent;
1446  }
1447  }
1448  }
1449  }
1450  }
1451 
1452  $object->id = 0;
1453  $object->ref = '';
1454  $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1455  $object->statut = self::STATUS_DRAFT;
1456 
1457  // Clear fields
1458  $object->user_author = $user->id;
1459  $object->user_valid = 0;
1460  $object->date = $now;
1461  $object->datep = $now; // deprecated
1462  $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1463  if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
1464  $object->ref_client = '';
1465  }
1466  if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1467  $object->note_private = '';
1468  $object->note_public = '';
1469  }
1470  // Create clone
1471  $object->context['createfromclone'] = 'createfromclone';
1472  $result = $object->create($user);
1473  if ($result < 0) {
1474  $this->error = $object->error;
1475  $this->errors = array_merge($this->errors, $object->errors);
1476  $error++;
1477  }
1478 
1479  if (!$error) {
1480  // copy internal contacts
1481  if ($object->copy_linked_contact($this, 'internal') < 0) {
1482  $error++;
1483  }
1484  }
1485 
1486  if (!$error) {
1487  // copy external contacts if same company
1488  if ($this->socid == $object->socid) {
1489  if ($object->copy_linked_contact($this, 'external') < 0) {
1490  $error++;
1491  }
1492  }
1493  }
1494 
1495  if (!$error) {
1496  // Hook of thirdparty module
1497  if (is_object($hookmanager)) {
1498  $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1499  $action = '';
1500  $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1501  if ($reshook < 0) {
1502  $this->errors += $hookmanager->errors;
1503  $this->error = $hookmanager->error;
1504  $error++;
1505  }
1506  }
1507  }
1508 
1509  unset($object->context['createfromclone']);
1510 
1511  // End
1512  if (!$error) {
1513  $this->db->commit();
1514  return $object->id;
1515  } else {
1516  $this->db->rollback();
1517  return -1;
1518  }
1519  }
1520 
1530  public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1531  {
1532  $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1533  $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1534  $sql .= ", p.datec";
1535  $sql .= ", p.date_signature as dates";
1536  $sql .= ", p.date_valid as datev";
1537  $sql .= ", p.datep as dp";
1538  $sql .= ", p.fin_validite as dfv";
1539  $sql .= ", p.date_livraison as delivery_date";
1540  $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1541  $sql .= ", p.note_private, p.note_public";
1542  $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1543  $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1544  $sql .= ", p.fk_delivery_address";
1545  $sql .= ", p.fk_availability";
1546  $sql .= ", p.fk_input_reason";
1547  $sql .= ", p.fk_cond_reglement";
1548  $sql .= ", p.fk_mode_reglement";
1549  $sql .= ', p.fk_account';
1550  $sql .= ", p.fk_shipping_method";
1551  $sql .= ", p.fk_warehouse";
1552  $sql .= ", p.fk_incoterms, p.location_incoterms";
1553  $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1554  $sql .= ", p.tms as date_modification";
1555  $sql .= ", i.libelle as label_incoterms";
1556  $sql .= ", c.label as statut_label";
1557  $sql .= ", ca.code as availability_code, ca.label as availability";
1558  $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1559  $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1560  $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1561  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1562  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1563  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1564  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON p.fk_cond_reglement = cr.rowid AND cr.entity IN ('.getEntity('c_payment_term').')';
1565  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1566  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1567  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1568 
1569  if (!empty($ref)) {
1570  if (!empty($forceentity)) {
1571  $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1572  } else {
1573  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1574  }
1575  $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1576  } else {
1577  // Dont't use entity if you use rowid
1578  $sql .= " WHERE p.rowid = ".((int) $rowid);
1579  }
1580 
1581  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1582  $resql = $this->db->query($sql);
1583  if ($resql) {
1584  if ($this->db->num_rows($resql)) {
1585  $obj = $this->db->fetch_object($resql);
1586 
1587  $this->id = $obj->rowid;
1588  $this->entity = $obj->entity;
1589 
1590  $this->ref = $obj->ref;
1591  $this->ref_client = $obj->ref_client;
1592  $this->ref_ext = $obj->ref_ext;
1593  $this->remise = $obj->remise;
1594  $this->remise_percent = $obj->remise_percent;
1595  $this->remise_absolue = $obj->remise_absolue;
1596  $this->total = $obj->total_ttc; // TODO deprecated
1597  $this->total_ttc = $obj->total_ttc;
1598  $this->total_ht = $obj->total_ht;
1599  $this->total_tva = $obj->total_tva;
1600  $this->total_localtax1 = $obj->localtax1;
1601  $this->total_localtax2 = $obj->localtax2;
1602 
1603  $this->socid = $obj->fk_soc;
1604  $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1605 
1606  $this->fk_project = $obj->fk_project;
1607  $this->project = null; // Clear if another value was already set by fetch_projet
1608 
1609  $this->model_pdf = $obj->model_pdf;
1610  $this->modelpdf = $obj->model_pdf; // deprecated
1611  $this->last_main_doc = $obj->last_main_doc;
1612  $this->note = $obj->note_private; // TODO deprecated
1613  $this->note_private = $obj->note_private;
1614  $this->note_public = $obj->note_public;
1615 
1616  $this->status = (int) $obj->fk_statut;
1617  $this->statut = $this->status; // deprecated
1618  $this->statut_libelle = $obj->statut_label;
1619 
1620  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1621  $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1622  $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1623  $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1624  $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1625  $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1626  $this->date = $this->db->jdate($obj->dp); // Proposal date
1627  $this->datep = $this->db->jdate($obj->dp); // deprecated
1628  $this->fin_validite = $this->db->jdate($obj->dfv);
1629  $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
1630  $this->delivery_date = $this->db->jdate($obj->delivery_date);
1631  $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1632  $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1633  $this->availability_id = $obj->fk_availability;
1634  $this->availability_code = $obj->availability_code;
1635  $this->availability = $obj->availability;
1636  $this->demand_reason_id = $obj->fk_input_reason;
1637  $this->demand_reason_code = $obj->demand_reason_code;
1638  $this->demand_reason = $obj->demand_reason;
1639  $this->fk_address = $obj->fk_delivery_address;
1640 
1641  $this->mode_reglement_id = $obj->fk_mode_reglement;
1642  $this->mode_reglement_code = $obj->mode_reglement_code;
1643  $this->mode_reglement = $obj->mode_reglement;
1644  $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1645  $this->cond_reglement_id = $obj->fk_cond_reglement;
1646  $this->cond_reglement_code = $obj->cond_reglement_code;
1647  $this->cond_reglement = $obj->cond_reglement;
1648  $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1649  $this->deposit_percent = $obj->deposit_percent;
1650 
1651  $this->extraparams = (array) json_decode($obj->extraparams, true);
1652 
1653  $this->user_author_id = $obj->fk_user_author;
1654  $this->user_valid_id = $obj->fk_user_valid;
1655  $this->user_close_id = $obj->fk_user_cloture;
1656 
1657  //Incoterms
1658  $this->fk_incoterms = $obj->fk_incoterms;
1659  $this->location_incoterms = $obj->location_incoterms;
1660  $this->label_incoterms = $obj->label_incoterms;
1661 
1662  // Multicurrency
1663  $this->fk_multicurrency = $obj->fk_multicurrency;
1664  $this->multicurrency_code = $obj->multicurrency_code;
1665  $this->multicurrency_tx = $obj->multicurrency_tx;
1666  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1667  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1668  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1669 
1670  if ($obj->fk_statut == self::STATUS_DRAFT) {
1671  $this->brouillon = 1;
1672  }
1673 
1674  // Retrieve all extrafield
1675  // fetch optionals attributes and labels
1676  $this->fetch_optionals();
1677 
1678  $this->db->free($resql);
1679 
1680  $this->lines = array();
1681 
1682  // Lines
1683  $result = $this->fetch_lines();
1684  if ($result < 0) {
1685  return -3;
1686  }
1687 
1688  return 1;
1689  }
1690 
1691  $this->error = "Record Not Found";
1692  return 0;
1693  } else {
1694  $this->error = $this->db->lasterror();
1695  return -1;
1696  }
1697  }
1698 
1706  public function update(User $user, $notrigger = 0)
1707  {
1708  global $conf;
1709 
1710  $error = 0;
1711 
1712  // Clean parameters
1713  if (isset($this->ref)) {
1714  $this->ref = trim($this->ref);
1715  }
1716  if (isset($this->ref_client)) {
1717  $this->ref_client = trim($this->ref_client);
1718  }
1719  if (isset($this->note) || isset($this->note_private)) {
1720  $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1721  }
1722  if (isset($this->note_public)) {
1723  $this->note_public = trim($this->note_public);
1724  }
1725  if (isset($this->model_pdf)) {
1726  $this->model_pdf = trim($this->model_pdf);
1727  }
1728  if (isset($this->import_key)) {
1729  $this->import_key = trim($this->import_key);
1730  }
1731  if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1732  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1733  }
1734 
1735  // Check parameters
1736  // Put here code to add control on parameters values
1737 
1738  // Update request
1739  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1740  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1741  $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1742  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1743  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1744  $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1745  if (!empty($this->fin_validite)) {
1746  $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1747  }
1748  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1749  $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1750  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1751  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1752  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1753  $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1754  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1755  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1756  $sql .= " fk_user_valid=".(isset($this->user_valid) ? $this->user_valid : "null").",";
1757  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1758  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1759  $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1760  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1761  $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1762  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1763  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1764  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1765  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null")."";
1766  $sql .= " WHERE rowid=".((int) $this->id);
1767 
1768  $this->db->begin();
1769 
1770  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1771  $resql = $this->db->query($sql);
1772  if (!$resql) {
1773  $error++;
1774  $this->errors[] = "Error ".$this->db->lasterror();
1775  }
1776 
1777  if (!$error) {
1778  $result = $this->insertExtraFields();
1779  if ($result < 0) {
1780  $error++;
1781  }
1782  }
1783 
1784  if (!$error && !$notrigger) {
1785  // Call trigger
1786  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1787  if ($result < 0) {
1788  $error++;
1789  }
1790  // End call triggers
1791  }
1792 
1793  // Commit or rollback
1794  if ($error) {
1795  foreach ($this->errors as $errmsg) {
1796  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1797  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1798  }
1799  $this->db->rollback();
1800  return -1 * $error;
1801  } else {
1802  $this->db->commit();
1803  return 1;
1804  }
1805  }
1806 
1807 
1808  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1818  public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $filters = '')
1819  {
1820  // phpcs:enable
1821  global $langs, $conf;
1822 
1823  $this->lines = array();
1824 
1825  $sql = 'SELECT d.rowid, d.fk_propal, d.fk_parent_line, d.label as custom_label, d.description, d.price, d.vat_src_code, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.localtax1_type, d.localtax2_type, d.qty, d.fk_remise_except, d.remise_percent, d.subprice, d.fk_product,';
1826  $sql .= ' d.info_bits, d.total_ht, d.total_tva, d.total_localtax1, d.total_localtax2, d.total_ttc, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht, d.special_code, d.rang, d.product_type,';
1827  $sql .= ' d.fk_unit,';
1828  $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tobatch as product_tobatch, p.barcode as product_barcode,';
1829  $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1830  $sql .= ' d.date_start, d.date_end,';
1831  $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1832  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1833  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1834  $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1835  if ($only_product) {
1836  $sql .= ' AND p.fk_product_type = 0';
1837  }
1838  if ($filters) {
1839  $sql .= $filters;
1840  }
1841  $sql .= ' ORDER by d.rang';
1842 
1843  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1844  $result = $this->db->query($sql);
1845  if ($result) {
1846  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1847 
1848  $num = $this->db->num_rows($result);
1849 
1850  $i = 0;
1851  while ($i < $num) {
1852  $objp = $this->db->fetch_object($result);
1853 
1854  $line = new PropaleLigne($this->db);
1855 
1856  $line->rowid = $objp->rowid; //Deprecated
1857  $line->id = $objp->rowid;
1858  $line->fk_propal = $objp->fk_propal;
1859  $line->fk_parent_line = $objp->fk_parent_line;
1860  $line->product_type = $objp->product_type;
1861  $line->label = $objp->custom_label;
1862  $line->desc = $objp->description; // Description ligne
1863  $line->description = $objp->description; // Description ligne
1864  $line->qty = $objp->qty;
1865  $line->vat_src_code = $objp->vat_src_code;
1866  $line->tva_tx = $objp->tva_tx;
1867  $line->localtax1_tx = $objp->localtax1_tx;
1868  $line->localtax2_tx = $objp->localtax2_tx;
1869  $line->localtax1_type = $objp->localtax1_type;
1870  $line->localtax2_type = $objp->localtax2_type;
1871  $line->subprice = $objp->subprice;
1872  $line->fk_remise_except = $objp->fk_remise_except;
1873  $line->remise_percent = $objp->remise_percent;
1874  $line->price = $objp->price; // TODO deprecated
1875 
1876  $line->info_bits = $objp->info_bits;
1877  $line->total_ht = $objp->total_ht;
1878  $line->total_tva = $objp->total_tva;
1879  $line->total_localtax1 = $objp->total_localtax1;
1880  $line->total_localtax2 = $objp->total_localtax2;
1881  $line->total_ttc = $objp->total_ttc;
1882  $line->fk_fournprice = $objp->fk_fournprice;
1883  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1884  $line->pa_ht = $marginInfos[0];
1885  $line->marge_tx = $marginInfos[1];
1886  $line->marque_tx = $marginInfos[2];
1887  $line->special_code = $objp->special_code;
1888  $line->rang = $objp->rang;
1889 
1890  $line->fk_product = $objp->fk_product;
1891 
1892  $line->ref = $objp->product_ref; // deprecated
1893  $line->libelle = $objp->product_label; // deprecated
1894 
1895  $line->product_ref = $objp->product_ref;
1896  $line->product_label = $objp->product_label;
1897  $line->product_desc = $objp->product_desc; // Description produit
1898  $line->product_tobatch = $objp->product_tobatch;
1899  $line->product_barcode = $objp->product_barcode;
1900 
1901  $line->fk_product_type = $objp->fk_product_type; // deprecated
1902  $line->fk_unit = $objp->fk_unit;
1903  $line->weight = $objp->weight;
1904  $line->weight_units = $objp->weight_units;
1905  $line->volume = $objp->volume;
1906  $line->volume_units = $objp->volume_units;
1907 
1908  $line->date_start = $this->db->jdate($objp->date_start);
1909  $line->date_end = $this->db->jdate($objp->date_end);
1910 
1911  // Multicurrency
1912  $line->fk_multicurrency = $objp->fk_multicurrency;
1913  $line->multicurrency_code = $objp->multicurrency_code;
1914  $line->multicurrency_subprice = $objp->multicurrency_subprice;
1915  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1916  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1917  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1918 
1919  $line->fetch_optionals();
1920 
1921  // multilangs
1922  if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1923  $tmpproduct = new Product($this->db);
1924  $tmpproduct->fetch($objp->fk_product);
1925  $tmpproduct->getMultiLangs();
1926 
1927  $line->multilangs = $tmpproduct->multilangs;
1928  }
1929 
1930  $this->lines[$i] = $line;
1931 
1932  $i++;
1933  }
1934 
1935  $this->db->free($result);
1936 
1937  return $num;
1938  } else {
1939  $this->error = $this->db->lasterror();
1940  return -3;
1941  }
1942  }
1943 
1951  public function valid($user, $notrigger = 0)
1952  {
1953  global $conf;
1954 
1955  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1956 
1957  $error = 0;
1958 
1959  // Protection
1960  if ($this->statut == self::STATUS_VALIDATED) {
1961  dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1962  return 0;
1963  }
1964 
1965  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
1966  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
1967  $this->error = 'ErrorPermissionDenied';
1968  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1969  return -1;
1970  }
1971 
1972  $now = dol_now();
1973 
1974  $this->db->begin();
1975 
1976  // Numbering module definition
1977  $soc = new Societe($this->db);
1978  $soc->fetch($this->socid);
1979 
1980  // Define new ref
1981  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1982  $num = $this->getNextNumRef($soc);
1983  } else {
1984  $num = $this->ref;
1985  }
1986  $this->newref = dol_sanitizeFileName($num);
1987 
1988  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1989  $sql .= " SET ref = '".$this->db->escape($num)."',";
1990  $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
1991  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
1992 
1993  dol_syslog(get_class($this)."::valid", LOG_DEBUG);
1994  $resql = $this->db->query($sql);
1995  if (!$resql) {
1996  dol_print_error($this->db);
1997  $error++;
1998  }
1999 
2000  // Trigger calls
2001  if (!$error && !$notrigger) {
2002  // Call trigger
2003  $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2004  if ($result < 0) {
2005  $error++;
2006  }
2007  // End call triggers
2008  }
2009 
2010  if (!$error) {
2011  $this->oldref = $this->ref;
2012 
2013  // Rename directory if dir was a temporary ref
2014  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2015  // Now we rename also files into index
2016  $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'propale/".$this->db->escape($this->newref)."'";
2017  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2018  $resql = $this->db->query($sql);
2019  if (!$resql) {
2020  $error++;
2021  $this->error = $this->db->lasterror();
2022  }
2023 
2024  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2025  $oldref = dol_sanitizeFileName($this->ref);
2026  $newref = dol_sanitizeFileName($num);
2027  $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2028  $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2029  if (!$error && file_exists($dirsource)) {
2030  dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2031  if (@rename($dirsource, $dirdest)) {
2032  dol_syslog("Rename ok");
2033  // Rename docs starting with $oldref with $newref
2034  $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2035  foreach ($listoffiles as $fileentry) {
2036  $dirsource = $fileentry['name'];
2037  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2038  $dirsource = $fileentry['path'].'/'.$dirsource;
2039  $dirdest = $fileentry['path'].'/'.$dirdest;
2040  @rename($dirsource, $dirdest);
2041  }
2042  }
2043  }
2044  }
2045 
2046  $this->ref = $num;
2047  $this->brouillon = 0;
2048  $this->statut = self::STATUS_VALIDATED;
2049  $this->user_valid_id = $user->id;
2050  $this->datev = $now;
2051 
2052  $this->db->commit();
2053  return 1;
2054  } else {
2055  $this->db->rollback();
2056  return -1;
2057  }
2058  }
2059 
2060 
2061  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2070  public function set_date($user, $date, $notrigger = 0)
2071  {
2072  // phpcs:enable
2073  if (empty($date)) {
2074  $this->error = 'ErrorBadParameter';
2075  dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2076  return -1;
2077  }
2078 
2079  if (!empty($user->rights->propal->creer)) {
2080  $error = 0;
2081 
2082  $this->db->begin();
2083 
2084  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2085  $sql .= " WHERE rowid = ".((int) $this->id);
2086 
2087  dol_syslog(__METHOD__, LOG_DEBUG);
2088  $resql = $this->db->query($sql);
2089  if (!$resql) {
2090  $this->errors[] = $this->db->error();
2091  $error++;
2092  }
2093 
2094  if (!$error) {
2095  $this->oldcopy = clone $this;
2096  $this->date = $date;
2097  $this->datep = $date; // deprecated
2098  }
2099 
2100  if (!$notrigger && empty($error)) {
2101  // Call trigger
2102  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2103  if ($result < 0) {
2104  $error++;
2105  }
2106  // End call triggers
2107  }
2108 
2109  if (!$error) {
2110  $this->db->commit();
2111  return 1;
2112  } else {
2113  foreach ($this->errors as $errmsg) {
2114  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2115  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2116  }
2117  $this->db->rollback();
2118  return -1 * $error;
2119  }
2120  }
2121  }
2122 
2123  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2132  public function set_echeance($user, $date_end_validity, $notrigger = 0)
2133  {
2134  // phpcs:enable
2135  if (!empty($user->rights->propal->creer)) {
2136  $error = 0;
2137 
2138  $this->db->begin();
2139 
2140  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2141  $sql .= " WHERE rowid = ".((int) $this->id);
2142 
2143  dol_syslog(__METHOD__, LOG_DEBUG);
2144 
2145  $resql = $this->db->query($sql);
2146  if (!$resql) {
2147  $this->errors[] = $this->db->error();
2148  $error++;
2149  }
2150 
2151 
2152  if (!$error) {
2153  $this->oldcopy = clone $this;
2154  $this->fin_validite = $date_end_validity;
2155  }
2156 
2157  if (!$notrigger && empty($error)) {
2158  // Call trigger
2159  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2160  if ($result < 0) {
2161  $error++;
2162  }
2163  // End call triggers
2164  }
2165 
2166  if (!$error) {
2167  $this->db->commit();
2168  return 1;
2169  } else {
2170  foreach ($this->errors as $errmsg) {
2171  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2172  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2173  }
2174  $this->db->rollback();
2175  return -1 * $error;
2176  }
2177  }
2178  }
2179 
2180  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2190  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2191  {
2192  // phpcs:enable
2193  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2194  }
2195 
2204  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2205  {
2206  if (!empty($user->rights->propal->creer)) {
2207  $error = 0;
2208 
2209  $this->db->begin();
2210 
2211  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2212  $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2213  $sql .= " WHERE rowid = ".((int) $this->id);
2214 
2215  dol_syslog(__METHOD__, LOG_DEBUG);
2216  $resql = $this->db->query($sql);
2217  if (!$resql) {
2218  $this->errors[] = $this->db->error();
2219  $error++;
2220  }
2221 
2222  if (!$error) {
2223  $this->oldcopy = clone $this;
2224  $this->date_livraison = $delivery_date;
2225  $this->delivery_date = $delivery_date;
2226  }
2227 
2228  if (!$notrigger && empty($error)) {
2229  // Call trigger
2230  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2231  if ($result < 0) {
2232  $error++;
2233  }
2234  // End call triggers
2235  }
2236 
2237  if (!$error) {
2238  $this->db->commit();
2239  return 1;
2240  } else {
2241  foreach ($this->errors as $errmsg) {
2242  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2243  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2244  }
2245  $this->db->rollback();
2246  return -1 * $error;
2247  }
2248  }
2249  }
2250 
2251  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2260  public function set_availability($user, $id, $notrigger = 0)
2261  {
2262  // phpcs:enable
2263  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2264  $error = 0;
2265 
2266  $this->db->begin();
2267 
2268  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2269  $sql .= " SET fk_availability = ".((int) $id);
2270  $sql .= " WHERE rowid = ".((int) $this->id);
2271 
2272  dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2273  $resql = $this->db->query($sql);
2274  if (!$resql) {
2275  $this->errors[] = $this->db->error();
2276  $error++;
2277  }
2278 
2279  if (!$error) {
2280  $this->oldcopy = clone $this;
2281  $this->fk_availability = $id;
2282  $this->availability_id = $id;
2283  }
2284 
2285  if (!$notrigger && empty($error)) {
2286  // Call trigger
2287  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2288  if ($result < 0) {
2289  $error++;
2290  }
2291  // End call triggers
2292  }
2293 
2294  if (!$error) {
2295  $this->db->commit();
2296  return 1;
2297  } else {
2298  foreach ($this->errors as $errmsg) {
2299  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2300  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2301  }
2302  $this->db->rollback();
2303  return -1 * $error;
2304  }
2305  } else {
2306  $error_str = 'Propal status do not meet requirement '.$this->statut;
2307  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2308  $this->error = $error_str;
2309  $this->errors[] = $this->error;
2310  return -2;
2311  }
2312  }
2313 
2314  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2323  public function set_demand_reason($user, $id, $notrigger = 0)
2324  {
2325  // phpcs:enable
2326  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2327  $error = 0;
2328 
2329  $this->db->begin();
2330 
2331  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2332  $sql .= " SET fk_input_reason = ".((int) $id);
2333  $sql .= " WHERE rowid = ".((int) $this->id);
2334 
2335  dol_syslog(__METHOD__, LOG_DEBUG);
2336  $resql = $this->db->query($sql);
2337  if (!$resql) {
2338  $this->errors[] = $this->db->error();
2339  $error++;
2340  }
2341 
2342 
2343  if (!$error) {
2344  $this->oldcopy = clone $this;
2345  $this->fk_input_reason = $id;
2346  $this->demand_reason_id = $id;
2347  }
2348 
2349 
2350  if (!$notrigger && empty($error)) {
2351  // Call trigger
2352  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2353  if ($result < 0) {
2354  $error++;
2355  }
2356  // End call triggers
2357  }
2358 
2359  if (!$error) {
2360  $this->db->commit();
2361  return 1;
2362  } else {
2363  foreach ($this->errors as $errmsg) {
2364  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2365  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2366  }
2367  $this->db->rollback();
2368  return -1 * $error;
2369  }
2370  } else {
2371  $error_str = 'Propal status do not meet requirement '.$this->statut;
2372  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2373  $this->error = $error_str;
2374  $this->errors[] = $this->error;
2375  return -2;
2376  }
2377  }
2378 
2379  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2388  public function set_ref_client($user, $ref_client, $notrigger = 0)
2389  {
2390  // phpcs:enable
2391  if (!empty($user->rights->propal->creer)) {
2392  $error = 0;
2393 
2394  $this->db->begin();
2395 
2396  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2397  $sql .= " WHERE rowid = ".((int) $this->id);
2398 
2399  dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2400  $resql = $this->db->query($sql);
2401  if (!$resql) {
2402  $this->errors[] = $this->db->error();
2403  $error++;
2404  }
2405 
2406  if (!$error) {
2407  $this->oldcopy = clone $this;
2408  $this->ref_client = $ref_client;
2409  }
2410 
2411  if (!$notrigger && empty($error)) {
2412  // Call trigger
2413  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2414  if ($result < 0) {
2415  $error++;
2416  }
2417  // End call triggers
2418  }
2419 
2420  if (!$error) {
2421  $this->db->commit();
2422  return 1;
2423  } else {
2424  foreach ($this->errors as $errmsg) {
2425  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2426  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2427  }
2428  $this->db->rollback();
2429  return -1 * $error;
2430  }
2431  } else {
2432  return -1;
2433  }
2434  }
2435 
2436  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2445  public function set_remise_percent($user, $remise, $notrigger = 0)
2446  {
2447  // phpcs:enable
2448  $remise = trim($remise) ?trim($remise) : 0;
2449 
2450  if (!empty($user->rights->propal->creer)) {
2451  $remise = price2num($remise, 2);
2452 
2453  $error = 0;
2454 
2455  $this->db->begin();
2456 
2457  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
2458  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2459 
2460  dol_syslog(__METHOD__, LOG_DEBUG);
2461  $resql = $this->db->query($sql);
2462  if (!$resql) {
2463  $this->errors[] = $this->db->error();
2464  $error++;
2465  }
2466 
2467  if (!$error) {
2468  $this->oldcopy = clone $this;
2469  $this->remise_percent = $remise;
2470  $this->update_price(1);
2471  }
2472 
2473  if (!$notrigger && empty($error)) {
2474  // Call trigger
2475  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2476  if ($result < 0) {
2477  $error++;
2478  }
2479  // End call triggers
2480  }
2481 
2482  if (!$error) {
2483  $this->db->commit();
2484  return 1;
2485  } else {
2486  foreach ($this->errors as $errmsg) {
2487  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2488  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2489  }
2490  $this->db->rollback();
2491  return -1 * $error;
2492  }
2493  }
2494  }
2495 
2496 
2497  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2506  public function set_remise_absolue($user, $remise, $notrigger = 0)
2507  {
2508  // phpcs:enable
2509  if (empty($remise)) {
2510  $remise = 0;
2511  }
2513 
2514  if (!empty($user->rights->propal->creer)) {
2515  $error = 0;
2516 
2517  $this->db->begin();
2518 
2519  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2520  $sql .= " SET remise_absolue = ".((float) $remise);
2521  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2522 
2523  dol_syslog(__METHOD__, LOG_DEBUG);
2524  $resql = $this->db->query($sql);
2525  if (!$resql) {
2526  $this->errors[] = $this->db->error();
2527  $error++;
2528  }
2529 
2530  if (!$error) {
2531  $this->oldcopy = clone $this;
2532  $this->update_price(1);
2533  }
2534 
2535  if (!$notrigger && empty($error)) {
2536  // Call trigger
2537  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2538  if ($result < 0) {
2539  $error++;
2540  }
2541  // End call triggers
2542  }
2543 
2544  if (!$error) {
2545  $this->db->commit();
2546  return 1;
2547  } else {
2548  foreach ($this->errors as $errmsg) {
2549  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2550  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2551  }
2552  $this->db->rollback();
2553  return -1 * $error;
2554  }
2555  }
2556  }
2557 
2558 
2559 
2569  public function reopen($user, $status, $note = '', $notrigger = 0)
2570  {
2571  $error = 0;
2572 
2573  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2574  $sql .= " SET fk_statut = ".((int) $status).",";
2575  if (!empty($note)) {
2576  $sql .= " note_private = '".$this->db->escape($note)."',";
2577  }
2578  $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2579  $sql .= " WHERE rowid = ".((int) $this->id);
2580 
2581  $this->db->begin();
2582 
2583  dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2584  $resql = $this->db->query($sql);
2585  if (!$resql) {
2586  $error++;
2587  $this->errors[] = "Error ".$this->db->lasterror();
2588  }
2589  if (!$error) {
2590  if (!$notrigger) {
2591  // Call trigger
2592  $result = $this->call_trigger('PROPAL_REOPEN', $user);
2593  if ($result < 0) {
2594  $error++;
2595  }
2596  // End call triggers
2597  }
2598  }
2599 
2600  // Commit or rollback
2601  if ($error) {
2602  if (!empty($this->errors)) {
2603  foreach ($this->errors as $errmsg) {
2604  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2605  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2606  }
2607  }
2608  $this->db->rollback();
2609  return -1 * $error;
2610  } else {
2611  $this->statut = $status;
2612  $this->status = $status;
2613 
2614  $this->db->commit();
2615  return 1;
2616  }
2617  }
2618 
2628  public function closeProposal($user, $status, $note = '', $notrigger = 0)
2629  {
2630  global $langs,$conf;
2631 
2632  $error = 0;
2633  $now = dol_now();
2634 
2635  $this->db->begin();
2636 
2637  $newprivatenote = dol_concatdesc($this->note_private, $note);
2638 
2639  if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
2640  $date_signature = $now;
2641  $fk_user_signature = $user->id;
2642  } else {
2643  $this->info($this->id);
2644  if (!isset($this->date_signature) || $this->date_signature == '') {
2645  $date_signature = $now;
2646  $fk_user_signature = $user->id;
2647  } else {
2648  $date_signature = $this->date_signature;
2649  $fk_user_signature = $this->user_signature->id;
2650  }
2651  }
2652 
2653  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2654  $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($newprivatenote)."'";
2655  if ($status == self::STATUS_SIGNED) {
2656  $sql .= ", date_signature='".$this->db->idate($now)."', fk_user_signature = ".($fk_user_signature);
2657  }
2658  $sql .= " WHERE rowid = ".((int) $this->id);
2659 
2660  $resql = $this->db->query($sql);
2661  if ($resql) {
2662  // Status self::STATUS_REFUSED by default
2663  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2664  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2665 
2666  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2667  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2668  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2669 
2670  // The connected company is classified as a client
2671  $soc=new Societe($this->db);
2672  $soc->id = $this->socid;
2673  $result = $soc->set_as_client();
2674 
2675  if ($result < 0) {
2676  $this->error=$this->db->lasterror();
2677  $this->db->rollback();
2678  return -2;
2679  }
2680  }
2681 
2682  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2683  // Define output language
2684  $outputlangs = $langs;
2685  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2686  $outputlangs = new Translate("", $conf);
2687  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2688  $outputlangs->setDefaultLang($newlang);
2689  }
2690 
2691  // PDF
2692  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2693  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2694  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2695 
2696  //$ret=$object->fetch($id); // Reload to get new records
2697  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2698  }
2699 
2700  if (!$error) {
2701  $this->oldcopy= clone $this;
2702  $this->statut = $status;
2703  $this->status = $status;
2704  $this->date_signature = $date_signature;
2705  $this->note_private = $newprivatenote;
2706  }
2707 
2708  if (!$notrigger && empty($error)) {
2709  // Call trigger
2710  $result=$this->call_trigger($trigger_name, $user);
2711  if ($result < 0) {
2712  $error++;
2713  }
2714  // End call triggers
2715  }
2716 
2717  if (!$error ) {
2718  $this->db->commit();
2719  return 1;
2720  } else {
2721  $this->statut = $this->oldcopy->statut;
2722  $this->status = $this->oldcopy->statut;
2723  $this->date_signature = $this->oldcopy->date_signature;
2724  $this->note_private = $this->oldcopy->note_private;
2725 
2726  $this->db->rollback();
2727  return -1;
2728  }
2729  } else {
2730  $this->error = $this->db->lasterror();
2731  $this->db->rollback();
2732  return -1;
2733  }
2734  }
2735 
2744  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2745  {
2746  global $conf, $langs;
2747 
2748  $error = 0;
2749 
2750  $now = dol_now();
2751  $num = 0;
2752 
2753  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2754 
2755  $this->db->begin();
2756 
2757  $newprivatenote = dol_concatdesc($this->note_private, $note);
2758 
2759  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2760  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2761  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2762 
2763  dol_syslog(__METHOD__, LOG_DEBUG);
2764  $resql = $this->db->query($sql);
2765  if (!$resql) {
2766  $this->errors[] = $this->db->error();
2767  $error++;
2768  } else {
2769  $num = $this->db->affected_rows($resql);
2770  }
2771 
2772  if (!$error) {
2773  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2774 
2775  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2776  // Define output language
2777  $outputlangs = $langs;
2778  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2779  $outputlangs = new Translate("", $conf);
2780  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2781  $outputlangs->setDefaultLang($newlang);
2782  }
2783 
2784  // PDF
2785  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2786  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2787  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2788 
2789  //$ret=$object->fetch($id); // Reload to get new records
2790  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2791  }
2792 
2793  $this->oldcopy = clone $this;
2794  $this->statut = self::STATUS_BILLED;
2795  $this->date_cloture = $now;
2796  $this->note_private = $newprivatenote;
2797  }
2798 
2799  if (!$notrigger && empty($error)) {
2800  // Call trigger
2801  $result = $this->call_trigger($triggerName, $user);
2802  if ($result < 0) {
2803  $error++;
2804  }
2805  // End call triggers
2806  }
2807 
2808  if (!$error) {
2809  $this->db->commit();
2810  return $num;
2811  } else {
2812  foreach ($this->errors as $errmsg) {
2813  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2814  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2815  }
2816  $this->db->rollback();
2817  return -1 * $error;
2818  }
2819  }
2820 
2821  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2829  public function setDraft($user, $notrigger = 0)
2830  {
2831  // phpcs:enable
2832  $error = 0;
2833 
2834  // Protection
2835  if ($this->statut <= self::STATUS_DRAFT) {
2836  return 0;
2837  }
2838 
2839  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2840 
2841  $this->db->begin();
2842 
2843  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2844  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2845  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2846  $sql .= " WHERE rowid = ".((int) $this->id);
2847 
2848  $resql = $this->db->query($sql);
2849  if (!$resql) {
2850  $this->errors[] = $this->db->error();
2851  $error++;
2852  }
2853 
2854  if (!$error) {
2855  $this->oldcopy = clone $this;
2856  }
2857 
2858  if (!$notrigger && empty($error)) {
2859  // Call trigger
2860  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2861  if ($result < 0) {
2862  $error++;
2863  }
2864  // End call triggers
2865  }
2866 
2867  if (!$error) {
2868  $this->statut = self::STATUS_DRAFT;
2869  $this->brouillon = 1;
2870 
2871  $this->db->commit();
2872  return 1;
2873  } else {
2874  foreach ($this->errors as $errmsg) {
2875  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2876  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2877  }
2878  $this->db->rollback();
2879  return -1 * $error;
2880  }
2881  }
2882 
2883 
2884  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2898  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2899  {
2900  // phpcs:enable
2901  global $user;
2902 
2903  $ga = array();
2904 
2905  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2906  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2907  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2908  if (empty($user->rights->societe->client->voir) && !$socid) {
2909  $sql .= ", sc.fk_soc, sc.fk_user";
2910  }
2911  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2912  if (empty($user->rights->societe->client->voir) && !$socid) {
2913  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2914  }
2915  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2916  $sql .= " AND p.fk_soc = s.rowid";
2917  $sql .= " AND p.fk_statut = c.id";
2918  if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2919  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2920  }
2921  if ($socid) {
2922  $sql .= " AND s.rowid = ".((int) $socid);
2923  }
2924  if ($draft) {
2925  $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2926  }
2927  if ($notcurrentuser > 0) {
2928  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2929  }
2930  $sql .= $this->db->order($sortfield, $sortorder);
2931  $sql .= $this->db->plimit($limit, $offset);
2932 
2933  $result = $this->db->query($sql);
2934  if ($result) {
2935  $num = $this->db->num_rows($result);
2936  if ($num) {
2937  $i = 0;
2938  while ($i < $num) {
2939  $obj = $this->db->fetch_object($result);
2940 
2941  if ($shortlist == 1) {
2942  $ga[$obj->propalid] = $obj->ref;
2943  } elseif ($shortlist == 2) {
2944  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2945  } else {
2946  $ga[$i]['id'] = $obj->propalid;
2947  $ga[$i]['ref'] = $obj->ref;
2948  $ga[$i]['name'] = $obj->name;
2949  }
2950 
2951  $i++;
2952  }
2953  }
2954  return $ga;
2955  } else {
2956  dol_print_error($this->db);
2957  return -1;
2958  }
2959  }
2960 
2966  public function getInvoiceArrayList()
2967  {
2968  return $this->InvoiceArrayList($this->id);
2969  }
2970 
2971  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2978  public function InvoiceArrayList($id)
2979  {
2980  // phpcs:enable
2981  $ga = array();
2982  $linkedInvoices = array();
2983 
2984  $this->fetchObjectLinked($id, $this->element);
2985  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2986  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2987  // On parcourt donc une liste d'objets en tant qu'objet unique
2988  foreach ($objectid as $key => $object) {
2989  // Cas des factures liees directement
2990  if ($objecttype == 'facture') {
2991  $linkedInvoices[] = $object;
2992  } else {
2993  // Cas des factures liees par un autre objet (ex: commande)
2994  $this->fetchObjectLinked($object, $objecttype);
2995  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
2996  foreach ($subobjectid as $subkey => $subobject) {
2997  if ($subobjecttype == 'facture') {
2998  $linkedInvoices[] = $subobject;
2999  }
3000  }
3001  }
3002  }
3003  }
3004  }
3005 
3006  if (count($linkedInvoices) > 0) {
3007  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3008  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3009  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3010 
3011  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3012  $resql = $this->db->query($sql);
3013 
3014  if ($resql) {
3015  $tab_sqlobj = array();
3016  $nump = $this->db->num_rows($resql);
3017  for ($i = 0; $i < $nump; $i++) {
3018  $sqlobj = $this->db->fetch_object($resql);
3019  $tab_sqlobj[] = $sqlobj;
3020  }
3021  $this->db->free($resql);
3022 
3023  $nump = count($tab_sqlobj);
3024 
3025  if ($nump) {
3026  $i = 0;
3027  while ($i < $nump) {
3028  $obj = array_shift($tab_sqlobj);
3029 
3030  $ga[$i] = $obj;
3031 
3032  $i++;
3033  }
3034  }
3035  return $ga;
3036  } else {
3037  return -1;
3038  }
3039  } else {
3040  return $ga;
3041  }
3042  }
3043 
3051  public function delete($user, $notrigger = 0)
3052  {
3053  global $conf;
3054  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3055 
3056  $error = 0;
3057 
3058  $this->db->begin();
3059 
3060  if (!$notrigger) {
3061  // Call trigger
3062  $result = $this->call_trigger('PROPAL_DELETE', $user);
3063  if ($result < 0) {
3064  $error++;
3065  }
3066  // End call triggers
3067  }
3068 
3069  // Delete extrafields of lines and lines
3070  if (!$error && !empty($this->table_element_line)) {
3071  $tabletodelete = $this->table_element_line;
3072  $sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id).")";
3073  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3074  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3075  $error++;
3076  $this->error = $this->db->lasterror();
3077  $this->errors[] = $this->error;
3078  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3079  }
3080  }
3081 
3082  if (!$error) {
3083  // Delete linked object
3084  $res = $this->deleteObjectLinked();
3085  if ($res < 0) {
3086  $error++;
3087  }
3088  }
3089 
3090  if (!$error) {
3091  // Delete linked contacts
3092  $res = $this->delete_linked_contact();
3093  if ($res < 0) {
3094  $error++;
3095  }
3096  }
3097 
3098  // Removed extrafields of object
3099  if (!$error) {
3100  $result = $this->deleteExtraFields();
3101  if ($result < 0) {
3102  $error++;
3103  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3104  }
3105  }
3106 
3107  // Delete main record
3108  if (!$error) {
3109  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3110  $res = $this->db->query($sql);
3111  if (!$res) {
3112  $error++;
3113  $this->error = $this->db->lasterror();
3114  $this->errors[] = $this->error;
3115  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3116  }
3117  }
3118 
3119  // Delete record into ECM index and physically
3120  if (!$error) {
3121  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3122  if (!$res) {
3123  $error++;
3124  }
3125  }
3126 
3127  if (!$error) {
3128  // We remove directory
3129  $ref = dol_sanitizeFileName($this->ref);
3130  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3131  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3132  $file = $dir."/".$ref.".pdf";
3133  if (file_exists($file)) {
3134  dol_delete_preview($this);
3135 
3136  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3137  $this->error = 'ErrorFailToDeleteFile';
3138  $this->errors[] = $this->error;
3139  $this->db->rollback();
3140  return 0;
3141  }
3142  }
3143  if (file_exists($dir)) {
3144  $res = @dol_delete_dir_recursive($dir);
3145  if (!$res) {
3146  $this->error = 'ErrorFailToDeleteDir';
3147  $this->errors[] = $this->error;
3148  $this->db->rollback();
3149  return 0;
3150  }
3151  }
3152  }
3153  }
3154 
3155  if (!$error) {
3156  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3157  $this->db->commit();
3158  return 1;
3159  } else {
3160  $this->db->rollback();
3161  return -1;
3162  }
3163  }
3164 
3173  public function availability($availability_id, $notrigger = 0)
3174  {
3175  global $user;
3176 
3177  if ($this->statut >= self::STATUS_DRAFT) {
3178  $error = 0;
3179 
3180  $this->db->begin();
3181 
3182  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3183  $sql .= ' SET fk_availability = '.((int) $availability_id);
3184  $sql .= ' WHERE rowid='.((int) $this->id);
3185 
3186  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3187  $resql = $this->db->query($sql);
3188  if (!$resql) {
3189  $this->errors[] = $this->db->error();
3190  $error++;
3191  }
3192 
3193  if (!$error) {
3194  $this->oldcopy = clone $this;
3195  $this->availability_id = $availability_id;
3196  }
3197 
3198  if (!$notrigger && empty($error)) {
3199  // Call trigger
3200  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3201  if ($result < 0) {
3202  $error++;
3203  }
3204  // End call triggers
3205  }
3206 
3207  if (!$error) {
3208  $this->db->commit();
3209  return 1;
3210  } else {
3211  foreach ($this->errors as $errmsg) {
3212  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3213  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3214  }
3215  $this->db->rollback();
3216  return -1 * $error;
3217  }
3218  } else {
3219  $error_str = 'Propal status do not meet requirement '.$this->statut;
3220  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3221  $this->error = $error_str;
3222  $this->errors[] = $this->error;
3223  return -2;
3224  }
3225  }
3226 
3227  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3236  public function demand_reason($demand_reason_id, $notrigger = 0)
3237  {
3238  // phpcs:enable
3239  global $user;
3240 
3241  if ($this->statut >= self::STATUS_DRAFT) {
3242  $error = 0;
3243 
3244  $this->db->begin();
3245 
3246  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3247  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3248  $sql .= ' WHERE rowid='.((int) $this->id);
3249 
3250  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3251  $resql = $this->db->query($sql);
3252  if (!$resql) {
3253  $this->errors[] = $this->db->error();
3254  $error++;
3255  }
3256 
3257  if (!$error) {
3258  $this->oldcopy = clone $this;
3259  $this->demand_reason_id = $demand_reason_id;
3260  }
3261 
3262  if (!$notrigger && empty($error)) {
3263  // Call trigger
3264  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3265  if ($result < 0) {
3266  $error++;
3267  }
3268  // End call triggers
3269  }
3270 
3271  if (!$error) {
3272  $this->db->commit();
3273  return 1;
3274  } else {
3275  foreach ($this->errors as $errmsg) {
3276  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3277  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3278  }
3279  $this->db->rollback();
3280  return -1 * $error;
3281  }
3282  } else {
3283  $error_str = 'Propal status do not meet requirement '.$this->statut;
3284  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3285  $this->error = $error_str;
3286  $this->errors[] = $this->error;
3287  return -2;
3288  }
3289  }
3290 
3291 
3298  public function info($id)
3299  {
3300  $sql = "SELECT c.rowid, ";
3301  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3302  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3303  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3304  $sql .= " WHERE c.rowid = ".((int) $id);
3305 
3306  $result = $this->db->query($sql);
3307 
3308  if ($result) {
3309  if ($this->db->num_rows($result)) {
3310  $obj = $this->db->fetch_object($result);
3311 
3312  $this->id = $obj->rowid;
3313 
3314  $this->date_creation = $this->db->jdate($obj->datec);
3315  $this->date_validation = $this->db->jdate($obj->datev);
3316  $this->date_signature = $this->db->jdate($obj->date_signature);
3317  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3318 
3319  $cuser = new User($this->db);
3320  $cuser->fetch($obj->fk_user_author);
3321  $this->user_creation = $cuser;
3322 
3323  if ($obj->fk_user_valid) {
3324  $vuser = new User($this->db);
3325  $vuser->fetch($obj->fk_user_valid);
3326  $this->user_validation = $vuser;
3327  }
3328 
3329  if ($obj->fk_user_signature) {
3330  $user_signature = new User($this->db);
3331  $user_signature->fetch($obj->fk_user_signature);
3332  $this->user_signature = $user_signature;
3333  }
3334 
3335  if ($obj->fk_user_cloture) {
3336  $cluser = new User($this->db);
3337  $cluser->fetch($obj->fk_user_cloture);
3338  $this->user_cloture = $cluser;
3339  }
3340  }
3341  $this->db->free($result);
3342  } else {
3343  dol_print_error($this->db);
3344  }
3345  }
3346 
3347 
3354  public function getLibStatut($mode = 0)
3355  {
3356  return $this->LibStatut($this->statut, $mode);
3357  }
3358 
3359  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3367  public function LibStatut($status, $mode = 1)
3368  {
3369  // phpcs:enable
3370  global $conf, $hookmanager;
3371 
3372  // Init/load array of translation of status
3373  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3374  global $langs;
3375  $langs->load("propal");
3376  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3377  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3378  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3379  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3380  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3381  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3382  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3383  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3384  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3385  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3386  }
3387 
3388  $statusType = '';
3389  if ($status == self::STATUS_DRAFT) {
3390  $statusType = 'status0';
3391  } elseif ($status == self::STATUS_VALIDATED) {
3392  $statusType = 'status1';
3393  } elseif ($status == self::STATUS_SIGNED) {
3394  $statusType = 'status4';
3395  } elseif ($status == self::STATUS_NOTSIGNED) {
3396  $statusType = 'status9';
3397  } elseif ($status == self::STATUS_BILLED) {
3398  $statusType = 'status6';
3399  }
3400 
3401 
3402  $parameters = array('status' => $status, 'mode' => $mode);
3403  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3404 
3405  if ($reshook > 0) {
3406  return $hookmanager->resPrint;
3407  }
3408 
3409  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3410  }
3411 
3412 
3413  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3421  public function load_board($user, $mode)
3422  {
3423  // phpcs:enable
3424  global $conf, $langs;
3425 
3426  $clause = " WHERE";
3427 
3428  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3429  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3430  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3431  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3432  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3433  $clause = " AND";
3434  }
3435  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3436  if ($mode == 'opened') {
3437  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3438  }
3439  if ($mode == 'signed') {
3440  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3441  }
3442  if ($user->socid) {
3443  $sql .= " AND p.fk_soc = ".((int) $user->socid);
3444  }
3445 
3446  $resql = $this->db->query($sql);
3447  if ($resql) {
3448  $langs->load("propal");
3449  $now = dol_now();
3450 
3451  $delay_warning = 0;
3452  $status = 0;
3453  $label = $labelShort = '';
3454  if ($mode == 'opened') {
3455  $delay_warning = $conf->propal->cloture->warning_delay;
3456  $status = self::STATUS_VALIDATED;
3457  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3458  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3459  }
3460  if ($mode == 'signed') {
3461  $delay_warning = $conf->propal->facturation->warning_delay;
3462  $status = self::STATUS_SIGNED;
3463  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3464  $labelShort = $langs->trans("ToBill");
3465  }
3466 
3467  $response = new WorkboardResponse();
3468  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3469  $response->label = $label;
3470  $response->labelShort = $labelShort;
3471  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3472  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3473  $response->img = img_object('', "propal");
3474 
3475  // This assignment in condition is not a bug. It allows walking the results.
3476  while ($obj = $this->db->fetch_object($resql)) {
3477  $response->nbtodo++;
3478  $response->total += $obj->total_ht;
3479 
3480  if ($mode == 'opened') {
3481  $datelimit = $this->db->jdate($obj->datefin);
3482  if ($datelimit < ($now - $delay_warning)) {
3483  $response->nbtodolate++;
3484  }
3485  }
3486  // TODO Definir regle des propales a facturer en retard
3487  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3488  }
3489 
3490  return $response;
3491  } else {
3492  $this->error = $this->db->error();
3493  return -1;
3494  }
3495  }
3496 
3497 
3505  public function initAsSpecimen()
3506  {
3507  global $conf, $langs;
3508 
3509  // Load array of products prodids
3510  $num_prods = 0;
3511  $prodids = array();
3512  $sql = "SELECT rowid";
3513  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3514  $sql .= " WHERE entity IN (".getEntity('product').")";
3515  $sql .= $this->db->plimit(100);
3516 
3517  $resql = $this->db->query($sql);
3518  if ($resql) {
3519  $num_prods = $this->db->num_rows($resql);
3520  $i = 0;
3521  while ($i < $num_prods) {
3522  $i++;
3523  $row = $this->db->fetch_row($resql);
3524  $prodids[$i] = $row[0];
3525  }
3526  }
3527 
3528  // Initialise parametres
3529  $this->id = 0;
3530  $this->ref = 'SPECIMEN';
3531  $this->ref_client = 'NEMICEPS';
3532  $this->specimen = 1;
3533  $this->socid = 1;
3534  $this->date = time();
3535  $this->fin_validite = $this->date + 3600 * 24 * 30;
3536  $this->cond_reglement_id = 1;
3537  $this->cond_reglement_code = 'RECEP';
3538  $this->mode_reglement_id = 7;
3539  $this->mode_reglement_code = 'CHQ';
3540  $this->availability_id = 1;
3541  $this->availability_code = 'AV_NOW';
3542  $this->demand_reason_id = 1;
3543  $this->demand_reason_code = 'SRC_00';
3544  $this->note_public = 'This is a comment (public)';
3545  $this->note_private = 'This is a comment (private)';
3546 
3547  $this->multicurrency_tx = 1;
3548  $this->multicurrency_code = $conf->currency;
3549 
3550  // Lines
3551  $nbp = 5;
3552  $xnbp = 0;
3553  while ($xnbp < $nbp) {
3554  $line = new PropaleLigne($this->db);
3555  $line->desc = $langs->trans("Description")." ".$xnbp;
3556  $line->qty = 1;
3557  $line->subprice = 100;
3558  $line->price = 100;
3559  $line->tva_tx = 20;
3560  $line->localtax1_tx = 0;
3561  $line->localtax2_tx = 0;
3562  if ($xnbp == 2) {
3563  $line->total_ht = 50;
3564  $line->total_ttc = 60;
3565  $line->total_tva = 10;
3566  $line->remise_percent = 50;
3567  } else {
3568  $line->total_ht = 100;
3569  $line->total_ttc = 120;
3570  $line->total_tva = 20;
3571  $line->remise_percent = 00;
3572  }
3573 
3574  if ($num_prods > 0) {
3575  $prodid = mt_rand(1, $num_prods);
3576  $line->fk_product = $prodids[$prodid];
3577  $line->product_ref = 'SPECIMEN';
3578  }
3579 
3580  $this->lines[$xnbp] = $line;
3581 
3582  $this->total_ht += $line->total_ht;
3583  $this->total_tva += $line->total_tva;
3584  $this->total_ttc += $line->total_ttc;
3585 
3586  $xnbp++;
3587  }
3588  }
3589 
3590  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3596  public function load_state_board()
3597  {
3598  // phpcs:enable
3599  global $user;
3600 
3601  $this->nb = array();
3602  $clause = "WHERE";
3603 
3604  $sql = "SELECT count(p.rowid) as nb";
3605  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3606  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3607  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3608  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3609  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3610  $clause = "AND";
3611  }
3612  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3613 
3614  $resql = $this->db->query($sql);
3615  if ($resql) {
3616  // This assignment in condition is not a bug. It allows walking the results.
3617  while ($obj = $this->db->fetch_object($resql)) {
3618  $this->nb["proposals"] = $obj->nb;
3619  }
3620  $this->db->free($resql);
3621  return 1;
3622  } else {
3623  dol_print_error($this->db);
3624  $this->error = $this->db->error();
3625  return -1;
3626  }
3627  }
3628 
3629 
3637  public function getNextNumRef($soc)
3638  {
3639  global $conf, $langs;
3640  $langs->load("propal");
3641 
3642  $classname = $conf->global->PROPALE_ADDON;
3643 
3644  if (!empty($classname)) {
3645  $mybool = false;
3646 
3647  $file = $classname.".php";
3648 
3649  // Include file with class
3650  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3651  foreach ($dirmodels as $reldir) {
3652  $dir = dol_buildpath($reldir."core/modules/propale/");
3653 
3654  // Load file with numbering class (if found)
3655  $mybool |= @include_once $dir.$file;
3656  }
3657 
3658  if (!$mybool) {
3659  dol_print_error('', "Failed to include file ".$file);
3660  return '';
3661  }
3662 
3663  $obj = new $classname();
3664  $numref = "";
3665  $numref = $obj->getNextValue($soc, $this);
3666 
3667  if ($numref != "") {
3668  return $numref;
3669  } else {
3670  $this->error = $obj->error;
3671  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3672  return "";
3673  }
3674  } else {
3675  $langs->load("errors");
3676  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3677  return "";
3678  }
3679  }
3680 
3692  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3693  {
3694  global $langs, $conf, $user, $hookmanager;
3695 
3696  if (!empty($conf->dol_no_mouse_hover)) {
3697  $notooltip = 1; // Force disable tooltips
3698  }
3699 
3700  $result = '';
3701  $label = '';
3702  $url = '';
3703 
3704  if ($user->rights->propal->lire) {
3705  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3706  if (isset($this->statut)) {
3707  $label .= ' '.$this->getLibStatut(5);
3708  }
3709  if (!empty($this->ref)) {
3710  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3711  }
3712  if (!empty($this->ref_client)) {
3713  $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3714  }
3715  if (!empty($this->total_ht)) {
3716  $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3717  }
3718  if (!empty($this->total_tva)) {
3719  $label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3720  }
3721  if (!empty($this->total_ttc)) {
3722  $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3723  }
3724  if (!empty($this->date)) {
3725  $label .= '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3726  }
3727  if (!empty($this->delivery_date)) {
3728  $label .= '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3729  }
3730 
3731  if ($option == '') {
3732  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3733  } elseif ($option == 'compta') { // deprecated
3734  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3735  } elseif ($option == 'expedition') {
3736  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3737  } elseif ($option == 'document') {
3738  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3739  }
3740 
3741  if ($option != 'nolink') {
3742  // Add param to save lastsearch_values or not
3743  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3744  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3745  $add_save_lastsearch_values = 1;
3746  }
3747  if ($add_save_lastsearch_values) {
3748  $url .= '&save_lastsearch_values=1';
3749  }
3750  }
3751  }
3752 
3753  $linkclose = '';
3754  if (empty($notooltip) && $user->rights->propal->lire) {
3755  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3756  $label = $langs->trans("Proposal");
3757  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3758  }
3759  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
3760  $linkclose .= ' class="classfortooltip"';
3761  }
3762 
3763  $linkstart = '<a href="'.$url.'"';
3764  $linkstart .= $linkclose.'>';
3765  $linkend = '</a>';
3766 
3767  $result .= $linkstart;
3768  if ($withpicto) {
3769  $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
3770  }
3771  if ($withpicto != 2) {
3772  $result .= $this->ref;
3773  }
3774  $result .= $linkend;
3775 
3776  if ($addlinktonotes >= 0) {
3777  $txttoshow = '';
3778 
3779  if ($addlinktonotes == 0) {
3780  if (!empty($this->note_private) || !empty($this->note_public)) {
3781  $txttoshow = $langs->trans('ViewPrivateNote');
3782  }
3783  } elseif ($addlinktonotes == 1) {
3784  if (!empty($this->note_private)) {
3785  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3786  }
3787  } elseif ($addlinktonotes == 2) {
3788  if (!empty($this->note_public)) {
3789  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3790  }
3791  } elseif ($addlinktonotes == 3) {
3792  if ($user->socid > 0) {
3793  if (!empty($this->note_public)) {
3794  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3795  }
3796  } else {
3797  if (!empty($this->note_public)) {
3798  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3799  }
3800  if (!empty($this->note_private)) {
3801  if (!empty($txttoshow)) {
3802  $txttoshow .= '<br><br>';
3803  }
3804  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3805  }
3806  }
3807  }
3808 
3809  if ($txttoshow) {
3810  $result .= ' <span class="note inline-block">';
3811  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3812  $result .= img_picto('', 'note');
3813  $result .= '</a>';
3814  $result .= '</span>';
3815  }
3816  }
3817 
3818  global $action;
3819  $hookmanager->initHooks(array($this->element . 'dao'));
3820  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3821  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3822  if ($reshook > 0) {
3823  $result = $hookmanager->resPrint;
3824  } else {
3825  $result .= $hookmanager->resPrint;
3826  }
3827  return $result;
3828  }
3829 
3836  public function getLinesArray($filters = '')
3837  {
3838  return $this->fetch_lines(0, 0, $filters);
3839  }
3840 
3852  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3853  {
3854  global $conf, $langs;
3855 
3856  $langs->load("propale");
3857  $outputlangs->load("products");
3858 
3859  if (!dol_strlen($modele)) {
3860  $modele = 'azur';
3861 
3862  if ($this->model_pdf) {
3863  $modele = $this->model_pdf;
3864  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3865  $modele = $conf->global->PROPALE_ADDON_PDF;
3866  }
3867  }
3868 
3869  $modelpath = "core/modules/propale/doc/";
3870 
3871  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3872  }
3873 
3882  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
3883  {
3884  $tables = array(
3885  'propal'
3886  );
3887 
3888  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3889  }
3890 
3899  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3900  {
3901  $tables = array(
3902  'propaldet'
3903  );
3904 
3905  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3906  }
3907 }
3908 
3913 {
3917  public $element = 'propaldet';
3918 
3922  public $table_element = 'propaldet';
3923 
3924  public $oldline;
3925 
3926  // From llx_propaldet
3927  public $fk_propal;
3928  public $fk_parent_line;
3929  public $desc; // Description ligne
3930  public $fk_product; // Id produit predefini
3941  public $product_type = Product::TYPE_PRODUCT;
3942 
3943  public $qty;
3944 
3945  public $tva_tx;
3946  public $vat_src_code;
3947 
3948  public $subprice;
3949  public $remise_percent;
3950  public $fk_remise_except;
3951 
3952  public $rang = 0;
3953 
3954  public $fk_fournprice;
3955  public $pa_ht;
3956  public $marge_tx;
3957  public $marque_tx;
3958 
3959  public $special_code; // Tag for special lines (exlusive tags)
3960  // 1: frais de port
3961  // 2: ecotaxe
3962  // 3: option line (when qty = 0)
3963 
3964  public $info_bits = 0; // Some other info:
3965  // Bit 0: 0 si TVA normal - 1 si TVA NPR
3966  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
3967 
3968  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
3969  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
3970  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
3971 
3976  public $remise;
3981  public $price;
3982 
3983  // From llx_product
3988  public $ref;
3993  public $product_ref;
3998  public $libelle;
4003  public $label;
4008  public $product_label;
4013  public $product_desc;
4014 
4019  public $product_tobatch;
4020 
4025  public $product_barcode;
4026 
4027  public $localtax1_tx; // Local tax 1
4028  public $localtax2_tx; // Local tax 2
4029  public $localtax1_type; // Local tax 1 type
4030  public $localtax2_type; // Local tax 2 type
4031  public $total_localtax1; // Line total local tax 1
4032  public $total_localtax2; // Line total local tax 2
4033 
4034  public $date_start;
4035  public $date_end;
4036 
4037  public $skip_update_total; // Skip update price total for special lines
4038 
4039  // Multicurrency
4040  public $fk_multicurrency;
4041  public $multicurrency_code;
4042  public $multicurrency_subprice;
4043  public $multicurrency_total_ht;
4044  public $multicurrency_total_tva;
4045  public $multicurrency_total_ttc;
4046 
4047 
4053  public function __construct($db)
4054  {
4055  $this->db = $db;
4056  }
4057 
4064  public function fetch($rowid)
4065  {
4066  $sql = 'SELECT pd.rowid, pd.fk_propal, pd.fk_parent_line, pd.fk_product, pd.label as custom_label, pd.description, pd.price, pd.qty, pd.vat_src_code, pd.tva_tx,';
4067  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4068  $sql .= ' pd.info_bits, pd.total_ht, pd.total_tva, pd.total_ttc, pd.fk_product_fournisseur_price as fk_fournprice, pd.buy_price_ht as pa_ht, pd.special_code, pd.rang,';
4069  $sql .= ' pd.fk_unit,';
4070  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4071  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4072  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4073  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4074  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4075  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4076  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4077 
4078  $result = $this->db->query($sql);
4079  if ($result) {
4080  $objp = $this->db->fetch_object($result);
4081 
4082  if ($objp) {
4083  $this->id = $objp->rowid;
4084  $this->rowid = $objp->rowid; // deprecated
4085  $this->fk_propal = $objp->fk_propal;
4086  $this->fk_parent_line = $objp->fk_parent_line;
4087  $this->label = $objp->custom_label;
4088  $this->desc = $objp->description;
4089  $this->qty = $objp->qty;
4090  $this->price = $objp->price; // deprecated
4091  $this->subprice = $objp->subprice;
4092  $this->vat_src_code = $objp->vat_src_code;
4093  $this->tva_tx = $objp->tva_tx;
4094  $this->remise = $objp->remise; // deprecated
4095  $this->remise_percent = $objp->remise_percent;
4096  $this->fk_remise_except = $objp->fk_remise_except;
4097  $this->fk_product = $objp->fk_product;
4098  $this->info_bits = $objp->info_bits;
4099 
4100  $this->total_ht = $objp->total_ht;
4101  $this->total_tva = $objp->total_tva;
4102  $this->total_ttc = $objp->total_ttc;
4103 
4104  $this->fk_fournprice = $objp->fk_fournprice;
4105 
4106  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4107  $this->pa_ht = $marginInfos[0];
4108  $this->marge_tx = $marginInfos[1];
4109  $this->marque_tx = $marginInfos[2];
4110 
4111  $this->special_code = $objp->special_code;
4112  $this->product_type = $objp->product_type;
4113  $this->rang = $objp->rang;
4114 
4115  $this->ref = $objp->product_ref; // deprecated
4116  $this->product_ref = $objp->product_ref;
4117  $this->libelle = $objp->product_label; // deprecated
4118  $this->product_label = $objp->product_label;
4119  $this->product_desc = $objp->product_desc;
4120  $this->fk_unit = $objp->fk_unit;
4121 
4122  $this->date_start = $this->db->jdate($objp->date_start);
4123  $this->date_end = $this->db->jdate($objp->date_end);
4124 
4125  // Multicurrency
4126  $this->fk_multicurrency = $objp->fk_multicurrency;
4127  $this->multicurrency_code = $objp->multicurrency_code;
4128  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4129  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4130  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4131  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4132 
4133  $this->fetch_optionals();
4134 
4135  $this->db->free($result);
4136 
4137  return 1;
4138  } else {
4139  return 0;
4140  }
4141  } else {
4142  return -1;
4143  }
4144  }
4145 
4152  public function insert($notrigger = 0)
4153  {
4154  global $conf, $user;
4155 
4156  $error = 0;
4157 
4158  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4159 
4160  $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4161 
4162  // Clean parameters
4163  if (empty($this->tva_tx)) {
4164  $this->tva_tx = 0;
4165  }
4166  if (empty($this->localtax1_tx)) {
4167  $this->localtax1_tx = 0;
4168  }
4169  if (empty($this->localtax2_tx)) {
4170  $this->localtax2_tx = 0;
4171  }
4172  if (empty($this->localtax1_type)) {
4173  $this->localtax1_type = 0;
4174  }
4175  if (empty($this->localtax2_type)) {
4176  $this->localtax2_type = 0;
4177  }
4178  if (empty($this->total_localtax1)) {
4179  $this->total_localtax1 = 0;
4180  }
4181  if (empty($this->total_localtax2)) {
4182  $this->total_localtax2 = 0;
4183  }
4184  if (empty($this->rang)) {
4185  $this->rang = 0;
4186  }
4187  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4188  $this->remise_percent = 0;
4189  }
4190  if (empty($this->info_bits)) {
4191  $this->info_bits = 0;
4192  }
4193  if (empty($this->special_code)) {
4194  $this->special_code = 0;
4195  }
4196  if (empty($this->fk_parent_line)) {
4197  $this->fk_parent_line = 0;
4198  }
4199  if (empty($this->fk_fournprice)) {
4200  $this->fk_fournprice = 0;
4201  }
4202  if (!is_numeric($this->qty)) {
4203  $this->qty = 0;
4204  }
4205  if (empty($this->pa_ht)) {
4206  $this->pa_ht = 0;
4207  }
4208  if (empty($this->multicurrency_subprice)) {
4209  $this->multicurrency_subprice = 0;
4210  }
4211  if (empty($this->multicurrency_total_ht)) {
4212  $this->multicurrency_total_ht = 0;
4213  }
4214  if (empty($this->multicurrency_total_tva)) {
4215  $this->multicurrency_total_tva = 0;
4216  }
4217  if (empty($this->multicurrency_total_ttc)) {
4218  $this->multicurrency_total_ttc = 0;
4219  }
4220 
4221  // if buy price not defined, define buyprice as configured in margin admin
4222  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4223  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4224  return $result;
4225  } else {
4226  $this->pa_ht = $result;
4227  }
4228  }
4229 
4230  // Check parameters
4231  if ($this->product_type < 0) {
4232  return -1;
4233  }
4234 
4235  $this->db->begin();
4236 
4237  // Insert line into database
4238  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4239  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4240  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4241  $sql .= ' subprice, remise_percent, ';
4242  $sql .= ' info_bits, ';
4243  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4244  $sql .= ' fk_unit,';
4245  $sql .= ' date_start, date_end';
4246  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4247  $sql .= " VALUES (".$this->fk_propal.",";
4248  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4249  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4250  $sql .= " '".$this->db->escape($this->desc)."',";
4251  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4252  $sql .= " '".$this->db->escape($this->product_type)."',";
4253  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4254  $sql .= " ".price2num($this->qty, 'MS').",";
4255  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4256  $sql .= " ".price2num($this->tva_tx).",";
4257  $sql .= " ".price2num($this->localtax1_tx).",";
4258  $sql .= " ".price2num($this->localtax2_tx).",";
4259  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4260  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4261  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4262  $sql .= " ".price2num($this->remise_percent, 3).",";
4263  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4264  $sql .= " ".price2num($this->total_ht, 'MT').",";
4265  $sql .= " ".price2num($this->total_tva, 'MT').",";
4266  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4267  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4268  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4269  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4270  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4271  $sql .= ' '.((int) $this->special_code).',';
4272  $sql .= ' '.((int) $this->rang).',';
4273  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4274  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4275  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4276  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4277  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4278  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4279  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4280  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4281  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4282  $sql .= ')';
4283 
4284  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4285  $resql = $this->db->query($sql);
4286  if ($resql) {
4287  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4288 
4289  if (!$error) {
4290  $this->id = $this->rowid;
4291  $result = $this->insertExtraFields();
4292  if ($result < 0) {
4293  $error++;
4294  }
4295  }
4296 
4297  if (!$error && !$notrigger) {
4298  // Call trigger
4299  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4300  if ($result < 0) {
4301  $this->db->rollback();
4302  return -1;
4303  }
4304  // End call triggers
4305  }
4306 
4307  $this->db->commit();
4308  return 1;
4309  } else {
4310  $this->error = $this->db->error()." sql=".$sql;
4311  $this->db->rollback();
4312  return -1;
4313  }
4314  }
4315 
4323  public function delete(User $user, $notrigger = 0)
4324  {
4325  global $conf;
4326 
4327  $error = 0;
4328  $this->db->begin();
4329 
4330  if (!$notrigger) {
4331  // Call trigger
4332  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4333  if ($result < 0) {
4334  $error++;
4335  }
4336  }
4337  // End call triggers
4338 
4339  if (!$error) {
4340  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4341  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4342  if ($this->db->query($sql)) {
4343  // Remove extrafields
4344  if (!$error) {
4345  $this->id = $this->rowid;
4346  $result = $this->deleteExtraFields();
4347  if ($result < 0) {
4348  $error++;
4349  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4350  }
4351  }
4352  } else {
4353  $this->error = $this->db->error() . " sql=" . $sql;
4354  $error++;
4355  }
4356  }
4357 
4358  if ($error) {
4359  $this->db->rollback();
4360  return -1;
4361  } else {
4362  $this->db->commit();
4363  return 1;
4364  }
4365  }
4366 
4373  public function update($notrigger = 0)
4374  {
4375  global $conf, $user;
4376 
4377  $error = 0;
4378 
4379  $pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
4380 
4381  if (empty($this->id) && !empty($this->rowid)) {
4382  $this->id = $this->rowid;
4383  }
4384 
4385  // Clean parameters
4386  if (empty($this->tva_tx)) {
4387  $this->tva_tx = 0;
4388  }
4389  if (empty($this->localtax1_tx)) {
4390  $this->localtax1_tx = 0;
4391  }
4392  if (empty($this->localtax2_tx)) {
4393  $this->localtax2_tx = 0;
4394  }
4395  if (empty($this->total_localtax1)) {
4396  $this->total_localtax1 = 0;
4397  }
4398  if (empty($this->total_localtax2)) {
4399  $this->total_localtax2 = 0;
4400  }
4401  if (empty($this->localtax1_type)) {
4402  $this->localtax1_type = 0;
4403  }
4404  if (empty($this->localtax2_type)) {
4405  $this->localtax2_type = 0;
4406  }
4407  if (empty($this->marque_tx)) {
4408  $this->marque_tx = 0;
4409  }
4410  if (empty($this->marge_tx)) {
4411  $this->marge_tx = 0;
4412  }
4413  if (empty($this->price)) {
4414  $this->price = 0; // TODO A virer
4415  }
4416  if (empty($this->remise_percent)) {
4417  $this->remise_percent = 0;
4418  }
4419  if (empty($this->info_bits)) {
4420  $this->info_bits = 0;
4421  }
4422  if (empty($this->special_code)) {
4423  $this->special_code = 0;
4424  }
4425  if (empty($this->fk_parent_line)) {
4426  $this->fk_parent_line = 0;
4427  }
4428  if (empty($this->fk_fournprice)) {
4429  $this->fk_fournprice = 0;
4430  }
4431  if (empty($this->subprice)) {
4432  $this->subprice = 0;
4433  }
4434  if (empty($this->pa_ht)) {
4435  $this->pa_ht = 0;
4436  }
4437 
4438  // if buy price not defined, define buyprice as configured in margin admin
4439  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4440  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4441  return $result;
4442  } else {
4443  $this->pa_ht = $result;
4444  }
4445  }
4446 
4447  $this->db->begin();
4448 
4449  // Mise a jour ligne en base
4450  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4451  $sql .= " description='".$this->db->escape($this->desc)."'";
4452  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4453  $sql .= ", product_type=".$this->product_type;
4454  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4455  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4456  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4457  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4458  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4459  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4460  $sql .= ", qty='".price2num($this->qty)."'";
4461  $sql .= ", subprice=".price2num($this->subprice)."";
4462  $sql .= ", remise_percent=".price2num($this->remise_percent)."";
4463  $sql .= ", price=".(float) price2num($this->price).""; // TODO A virer
4464  $sql .= ", remise=".(float) price2num($this->remise).""; // TODO A virer
4465  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4466  if (empty($this->skip_update_total)) {
4467  $sql .= ", total_ht=".price2num($this->total_ht)."";
4468  $sql .= ", total_tva=".price2num($this->total_tva)."";
4469  $sql .= ", total_ttc=".price2num($this->total_ttc)."";
4470  $sql .= ", total_localtax1=".price2num($this->total_localtax1)."";
4471  $sql .= ", total_localtax2=".price2num($this->total_localtax2)."";
4472  }
4473  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4474  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4475  if (strlen($this->special_code)) {
4476  $sql .= ", special_code=".$this->special_code;
4477  }
4478  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4479  if (!empty($this->rang)) {
4480  $sql .= ", rang=".((int) $this->rang);
4481  }
4482  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4483  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4484  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4485 
4486  // Multicurrency
4487  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4488  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4489  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4490  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4491 
4492  $sql .= " WHERE rowid = ".((int) $this->id);
4493 
4494  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4495  $resql = $this->db->query($sql);
4496  if ($resql) {
4497  if (!$error) {
4498  $result = $this->insertExtraFields();
4499  if ($result < 0) {
4500  $error++;
4501  }
4502  }
4503 
4504  if (!$error && !$notrigger) {
4505  // Call trigger
4506  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4507  if ($result < 0) {
4508  $this->db->rollback();
4509  return -1;
4510  }
4511  // End call triggers
4512  }
4513 
4514  $this->db->commit();
4515  return 1;
4516  } else {
4517  $this->error = $this->db->error();
4518  $this->db->rollback();
4519  return -2;
4520  }
4521  }
4522 
4523  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4530  public function update_total()
4531  {
4532  // phpcs:enable
4533  $this->db->begin();
4534 
4535  // Mise a jour ligne en base
4536  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4537  $sql .= " total_ht=".price2num($this->total_ht, 'MT')."";
4538  $sql .= ",total_tva=".price2num($this->total_tva, 'MT')."";
4539  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT')."";
4540  $sql .= " WHERE rowid = ".((int) $this->rowid);
4541 
4542  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4543 
4544  $resql = $this->db->query($sql);
4545  if ($resql) {
4546  $this->db->commit();
4547  return 1;
4548  } else {
4549  $this->error = $this->db->error();
4550  $this->db->rollback();
4551  return -2;
4552  }
4553  }
4554 }
$object ref
Definition: info.php:78
Parent class of all other business classes (invoices, contracts, proposals, 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.
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
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.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check an object id/ref exists If you don't need/want to instantiate object and just need to know if o...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid='', $f_user=null, $notrigger=0)
Delete all links between an object $this.
update_price($exclspec=0, $roundingadjust='none', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceProduct(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product 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 commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty 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.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts in llx_element_contact.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage absolute discounts.
Class to manage Dolibarr database access.
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 products or services.
const TYPE_PRODUCT
Regular product.
File of class to manage predefined price products or services by customer.
Class to manage proposals.
const STATUS_DRAFT
Draft status.
fetch_lines($only_product=0, $loadalsotranslation=0, $filters='')
Load array lines.
set_date($user, $date, $notrigger=0)
Define proposal date.
const STATUS_SIGNED
Signed quote.
getNomUrl($withpicto=0, $option='', $get_params='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=-1)
Return clicable link of object (with eventually picto)
InvoiceArrayList($id)
Returns an array with id and ref of related invoices.
availability($availability_id, $notrigger=0)
Change the delivery time.
const STATUS_NOTSIGNED
Not signed quote.
$table_ref_field
{}
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set delivery date.
load_state_board()
Charge indicateurs this->nb de tableau de bord.
set_remise_percent($user, $remise, $notrigger=0)
Set an overall discount on the proposal.
classifyBilled(User $user, $notrigger=0, $note='')
Classify the proposal to status Billed.
fetch($rowid, $ref='', $ref_ext='', $forceentity=0)
Load a proposal from database.
set_availability($user, $id, $notrigger=0)
Set delivery.
update(User $user, $notrigger=0)
Update database.
demand_reason($demand_reason_id, $notrigger=0)
Change source demand.
info($id)
Object Proposal Information.
const STATUS_BILLED
Billed or processed quote.
getLibStatut($mode=0)
Return label of status of proposal (draft, validated, ...)
getLinesArray($filters='')
Retrieve an array of proposal lines.
insert_discount($idremise)
Adding line of fixed discount in the proposal in DB.
deleteline($lineid)
Delete detail line.
getNextNumRef($soc)
Returns the reference to the following non used Proposal used depending on the active numbering modul...
LibStatut($status, $mode=1)
Return label of a status (draft, validated, ...)
valid($user, $notrigger=0)
Set status to validated.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
set_ref_client($user, $ref_client, $notrigger=0)
Set customer reference number.
setDraft($user, $notrigger=0)
Set draft status.
set_echeance($user, $date_end_validity, $notrigger=0)
Define end validity date.
set_demand_reason($user, $id, $notrigger=0)
Set source of demand.
set_remise_absolue($user, $remise, $notrigger=0)
Set an absolute overall discount on the proposal.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
create($user, $notrigger=0)
Create commercial proposal into database this->ref can be set or empty.
liste_array($shortlist=0, $draft=0, $notcurrentuser=0, $socid=0, $limit=0, $offset=0, $sortfield='p.datep', $sortorder='DESC')
Return list of proposal (eventually filtered on user) into an array.
add_product($idproduct, $qty, $remise_percent=0)
Add line into array ->lines $this->thirdparty should be loaded.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $fk_product=0, $remise_percent=0.0, $price_base_type='HT', $pu_ttc=0.0, $info_bits=0, $type=0, $rang=-1, $special_code=0, $fk_parent_line=0, $fk_fournprice=0, $pa_ht=0, $label='', $date_start='', $date_end='', $array_options=0, $fk_unit=null, $origin='', $origin_id=0, $pu_ht_devise=0, $fk_remise_except=0, $noupdateafterinsertline=0)
Add a proposal line into database (linked to product/service or not) The parameters are already suppo...
initAsSpecimen()
Initialise an instance with random values.
closeProposal($user, $status, $note='', $notrigger=0)
Close/set the commercial proposal to status signed or refused (fill also date signature)
reopen($user, $status, $note='', $notrigger=0)
Reopen the commercial proposal.
__construct($db, $socid=0, $propalid=0)
Constructor.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0.0, $txlocaltax2=0.0, $desc='', $price_base_type='HT', $info_bits=0, $special_code=0, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=0, $pa_ht=0, $label='', $type=0, $date_start='', $date_end='', $array_options=0, $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $rang=0)
Update a proposal line.
createFromClone(User $user, $socid=0, $forceentity=null, $update_prices=false)
Load an object from its id and create a new one in database.
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getInvoiceArrayList()
Returns an array with the numbers of related invoices.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
const STATUS_VALIDATED
Validated status.
Class to manage commercial proposal lines.
__construct($db)
Class line Contructor.
update($notrigger=0)
Update propal line object into DB.
fetch($rowid)
Retrieve the propal line object.
insert($notrigger=0)
Insert object line propal in database.
update_total()
Update DB line fields total_xxx Used by migration.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
Definition: user.class.php:47
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
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:1402
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:1251
dol_dir_list($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:61
dol_delete_preview($object)
Delete all preview files linked to object instance.
Definition: files.lib.php:1454
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Fonction qui renvoie si tva doit etre tva percue recuperable.
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.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
Return localtax rate for a particular vat, when selling a product with vat $vatrate,...
dol_sanitizeFileName($str, $newstr='_', $unaccent=1)
Clean a string to use it as a file name.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
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.
getMarginInfos($pvht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $paht)
Return an array with margins information of a line.
div float
Buy price without taxes.
Definition: style.css.php:913
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:86
$conf db
API class for accounts.
Definition: inc.php:41