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)."', date_signature='".$this->db->idate($date_signature)."', fk_user_signature=".$fk_user_signature;
2655  $sql .= " WHERE rowid = ".((int) $this->id);
2656 
2657  $resql = $this->db->query($sql);
2658  if ($resql) {
2659  // Status self::STATUS_REFUSED by default
2660  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2661  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2662 
2663  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2664  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2665  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2666 
2667  // The connected company is classified as a client
2668  $soc=new Societe($this->db);
2669  $soc->id = $this->socid;
2670  $result = $soc->set_as_client();
2671 
2672  if ($result < 0) {
2673  $this->error=$this->db->lasterror();
2674  $this->db->rollback();
2675  return -2;
2676  }
2677  }
2678 
2679  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2680  // Define output language
2681  $outputlangs = $langs;
2682  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2683  $outputlangs = new Translate("", $conf);
2684  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2685  $outputlangs->setDefaultLang($newlang);
2686  }
2687 
2688  // PDF
2689  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2690  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2691  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2692 
2693  //$ret=$object->fetch($id); // Reload to get new records
2694  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2695  }
2696 
2697  if (!$error) {
2698  $this->oldcopy= clone $this;
2699  $this->statut = $status;
2700  $this->status = $status;
2701  $this->date_signature = $date_signature;
2702  $this->note_private = $newprivatenote;
2703  }
2704 
2705  if (!$notrigger && empty($error)) {
2706  // Call trigger
2707  $result=$this->call_trigger($trigger_name, $user);
2708  if ($result < 0) {
2709  $error++;
2710  }
2711  // End call triggers
2712  }
2713 
2714  if (!$error ) {
2715  $this->db->commit();
2716  return 1;
2717  } else {
2718  $this->statut = $this->oldcopy->statut;
2719  $this->status = $this->oldcopy->statut;
2720  $this->date_signature = $this->oldcopy->date_signature;
2721  $this->note_private = $this->oldcopy->note_private;
2722 
2723  $this->db->rollback();
2724  return -1;
2725  }
2726  } else {
2727  $this->error = $this->db->lasterror();
2728  $this->db->rollback();
2729  return -1;
2730  }
2731  }
2732 
2741  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2742  {
2743  global $conf, $langs;
2744 
2745  $error = 0;
2746 
2747  $now = dol_now();
2748  $num = 0;
2749 
2750  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2751 
2752  $this->db->begin();
2753 
2754  $newprivatenote = dol_concatdesc($this->note_private, $note);
2755 
2756  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2757  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2758  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2759 
2760  dol_syslog(__METHOD__, LOG_DEBUG);
2761  $resql = $this->db->query($sql);
2762  if (!$resql) {
2763  $this->errors[] = $this->db->error();
2764  $error++;
2765  } else {
2766  $num = $this->db->affected_rows($resql);
2767  }
2768 
2769  if (!$error) {
2770  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2771 
2772  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2773  // Define output language
2774  $outputlangs = $langs;
2775  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2776  $outputlangs = new Translate("", $conf);
2777  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2778  $outputlangs->setDefaultLang($newlang);
2779  }
2780 
2781  // PDF
2782  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2783  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2784  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2785 
2786  //$ret=$object->fetch($id); // Reload to get new records
2787  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2788  }
2789 
2790  $this->oldcopy = clone $this;
2791  $this->statut = self::STATUS_BILLED;
2792  $this->date_cloture = $now;
2793  $this->note_private = $newprivatenote;
2794  }
2795 
2796  if (!$notrigger && empty($error)) {
2797  // Call trigger
2798  $result = $this->call_trigger($triggerName, $user);
2799  if ($result < 0) {
2800  $error++;
2801  }
2802  // End call triggers
2803  }
2804 
2805  if (!$error) {
2806  $this->db->commit();
2807  return $num;
2808  } else {
2809  foreach ($this->errors as $errmsg) {
2810  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2811  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2812  }
2813  $this->db->rollback();
2814  return -1 * $error;
2815  }
2816  }
2817 
2818  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2826  public function setDraft($user, $notrigger = 0)
2827  {
2828  // phpcs:enable
2829  $error = 0;
2830 
2831  // Protection
2832  if ($this->statut <= self::STATUS_DRAFT) {
2833  return 0;
2834  }
2835 
2836  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2837 
2838  $this->db->begin();
2839 
2840  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2841  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2842  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2843  $sql .= " WHERE rowid = ".((int) $this->id);
2844 
2845  $resql = $this->db->query($sql);
2846  if (!$resql) {
2847  $this->errors[] = $this->db->error();
2848  $error++;
2849  }
2850 
2851  if (!$error) {
2852  $this->oldcopy = clone $this;
2853  }
2854 
2855  if (!$notrigger && empty($error)) {
2856  // Call trigger
2857  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2858  if ($result < 0) {
2859  $error++;
2860  }
2861  // End call triggers
2862  }
2863 
2864  if (!$error) {
2865  $this->statut = self::STATUS_DRAFT;
2866  $this->brouillon = 1;
2867 
2868  $this->db->commit();
2869  return 1;
2870  } else {
2871  foreach ($this->errors as $errmsg) {
2872  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2873  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2874  }
2875  $this->db->rollback();
2876  return -1 * $error;
2877  }
2878  }
2879 
2880 
2881  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2895  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2896  {
2897  // phpcs:enable
2898  global $user;
2899 
2900  $ga = array();
2901 
2902  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2903  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2904  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2905  if (empty($user->rights->societe->client->voir) && !$socid) {
2906  $sql .= ", sc.fk_soc, sc.fk_user";
2907  }
2908  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2909  if (empty($user->rights->societe->client->voir) && !$socid) {
2910  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2911  }
2912  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2913  $sql .= " AND p.fk_soc = s.rowid";
2914  $sql .= " AND p.fk_statut = c.id";
2915  if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2916  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2917  }
2918  if ($socid) {
2919  $sql .= " AND s.rowid = ".((int) $socid);
2920  }
2921  if ($draft) {
2922  $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2923  }
2924  if ($notcurrentuser > 0) {
2925  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2926  }
2927  $sql .= $this->db->order($sortfield, $sortorder);
2928  $sql .= $this->db->plimit($limit, $offset);
2929 
2930  $result = $this->db->query($sql);
2931  if ($result) {
2932  $num = $this->db->num_rows($result);
2933  if ($num) {
2934  $i = 0;
2935  while ($i < $num) {
2936  $obj = $this->db->fetch_object($result);
2937 
2938  if ($shortlist == 1) {
2939  $ga[$obj->propalid] = $obj->ref;
2940  } elseif ($shortlist == 2) {
2941  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2942  } else {
2943  $ga[$i]['id'] = $obj->propalid;
2944  $ga[$i]['ref'] = $obj->ref;
2945  $ga[$i]['name'] = $obj->name;
2946  }
2947 
2948  $i++;
2949  }
2950  }
2951  return $ga;
2952  } else {
2953  dol_print_error($this->db);
2954  return -1;
2955  }
2956  }
2957 
2963  public function getInvoiceArrayList()
2964  {
2965  return $this->InvoiceArrayList($this->id);
2966  }
2967 
2968  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2975  public function InvoiceArrayList($id)
2976  {
2977  // phpcs:enable
2978  $ga = array();
2979  $linkedInvoices = array();
2980 
2981  $this->fetchObjectLinked($id, $this->element);
2982  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2983  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2984  // On parcourt donc une liste d'objets en tant qu'objet unique
2985  foreach ($objectid as $key => $object) {
2986  // Cas des factures liees directement
2987  if ($objecttype == 'facture') {
2988  $linkedInvoices[] = $object;
2989  } else {
2990  // Cas des factures liees par un autre objet (ex: commande)
2991  $this->fetchObjectLinked($object, $objecttype);
2992  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
2993  foreach ($subobjectid as $subkey => $subobject) {
2994  if ($subobjecttype == 'facture') {
2995  $linkedInvoices[] = $subobject;
2996  }
2997  }
2998  }
2999  }
3000  }
3001  }
3002 
3003  if (count($linkedInvoices) > 0) {
3004  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3005  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3006  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3007 
3008  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3009  $resql = $this->db->query($sql);
3010 
3011  if ($resql) {
3012  $tab_sqlobj = array();
3013  $nump = $this->db->num_rows($resql);
3014  for ($i = 0; $i < $nump; $i++) {
3015  $sqlobj = $this->db->fetch_object($resql);
3016  $tab_sqlobj[] = $sqlobj;
3017  }
3018  $this->db->free($resql);
3019 
3020  $nump = count($tab_sqlobj);
3021 
3022  if ($nump) {
3023  $i = 0;
3024  while ($i < $nump) {
3025  $obj = array_shift($tab_sqlobj);
3026 
3027  $ga[$i] = $obj;
3028 
3029  $i++;
3030  }
3031  }
3032  return $ga;
3033  } else {
3034  return -1;
3035  }
3036  } else {
3037  return $ga;
3038  }
3039  }
3040 
3048  public function delete($user, $notrigger = 0)
3049  {
3050  global $conf;
3051  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3052 
3053  $error = 0;
3054 
3055  $this->db->begin();
3056 
3057  if (!$notrigger) {
3058  // Call trigger
3059  $result = $this->call_trigger('PROPAL_DELETE', $user);
3060  if ($result < 0) {
3061  $error++;
3062  }
3063  // End call triggers
3064  }
3065 
3066  // Delete extrafields of lines and lines
3067  if (!$error && !empty($this->table_element_line)) {
3068  $tabletodelete = $this->table_element_line;
3069  $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).")";
3070  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3071  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3072  $error++;
3073  $this->error = $this->db->lasterror();
3074  $this->errors[] = $this->error;
3075  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3076  }
3077  }
3078 
3079  if (!$error) {
3080  // Delete linked object
3081  $res = $this->deleteObjectLinked();
3082  if ($res < 0) {
3083  $error++;
3084  }
3085  }
3086 
3087  if (!$error) {
3088  // Delete linked contacts
3089  $res = $this->delete_linked_contact();
3090  if ($res < 0) {
3091  $error++;
3092  }
3093  }
3094 
3095  // Removed extrafields of object
3096  if (!$error) {
3097  $result = $this->deleteExtraFields();
3098  if ($result < 0) {
3099  $error++;
3100  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3101  }
3102  }
3103 
3104  // Delete main record
3105  if (!$error) {
3106  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3107  $res = $this->db->query($sql);
3108  if (!$res) {
3109  $error++;
3110  $this->error = $this->db->lasterror();
3111  $this->errors[] = $this->error;
3112  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3113  }
3114  }
3115 
3116  // Delete record into ECM index and physically
3117  if (!$error) {
3118  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3119  if (!$res) {
3120  $error++;
3121  }
3122  }
3123 
3124  if (!$error) {
3125  // We remove directory
3126  $ref = dol_sanitizeFileName($this->ref);
3127  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3128  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3129  $file = $dir."/".$ref.".pdf";
3130  if (file_exists($file)) {
3131  dol_delete_preview($this);
3132 
3133  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3134  $this->error = 'ErrorFailToDeleteFile';
3135  $this->errors[] = $this->error;
3136  $this->db->rollback();
3137  return 0;
3138  }
3139  }
3140  if (file_exists($dir)) {
3141  $res = @dol_delete_dir_recursive($dir);
3142  if (!$res) {
3143  $this->error = 'ErrorFailToDeleteDir';
3144  $this->errors[] = $this->error;
3145  $this->db->rollback();
3146  return 0;
3147  }
3148  }
3149  }
3150  }
3151 
3152  if (!$error) {
3153  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3154  $this->db->commit();
3155  return 1;
3156  } else {
3157  $this->db->rollback();
3158  return -1;
3159  }
3160  }
3161 
3170  public function availability($availability_id, $notrigger = 0)
3171  {
3172  global $user;
3173 
3174  if ($this->statut >= self::STATUS_DRAFT) {
3175  $error = 0;
3176 
3177  $this->db->begin();
3178 
3179  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3180  $sql .= ' SET fk_availability = '.((int) $availability_id);
3181  $sql .= ' WHERE rowid='.((int) $this->id);
3182 
3183  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3184  $resql = $this->db->query($sql);
3185  if (!$resql) {
3186  $this->errors[] = $this->db->error();
3187  $error++;
3188  }
3189 
3190  if (!$error) {
3191  $this->oldcopy = clone $this;
3192  $this->availability_id = $availability_id;
3193  }
3194 
3195  if (!$notrigger && empty($error)) {
3196  // Call trigger
3197  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3198  if ($result < 0) {
3199  $error++;
3200  }
3201  // End call triggers
3202  }
3203 
3204  if (!$error) {
3205  $this->db->commit();
3206  return 1;
3207  } else {
3208  foreach ($this->errors as $errmsg) {
3209  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3210  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3211  }
3212  $this->db->rollback();
3213  return -1 * $error;
3214  }
3215  } else {
3216  $error_str = 'Propal status do not meet requirement '.$this->statut;
3217  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3218  $this->error = $error_str;
3219  $this->errors[] = $this->error;
3220  return -2;
3221  }
3222  }
3223 
3224  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3233  public function demand_reason($demand_reason_id, $notrigger = 0)
3234  {
3235  // phpcs:enable
3236  global $user;
3237 
3238  if ($this->statut >= self::STATUS_DRAFT) {
3239  $error = 0;
3240 
3241  $this->db->begin();
3242 
3243  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3244  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3245  $sql .= ' WHERE rowid='.((int) $this->id);
3246 
3247  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3248  $resql = $this->db->query($sql);
3249  if (!$resql) {
3250  $this->errors[] = $this->db->error();
3251  $error++;
3252  }
3253 
3254  if (!$error) {
3255  $this->oldcopy = clone $this;
3256  $this->demand_reason_id = $demand_reason_id;
3257  }
3258 
3259  if (!$notrigger && empty($error)) {
3260  // Call trigger
3261  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3262  if ($result < 0) {
3263  $error++;
3264  }
3265  // End call triggers
3266  }
3267 
3268  if (!$error) {
3269  $this->db->commit();
3270  return 1;
3271  } else {
3272  foreach ($this->errors as $errmsg) {
3273  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3274  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3275  }
3276  $this->db->rollback();
3277  return -1 * $error;
3278  }
3279  } else {
3280  $error_str = 'Propal status do not meet requirement '.$this->statut;
3281  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3282  $this->error = $error_str;
3283  $this->errors[] = $this->error;
3284  return -2;
3285  }
3286  }
3287 
3288 
3295  public function info($id)
3296  {
3297  $sql = "SELECT c.rowid, ";
3298  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3299  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3300  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3301  $sql .= " WHERE c.rowid = ".((int) $id);
3302 
3303  $result = $this->db->query($sql);
3304 
3305  if ($result) {
3306  if ($this->db->num_rows($result)) {
3307  $obj = $this->db->fetch_object($result);
3308 
3309  $this->id = $obj->rowid;
3310 
3311  $this->date_creation = $this->db->jdate($obj->datec);
3312  $this->date_validation = $this->db->jdate($obj->datev);
3313  $this->date_signature = $this->db->jdate($obj->date_signature);
3314  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3315 
3316  $cuser = new User($this->db);
3317  $cuser->fetch($obj->fk_user_author);
3318  $this->user_creation = $cuser;
3319 
3320  if ($obj->fk_user_valid) {
3321  $vuser = new User($this->db);
3322  $vuser->fetch($obj->fk_user_valid);
3323  $this->user_validation = $vuser;
3324  }
3325 
3326  if ($obj->fk_user_signature) {
3327  $user_signature = new User($this->db);
3328  $user_signature->fetch($obj->fk_user_signature);
3329  $this->user_signature = $user_signature;
3330  }
3331 
3332  if ($obj->fk_user_cloture) {
3333  $cluser = new User($this->db);
3334  $cluser->fetch($obj->fk_user_cloture);
3335  $this->user_cloture = $cluser;
3336  }
3337  }
3338  $this->db->free($result);
3339  } else {
3340  dol_print_error($this->db);
3341  }
3342  }
3343 
3344 
3351  public function getLibStatut($mode = 0)
3352  {
3353  return $this->LibStatut($this->statut, $mode);
3354  }
3355 
3356  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3364  public function LibStatut($status, $mode = 1)
3365  {
3366  // phpcs:enable
3367  global $conf, $hookmanager;
3368 
3369  // Init/load array of translation of status
3370  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3371  global $langs;
3372  $langs->load("propal");
3373  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3374  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3375  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3376  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3377  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3378  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3379  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3380  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3381  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3382  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3383  }
3384 
3385  $statusType = '';
3386  if ($status == self::STATUS_DRAFT) {
3387  $statusType = 'status0';
3388  } elseif ($status == self::STATUS_VALIDATED) {
3389  $statusType = 'status1';
3390  } elseif ($status == self::STATUS_SIGNED) {
3391  $statusType = 'status4';
3392  } elseif ($status == self::STATUS_NOTSIGNED) {
3393  $statusType = 'status9';
3394  } elseif ($status == self::STATUS_BILLED) {
3395  $statusType = 'status6';
3396  }
3397 
3398 
3399  $parameters = array('status' => $status, 'mode' => $mode);
3400  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3401 
3402  if ($reshook > 0) {
3403  return $hookmanager->resPrint;
3404  }
3405 
3406  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3407  }
3408 
3409 
3410  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3418  public function load_board($user, $mode)
3419  {
3420  // phpcs:enable
3421  global $conf, $langs;
3422 
3423  $clause = " WHERE";
3424 
3425  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3426  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3427  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3428  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3429  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3430  $clause = " AND";
3431  }
3432  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3433  if ($mode == 'opened') {
3434  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3435  }
3436  if ($mode == 'signed') {
3437  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3438  }
3439  if ($user->socid) {
3440  $sql .= " AND p.fk_soc = ".((int) $user->socid);
3441  }
3442 
3443  $resql = $this->db->query($sql);
3444  if ($resql) {
3445  $langs->load("propal");
3446  $now = dol_now();
3447 
3448  $delay_warning = 0;
3449  $status = 0;
3450  $label = $labelShort = '';
3451  if ($mode == 'opened') {
3452  $delay_warning = $conf->propal->cloture->warning_delay;
3453  $status = self::STATUS_VALIDATED;
3454  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3455  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3456  }
3457  if ($mode == 'signed') {
3458  $delay_warning = $conf->propal->facturation->warning_delay;
3459  $status = self::STATUS_SIGNED;
3460  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3461  $labelShort = $langs->trans("ToBill");
3462  }
3463 
3464  $response = new WorkboardResponse();
3465  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3466  $response->label = $label;
3467  $response->labelShort = $labelShort;
3468  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3469  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3470  $response->img = img_object('', "propal");
3471 
3472  // This assignment in condition is not a bug. It allows walking the results.
3473  while ($obj = $this->db->fetch_object($resql)) {
3474  $response->nbtodo++;
3475  $response->total += $obj->total_ht;
3476 
3477  if ($mode == 'opened') {
3478  $datelimit = $this->db->jdate($obj->datefin);
3479  if ($datelimit < ($now - $delay_warning)) {
3480  $response->nbtodolate++;
3481  }
3482  }
3483  // TODO Definir regle des propales a facturer en retard
3484  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3485  }
3486 
3487  return $response;
3488  } else {
3489  $this->error = $this->db->error();
3490  return -1;
3491  }
3492  }
3493 
3494 
3502  public function initAsSpecimen()
3503  {
3504  global $conf, $langs;
3505 
3506  // Load array of products prodids
3507  $num_prods = 0;
3508  $prodids = array();
3509  $sql = "SELECT rowid";
3510  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3511  $sql .= " WHERE entity IN (".getEntity('product').")";
3512  $sql .= $this->db->plimit(100);
3513 
3514  $resql = $this->db->query($sql);
3515  if ($resql) {
3516  $num_prods = $this->db->num_rows($resql);
3517  $i = 0;
3518  while ($i < $num_prods) {
3519  $i++;
3520  $row = $this->db->fetch_row($resql);
3521  $prodids[$i] = $row[0];
3522  }
3523  }
3524 
3525  // Initialise parametres
3526  $this->id = 0;
3527  $this->ref = 'SPECIMEN';
3528  $this->ref_client = 'NEMICEPS';
3529  $this->specimen = 1;
3530  $this->socid = 1;
3531  $this->date = time();
3532  $this->fin_validite = $this->date + 3600 * 24 * 30;
3533  $this->cond_reglement_id = 1;
3534  $this->cond_reglement_code = 'RECEP';
3535  $this->mode_reglement_id = 7;
3536  $this->mode_reglement_code = 'CHQ';
3537  $this->availability_id = 1;
3538  $this->availability_code = 'AV_NOW';
3539  $this->demand_reason_id = 1;
3540  $this->demand_reason_code = 'SRC_00';
3541  $this->note_public = 'This is a comment (public)';
3542  $this->note_private = 'This is a comment (private)';
3543 
3544  $this->multicurrency_tx = 1;
3545  $this->multicurrency_code = $conf->currency;
3546 
3547  // Lines
3548  $nbp = 5;
3549  $xnbp = 0;
3550  while ($xnbp < $nbp) {
3551  $line = new PropaleLigne($this->db);
3552  $line->desc = $langs->trans("Description")." ".$xnbp;
3553  $line->qty = 1;
3554  $line->subprice = 100;
3555  $line->price = 100;
3556  $line->tva_tx = 20;
3557  $line->localtax1_tx = 0;
3558  $line->localtax2_tx = 0;
3559  if ($xnbp == 2) {
3560  $line->total_ht = 50;
3561  $line->total_ttc = 60;
3562  $line->total_tva = 10;
3563  $line->remise_percent = 50;
3564  } else {
3565  $line->total_ht = 100;
3566  $line->total_ttc = 120;
3567  $line->total_tva = 20;
3568  $line->remise_percent = 00;
3569  }
3570 
3571  if ($num_prods > 0) {
3572  $prodid = mt_rand(1, $num_prods);
3573  $line->fk_product = $prodids[$prodid];
3574  $line->product_ref = 'SPECIMEN';
3575  }
3576 
3577  $this->lines[$xnbp] = $line;
3578 
3579  $this->total_ht += $line->total_ht;
3580  $this->total_tva += $line->total_tva;
3581  $this->total_ttc += $line->total_ttc;
3582 
3583  $xnbp++;
3584  }
3585  }
3586 
3587  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3593  public function load_state_board()
3594  {
3595  // phpcs:enable
3596  global $user;
3597 
3598  $this->nb = array();
3599  $clause = "WHERE";
3600 
3601  $sql = "SELECT count(p.rowid) as nb";
3602  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3603  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3604  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3605  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3606  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3607  $clause = "AND";
3608  }
3609  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3610 
3611  $resql = $this->db->query($sql);
3612  if ($resql) {
3613  // This assignment in condition is not a bug. It allows walking the results.
3614  while ($obj = $this->db->fetch_object($resql)) {
3615  $this->nb["proposals"] = $obj->nb;
3616  }
3617  $this->db->free($resql);
3618  return 1;
3619  } else {
3620  dol_print_error($this->db);
3621  $this->error = $this->db->error();
3622  return -1;
3623  }
3624  }
3625 
3626 
3634  public function getNextNumRef($soc)
3635  {
3636  global $conf, $langs;
3637  $langs->load("propal");
3638 
3639  $classname = $conf->global->PROPALE_ADDON;
3640 
3641  if (!empty($classname)) {
3642  $mybool = false;
3643 
3644  $file = $classname.".php";
3645 
3646  // Include file with class
3647  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3648  foreach ($dirmodels as $reldir) {
3649  $dir = dol_buildpath($reldir."core/modules/propale/");
3650 
3651  // Load file with numbering class (if found)
3652  $mybool |= @include_once $dir.$file;
3653  }
3654 
3655  if (!$mybool) {
3656  dol_print_error('', "Failed to include file ".$file);
3657  return '';
3658  }
3659 
3660  $obj = new $classname();
3661  $numref = "";
3662  $numref = $obj->getNextValue($soc, $this);
3663 
3664  if ($numref != "") {
3665  return $numref;
3666  } else {
3667  $this->error = $obj->error;
3668  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3669  return "";
3670  }
3671  } else {
3672  $langs->load("errors");
3673  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3674  return "";
3675  }
3676  }
3677 
3689  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3690  {
3691  global $langs, $conf, $user, $hookmanager;
3692 
3693  if (!empty($conf->dol_no_mouse_hover)) {
3694  $notooltip = 1; // Force disable tooltips
3695  }
3696 
3697  $result = '';
3698  $label = '';
3699  $url = '';
3700 
3701  if ($user->rights->propal->lire) {
3702  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3703  if (isset($this->statut)) {
3704  $label .= ' '.$this->getLibStatut(5);
3705  }
3706  if (!empty($this->ref)) {
3707  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3708  }
3709  if (!empty($this->ref_client)) {
3710  $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3711  }
3712  if (!empty($this->total_ht)) {
3713  $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3714  }
3715  if (!empty($this->total_tva)) {
3716  $label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3717  }
3718  if (!empty($this->total_ttc)) {
3719  $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3720  }
3721  if (!empty($this->date)) {
3722  $label .= '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3723  }
3724  if (!empty($this->delivery_date)) {
3725  $label .= '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3726  }
3727 
3728  if ($option == '') {
3729  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3730  } elseif ($option == 'compta') { // deprecated
3731  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3732  } elseif ($option == 'expedition') {
3733  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3734  } elseif ($option == 'document') {
3735  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3736  }
3737 
3738  if ($option != 'nolink') {
3739  // Add param to save lastsearch_values or not
3740  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3741  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3742  $add_save_lastsearch_values = 1;
3743  }
3744  if ($add_save_lastsearch_values) {
3745  $url .= '&save_lastsearch_values=1';
3746  }
3747  }
3748  }
3749 
3750  $linkclose = '';
3751  if (empty($notooltip) && $user->rights->propal->lire) {
3752  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3753  $label = $langs->trans("Proposal");
3754  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3755  }
3756  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
3757  $linkclose .= ' class="classfortooltip"';
3758  }
3759 
3760  $linkstart = '<a href="'.$url.'"';
3761  $linkstart .= $linkclose.'>';
3762  $linkend = '</a>';
3763 
3764  $result .= $linkstart;
3765  if ($withpicto) {
3766  $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
3767  }
3768  if ($withpicto != 2) {
3769  $result .= $this->ref;
3770  }
3771  $result .= $linkend;
3772 
3773  if ($addlinktonotes >= 0) {
3774  $txttoshow = '';
3775 
3776  if ($addlinktonotes == 0) {
3777  if (!empty($this->note_private) || !empty($this->note_public)) {
3778  $txttoshow = $langs->trans('ViewPrivateNote');
3779  }
3780  } elseif ($addlinktonotes == 1) {
3781  if (!empty($this->note_private)) {
3782  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3783  }
3784  } elseif ($addlinktonotes == 2) {
3785  if (!empty($this->note_public)) {
3786  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3787  }
3788  } elseif ($addlinktonotes == 3) {
3789  if ($user->socid > 0) {
3790  if (!empty($this->note_public)) {
3791  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3792  }
3793  } else {
3794  if (!empty($this->note_public)) {
3795  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3796  }
3797  if (!empty($this->note_private)) {
3798  if (!empty($txttoshow)) {
3799  $txttoshow .= '<br><br>';
3800  }
3801  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3802  }
3803  }
3804  }
3805 
3806  if ($txttoshow) {
3807  $result .= ' <span class="note inline-block">';
3808  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3809  $result .= img_picto('', 'note');
3810  $result .= '</a>';
3811  $result .= '</span>';
3812  }
3813  }
3814 
3815  global $action;
3816  $hookmanager->initHooks(array($this->element . 'dao'));
3817  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3818  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3819  if ($reshook > 0) {
3820  $result = $hookmanager->resPrint;
3821  } else {
3822  $result .= $hookmanager->resPrint;
3823  }
3824  return $result;
3825  }
3826 
3833  public function getLinesArray($filters = '')
3834  {
3835  return $this->fetch_lines(0, 0, $filters);
3836  }
3837 
3849  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3850  {
3851  global $conf, $langs;
3852 
3853  $langs->load("propale");
3854  $outputlangs->load("products");
3855 
3856  if (!dol_strlen($modele)) {
3857  $modele = 'azur';
3858 
3859  if ($this->model_pdf) {
3860  $modele = $this->model_pdf;
3861  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3862  $modele = $conf->global->PROPALE_ADDON_PDF;
3863  }
3864  }
3865 
3866  $modelpath = "core/modules/propale/doc/";
3867 
3868  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3869  }
3870 
3879  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
3880  {
3881  $tables = array(
3882  'propal'
3883  );
3884 
3885  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3886  }
3887 
3896  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3897  {
3898  $tables = array(
3899  'propaldet'
3900  );
3901 
3902  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3903  }
3904 }
3905 
3910 {
3914  public $element = 'propaldet';
3915 
3919  public $table_element = 'propaldet';
3920 
3921  public $oldline;
3922 
3923  // From llx_propaldet
3924  public $fk_propal;
3925  public $fk_parent_line;
3926  public $desc; // Description ligne
3927  public $fk_product; // Id produit predefini
3938  public $product_type = Product::TYPE_PRODUCT;
3939 
3940  public $qty;
3941 
3942  public $tva_tx;
3943  public $vat_src_code;
3944 
3945  public $subprice;
3946  public $remise_percent;
3947  public $fk_remise_except;
3948 
3949  public $rang = 0;
3950 
3951  public $fk_fournprice;
3952  public $pa_ht;
3953  public $marge_tx;
3954  public $marque_tx;
3955 
3956  public $special_code; // Tag for special lines (exlusive tags)
3957  // 1: frais de port
3958  // 2: ecotaxe
3959  // 3: option line (when qty = 0)
3960 
3961  public $info_bits = 0; // Some other info:
3962  // Bit 0: 0 si TVA normal - 1 si TVA NPR
3963  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
3964 
3965  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
3966  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
3967  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
3968 
3973  public $remise;
3978  public $price;
3979 
3980  // From llx_product
3985  public $ref;
3990  public $product_ref;
3995  public $libelle;
4000  public $label;
4005  public $product_label;
4010  public $product_desc;
4011 
4016  public $product_tobatch;
4017 
4022  public $product_barcode;
4023 
4024  public $localtax1_tx; // Local tax 1
4025  public $localtax2_tx; // Local tax 2
4026  public $localtax1_type; // Local tax 1 type
4027  public $localtax2_type; // Local tax 2 type
4028  public $total_localtax1; // Line total local tax 1
4029  public $total_localtax2; // Line total local tax 2
4030 
4031  public $date_start;
4032  public $date_end;
4033 
4034  public $skip_update_total; // Skip update price total for special lines
4035 
4036  // Multicurrency
4037  public $fk_multicurrency;
4038  public $multicurrency_code;
4039  public $multicurrency_subprice;
4040  public $multicurrency_total_ht;
4041  public $multicurrency_total_tva;
4042  public $multicurrency_total_ttc;
4043 
4044 
4050  public function __construct($db)
4051  {
4052  $this->db = $db;
4053  }
4054 
4061  public function fetch($rowid)
4062  {
4063  $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,';
4064  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4065  $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,';
4066  $sql .= ' pd.fk_unit,';
4067  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4068  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4069  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4070  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4071  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4072  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4073  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4074 
4075  $result = $this->db->query($sql);
4076  if ($result) {
4077  $objp = $this->db->fetch_object($result);
4078 
4079  if ($objp) {
4080  $this->id = $objp->rowid;
4081  $this->rowid = $objp->rowid; // deprecated
4082  $this->fk_propal = $objp->fk_propal;
4083  $this->fk_parent_line = $objp->fk_parent_line;
4084  $this->label = $objp->custom_label;
4085  $this->desc = $objp->description;
4086  $this->qty = $objp->qty;
4087  $this->price = $objp->price; // deprecated
4088  $this->subprice = $objp->subprice;
4089  $this->vat_src_code = $objp->vat_src_code;
4090  $this->tva_tx = $objp->tva_tx;
4091  $this->remise = $objp->remise; // deprecated
4092  $this->remise_percent = $objp->remise_percent;
4093  $this->fk_remise_except = $objp->fk_remise_except;
4094  $this->fk_product = $objp->fk_product;
4095  $this->info_bits = $objp->info_bits;
4096 
4097  $this->total_ht = $objp->total_ht;
4098  $this->total_tva = $objp->total_tva;
4099  $this->total_ttc = $objp->total_ttc;
4100 
4101  $this->fk_fournprice = $objp->fk_fournprice;
4102 
4103  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4104  $this->pa_ht = $marginInfos[0];
4105  $this->marge_tx = $marginInfos[1];
4106  $this->marque_tx = $marginInfos[2];
4107 
4108  $this->special_code = $objp->special_code;
4109  $this->product_type = $objp->product_type;
4110  $this->rang = $objp->rang;
4111 
4112  $this->ref = $objp->product_ref; // deprecated
4113  $this->product_ref = $objp->product_ref;
4114  $this->libelle = $objp->product_label; // deprecated
4115  $this->product_label = $objp->product_label;
4116  $this->product_desc = $objp->product_desc;
4117  $this->fk_unit = $objp->fk_unit;
4118 
4119  $this->date_start = $this->db->jdate($objp->date_start);
4120  $this->date_end = $this->db->jdate($objp->date_end);
4121 
4122  // Multicurrency
4123  $this->fk_multicurrency = $objp->fk_multicurrency;
4124  $this->multicurrency_code = $objp->multicurrency_code;
4125  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4126  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4127  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4128  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4129 
4130  $this->fetch_optionals();
4131 
4132  $this->db->free($result);
4133 
4134  return 1;
4135  } else {
4136  return 0;
4137  }
4138  } else {
4139  return -1;
4140  }
4141  }
4142 
4149  public function insert($notrigger = 0)
4150  {
4151  global $conf, $user;
4152 
4153  $error = 0;
4154 
4155  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4156 
4157  $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'.
4158 
4159  // Clean parameters
4160  if (empty($this->tva_tx)) {
4161  $this->tva_tx = 0;
4162  }
4163  if (empty($this->localtax1_tx)) {
4164  $this->localtax1_tx = 0;
4165  }
4166  if (empty($this->localtax2_tx)) {
4167  $this->localtax2_tx = 0;
4168  }
4169  if (empty($this->localtax1_type)) {
4170  $this->localtax1_type = 0;
4171  }
4172  if (empty($this->localtax2_type)) {
4173  $this->localtax2_type = 0;
4174  }
4175  if (empty($this->total_localtax1)) {
4176  $this->total_localtax1 = 0;
4177  }
4178  if (empty($this->total_localtax2)) {
4179  $this->total_localtax2 = 0;
4180  }
4181  if (empty($this->rang)) {
4182  $this->rang = 0;
4183  }
4184  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4185  $this->remise_percent = 0;
4186  }
4187  if (empty($this->info_bits)) {
4188  $this->info_bits = 0;
4189  }
4190  if (empty($this->special_code)) {
4191  $this->special_code = 0;
4192  }
4193  if (empty($this->fk_parent_line)) {
4194  $this->fk_parent_line = 0;
4195  }
4196  if (empty($this->fk_fournprice)) {
4197  $this->fk_fournprice = 0;
4198  }
4199  if (!is_numeric($this->qty)) {
4200  $this->qty = 0;
4201  }
4202  if (empty($this->pa_ht)) {
4203  $this->pa_ht = 0;
4204  }
4205  if (empty($this->multicurrency_subprice)) {
4206  $this->multicurrency_subprice = 0;
4207  }
4208  if (empty($this->multicurrency_total_ht)) {
4209  $this->multicurrency_total_ht = 0;
4210  }
4211  if (empty($this->multicurrency_total_tva)) {
4212  $this->multicurrency_total_tva = 0;
4213  }
4214  if (empty($this->multicurrency_total_ttc)) {
4215  $this->multicurrency_total_ttc = 0;
4216  }
4217 
4218  // if buy price not defined, define buyprice as configured in margin admin
4219  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4220  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4221  return $result;
4222  } else {
4223  $this->pa_ht = $result;
4224  }
4225  }
4226 
4227  // Check parameters
4228  if ($this->product_type < 0) {
4229  return -1;
4230  }
4231 
4232  $this->db->begin();
4233 
4234  // Insert line into database
4235  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4236  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4237  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4238  $sql .= ' subprice, remise_percent, ';
4239  $sql .= ' info_bits, ';
4240  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4241  $sql .= ' fk_unit,';
4242  $sql .= ' date_start, date_end';
4243  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4244  $sql .= " VALUES (".$this->fk_propal.",";
4245  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4246  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4247  $sql .= " '".$this->db->escape($this->desc)."',";
4248  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4249  $sql .= " '".$this->db->escape($this->product_type)."',";
4250  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4251  $sql .= " ".price2num($this->qty, 'MS').",";
4252  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4253  $sql .= " ".price2num($this->tva_tx).",";
4254  $sql .= " ".price2num($this->localtax1_tx).",";
4255  $sql .= " ".price2num($this->localtax2_tx).",";
4256  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4257  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4258  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4259  $sql .= " ".price2num($this->remise_percent, 3).",";
4260  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4261  $sql .= " ".price2num($this->total_ht, 'MT').",";
4262  $sql .= " ".price2num($this->total_tva, 'MT').",";
4263  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4264  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4265  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4266  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4267  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4268  $sql .= ' '.((int) $this->special_code).',';
4269  $sql .= ' '.((int) $this->rang).',';
4270  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4271  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4272  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4273  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4274  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4275  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4276  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4277  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4278  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4279  $sql .= ')';
4280 
4281  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4282  $resql = $this->db->query($sql);
4283  if ($resql) {
4284  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4285 
4286  if (!$error) {
4287  $this->id = $this->rowid;
4288  $result = $this->insertExtraFields();
4289  if ($result < 0) {
4290  $error++;
4291  }
4292  }
4293 
4294  if (!$error && !$notrigger) {
4295  // Call trigger
4296  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4297  if ($result < 0) {
4298  $this->db->rollback();
4299  return -1;
4300  }
4301  // End call triggers
4302  }
4303 
4304  $this->db->commit();
4305  return 1;
4306  } else {
4307  $this->error = $this->db->error()." sql=".$sql;
4308  $this->db->rollback();
4309  return -1;
4310  }
4311  }
4312 
4320  public function delete(User $user, $notrigger = 0)
4321  {
4322  global $conf;
4323 
4324  $error = 0;
4325  $this->db->begin();
4326 
4327  if (!$notrigger) {
4328  // Call trigger
4329  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4330  if ($result < 0) {
4331  $error++;
4332  }
4333  }
4334  // End call triggers
4335 
4336  if (!$error) {
4337  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4338  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4339  if ($this->db->query($sql)) {
4340  // Remove extrafields
4341  if (!$error) {
4342  $this->id = $this->rowid;
4343  $result = $this->deleteExtraFields();
4344  if ($result < 0) {
4345  $error++;
4346  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4347  }
4348  }
4349  } else {
4350  $this->error = $this->db->error() . " sql=" . $sql;
4351  $error++;
4352  }
4353  }
4354 
4355  if ($error) {
4356  $this->db->rollback();
4357  return -1;
4358  } else {
4359  $this->db->commit();
4360  return 1;
4361  }
4362  }
4363 
4370  public function update($notrigger = 0)
4371  {
4372  global $conf, $user;
4373 
4374  $error = 0;
4375 
4376  $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'.
4377 
4378  if (empty($this->id) && !empty($this->rowid)) {
4379  $this->id = $this->rowid;
4380  }
4381 
4382  // Clean parameters
4383  if (empty($this->tva_tx)) {
4384  $this->tva_tx = 0;
4385  }
4386  if (empty($this->localtax1_tx)) {
4387  $this->localtax1_tx = 0;
4388  }
4389  if (empty($this->localtax2_tx)) {
4390  $this->localtax2_tx = 0;
4391  }
4392  if (empty($this->total_localtax1)) {
4393  $this->total_localtax1 = 0;
4394  }
4395  if (empty($this->total_localtax2)) {
4396  $this->total_localtax2 = 0;
4397  }
4398  if (empty($this->localtax1_type)) {
4399  $this->localtax1_type = 0;
4400  }
4401  if (empty($this->localtax2_type)) {
4402  $this->localtax2_type = 0;
4403  }
4404  if (empty($this->marque_tx)) {
4405  $this->marque_tx = 0;
4406  }
4407  if (empty($this->marge_tx)) {
4408  $this->marge_tx = 0;
4409  }
4410  if (empty($this->price)) {
4411  $this->price = 0; // TODO A virer
4412  }
4413  if (empty($this->remise_percent)) {
4414  $this->remise_percent = 0;
4415  }
4416  if (empty($this->info_bits)) {
4417  $this->info_bits = 0;
4418  }
4419  if (empty($this->special_code)) {
4420  $this->special_code = 0;
4421  }
4422  if (empty($this->fk_parent_line)) {
4423  $this->fk_parent_line = 0;
4424  }
4425  if (empty($this->fk_fournprice)) {
4426  $this->fk_fournprice = 0;
4427  }
4428  if (empty($this->subprice)) {
4429  $this->subprice = 0;
4430  }
4431  if (empty($this->pa_ht)) {
4432  $this->pa_ht = 0;
4433  }
4434 
4435  // if buy price not defined, define buyprice as configured in margin admin
4436  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4437  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4438  return $result;
4439  } else {
4440  $this->pa_ht = $result;
4441  }
4442  }
4443 
4444  $this->db->begin();
4445 
4446  // Mise a jour ligne en base
4447  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4448  $sql .= " description='".$this->db->escape($this->desc)."'";
4449  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4450  $sql .= ", product_type=".$this->product_type;
4451  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4452  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4453  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4454  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4455  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4456  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4457  $sql .= ", qty='".price2num($this->qty)."'";
4458  $sql .= ", subprice=".price2num($this->subprice)."";
4459  $sql .= ", remise_percent=".price2num($this->remise_percent)."";
4460  $sql .= ", price=".(float) price2num($this->price).""; // TODO A virer
4461  $sql .= ", remise=".(float) price2num($this->remise).""; // TODO A virer
4462  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4463  if (empty($this->skip_update_total)) {
4464  $sql .= ", total_ht=".price2num($this->total_ht)."";
4465  $sql .= ", total_tva=".price2num($this->total_tva)."";
4466  $sql .= ", total_ttc=".price2num($this->total_ttc)."";
4467  $sql .= ", total_localtax1=".price2num($this->total_localtax1)."";
4468  $sql .= ", total_localtax2=".price2num($this->total_localtax2)."";
4469  }
4470  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4471  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4472  if (strlen($this->special_code)) {
4473  $sql .= ", special_code=".$this->special_code;
4474  }
4475  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4476  if (!empty($this->rang)) {
4477  $sql .= ", rang=".((int) $this->rang);
4478  }
4479  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4480  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4481  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4482 
4483  // Multicurrency
4484  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4485  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4486  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4487  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4488 
4489  $sql .= " WHERE rowid = ".((int) $this->id);
4490 
4491  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4492  $resql = $this->db->query($sql);
4493  if ($resql) {
4494  if (!$error) {
4495  $result = $this->insertExtraFields();
4496  if ($result < 0) {
4497  $error++;
4498  }
4499  }
4500 
4501  if (!$error && !$notrigger) {
4502  // Call trigger
4503  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4504  if ($result < 0) {
4505  $this->db->rollback();
4506  return -1;
4507  }
4508  // End call triggers
4509  }
4510 
4511  $this->db->commit();
4512  return 1;
4513  } else {
4514  $this->error = $this->db->error();
4515  $this->db->rollback();
4516  return -2;
4517  }
4518  }
4519 
4520  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4527  public function update_total()
4528  {
4529  // phpcs:enable
4530  $this->db->begin();
4531 
4532  // Mise a jour ligne en base
4533  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4534  $sql .= " total_ht=".price2num($this->total_ht, 'MT')."";
4535  $sql .= ",total_tva=".price2num($this->total_tva, 'MT')."";
4536  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT')."";
4537  $sql .= " WHERE rowid = ".((int) $this->rowid);
4538 
4539  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4540 
4541  $resql = $this->db->query($sql);
4542  if ($resql) {
4543  $this->db->commit();
4544  return 1;
4545  } else {
4546  $this->error = $this->db->error();
4547  $this->db->rollback();
4548  return -2;
4549  }
4550  }
4551 }
$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