dolibarr  x.y.z
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  return 1;
459  }
460 
461  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
468  public function insert_discount($idremise)
469  {
470  // phpcs:enable
471  global $langs;
472 
473  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
474  include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
475 
476  $this->db->begin();
477 
478  $remise = new DiscountAbsolute($this->db);
479  $result = $remise->fetch($idremise);
480 
481  if ($result > 0) {
482  if ($remise->fk_facture) { // Protection against multiple submission
483  $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
484  $this->db->rollback();
485  return -5;
486  }
487 
488  $line = new PropaleLigne($this->db);
489 
490  $this->line->context = $this->context;
491 
492  $line->fk_propal = $this->id;
493  $line->fk_remise_except = $remise->id;
494  $line->desc = $remise->description; // Description ligne
495  $line->vat_src_code = $remise->vat_src_code;
496  $line->tva_tx = $remise->tva_tx;
497  $line->subprice = -$remise->amount_ht;
498  $line->fk_product = 0; // Id produit predefined
499  $line->qty = 1;
500  $line->remise_percent = 0;
501  $line->rang = -1;
502  $line->info_bits = 2;
503 
504  // TODO deprecated
505  $line->price = -$remise->amount_ht;
506 
507  $line->total_ht = -$remise->amount_ht;
508  $line->total_tva = -$remise->amount_tva;
509  $line->total_ttc = -$remise->amount_ttc;
510 
511  $result = $line->insert();
512  if ($result > 0) {
513  $result = $this->update_price(1);
514  if ($result > 0) {
515  $this->db->commit();
516  return 1;
517  } else {
518  $this->db->rollback();
519  return -1;
520  }
521  } else {
522  $this->error = $line->error;
523  $this->errors = $line->errors;
524  $this->db->rollback();
525  return -2;
526  }
527  } else {
528  $this->db->rollback();
529  return -2;
530  }
531  }
532 
570  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)
571  {
572  global $mysoc, $conf, $langs;
573 
574  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);
575 
576  if ($this->statut == self::STATUS_DRAFT) {
577  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
578 
579  // Clean parameters
580  if (empty($remise_percent)) {
581  $remise_percent = 0;
582  }
583  if (empty($qty)) {
584  $qty = 0;
585  }
586  if (empty($info_bits)) {
587  $info_bits = 0;
588  }
589  if (empty($rang)) {
590  $rang = 0;
591  }
592  if (empty($fk_parent_line) || $fk_parent_line < 0) {
593  $fk_parent_line = 0;
594  }
595 
596  $remise_percent = price2num($remise_percent);
597  $qty = price2num($qty);
598  $pu_ht = price2num($pu_ht);
599  $pu_ht_devise = price2num($pu_ht_devise);
600  $pu_ttc = price2num($pu_ttc);
601  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
602  $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
603  }
604  $txlocaltax1 = price2num($txlocaltax1);
605  $txlocaltax2 = price2num($txlocaltax2);
606  $pa_ht = price2num($pa_ht);
607  if ($price_base_type == 'HT') {
608  $pu = $pu_ht;
609  } else {
610  $pu = $pu_ttc;
611  }
612 
613  // Check parameters
614  if ($type < 0) {
615  return -1;
616  }
617 
618  if ($date_start && $date_end && $date_start > $date_end) {
619  $langs->load("errors");
620  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
621  return -1;
622  }
623 
624  $this->db->begin();
625 
626  $product_type = $type;
627  if (!empty($fk_product) && $fk_product > 0) {
628  $product = new Product($this->db);
629  $result = $product->fetch($fk_product);
630  $product_type = $product->type;
631 
632  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
633  $langs->load("errors");
634  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
635  $this->db->rollback();
636  return -3;
637  }
638  }
639 
640  // Calcul du total TTC et de la TVA pour la ligne a partir de
641  // qty, pu, remise_percent et txtva
642  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
643  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
644 
645  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
646 
647  // Clean vat code
648  $reg = array();
649  $vat_src_code = '';
650  $reg = array();
651  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
652  $vat_src_code = $reg[1];
653  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
654  }
655 
656  $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);
657 
658  $total_ht = $tabprice[0];
659  $total_tva = $tabprice[1];
660  $total_ttc = $tabprice[2];
661  $total_localtax1 = $tabprice[9];
662  $total_localtax2 = $tabprice[10];
663  $pu_ht = $tabprice[3];
664  $pu_tva = $tabprice[4];
665  $pu_ttc = $tabprice[5];
666 
667  // MultiCurrency
668  $multicurrency_total_ht = $tabprice[16];
669  $multicurrency_total_tva = $tabprice[17];
670  $multicurrency_total_ttc = $tabprice[18];
671  $pu_ht_devise = $tabprice[19];
672 
673  // Rang to use
674  $ranktouse = $rang;
675  if ($ranktouse == -1) {
676  $rangmax = $this->line_max($fk_parent_line);
677  $ranktouse = $rangmax + 1;
678  }
679 
680  // TODO A virer
681  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
682  $price = $pu;
683  $remise = 0;
684  if ($remise_percent > 0) {
685  $remise = round(($pu * $remise_percent / 100), 2);
686  $price = $pu - $remise;
687  }
688 
689  // Insert line
690  $this->line = new PropaleLigne($this->db);
691 
692  $this->line->context = $this->context;
693 
694  $this->line->fk_propal = $this->id;
695  $this->line->label = $label;
696  $this->line->desc = $desc;
697  $this->line->qty = $qty;
698 
699  $this->line->vat_src_code = $vat_src_code;
700  $this->line->tva_tx = $txtva;
701  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
702  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
703  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
704  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
705  $this->line->fk_product = $fk_product;
706  $this->line->product_type = $type;
707  $this->line->fk_remise_except = $fk_remise_except;
708  $this->line->remise_percent = $remise_percent;
709  $this->line->subprice = $pu_ht;
710  $this->line->rang = $ranktouse;
711  $this->line->info_bits = $info_bits;
712  $this->line->total_ht = $total_ht;
713  $this->line->total_tva = $total_tva;
714  $this->line->total_localtax1 = $total_localtax1;
715  $this->line->total_localtax2 = $total_localtax2;
716  $this->line->total_ttc = $total_ttc;
717  $this->line->special_code = $special_code;
718  $this->line->fk_parent_line = $fk_parent_line;
719  $this->line->fk_unit = $fk_unit;
720 
721  $this->line->date_start = $date_start;
722  $this->line->date_end = $date_end;
723 
724  $this->line->fk_fournprice = $fk_fournprice;
725  $this->line->pa_ht = $pa_ht;
726 
727  $this->line->origin_id = $origin_id;
728  $this->line->origin = $origin;
729 
730  // Multicurrency
731  $this->line->fk_multicurrency = $this->fk_multicurrency;
732  $this->line->multicurrency_code = $this->multicurrency_code;
733  $this->line->multicurrency_subprice = $pu_ht_devise;
734  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
735  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
736  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
737 
738  // Mise en option de la ligne
739  if (empty($qty) && empty($special_code)) {
740  $this->line->special_code = 3;
741  }
742 
743  // TODO deprecated
744  $this->line->price = $price;
745 
746  if (is_array($array_options) && count($array_options) > 0) {
747  $this->line->array_options = $array_options;
748  }
749 
750  $result = $this->line->insert();
751  if ($result > 0) {
752  // Reorder if child line
753  if (!empty($fk_parent_line)) {
754  $this->line_order(true, 'DESC');
755  } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
756  $linecount = count($this->lines);
757  for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
758  $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
759  }
760  }
761 
762  // Mise a jour informations denormalisees au niveau de la propale meme
763  if (empty($noupdateafterinsertline)) {
764  $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.
765  }
766 
767  if ($result > 0) {
768  $this->db->commit();
769  return $this->line->id;
770  } else {
771  $this->error = $this->db->error();
772  $this->db->rollback();
773  return -1;
774  }
775  } else {
776  $this->error = $this->line->error;
777  $this->errors = $this->line->errors;
778  $this->db->rollback();
779  return -2;
780  }
781  } else {
782  dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
783  return -3;
784  }
785  }
786 
787 
817  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)
818  {
819  global $mysoc, $langs;
820 
821  dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
822  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");
823  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
824 
825  // Clean parameters
826  $remise_percent = price2num($remise_percent);
827  $qty = price2num($qty);
828  $pu = price2num($pu);
829  $pu_ht_devise = price2num($pu_ht_devise);
830  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
831  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
832  }
833  $txlocaltax1 = price2num($txlocaltax1);
834  $txlocaltax2 = price2num($txlocaltax2);
835  $pa_ht = price2num($pa_ht);
836  if (empty($qty) && empty($special_code)) {
837  $special_code = 3; // Set option tag
838  }
839  if (!empty($qty) && $special_code == 3) {
840  $special_code = 0; // Remove option tag
841  }
842  if (empty($type)) {
843  $type = 0;
844  }
845 
846  if ($date_start && $date_end && $date_start > $date_end) {
847  $langs->load("errors");
848  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
849  return -1;
850  }
851 
852  if ($this->statut == self::STATUS_DRAFT) {
853  $this->db->begin();
854 
855  // Calcul du total TTC et de la TVA pour la ligne a partir de
856  // qty, pu, remise_percent et txtva
857  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
858  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
859 
860  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
861 
862  // Clean vat code
863  $reg = array();
864  $vat_src_code = '';
865  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
866  $vat_src_code = $reg[1];
867  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
868  }
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);
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 
989  public function deleteline($lineid, $id = 0)
990  {
991  global $user;
992 
993  if ($this->statut == self::STATUS_DRAFT) {
994  $this->db->begin();
995 
996  $line = new PropaleLigne($this->db);
997 
998  $line->context = $this->context;
999 
1000  // Load data
1001  $line->fetch($lineid);
1002 
1003  if ($id > 0 && $line->fk_propal != $id) {
1004  $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1005  return -1;
1006  }
1007 
1008  // Memorize previous line for triggers
1009  $staticline = clone $line;
1010  $line->oldline = $staticline;
1011 
1012  if ($line->delete($user) > 0) {
1013  $this->update_price(1);
1014 
1015  $this->db->commit();
1016  return 1;
1017  } else {
1018  $this->error = $line->error;
1019  $this->errors = $line->errors;
1020  $this->db->rollback();
1021  return -1;
1022  }
1023  } else {
1024  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1025  return -2;
1026  }
1027  }
1028 
1029 
1038  public function create($user, $notrigger = 0)
1039  {
1040  global $conf, $hookmanager, $mysoc;
1041  $error = 0;
1042 
1043  $now = dol_now();
1044 
1045  // Clean parameters
1046  if (empty($this->date)) {
1047  $this->date = $this->datep;
1048  }
1049  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1050  if (empty($this->availability_id)) {
1051  $this->availability_id = 0;
1052  }
1053  if (empty($this->demand_reason_id)) {
1054  $this->demand_reason_id = 0;
1055  }
1056 
1057  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1058  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1059  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1060  } else {
1061  $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1062  }
1063  if (empty($this->fk_multicurrency)) {
1064  $this->multicurrency_code = $conf->currency;
1065  $this->fk_multicurrency = 0;
1066  $this->multicurrency_tx = 1;
1067  }
1068 
1069  // Set tmp vars
1070  $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1071 
1072  dol_syslog(get_class($this)."::create");
1073 
1074  // Check parameters
1075  $result = $this->fetch_thirdparty();
1076  if ($result < 0) {
1077  $this->error = "Failed to fetch company";
1078  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1079  return -3;
1080  }
1081 
1082  // Check parameters
1083  if (!empty($this->ref)) { // We check that ref is not already used
1084  $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1085  if ($result > 0) {
1086  $this->error = 'ErrorRefAlreadyExists';
1087  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1088  $this->db->rollback();
1089  return -1;
1090  }
1091  }
1092 
1093  if (empty($this->date)) {
1094  $this->error = "Date of proposal is required";
1095  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1096  return -4;
1097  }
1098 
1099 
1100  $this->db->begin();
1101 
1102  // Insert into database
1103  $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1104  $sql .= "fk_soc";
1105  $sql .= ", price";
1106  $sql .= ", remise";
1107  $sql .= ", remise_percent";
1108  $sql .= ", remise_absolue";
1109  $sql .= ", total_tva";
1110  $sql .= ", total_ttc";
1111  $sql .= ", datep";
1112  $sql .= ", datec";
1113  $sql .= ", ref";
1114  $sql .= ", fk_user_author";
1115  $sql .= ", note_private";
1116  $sql .= ", note_public";
1117  $sql .= ", model_pdf";
1118  $sql .= ", fin_validite";
1119  $sql .= ", fk_cond_reglement";
1120  $sql .= ", deposit_percent";
1121  $sql .= ", fk_mode_reglement";
1122  $sql .= ", fk_account";
1123  $sql .= ", ref_client";
1124  $sql .= ", ref_ext";
1125  $sql .= ", date_livraison";
1126  $sql .= ", fk_shipping_method";
1127  $sql .= ", fk_warehouse";
1128  $sql .= ", fk_availability";
1129  $sql .= ", fk_input_reason";
1130  $sql .= ", fk_projet";
1131  $sql .= ", fk_incoterms";
1132  $sql .= ", location_incoterms";
1133  $sql .= ", entity";
1134  $sql .= ", fk_multicurrency";
1135  $sql .= ", multicurrency_code";
1136  $sql .= ", multicurrency_tx";
1137  $sql .= ") ";
1138  $sql .= " VALUES (";
1139  $sql .= $this->socid;
1140  $sql .= ", 0";
1141  $sql .= ", ".((float) $this->remise); // deprecated
1142  $sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL');
1143  $sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL'); // deprecated
1144  $sql .= ", 0";
1145  $sql .= ", 0";
1146  $sql .= ", '".$this->db->idate($this->date)."'";
1147  $sql .= ", '".$this->db->idate($now)."'";
1148  $sql .= ", '(PROV)'";
1149  $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1150  $sql .= ", '".$this->db->escape($this->note_private)."'";
1151  $sql .= ", '".$this->db->escape($this->note_public)."'";
1152  $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1153  $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1154  $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1155  $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1156  $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1157  $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1158  $sql .= ", '".$this->db->escape($this->ref_client)."'";
1159  $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1160  $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1161  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1162  $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1163  $sql .= ", ".$this->availability_id;
1164  $sql .= ", ".$this->demand_reason_id;
1165  $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1166  $sql .= ", ".(int) $this->fk_incoterms;
1167  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1168  $sql .= ", ".setEntity($this);
1169  $sql .= ", ".(int) $this->fk_multicurrency;
1170  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1171  $sql .= ", ".(double) $this->multicurrency_tx;
1172  $sql .= ")";
1173 
1174  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1175  $resql = $this->db->query($sql);
1176  if ($resql) {
1177  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1178 
1179  if ($this->id) {
1180  $this->ref = '(PROV'.$this->id.')';
1181  $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1182 
1183  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1184  $resql = $this->db->query($sql);
1185  if (!$resql) {
1186  $error++;
1187  }
1188 
1189  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1190  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1191  }
1192 
1193  // Add object linked
1194  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1195  foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1196  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, ...))
1197  foreach ($tmp_origin_id as $origin_id) {
1198  $ret = $this->add_object_linked($origin, $origin_id);
1199  if (!$ret) {
1200  $this->error = $this->db->lasterror();
1201  $error++;
1202  }
1203  }
1204  } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1205  {
1206  $origin_id = $tmp_origin_id;
1207  $ret = $this->add_object_linked($origin, $origin_id);
1208  if (!$ret) {
1209  $this->error = $this->db->lasterror();
1210  $error++;
1211  }
1212  }
1213  }
1214  }
1215 
1216  /*
1217  * Insertion du detail des produits dans la base
1218  * Insert products detail in database
1219  */
1220  if (!$error) {
1221  $fk_parent_line = 0;
1222  $num = count($this->lines);
1223 
1224  for ($i = 0; $i < $num; $i++) {
1225  if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1226  // Convert into object this->lines[$i].
1227  $line = (object) $this->lines[$i];
1228  } else {
1229  $line = $this->lines[$i];
1230  }
1231  // Reset fk_parent_line for line that are not child lines or special product
1232  if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1233  $fk_parent_line = 0;
1234  }
1235  // Complete vat rate with code
1236  $vatrate = $line->tva_tx;
1237  if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1238  $vatrate .= ' ('.$line->vat_src_code.')';
1239  }
1240 
1241  if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1242  $originid = $line->origin_id;
1243  $origintype = $line->origin;
1244  } else {
1245  $originid = $line->id;
1246  $origintype = $this->element;
1247  }
1248 
1249  $result = $this->addline(
1250  $line->desc,
1251  $line->subprice,
1252  $line->qty,
1253  $vatrate,
1254  $line->localtax1_tx,
1255  $line->localtax2_tx,
1256  $line->fk_product,
1257  $line->remise_percent,
1258  'HT',
1259  0,
1260  $line->info_bits,
1261  $line->product_type,
1262  $line->rang,
1263  $line->special_code,
1264  $fk_parent_line,
1265  $line->fk_fournprice,
1266  $line->pa_ht,
1267  $line->label,
1268  $line->date_start,
1269  $line->date_end,
1270  $line->array_options,
1271  $line->fk_unit,
1272  $origintype,
1273  $originid,
1274  0,
1275  0,
1276  1
1277  );
1278 
1279  if ($result < 0) {
1280  $error++;
1281  $this->error = $this->db->error;
1282  dol_print_error($this->db);
1283  break;
1284  }
1285  // Defined the new fk_parent_line
1286  if ($result > 0 && $line->product_type == 9) {
1287  $fk_parent_line = $result;
1288  }
1289  }
1290  }
1291 
1292  // Set delivery address
1293  /*if (! $error && $this->fk_delivery_address)
1294  {
1295  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1296  $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1297  $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1298  $sql.= " AND entity = ".setEntity($this);
1299 
1300  $result=$this->db->query($sql);
1301  }*/
1302 
1303  if (!$error) {
1304  // Mise a jour infos denormalisees
1305  $resql = $this->update_price(1, 'auto', 0, $mysoc);
1306  if ($resql) {
1307  $action = 'update';
1308 
1309  // Actions on extra fields
1310  if (!$error) {
1311  $result = $this->insertExtraFields();
1312  if ($result < 0) {
1313  $error++;
1314  }
1315  }
1316 
1317  if (!$error && !$notrigger) {
1318  // Call trigger
1319  $result = $this->call_trigger('PROPAL_CREATE', $user);
1320  if ($result < 0) {
1321  $error++;
1322  }
1323  // End call triggers
1324  }
1325  } else {
1326  $this->error = $this->db->lasterror();
1327  $error++;
1328  }
1329  }
1330  } else {
1331  $this->error = $this->db->lasterror();
1332  $error++;
1333  }
1334 
1335  if (!$error) {
1336  $this->db->commit();
1337  dol_syslog(get_class($this)."::create done id=".$this->id);
1338  return $this->id;
1339  } else {
1340  $this->db->rollback();
1341  return -2;
1342  }
1343  } else {
1344  $this->error = $this->db->lasterror();
1345  $this->db->rollback();
1346  return -1;
1347  }
1348  }
1349 
1359  public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false)
1360  {
1361  global $conf, $hookmanager, $mysoc;
1362 
1363  dol_include_once('/projet/class/project.class.php');
1364 
1365  $error = 0;
1366  $now = dol_now();
1367 
1368  dol_syslog(__METHOD__, LOG_DEBUG);
1369 
1370  $object = new self($this->db);
1371 
1372  $this->db->begin();
1373 
1374  // Load source object
1375  $object->fetch($this->id);
1376 
1377  $objsoc = new Societe($this->db);
1378 
1379  // Change socid if needed
1380  if (!empty($socid) && $socid != $object->socid) {
1381  if ($objsoc->fetch($socid) > 0) {
1382  $object->socid = $objsoc->id;
1383  $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1384  $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1385  $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1386  $object->fk_delivery_address = '';
1387 
1388  /*if (isModEnabled('project'))
1389  {
1390  $project = new Project($db);
1391  if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1392  if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1393  else $clonedObj->fk_project = '';
1394  } else {
1395  $clonedObj->fk_project = '';
1396  }
1397  }*/
1398  $object->fk_project = ''; // A cloned proposal is set by default to no project.
1399  }
1400 
1401  // reset ref_client
1402  $object->ref_client = '';
1403 
1404  // TODO Change product price if multi-prices
1405  } else {
1406  $objsoc->fetch($object->socid);
1407  }
1408 
1409  // update prices
1410  if ($update_prices === true) {
1411  if ($objsoc->id > 0 && !empty($object->lines)) {
1412  if (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1413  // If price per customer
1414  require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1415  }
1416 
1417  foreach ($object->lines as $line) {
1418  if ($line->fk_product > 0) {
1419  $prod = new Product($this->db);
1420  $res = $prod->fetch($line->fk_product);
1421  if ($res > 0) {
1422  $pu_ht = $prod->price;
1423  $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1424  $remise_percent = $objsoc->remise_percent;
1425 
1426  if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
1427  $pu_ht = $prod->multiprices[$objsoc->price_level];
1428  if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
1429  if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1430  $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1431  }
1432  }
1433  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1434  $prodcustprice = new Productcustomerprice($this->db);
1435  $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1436  $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1437  if ($result) {
1438  // If there is some prices specific to the customer
1439  if (count($prodcustprice->lines) > 0) {
1440  $pu_ht = price($prodcustprice->lines[0]->price);
1441  $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1442  if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1443  $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1444  }
1445  }
1446  }
1447  }
1448 
1449  $line->subprice = $pu_ht;
1450  $line->tva_tx = $tva_tx;
1451  $line->remise_percent = $remise_percent;
1452  }
1453  }
1454  }
1455  }
1456  }
1457 
1458  $object->id = 0;
1459  $object->ref = '';
1460  $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1461  $object->statut = self::STATUS_DRAFT;
1462 
1463  // Clear fields
1464  $object->user_author = $user->id;
1465  $object->user_valid = 0;
1466  $object->date = $now;
1467  $object->datep = $now; // deprecated
1468  $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1469  if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
1470  $object->ref_client = '';
1471  }
1472  if ($conf->global->MAIN_DONT_KEEP_NOTE_ON_CLONING == 1) {
1473  $object->note_private = '';
1474  $object->note_public = '';
1475  }
1476  // Create clone
1477  $object->context['createfromclone'] = 'createfromclone';
1478  $result = $object->create($user);
1479  if ($result < 0) {
1480  $this->error = $object->error;
1481  $this->errors = array_merge($this->errors, $object->errors);
1482  $error++;
1483  }
1484 
1485  if (!$error) {
1486  // copy internal contacts
1487  if ($object->copy_linked_contact($this, 'internal') < 0) {
1488  $error++;
1489  }
1490  }
1491 
1492  if (!$error) {
1493  // copy external contacts if same company
1494  if ($this->socid == $object->socid) {
1495  if ($object->copy_linked_contact($this, 'external') < 0) {
1496  $error++;
1497  }
1498  }
1499  }
1500 
1501  if (!$error) {
1502  // Hook of thirdparty module
1503  if (is_object($hookmanager)) {
1504  $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1505  $action = '';
1506  $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1507  if ($reshook < 0) {
1508  $error++;
1509  }
1510  }
1511  }
1512 
1513  unset($object->context['createfromclone']);
1514 
1515  // End
1516  if (!$error) {
1517  $this->db->commit();
1518  return $object->id;
1519  } else {
1520  $this->db->rollback();
1521  return -1;
1522  }
1523  }
1524 
1534  public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1535  {
1536  $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1537  $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1538  $sql .= ", p.datec";
1539  $sql .= ", p.date_signature as dates";
1540  $sql .= ", p.date_valid as datev";
1541  $sql .= ", p.datep as dp";
1542  $sql .= ", p.fin_validite as dfv";
1543  $sql .= ", p.date_livraison as delivery_date";
1544  $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1545  $sql .= ", p.note_private, p.note_public";
1546  $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1547  $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1548  $sql .= ", p.fk_delivery_address";
1549  $sql .= ", p.fk_availability";
1550  $sql .= ", p.fk_input_reason";
1551  $sql .= ", p.fk_cond_reglement";
1552  $sql .= ", p.fk_mode_reglement";
1553  $sql .= ', p.fk_account';
1554  $sql .= ", p.fk_shipping_method";
1555  $sql .= ", p.fk_warehouse";
1556  $sql .= ", p.fk_incoterms, p.location_incoterms";
1557  $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1558  $sql .= ", p.tms as date_modification";
1559  $sql .= ", i.libelle as label_incoterms";
1560  $sql .= ", c.label as statut_label";
1561  $sql .= ", ca.code as availability_code, ca.label as availability";
1562  $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1563  $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1564  $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1565  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1566  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1567  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1568  $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').')';
1569  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1570  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1571  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1572 
1573  if (!empty($ref)) {
1574  if (!empty($forceentity)) {
1575  $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1576  } else {
1577  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1578  }
1579  $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1580  } else {
1581  // Dont't use entity if you use rowid
1582  $sql .= " WHERE p.rowid = ".((int) $rowid);
1583  }
1584 
1585  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1586  $resql = $this->db->query($sql);
1587  if ($resql) {
1588  if ($this->db->num_rows($resql)) {
1589  $obj = $this->db->fetch_object($resql);
1590 
1591  $this->id = $obj->rowid;
1592  $this->entity = $obj->entity;
1593 
1594  $this->ref = $obj->ref;
1595  $this->ref_client = $obj->ref_client;
1596  $this->ref_ext = $obj->ref_ext;
1597  $this->remise = $obj->remise;
1598  $this->remise_percent = $obj->remise_percent;
1599  $this->remise_absolue = $obj->remise_absolue;
1600  $this->total = $obj->total_ttc; // TODO deprecated
1601  $this->total_ttc = $obj->total_ttc;
1602  $this->total_ht = $obj->total_ht;
1603  $this->total_tva = $obj->total_tva;
1604  $this->total_localtax1 = $obj->localtax1;
1605  $this->total_localtax2 = $obj->localtax2;
1606 
1607  $this->socid = $obj->fk_soc;
1608  $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1609 
1610  $this->fk_project = $obj->fk_project;
1611  $this->project = null; // Clear if another value was already set by fetch_projet
1612 
1613  $this->model_pdf = $obj->model_pdf;
1614  $this->modelpdf = $obj->model_pdf; // deprecated
1615  $this->last_main_doc = $obj->last_main_doc;
1616  $this->note = $obj->note_private; // TODO deprecated
1617  $this->note_private = $obj->note_private;
1618  $this->note_public = $obj->note_public;
1619 
1620  $this->status = (int) $obj->fk_statut;
1621  $this->statut = $this->status; // deprecated
1622  $this->statut_libelle = $obj->statut_label;
1623 
1624  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1625  $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1626  $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1627  $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1628  $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1629  $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1630  $this->date = $this->db->jdate($obj->dp); // Proposal date
1631  $this->datep = $this->db->jdate($obj->dp); // deprecated
1632  $this->fin_validite = $this->db->jdate($obj->dfv);
1633  $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
1634  $this->delivery_date = $this->db->jdate($obj->delivery_date);
1635  $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1636  $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1637  $this->availability_id = $obj->fk_availability;
1638  $this->availability_code = $obj->availability_code;
1639  $this->availability = $obj->availability;
1640  $this->demand_reason_id = $obj->fk_input_reason;
1641  $this->demand_reason_code = $obj->demand_reason_code;
1642  $this->demand_reason = $obj->demand_reason;
1643  $this->fk_address = $obj->fk_delivery_address;
1644 
1645  $this->mode_reglement_id = $obj->fk_mode_reglement;
1646  $this->mode_reglement_code = $obj->mode_reglement_code;
1647  $this->mode_reglement = $obj->mode_reglement;
1648  $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1649  $this->cond_reglement_id = $obj->fk_cond_reglement;
1650  $this->cond_reglement_code = $obj->cond_reglement_code;
1651  $this->cond_reglement = $obj->cond_reglement;
1652  $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1653  $this->deposit_percent = $obj->deposit_percent;
1654 
1655  $this->extraparams = (array) json_decode($obj->extraparams, true);
1656 
1657  $this->user_author_id = $obj->fk_user_author;
1658  $this->user_valid_id = $obj->fk_user_valid;
1659  $this->user_close_id = $obj->fk_user_cloture;
1660 
1661  //Incoterms
1662  $this->fk_incoterms = $obj->fk_incoterms;
1663  $this->location_incoterms = $obj->location_incoterms;
1664  $this->label_incoterms = $obj->label_incoterms;
1665 
1666  // Multicurrency
1667  $this->fk_multicurrency = $obj->fk_multicurrency;
1668  $this->multicurrency_code = $obj->multicurrency_code;
1669  $this->multicurrency_tx = $obj->multicurrency_tx;
1670  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1671  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1672  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1673 
1674  if ($obj->fk_statut == self::STATUS_DRAFT) {
1675  $this->brouillon = 1;
1676  }
1677 
1678  // Retrieve all extrafield
1679  // fetch optionals attributes and labels
1680  $this->fetch_optionals();
1681 
1682  $this->db->free($resql);
1683 
1684  $this->lines = array();
1685 
1686  // Lines
1687  $result = $this->fetch_lines();
1688  if ($result < 0) {
1689  return -3;
1690  }
1691 
1692  return 1;
1693  }
1694 
1695  $this->error = "Record Not Found";
1696  return 0;
1697  } else {
1698  $this->error = $this->db->lasterror();
1699  return -1;
1700  }
1701  }
1702 
1710  public function update(User $user, $notrigger = 0)
1711  {
1712  global $conf;
1713 
1714  $error = 0;
1715 
1716  // Clean parameters
1717  if (isset($this->ref)) {
1718  $this->ref = trim($this->ref);
1719  }
1720  if (isset($this->ref_client)) {
1721  $this->ref_client = trim($this->ref_client);
1722  }
1723  if (isset($this->note) || isset($this->note_private)) {
1724  $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1725  }
1726  if (isset($this->note_public)) {
1727  $this->note_public = trim($this->note_public);
1728  }
1729  if (isset($this->model_pdf)) {
1730  $this->model_pdf = trim($this->model_pdf);
1731  }
1732  if (isset($this->import_key)) {
1733  $this->import_key = trim($this->import_key);
1734  }
1735  if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1736  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1737  }
1738 
1739  // Check parameters
1740  // Put here code to add control on parameters values
1741 
1742  // Update request
1743  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1744  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1745  $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1746  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1747  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1748  $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1749  if (!empty($this->fin_validite)) {
1750  $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1751  }
1752  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1753  $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1754  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1755  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1756  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1757  $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1758  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1759  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1760  $sql .= " fk_user_valid=".(isset($this->user_valid) ? $this->user_valid : "null").",";
1761  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1762  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1763  $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1764  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1765  $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1766  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1767  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1768  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1769  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1770  $sql .= " WHERE rowid=".((int) $this->id);
1771 
1772  $this->db->begin();
1773 
1774  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1775  $resql = $this->db->query($sql);
1776  if (!$resql) {
1777  $error++;
1778  $this->errors[] = "Error ".$this->db->lasterror();
1779  }
1780 
1781  if (!$error) {
1782  $result = $this->insertExtraFields();
1783  if ($result < 0) {
1784  $error++;
1785  }
1786  }
1787 
1788  if (!$error && !$notrigger) {
1789  // Call trigger
1790  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1791  if ($result < 0) {
1792  $error++;
1793  }
1794  // End call triggers
1795  }
1796 
1797  // Commit or rollback
1798  if ($error) {
1799  foreach ($this->errors as $errmsg) {
1800  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1801  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1802  }
1803  $this->db->rollback();
1804  return -1 * $error;
1805  } else {
1806  $this->db->commit();
1807  return 1;
1808  }
1809  }
1810 
1811 
1812  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1822  public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $filters = '')
1823  {
1824  // phpcs:enable
1825  global $langs, $conf;
1826 
1827  $this->lines = array();
1828 
1829  $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,';
1830  $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,';
1831  $sql .= ' d.fk_unit,';
1832  $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,';
1833  $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1834  $sql .= ' d.date_start, d.date_end,';
1835  $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1836  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1837  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1838  $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1839  if ($only_product) {
1840  $sql .= ' AND p.fk_product_type = 0';
1841  }
1842  if ($filters) {
1843  $sql .= $filters;
1844  }
1845  $sql .= ' ORDER by d.rang';
1846 
1847  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1848  $result = $this->db->query($sql);
1849  if ($result) {
1850  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1851 
1852  $num = $this->db->num_rows($result);
1853 
1854  $i = 0;
1855  while ($i < $num) {
1856  $objp = $this->db->fetch_object($result);
1857 
1858  $line = new PropaleLigne($this->db);
1859 
1860  $line->rowid = $objp->rowid; //Deprecated
1861  $line->id = $objp->rowid;
1862  $line->fk_propal = $objp->fk_propal;
1863  $line->fk_parent_line = $objp->fk_parent_line;
1864  $line->product_type = $objp->product_type;
1865  $line->label = $objp->custom_label;
1866  $line->desc = $objp->description; // Description ligne
1867  $line->description = $objp->description; // Description ligne
1868  $line->qty = $objp->qty;
1869  $line->vat_src_code = $objp->vat_src_code;
1870  $line->tva_tx = $objp->tva_tx;
1871  $line->localtax1_tx = $objp->localtax1_tx;
1872  $line->localtax2_tx = $objp->localtax2_tx;
1873  $line->localtax1_type = $objp->localtax1_type;
1874  $line->localtax2_type = $objp->localtax2_type;
1875  $line->subprice = $objp->subprice;
1876  $line->fk_remise_except = $objp->fk_remise_except;
1877  $line->remise_percent = $objp->remise_percent;
1878  $line->price = $objp->price; // TODO deprecated
1879 
1880  $line->info_bits = $objp->info_bits;
1881  $line->total_ht = $objp->total_ht;
1882  $line->total_tva = $objp->total_tva;
1883  $line->total_localtax1 = $objp->total_localtax1;
1884  $line->total_localtax2 = $objp->total_localtax2;
1885  $line->total_ttc = $objp->total_ttc;
1886  $line->fk_fournprice = $objp->fk_fournprice;
1887  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1888  $line->pa_ht = $marginInfos[0];
1889  $line->marge_tx = $marginInfos[1];
1890  $line->marque_tx = $marginInfos[2];
1891  $line->special_code = $objp->special_code;
1892  $line->rang = $objp->rang;
1893 
1894  $line->fk_product = $objp->fk_product;
1895 
1896  $line->ref = $objp->product_ref; // deprecated
1897  $line->libelle = $objp->product_label; // deprecated
1898 
1899  $line->product_ref = $objp->product_ref;
1900  $line->product_label = $objp->product_label;
1901  $line->product_desc = $objp->product_desc; // Description produit
1902  $line->product_tobatch = $objp->product_tobatch;
1903  $line->product_barcode = $objp->product_barcode;
1904 
1905  $line->fk_product_type = $objp->fk_product_type; // deprecated
1906  $line->fk_unit = $objp->fk_unit;
1907  $line->weight = $objp->weight;
1908  $line->weight_units = $objp->weight_units;
1909  $line->volume = $objp->volume;
1910  $line->volume_units = $objp->volume_units;
1911 
1912  $line->date_start = $this->db->jdate($objp->date_start);
1913  $line->date_end = $this->db->jdate($objp->date_end);
1914 
1915  // Multicurrency
1916  $line->fk_multicurrency = $objp->fk_multicurrency;
1917  $line->multicurrency_code = $objp->multicurrency_code;
1918  $line->multicurrency_subprice = $objp->multicurrency_subprice;
1919  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1920  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1921  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1922 
1923  $line->fetch_optionals();
1924 
1925  // multilangs
1926  if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1927  $tmpproduct = new Product($this->db);
1928  $tmpproduct->fetch($objp->fk_product);
1929  $tmpproduct->getMultiLangs();
1930 
1931  $line->multilangs = $tmpproduct->multilangs;
1932  }
1933 
1934  $this->lines[$i] = $line;
1935 
1936  $i++;
1937  }
1938 
1939  $this->db->free($result);
1940 
1941  return $num;
1942  } else {
1943  $this->error = $this->db->lasterror();
1944  return -3;
1945  }
1946  }
1947 
1955  public function valid($user, $notrigger = 0)
1956  {
1957  global $conf;
1958 
1959  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1960 
1961  $error = 0;
1962 
1963  // Protection
1964  if ($this->statut == self::STATUS_VALIDATED) {
1965  dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1966  return 0;
1967  }
1968 
1969  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
1970  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
1971  $this->error = 'ErrorPermissionDenied';
1972  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1973  return -1;
1974  }
1975 
1976  $now = dol_now();
1977 
1978  $this->db->begin();
1979 
1980  // Numbering module definition
1981  $soc = new Societe($this->db);
1982  $soc->fetch($this->socid);
1983 
1984  // Define new ref
1985  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
1986  $num = $this->getNextNumRef($soc);
1987  } else {
1988  $num = $this->ref;
1989  }
1990  $this->newref = dol_sanitizeFileName($num);
1991 
1992  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1993  $sql .= " SET ref = '".$this->db->escape($num)."',";
1994  $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
1995  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
1996 
1997  dol_syslog(get_class($this)."::valid", LOG_DEBUG);
1998  $resql = $this->db->query($sql);
1999  if (!$resql) {
2000  dol_print_error($this->db);
2001  $error++;
2002  }
2003 
2004  // Trigger calls
2005  if (!$error && !$notrigger) {
2006  // Call trigger
2007  $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2008  if ($result < 0) {
2009  $error++;
2010  }
2011  // End call triggers
2012  }
2013 
2014  if (!$error) {
2015  $this->oldref = $this->ref;
2016 
2017  // Rename directory if dir was a temporary ref
2018  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2019  // Now we rename also files into index
2020  $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)."'";
2021  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2022  $resql = $this->db->query($sql);
2023  if (!$resql) {
2024  $error++;
2025  $this->error = $this->db->lasterror();
2026  }
2027 
2028  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2029  $oldref = dol_sanitizeFileName($this->ref);
2030  $newref = dol_sanitizeFileName($num);
2031  $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2032  $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2033  if (!$error && file_exists($dirsource)) {
2034  dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2035  if (@rename($dirsource, $dirdest)) {
2036  dol_syslog("Rename ok");
2037  // Rename docs starting with $oldref with $newref
2038  $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2039  foreach ($listoffiles as $fileentry) {
2040  $dirsource = $fileentry['name'];
2041  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2042  $dirsource = $fileentry['path'].'/'.$dirsource;
2043  $dirdest = $fileentry['path'].'/'.$dirdest;
2044  @rename($dirsource, $dirdest);
2045  }
2046  }
2047  }
2048  }
2049 
2050  $this->ref = $num;
2051  $this->brouillon = 0;
2052  $this->statut = self::STATUS_VALIDATED;
2053  $this->user_valid_id = $user->id;
2054  $this->datev = $now;
2055 
2056  $this->db->commit();
2057  return 1;
2058  } else {
2059  $this->db->rollback();
2060  return -1;
2061  }
2062  }
2063 
2064 
2065  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2074  public function set_date($user, $date, $notrigger = 0)
2075  {
2076  // phpcs:enable
2077  if (empty($date)) {
2078  $this->error = 'ErrorBadParameter';
2079  dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2080  return -1;
2081  }
2082 
2083  if (!empty($user->rights->propal->creer)) {
2084  $error = 0;
2085 
2086  $this->db->begin();
2087 
2088  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2089  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2090 
2091  dol_syslog(__METHOD__, LOG_DEBUG);
2092  $resql = $this->db->query($sql);
2093  if (!$resql) {
2094  $this->errors[] = $this->db->error();
2095  $error++;
2096  }
2097 
2098  if (!$error) {
2099  $this->oldcopy = clone $this;
2100  $this->date = $date;
2101  $this->datep = $date; // deprecated
2102  }
2103 
2104  if (!$notrigger && empty($error)) {
2105  // Call trigger
2106  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2107  if ($result < 0) {
2108  $error++;
2109  }
2110  // End call triggers
2111  }
2112 
2113  if (!$error) {
2114  $this->db->commit();
2115  return 1;
2116  } else {
2117  foreach ($this->errors as $errmsg) {
2118  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2119  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2120  }
2121  $this->db->rollback();
2122  return -1 * $error;
2123  }
2124  }
2125 
2126  return -1;
2127  }
2128 
2129  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2138  public function set_echeance($user, $date_end_validity, $notrigger = 0)
2139  {
2140  // phpcs:enable
2141  if (!empty($user->rights->propal->creer)) {
2142  $error = 0;
2143 
2144  $this->db->begin();
2145 
2146  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2147  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2148 
2149  dol_syslog(__METHOD__, LOG_DEBUG);
2150  $resql = $this->db->query($sql);
2151  if (!$resql) {
2152  $this->errors[] = $this->db->error();
2153  $error++;
2154  }
2155 
2156 
2157  if (!$error) {
2158  $this->oldcopy = clone $this;
2159  $this->fin_validite = $date_end_validity;
2160  }
2161 
2162  if (!$notrigger && empty($error)) {
2163  // Call trigger
2164  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2165  if ($result < 0) {
2166  $error++;
2167  }
2168  // End call triggers
2169  }
2170 
2171  if (!$error) {
2172  $this->db->commit();
2173  return 1;
2174  } else {
2175  foreach ($this->errors as $errmsg) {
2176  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2177  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2178  }
2179  $this->db->rollback();
2180  return -1 * $error;
2181  }
2182  }
2183 
2184  return -1;
2185  }
2186 
2187  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2197  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2198  {
2199  // phpcs:enable
2200  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2201  }
2202 
2211  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2212  {
2213  if (!empty($user->rights->propal->creer)) {
2214  $error = 0;
2215 
2216  $this->db->begin();
2217 
2218  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2219  $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2220  $sql .= " WHERE rowid = ".((int) $this->id);
2221 
2222  dol_syslog(__METHOD__, LOG_DEBUG);
2223  $resql = $this->db->query($sql);
2224  if (!$resql) {
2225  $this->errors[] = $this->db->error();
2226  $error++;
2227  }
2228 
2229  if (!$error) {
2230  $this->oldcopy = clone $this;
2231  $this->date_livraison = $delivery_date;
2232  $this->delivery_date = $delivery_date;
2233  }
2234 
2235  if (!$notrigger && empty($error)) {
2236  // Call trigger
2237  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2238  if ($result < 0) {
2239  $error++;
2240  }
2241  // End call triggers
2242  }
2243 
2244  if (!$error) {
2245  $this->db->commit();
2246  return 1;
2247  } else {
2248  foreach ($this->errors as $errmsg) {
2249  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2250  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2251  }
2252  $this->db->rollback();
2253  return -1 * $error;
2254  }
2255  }
2256 
2257  return -1;
2258  }
2259 
2260  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2269  public function set_availability($user, $id, $notrigger = 0)
2270  {
2271  // phpcs:enable
2272  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2273  $error = 0;
2274 
2275  $this->db->begin();
2276 
2277  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2278  $sql .= " SET fk_availability = ".((int) $id);
2279  $sql .= " WHERE rowid = ".((int) $this->id);
2280 
2281  dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2282  $resql = $this->db->query($sql);
2283  if (!$resql) {
2284  $this->errors[] = $this->db->error();
2285  $error++;
2286  }
2287 
2288  if (!$error) {
2289  $this->oldcopy = clone $this;
2290  $this->fk_availability = $id;
2291  $this->availability_id = $id;
2292  }
2293 
2294  if (!$notrigger && empty($error)) {
2295  // Call trigger
2296  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2297  if ($result < 0) {
2298  $error++;
2299  }
2300  // End call triggers
2301  }
2302 
2303  if (!$error) {
2304  $this->db->commit();
2305  return 1;
2306  } else {
2307  foreach ($this->errors as $errmsg) {
2308  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2309  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2310  }
2311  $this->db->rollback();
2312  return -1 * $error;
2313  }
2314  } else {
2315  $error_str = 'Propal status do not meet requirement '.$this->statut;
2316  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2317  $this->error = $error_str;
2318  $this->errors[] = $this->error;
2319  return -2;
2320  }
2321  }
2322 
2323  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2332  public function set_demand_reason($user, $id, $notrigger = 0)
2333  {
2334  // phpcs:enable
2335  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2336  $error = 0;
2337 
2338  $this->db->begin();
2339 
2340  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2341  $sql .= " SET fk_input_reason = ".((int) $id);
2342  $sql .= " WHERE rowid = ".((int) $this->id);
2343 
2344  dol_syslog(__METHOD__, LOG_DEBUG);
2345  $resql = $this->db->query($sql);
2346  if (!$resql) {
2347  $this->errors[] = $this->db->error();
2348  $error++;
2349  }
2350 
2351 
2352  if (!$error) {
2353  $this->oldcopy = clone $this;
2354  $this->fk_input_reason = $id;
2355  $this->demand_reason_id = $id;
2356  }
2357 
2358 
2359  if (!$notrigger && empty($error)) {
2360  // Call trigger
2361  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2362  if ($result < 0) {
2363  $error++;
2364  }
2365  // End call triggers
2366  }
2367 
2368  if (!$error) {
2369  $this->db->commit();
2370  return 1;
2371  } else {
2372  foreach ($this->errors as $errmsg) {
2373  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2374  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2375  }
2376  $this->db->rollback();
2377  return -1 * $error;
2378  }
2379  } else {
2380  $error_str = 'Propal status do not meet requirement '.$this->statut;
2381  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2382  $this->error = $error_str;
2383  $this->errors[] = $this->error;
2384  return -2;
2385  }
2386  }
2387 
2388  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2397  public function set_ref_client($user, $ref_client, $notrigger = 0)
2398  {
2399  // phpcs:enable
2400  if (!empty($user->rights->propal->creer)) {
2401  $error = 0;
2402 
2403  $this->db->begin();
2404 
2405  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2406  $sql .= " WHERE rowid = ".((int) $this->id);
2407 
2408  dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2409  $resql = $this->db->query($sql);
2410  if (!$resql) {
2411  $this->errors[] = $this->db->error();
2412  $error++;
2413  }
2414 
2415  if (!$error) {
2416  $this->oldcopy = clone $this;
2417  $this->ref_client = $ref_client;
2418  }
2419 
2420  if (!$notrigger && empty($error)) {
2421  // Call trigger
2422  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2423  if ($result < 0) {
2424  $error++;
2425  }
2426  // End call triggers
2427  }
2428 
2429  if (!$error) {
2430  $this->db->commit();
2431  return 1;
2432  } else {
2433  foreach ($this->errors as $errmsg) {
2434  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2435  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2436  }
2437  $this->db->rollback();
2438  return -1 * $error;
2439  }
2440  }
2441 
2442  return -1;
2443  }
2444 
2445  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2454  public function set_remise_percent($user, $remise, $notrigger = 0)
2455  {
2456  // phpcs:enable
2457  $remise = trim($remise) ?trim($remise) : 0;
2458 
2459  if (!empty($user->rights->propal->creer)) {
2460  $remise = price2num($remise, 2);
2461 
2462  $error = 0;
2463 
2464  $this->db->begin();
2465 
2466  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
2467  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2468 
2469  dol_syslog(__METHOD__, LOG_DEBUG);
2470  $resql = $this->db->query($sql);
2471  if (!$resql) {
2472  $this->errors[] = $this->db->error();
2473  $error++;
2474  }
2475 
2476  if (!$error) {
2477  $this->oldcopy = clone $this;
2478  $this->remise_percent = $remise;
2479  $this->update_price(1);
2480  }
2481 
2482  if (!$notrigger && empty($error)) {
2483  // Call trigger
2484  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2485  if ($result < 0) {
2486  $error++;
2487  }
2488  // End call triggers
2489  }
2490 
2491  if (!$error) {
2492  $this->db->commit();
2493  return 1;
2494  } else {
2495  foreach ($this->errors as $errmsg) {
2496  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2497  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2498  }
2499  $this->db->rollback();
2500  return -1 * $error;
2501  }
2502  }
2503 
2504  return -1;
2505  }
2506 
2507 
2508  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2517  public function set_remise_absolue($user, $remise, $notrigger = 0)
2518  {
2519  // phpcs:enable
2520  if (empty($remise)) {
2521  $remise = 0;
2522  }
2524 
2525  if (!empty($user->rights->propal->creer)) {
2526  $error = 0;
2527 
2528  $this->db->begin();
2529 
2530  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2531  $sql .= " SET remise_absolue = ".((float) $remise);
2532  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2533 
2534  dol_syslog(__METHOD__, LOG_DEBUG);
2535  $resql = $this->db->query($sql);
2536  if (!$resql) {
2537  $this->errors[] = $this->db->error();
2538  $error++;
2539  }
2540 
2541  if (!$error) {
2542  $this->oldcopy = clone $this;
2543  $this->update_price(1);
2544  }
2545 
2546  if (!$notrigger && empty($error)) {
2547  // Call trigger
2548  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2549  if ($result < 0) {
2550  $error++;
2551  }
2552  // End call triggers
2553  }
2554 
2555  if (!$error) {
2556  $this->db->commit();
2557  return 1;
2558  } else {
2559  foreach ($this->errors as $errmsg) {
2560  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2561  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2562  }
2563  $this->db->rollback();
2564  return -1 * $error;
2565  }
2566  }
2567 
2568  return -1;
2569  }
2570 
2571 
2572 
2582  public function reopen($user, $status, $note = '', $notrigger = 0)
2583  {
2584  $error = 0;
2585 
2586  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2587  $sql .= " SET fk_statut = ".((int) $status).",";
2588  if (!empty($note)) {
2589  $sql .= " note_private = '".$this->db->escape($note)."',";
2590  }
2591  $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2592  $sql .= " WHERE rowid = ".((int) $this->id);
2593 
2594  $this->db->begin();
2595 
2596  dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2597  $resql = $this->db->query($sql);
2598  if (!$resql) {
2599  $error++;
2600  $this->errors[] = "Error ".$this->db->lasterror();
2601  }
2602  if (!$error) {
2603  if (!$notrigger) {
2604  // Call trigger
2605  $result = $this->call_trigger('PROPAL_REOPEN', $user);
2606  if ($result < 0) {
2607  $error++;
2608  }
2609  // End call triggers
2610  }
2611  }
2612 
2613  // Commit or rollback
2614  if ($error) {
2615  if (!empty($this->errors)) {
2616  foreach ($this->errors as $errmsg) {
2617  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2618  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2619  }
2620  }
2621  $this->db->rollback();
2622  return -1 * $error;
2623  } else {
2624  $this->statut = $status;
2625  $this->status = $status;
2626 
2627  $this->db->commit();
2628  return 1;
2629  }
2630  }
2631 
2641  public function closeProposal($user, $status, $note = '', $notrigger = 0)
2642  {
2643  global $langs,$conf;
2644 
2645  $error = 0;
2646  $now = dol_now();
2647 
2648  $this->db->begin();
2649 
2650  $newprivatenote = dol_concatdesc($this->note_private, $note);
2651 
2652  if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
2653  $date_signature = $now;
2654  $fk_user_signature = $user->id;
2655  } else {
2656  $this->info($this->id);
2657  if (!isset($this->date_signature) || $this->date_signature == '') {
2658  $date_signature = $now;
2659  $fk_user_signature = $user->id;
2660  } else {
2661  $date_signature = $this->date_signature;
2662  $fk_user_signature = $this->user_signature->id;
2663  }
2664  }
2665 
2666  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2667  $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;
2668  $sql .= " WHERE rowid = ".((int) $this->id);
2669 
2670  $resql = $this->db->query($sql);
2671  if ($resql) {
2672  // Status self::STATUS_REFUSED by default
2673  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2674  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2675 
2676  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2677  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2678  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2679 
2680  // The connected company is classified as a client
2681  $soc=new Societe($this->db);
2682  $soc->id = $this->socid;
2683  $result = $soc->set_as_client();
2684 
2685  if ($result < 0) {
2686  $this->error=$this->db->lasterror();
2687  $this->db->rollback();
2688  return -2;
2689  }
2690  }
2691 
2692  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2693  // Define output language
2694  $outputlangs = $langs;
2695  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2696  $outputlangs = new Translate("", $conf);
2697  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2698  $outputlangs->setDefaultLang($newlang);
2699  }
2700 
2701  // PDF
2702  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2703  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2704  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2705 
2706  //$ret=$object->fetch($id); // Reload to get new records
2707  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2708  }
2709 
2710  if (!$error) {
2711  $this->oldcopy= clone $this;
2712  $this->statut = $status;
2713  $this->status = $status;
2714  $this->date_signature = $date_signature;
2715  $this->note_private = $newprivatenote;
2716  }
2717 
2718  if (!$notrigger && empty($error)) {
2719  // Call trigger
2720  $result=$this->call_trigger($trigger_name, $user);
2721  if ($result < 0) {
2722  $error++;
2723  }
2724  // End call triggers
2725  }
2726 
2727  if (!$error ) {
2728  $this->db->commit();
2729  return 1;
2730  } else {
2731  $this->statut = $this->oldcopy->statut;
2732  $this->status = $this->oldcopy->statut;
2733  $this->date_signature = $this->oldcopy->date_signature;
2734  $this->note_private = $this->oldcopy->note_private;
2735 
2736  $this->db->rollback();
2737  return -1;
2738  }
2739  } else {
2740  $this->error = $this->db->lasterror();
2741  $this->db->rollback();
2742  return -1;
2743  }
2744  }
2745 
2754  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2755  {
2756  global $conf, $langs;
2757 
2758  $error = 0;
2759 
2760  $now = dol_now();
2761  $num = 0;
2762 
2763  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2764 
2765  $this->db->begin();
2766 
2767  $newprivatenote = dol_concatdesc($this->note_private, $note);
2768 
2769  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2770  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2771  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2772 
2773  dol_syslog(__METHOD__, LOG_DEBUG);
2774  $resql = $this->db->query($sql);
2775  if (!$resql) {
2776  $this->errors[] = $this->db->error();
2777  $error++;
2778  } else {
2779  $num = $this->db->affected_rows($resql);
2780  }
2781 
2782  if (!$error) {
2783  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2784 
2785  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2786  // Define output language
2787  $outputlangs = $langs;
2788  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2789  $outputlangs = new Translate("", $conf);
2790  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2791  $outputlangs->setDefaultLang($newlang);
2792  }
2793 
2794  // PDF
2795  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2796  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2797  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2798 
2799  //$ret=$object->fetch($id); // Reload to get new records
2800  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2801  }
2802 
2803  $this->oldcopy = clone $this;
2804  $this->statut = self::STATUS_BILLED;
2805  $this->date_cloture = $now;
2806  $this->note_private = $newprivatenote;
2807  }
2808 
2809  if (!$notrigger && empty($error)) {
2810  // Call trigger
2811  $result = $this->call_trigger($triggerName, $user);
2812  if ($result < 0) {
2813  $error++;
2814  }
2815  // End call triggers
2816  }
2817 
2818  if (!$error) {
2819  $this->db->commit();
2820  return $num;
2821  } else {
2822  foreach ($this->errors as $errmsg) {
2823  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2824  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2825  }
2826  $this->db->rollback();
2827  return -1 * $error;
2828  }
2829  }
2830 
2831  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2839  public function setDraft($user, $notrigger = 0)
2840  {
2841  // phpcs:enable
2842  $error = 0;
2843 
2844  // Protection
2845  if ($this->statut <= self::STATUS_DRAFT) {
2846  return 0;
2847  }
2848 
2849  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2850 
2851  $this->db->begin();
2852 
2853  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2854  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2855  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2856  $sql .= " WHERE rowid = ".((int) $this->id);
2857 
2858  $resql = $this->db->query($sql);
2859  if (!$resql) {
2860  $this->errors[] = $this->db->error();
2861  $error++;
2862  }
2863 
2864  if (!$error) {
2865  $this->oldcopy = clone $this;
2866  }
2867 
2868  if (!$notrigger && empty($error)) {
2869  // Call trigger
2870  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2871  if ($result < 0) {
2872  $error++;
2873  }
2874  // End call triggers
2875  }
2876 
2877  if (!$error) {
2878  $this->statut = self::STATUS_DRAFT;
2879  $this->brouillon = 1;
2880 
2881  $this->db->commit();
2882  return 1;
2883  } else {
2884  foreach ($this->errors as $errmsg) {
2885  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2886  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2887  }
2888  $this->db->rollback();
2889  return -1 * $error;
2890  }
2891  }
2892 
2893 
2894  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2908  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2909  {
2910  // phpcs:enable
2911  global $user;
2912 
2913  $ga = array();
2914 
2915  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2916  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2917  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2918  if (empty($user->rights->societe->client->voir) && !$socid) {
2919  $sql .= ", sc.fk_soc, sc.fk_user";
2920  }
2921  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2922  if (empty($user->rights->societe->client->voir) && !$socid) {
2923  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2924  }
2925  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2926  $sql .= " AND p.fk_soc = s.rowid";
2927  $sql .= " AND p.fk_statut = c.id";
2928  if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2929  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2930  }
2931  if ($socid) {
2932  $sql .= " AND s.rowid = ".((int) $socid);
2933  }
2934  if ($draft) {
2935  $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2936  }
2937  if ($notcurrentuser > 0) {
2938  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2939  }
2940  $sql .= $this->db->order($sortfield, $sortorder);
2941  $sql .= $this->db->plimit($limit, $offset);
2942 
2943  $result = $this->db->query($sql);
2944  if ($result) {
2945  $num = $this->db->num_rows($result);
2946  if ($num) {
2947  $i = 0;
2948  while ($i < $num) {
2949  $obj = $this->db->fetch_object($result);
2950 
2951  if ($shortlist == 1) {
2952  $ga[$obj->propalid] = $obj->ref;
2953  } elseif ($shortlist == 2) {
2954  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2955  } else {
2956  $ga[$i]['id'] = $obj->propalid;
2957  $ga[$i]['ref'] = $obj->ref;
2958  $ga[$i]['name'] = $obj->name;
2959  }
2960 
2961  $i++;
2962  }
2963  }
2964  return $ga;
2965  } else {
2966  dol_print_error($this->db);
2967  return -1;
2968  }
2969  }
2970 
2976  public function getInvoiceArrayList()
2977  {
2978  return $this->InvoiceArrayList($this->id);
2979  }
2980 
2981  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2988  public function InvoiceArrayList($id)
2989  {
2990  // phpcs:enable
2991  $ga = array();
2992  $linkedInvoices = array();
2993 
2994  $this->fetchObjectLinked($id, $this->element);
2995  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
2996  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2997  // On parcourt donc une liste d'objets en tant qu'objet unique
2998  foreach ($objectid as $key => $object) {
2999  // Cas des factures liees directement
3000  if ($objecttype == 'facture') {
3001  $linkedInvoices[] = $object;
3002  } else {
3003  // Cas des factures liees par un autre objet (ex: commande)
3004  $this->fetchObjectLinked($object, $objecttype);
3005  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3006  foreach ($subobjectid as $subkey => $subobject) {
3007  if ($subobjecttype == 'facture') {
3008  $linkedInvoices[] = $subobject;
3009  }
3010  }
3011  }
3012  }
3013  }
3014  }
3015 
3016  if (count($linkedInvoices) > 0) {
3017  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3018  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3019  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3020 
3021  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3022  $resql = $this->db->query($sql);
3023 
3024  if ($resql) {
3025  $tab_sqlobj = array();
3026  $nump = $this->db->num_rows($resql);
3027  for ($i = 0; $i < $nump; $i++) {
3028  $sqlobj = $this->db->fetch_object($resql);
3029  $tab_sqlobj[] = $sqlobj;
3030  }
3031  $this->db->free($resql);
3032 
3033  $nump = count($tab_sqlobj);
3034 
3035  if ($nump) {
3036  $i = 0;
3037  while ($i < $nump) {
3038  $obj = array_shift($tab_sqlobj);
3039 
3040  $ga[$i] = $obj;
3041 
3042  $i++;
3043  }
3044  }
3045  return $ga;
3046  } else {
3047  return -1;
3048  }
3049  } else {
3050  return $ga;
3051  }
3052  }
3053 
3061  public function delete($user, $notrigger = 0)
3062  {
3063  global $conf;
3064  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3065 
3066  $error = 0;
3067 
3068  $this->db->begin();
3069 
3070  if (!$notrigger) {
3071  // Call trigger
3072  $result = $this->call_trigger('PROPAL_DELETE', $user);
3073  if ($result < 0) {
3074  $error++;
3075  }
3076  // End call triggers
3077  }
3078 
3079  // Delete extrafields of lines and lines
3080  if (!$error && !empty($this->table_element_line)) {
3081  $tabletodelete = $this->table_element_line;
3082  $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).")";
3083  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3084  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3085  $error++;
3086  $this->error = $this->db->lasterror();
3087  $this->errors[] = $this->error;
3088  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3089  }
3090  }
3091 
3092  if (!$error) {
3093  // Delete linked object
3094  $res = $this->deleteObjectLinked();
3095  if ($res < 0) {
3096  $error++;
3097  }
3098  }
3099 
3100  if (!$error) {
3101  // Delete linked contacts
3102  $res = $this->delete_linked_contact();
3103  if ($res < 0) {
3104  $error++;
3105  }
3106  }
3107 
3108  // Removed extrafields of object
3109  if (!$error) {
3110  $result = $this->deleteExtraFields();
3111  if ($result < 0) {
3112  $error++;
3113  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3114  }
3115  }
3116 
3117  // Delete main record
3118  if (!$error) {
3119  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3120  $res = $this->db->query($sql);
3121  if (!$res) {
3122  $error++;
3123  $this->error = $this->db->lasterror();
3124  $this->errors[] = $this->error;
3125  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3126  }
3127  }
3128 
3129  // Delete record into ECM index and physically
3130  if (!$error) {
3131  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3132  if (!$res) {
3133  $error++;
3134  }
3135  }
3136 
3137  if (!$error) {
3138  // We remove directory
3139  $ref = dol_sanitizeFileName($this->ref);
3140  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3141  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3142  $file = $dir."/".$ref.".pdf";
3143  if (file_exists($file)) {
3144  dol_delete_preview($this);
3145 
3146  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3147  $this->error = 'ErrorFailToDeleteFile';
3148  $this->errors[] = $this->error;
3149  $this->db->rollback();
3150  return 0;
3151  }
3152  }
3153  if (file_exists($dir)) {
3154  $res = @dol_delete_dir_recursive($dir);
3155  if (!$res) {
3156  $this->error = 'ErrorFailToDeleteDir';
3157  $this->errors[] = $this->error;
3158  $this->db->rollback();
3159  return 0;
3160  }
3161  }
3162  }
3163  }
3164 
3165  if (!$error) {
3166  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3167  $this->db->commit();
3168  return 1;
3169  } else {
3170  $this->db->rollback();
3171  return -1;
3172  }
3173  }
3174 
3183  public function availability($availability_id, $notrigger = 0)
3184  {
3185  global $user;
3186 
3187  if ($this->statut >= self::STATUS_DRAFT) {
3188  $error = 0;
3189 
3190  $this->db->begin();
3191 
3192  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3193  $sql .= ' SET fk_availability = '.((int) $availability_id);
3194  $sql .= ' WHERE rowid='.((int) $this->id);
3195 
3196  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3197  $resql = $this->db->query($sql);
3198  if (!$resql) {
3199  $this->errors[] = $this->db->error();
3200  $error++;
3201  }
3202 
3203  if (!$error) {
3204  $this->oldcopy = clone $this;
3205  $this->availability_id = $availability_id;
3206  }
3207 
3208  if (!$notrigger && empty($error)) {
3209  // Call trigger
3210  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3211  if ($result < 0) {
3212  $error++;
3213  }
3214  // End call triggers
3215  }
3216 
3217  if (!$error) {
3218  $this->db->commit();
3219  return 1;
3220  } else {
3221  foreach ($this->errors as $errmsg) {
3222  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3223  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3224  }
3225  $this->db->rollback();
3226  return -1 * $error;
3227  }
3228  } else {
3229  $error_str = 'Propal status do not meet requirement '.$this->statut;
3230  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3231  $this->error = $error_str;
3232  $this->errors[] = $this->error;
3233  return -2;
3234  }
3235  }
3236 
3237  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3246  public function demand_reason($demand_reason_id, $notrigger = 0)
3247  {
3248  // phpcs:enable
3249  global $user;
3250 
3251  if ($this->statut >= self::STATUS_DRAFT) {
3252  $error = 0;
3253 
3254  $this->db->begin();
3255 
3256  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3257  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3258  $sql .= ' WHERE rowid='.((int) $this->id);
3259 
3260  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3261  $resql = $this->db->query($sql);
3262  if (!$resql) {
3263  $this->errors[] = $this->db->error();
3264  $error++;
3265  }
3266 
3267  if (!$error) {
3268  $this->oldcopy = clone $this;
3269  $this->demand_reason_id = $demand_reason_id;
3270  }
3271 
3272  if (!$notrigger && empty($error)) {
3273  // Call trigger
3274  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3275  if ($result < 0) {
3276  $error++;
3277  }
3278  // End call triggers
3279  }
3280 
3281  if (!$error) {
3282  $this->db->commit();
3283  return 1;
3284  } else {
3285  foreach ($this->errors as $errmsg) {
3286  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3287  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3288  }
3289  $this->db->rollback();
3290  return -1 * $error;
3291  }
3292  } else {
3293  $error_str = 'Propal status do not meet requirement '.$this->statut;
3294  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3295  $this->error = $error_str;
3296  $this->errors[] = $this->error;
3297  return -2;
3298  }
3299  }
3300 
3301 
3308  public function info($id)
3309  {
3310  $sql = "SELECT c.rowid, ";
3311  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3312  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3313  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3314  $sql .= " WHERE c.rowid = ".((int) $id);
3315 
3316  $result = $this->db->query($sql);
3317 
3318  if ($result) {
3319  if ($this->db->num_rows($result)) {
3320  $obj = $this->db->fetch_object($result);
3321 
3322  $this->id = $obj->rowid;
3323 
3324  $this->date_creation = $this->db->jdate($obj->datec);
3325  $this->date_validation = $this->db->jdate($obj->datev);
3326  $this->date_signature = $this->db->jdate($obj->date_signature);
3327  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3328 
3329  $cuser = new User($this->db);
3330  $cuser->fetch($obj->fk_user_author);
3331  $this->user_creation = $cuser;
3332 
3333  if ($obj->fk_user_valid) {
3334  $vuser = new User($this->db);
3335  $vuser->fetch($obj->fk_user_valid);
3336  $this->user_validation = $vuser;
3337  }
3338 
3339  if ($obj->fk_user_signature) {
3340  $user_signature = new User($this->db);
3341  $user_signature->fetch($obj->fk_user_signature);
3342  $this->user_signature = $user_signature;
3343  }
3344 
3345  if ($obj->fk_user_cloture) {
3346  $cluser = new User($this->db);
3347  $cluser->fetch($obj->fk_user_cloture);
3348  $this->user_cloture = $cluser;
3349  }
3350  }
3351  $this->db->free($result);
3352  } else {
3353  dol_print_error($this->db);
3354  }
3355  }
3356 
3357 
3364  public function getLibStatut($mode = 0)
3365  {
3366  return $this->LibStatut($this->statut, $mode);
3367  }
3368 
3369  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3377  public function LibStatut($status, $mode = 1)
3378  {
3379  // phpcs:enable
3380  global $conf, $hookmanager;
3381 
3382  // Init/load array of translation of status
3383  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3384  global $langs;
3385  $langs->load("propal");
3386  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3387  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3388  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3389  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3390  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3391  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3392  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3393  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3394  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3395  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3396  }
3397 
3398  $statusType = '';
3399  if ($status == self::STATUS_DRAFT) {
3400  $statusType = 'status0';
3401  } elseif ($status == self::STATUS_VALIDATED) {
3402  $statusType = 'status1';
3403  } elseif ($status == self::STATUS_SIGNED) {
3404  $statusType = 'status4';
3405  } elseif ($status == self::STATUS_NOTSIGNED) {
3406  $statusType = 'status9';
3407  } elseif ($status == self::STATUS_BILLED) {
3408  $statusType = 'status6';
3409  }
3410 
3411 
3412  $parameters = array('status' => $status, 'mode' => $mode);
3413  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3414 
3415  if ($reshook > 0) {
3416  return $hookmanager->resPrint;
3417  }
3418 
3419  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3420  }
3421 
3422 
3423  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3431  public function load_board($user, $mode)
3432  {
3433  // phpcs:enable
3434  global $conf, $langs;
3435 
3436  $clause = " WHERE";
3437 
3438  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3439  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3440  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3441  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3442  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3443  $clause = " AND";
3444  }
3445  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3446  if ($mode == 'opened') {
3447  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3448  }
3449  if ($mode == 'signed') {
3450  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3451  }
3452  if ($user->socid) {
3453  $sql .= " AND p.fk_soc = ".((int) $user->socid);
3454  }
3455 
3456  $resql = $this->db->query($sql);
3457  if ($resql) {
3458  $langs->load("propal");
3459  $now = dol_now();
3460 
3461  $delay_warning = 0;
3462  $status = 0;
3463  $label = $labelShort = '';
3464  if ($mode == 'opened') {
3465  $delay_warning = $conf->propal->cloture->warning_delay;
3466  $status = self::STATUS_VALIDATED;
3467  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3468  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3469  }
3470  if ($mode == 'signed') {
3471  $delay_warning = $conf->propal->facturation->warning_delay;
3472  $status = self::STATUS_SIGNED;
3473  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3474  $labelShort = $langs->trans("ToBill");
3475  }
3476 
3477  $response = new WorkboardResponse();
3478  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3479  $response->label = $label;
3480  $response->labelShort = $labelShort;
3481  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3482  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3483  $response->img = img_object('', "propal");
3484 
3485  // This assignment in condition is not a bug. It allows walking the results.
3486  while ($obj = $this->db->fetch_object($resql)) {
3487  $response->nbtodo++;
3488  $response->total += $obj->total_ht;
3489 
3490  if ($mode == 'opened') {
3491  $datelimit = $this->db->jdate($obj->datefin);
3492  if ($datelimit < ($now - $delay_warning)) {
3493  $response->nbtodolate++;
3494  }
3495  }
3496  // TODO Definir regle des propales a facturer en retard
3497  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3498  }
3499 
3500  return $response;
3501  } else {
3502  $this->error = $this->db->error();
3503  return -1;
3504  }
3505  }
3506 
3507 
3515  public function initAsSpecimen()
3516  {
3517  global $conf, $langs;
3518 
3519  // Load array of products prodids
3520  $num_prods = 0;
3521  $prodids = array();
3522  $sql = "SELECT rowid";
3523  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3524  $sql .= " WHERE entity IN (".getEntity('product').")";
3525  $sql .= $this->db->plimit(100);
3526 
3527  $resql = $this->db->query($sql);
3528  if ($resql) {
3529  $num_prods = $this->db->num_rows($resql);
3530  $i = 0;
3531  while ($i < $num_prods) {
3532  $i++;
3533  $row = $this->db->fetch_row($resql);
3534  $prodids[$i] = $row[0];
3535  }
3536  }
3537 
3538  // Initialise parametres
3539  $this->id = 0;
3540  $this->ref = 'SPECIMEN';
3541  $this->ref_client = 'NEMICEPS';
3542  $this->specimen = 1;
3543  $this->socid = 1;
3544  $this->date = time();
3545  $this->fin_validite = $this->date + 3600 * 24 * 30;
3546  $this->cond_reglement_id = 1;
3547  $this->cond_reglement_code = 'RECEP';
3548  $this->mode_reglement_id = 7;
3549  $this->mode_reglement_code = 'CHQ';
3550  $this->availability_id = 1;
3551  $this->availability_code = 'AV_NOW';
3552  $this->demand_reason_id = 1;
3553  $this->demand_reason_code = 'SRC_00';
3554  $this->note_public = 'This is a comment (public)';
3555  $this->note_private = 'This is a comment (private)';
3556 
3557  $this->multicurrency_tx = 1;
3558  $this->multicurrency_code = $conf->currency;
3559 
3560  // Lines
3561  $nbp = 5;
3562  $xnbp = 0;
3563  while ($xnbp < $nbp) {
3564  $line = new PropaleLigne($this->db);
3565  $line->desc = $langs->trans("Description")." ".$xnbp;
3566  $line->qty = 1;
3567  $line->subprice = 100;
3568  $line->price = 100;
3569  $line->tva_tx = 20;
3570  $line->localtax1_tx = 0;
3571  $line->localtax2_tx = 0;
3572  if ($xnbp == 2) {
3573  $line->total_ht = 50;
3574  $line->total_ttc = 60;
3575  $line->total_tva = 10;
3576  $line->remise_percent = 50;
3577  } else {
3578  $line->total_ht = 100;
3579  $line->total_ttc = 120;
3580  $line->total_tva = 20;
3581  $line->remise_percent = 00;
3582  }
3583 
3584  if ($num_prods > 0) {
3585  $prodid = mt_rand(1, $num_prods);
3586  $line->fk_product = $prodids[$prodid];
3587  $line->product_ref = 'SPECIMEN';
3588  }
3589 
3590  $this->lines[$xnbp] = $line;
3591 
3592  $this->total_ht += $line->total_ht;
3593  $this->total_tva += $line->total_tva;
3594  $this->total_ttc += $line->total_ttc;
3595 
3596  $xnbp++;
3597  }
3598  }
3599 
3600  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3606  public function load_state_board()
3607  {
3608  // phpcs:enable
3609  global $user;
3610 
3611  $this->nb = array();
3612  $clause = "WHERE";
3613 
3614  $sql = "SELECT count(p.rowid) as nb";
3615  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3616  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3617  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3618  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3619  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3620  $clause = "AND";
3621  }
3622  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3623 
3624  $resql = $this->db->query($sql);
3625  if ($resql) {
3626  // This assignment in condition is not a bug. It allows walking the results.
3627  while ($obj = $this->db->fetch_object($resql)) {
3628  $this->nb["proposals"] = $obj->nb;
3629  }
3630  $this->db->free($resql);
3631  return 1;
3632  } else {
3633  dol_print_error($this->db);
3634  $this->error = $this->db->error();
3635  return -1;
3636  }
3637  }
3638 
3639 
3647  public function getNextNumRef($soc)
3648  {
3649  global $conf, $langs;
3650  $langs->load("propal");
3651 
3652  $classname = $conf->global->PROPALE_ADDON;
3653 
3654  if (!empty($classname)) {
3655  $mybool = false;
3656 
3657  $file = $classname.".php";
3658 
3659  // Include file with class
3660  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3661  foreach ($dirmodels as $reldir) {
3662  $dir = dol_buildpath($reldir."core/modules/propale/");
3663 
3664  // Load file with numbering class (if found)
3665  $mybool |= @include_once $dir.$file;
3666  }
3667 
3668  if (!$mybool) {
3669  dol_print_error('', "Failed to include file ".$file);
3670  return '';
3671  }
3672 
3673  $obj = new $classname();
3674  $numref = "";
3675  $numref = $obj->getNextValue($soc, $this);
3676 
3677  if ($numref != "") {
3678  return $numref;
3679  } else {
3680  $this->error = $obj->error;
3681  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3682  return "";
3683  }
3684  } else {
3685  $langs->load("errors");
3686  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3687  return "";
3688  }
3689  }
3690 
3702  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3703  {
3704  global $langs, $conf, $user, $hookmanager;
3705 
3706  if (!empty($conf->dol_no_mouse_hover)) {
3707  $notooltip = 1; // Force disable tooltips
3708  }
3709 
3710  $result = '';
3711  $label = '';
3712  $url = '';
3713 
3714  if ($user->rights->propal->lire) {
3715  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3716  if (isset($this->statut)) {
3717  $label .= ' '.$this->getLibStatut(5);
3718  }
3719  if (!empty($this->ref)) {
3720  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3721  }
3722  if (!empty($this->ref_client)) {
3723  $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3724  }
3725  if (!empty($this->total_ht)) {
3726  $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3727  }
3728  if (!empty($this->total_tva)) {
3729  $label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3730  }
3731  if (!empty($this->total_ttc)) {
3732  $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3733  }
3734  if (!empty($this->date)) {
3735  $label .= '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3736  }
3737  if (!empty($this->delivery_date)) {
3738  $label .= '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3739  }
3740 
3741  if ($option == '') {
3742  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3743  } elseif ($option == 'compta') { // deprecated
3744  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3745  } elseif ($option == 'expedition') {
3746  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3747  } elseif ($option == 'document') {
3748  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3749  }
3750 
3751  if ($option != 'nolink') {
3752  // Add param to save lastsearch_values or not
3753  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3754  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3755  $add_save_lastsearch_values = 1;
3756  }
3757  if ($add_save_lastsearch_values) {
3758  $url .= '&save_lastsearch_values=1';
3759  }
3760  }
3761  }
3762 
3763  $linkclose = '';
3764  if (empty($notooltip) && $user->rights->propal->lire) {
3765  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3766  $label = $langs->trans("Proposal");
3767  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3768  }
3769  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
3770  $linkclose .= ' class="classfortooltip"';
3771  }
3772 
3773  $linkstart = '<a href="'.$url.'"';
3774  $linkstart .= $linkclose.'>';
3775  $linkend = '</a>';
3776 
3777  $result .= $linkstart;
3778  if ($withpicto) {
3779  $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
3780  }
3781  if ($withpicto != 2) {
3782  $result .= $this->ref;
3783  }
3784  $result .= $linkend;
3785 
3786  if ($addlinktonotes >= 0) {
3787  $txttoshow = '';
3788 
3789  if ($addlinktonotes == 0) {
3790  if (!empty($this->note_private) || !empty($this->note_public)) {
3791  $txttoshow = $langs->trans('ViewPrivateNote');
3792  }
3793  } elseif ($addlinktonotes == 1) {
3794  if (!empty($this->note_private)) {
3795  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3796  }
3797  } elseif ($addlinktonotes == 2) {
3798  if (!empty($this->note_public)) {
3799  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3800  }
3801  } elseif ($addlinktonotes == 3) {
3802  if ($user->socid > 0) {
3803  if (!empty($this->note_public)) {
3804  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3805  }
3806  } else {
3807  if (!empty($this->note_public)) {
3808  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3809  }
3810  if (!empty($this->note_private)) {
3811  if (!empty($txttoshow)) {
3812  $txttoshow .= '<br><br>';
3813  }
3814  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3815  }
3816  }
3817  }
3818 
3819  if ($txttoshow) {
3820  $result .= ' <span class="note inline-block">';
3821  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3822  $result .= img_picto('', 'note');
3823  $result .= '</a>';
3824  $result .= '</span>';
3825  }
3826  }
3827 
3828  global $action;
3829  $hookmanager->initHooks(array($this->element . 'dao'));
3830  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3831  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3832  if ($reshook > 0) {
3833  $result = $hookmanager->resPrint;
3834  } else {
3835  $result .= $hookmanager->resPrint;
3836  }
3837  return $result;
3838  }
3839 
3846  public function getLinesArray($filters = '')
3847  {
3848  return $this->fetch_lines(0, 0, $filters);
3849  }
3850 
3862  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3863  {
3864  global $conf, $langs;
3865 
3866  $langs->load("propale");
3867  $outputlangs->load("products");
3868 
3869  if (!dol_strlen($modele)) {
3870  $modele = 'azur';
3871 
3872  if ($this->model_pdf) {
3873  $modele = $this->model_pdf;
3874  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3875  $modele = $conf->global->PROPALE_ADDON_PDF;
3876  }
3877  }
3878 
3879  $modelpath = "core/modules/propale/doc/";
3880 
3881  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3882  }
3883 
3892  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3893  {
3894  $tables = array(
3895  'propal'
3896  );
3897 
3898  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3899  }
3900 
3909  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3910  {
3911  $tables = array(
3912  'propaldet'
3913  );
3914 
3915  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3916  }
3917 }
3918 
3923 {
3927  public $element = 'propaldet';
3928 
3932  public $table_element = 'propaldet';
3933 
3934  public $oldline;
3935 
3936  // From llx_propaldet
3937  public $fk_propal;
3938  public $fk_parent_line;
3939  public $desc; // Description ligne
3940  public $fk_product; // Id produit predefini
3951  public $product_type = Product::TYPE_PRODUCT;
3952 
3953  public $qty;
3954 
3955  public $tva_tx;
3956  public $vat_src_code;
3957 
3958  public $subprice;
3959  public $remise_percent;
3960  public $fk_remise_except;
3961 
3962  public $rang = 0;
3963 
3964  public $fk_fournprice;
3965  public $pa_ht;
3966  public $marge_tx;
3967  public $marque_tx;
3968 
3969  public $special_code; // Tag for special lines (exlusive tags)
3970  // 1: frais de port
3971  // 2: ecotaxe
3972  // 3: option line (when qty = 0)
3973 
3974  public $info_bits = 0; // Some other info:
3975  // Bit 0: 0 si TVA normal - 1 si TVA NPR
3976  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
3977 
3978  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
3979  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
3980  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
3981 
3986  public $remise;
3991  public $price;
3992 
3993  // From llx_product
3998  public $ref;
4003  public $product_ref;
4008  public $libelle;
4013  public $label;
4018  public $product_label;
4023  public $product_desc;
4024 
4029  public $product_tobatch;
4030 
4035  public $product_barcode;
4036 
4037  public $localtax1_tx; // Local tax 1
4038  public $localtax2_tx; // Local tax 2
4039  public $localtax1_type; // Local tax 1 type
4040  public $localtax2_type; // Local tax 2 type
4041  public $total_localtax1; // Line total local tax 1
4042  public $total_localtax2; // Line total local tax 2
4043 
4044  public $date_start;
4045  public $date_end;
4046 
4047  public $skip_update_total; // Skip update price total for special lines
4048 
4049  // Multicurrency
4050  public $fk_multicurrency;
4051  public $multicurrency_code;
4052  public $multicurrency_subprice;
4053  public $multicurrency_total_ht;
4054  public $multicurrency_total_tva;
4055  public $multicurrency_total_ttc;
4056 
4057 
4063  public function __construct($db)
4064  {
4065  $this->db = $db;
4066  }
4067 
4074  public function fetch($rowid)
4075  {
4076  $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,';
4077  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4078  $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,';
4079  $sql .= ' pd.fk_unit,';
4080  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4081  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4082  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4083  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4084  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4085  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4086  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4087 
4088  $result = $this->db->query($sql);
4089  if ($result) {
4090  $objp = $this->db->fetch_object($result);
4091 
4092  if ($objp) {
4093  $this->id = $objp->rowid;
4094  $this->rowid = $objp->rowid; // deprecated
4095  $this->fk_propal = $objp->fk_propal;
4096  $this->fk_parent_line = $objp->fk_parent_line;
4097  $this->label = $objp->custom_label;
4098  $this->desc = $objp->description;
4099  $this->qty = $objp->qty;
4100  $this->price = $objp->price; // deprecated
4101  $this->subprice = $objp->subprice;
4102  $this->vat_src_code = $objp->vat_src_code;
4103  $this->tva_tx = $objp->tva_tx;
4104  $this->remise = $objp->remise; // deprecated
4105  $this->remise_percent = $objp->remise_percent;
4106  $this->fk_remise_except = $objp->fk_remise_except;
4107  $this->fk_product = $objp->fk_product;
4108  $this->info_bits = $objp->info_bits;
4109 
4110  $this->total_ht = $objp->total_ht;
4111  $this->total_tva = $objp->total_tva;
4112  $this->total_ttc = $objp->total_ttc;
4113 
4114  $this->fk_fournprice = $objp->fk_fournprice;
4115 
4116  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4117  $this->pa_ht = $marginInfos[0];
4118  $this->marge_tx = $marginInfos[1];
4119  $this->marque_tx = $marginInfos[2];
4120 
4121  $this->special_code = $objp->special_code;
4122  $this->product_type = $objp->product_type;
4123  $this->rang = $objp->rang;
4124 
4125  $this->ref = $objp->product_ref; // deprecated
4126  $this->product_ref = $objp->product_ref;
4127  $this->libelle = $objp->product_label; // deprecated
4128  $this->product_label = $objp->product_label;
4129  $this->product_desc = $objp->product_desc;
4130  $this->fk_unit = $objp->fk_unit;
4131 
4132  $this->date_start = $this->db->jdate($objp->date_start);
4133  $this->date_end = $this->db->jdate($objp->date_end);
4134 
4135  // Multicurrency
4136  $this->fk_multicurrency = $objp->fk_multicurrency;
4137  $this->multicurrency_code = $objp->multicurrency_code;
4138  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4139  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4140  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4141  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4142 
4143  $this->fetch_optionals();
4144 
4145  $this->db->free($result);
4146 
4147  return 1;
4148  } else {
4149  return 0;
4150  }
4151  } else {
4152  return -1;
4153  }
4154  }
4155 
4162  public function insert($notrigger = 0)
4163  {
4164  global $conf, $user;
4165 
4166  $error = 0;
4167 
4168  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4169 
4170  $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'.
4171 
4172  // Clean parameters
4173  if (empty($this->tva_tx)) {
4174  $this->tva_tx = 0;
4175  }
4176  if (empty($this->localtax1_tx)) {
4177  $this->localtax1_tx = 0;
4178  }
4179  if (empty($this->localtax2_tx)) {
4180  $this->localtax2_tx = 0;
4181  }
4182  if (empty($this->localtax1_type)) {
4183  $this->localtax1_type = 0;
4184  }
4185  if (empty($this->localtax2_type)) {
4186  $this->localtax2_type = 0;
4187  }
4188  if (empty($this->total_localtax1)) {
4189  $this->total_localtax1 = 0;
4190  }
4191  if (empty($this->total_localtax2)) {
4192  $this->total_localtax2 = 0;
4193  }
4194  if (empty($this->rang)) {
4195  $this->rang = 0;
4196  }
4197  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4198  $this->remise_percent = 0;
4199  }
4200  if (empty($this->info_bits)) {
4201  $this->info_bits = 0;
4202  }
4203  if (empty($this->special_code)) {
4204  $this->special_code = 0;
4205  }
4206  if (empty($this->fk_parent_line)) {
4207  $this->fk_parent_line = 0;
4208  }
4209  if (empty($this->fk_fournprice)) {
4210  $this->fk_fournprice = 0;
4211  }
4212  if (!is_numeric($this->qty)) {
4213  $this->qty = 0;
4214  }
4215  if (empty($this->pa_ht)) {
4216  $this->pa_ht = 0;
4217  }
4218  if (empty($this->multicurrency_subprice)) {
4219  $this->multicurrency_subprice = 0;
4220  }
4221  if (empty($this->multicurrency_total_ht)) {
4222  $this->multicurrency_total_ht = 0;
4223  }
4224  if (empty($this->multicurrency_total_tva)) {
4225  $this->multicurrency_total_tva = 0;
4226  }
4227  if (empty($this->multicurrency_total_ttc)) {
4228  $this->multicurrency_total_ttc = 0;
4229  }
4230 
4231  // if buy price not defined, define buyprice as configured in margin admin
4232  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4233  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4234  return $result;
4235  } else {
4236  $this->pa_ht = $result;
4237  }
4238  }
4239 
4240  // Check parameters
4241  if ($this->product_type < 0) {
4242  return -1;
4243  }
4244 
4245  $this->db->begin();
4246 
4247  // Insert line into database
4248  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4249  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4250  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4251  $sql .= ' subprice, remise_percent, ';
4252  $sql .= ' info_bits, ';
4253  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4254  $sql .= ' fk_unit,';
4255  $sql .= ' date_start, date_end';
4256  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4257  $sql .= " VALUES (".$this->fk_propal.",";
4258  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4259  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4260  $sql .= " '".$this->db->escape($this->desc)."',";
4261  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4262  $sql .= " '".$this->db->escape($this->product_type)."',";
4263  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4264  $sql .= " ".price2num($this->qty, 'MS').",";
4265  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4266  $sql .= " ".price2num($this->tva_tx).",";
4267  $sql .= " ".price2num($this->localtax1_tx).",";
4268  $sql .= " ".price2num($this->localtax2_tx).",";
4269  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4270  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4271  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4272  $sql .= " ".price2num($this->remise_percent, 3).",";
4273  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4274  $sql .= " ".price2num($this->total_ht, 'MT').",";
4275  $sql .= " ".price2num($this->total_tva, 'MT').",";
4276  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4277  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4278  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4279  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4280  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4281  $sql .= ' '.((int) $this->special_code).',';
4282  $sql .= ' '.((int) $this->rang).',';
4283  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4284  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4285  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4286  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4287  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4288  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4289  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4290  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4291  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4292  $sql .= ')';
4293 
4294  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4295  $resql = $this->db->query($sql);
4296  if ($resql) {
4297  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4298 
4299  if (!$error) {
4300  $this->id = $this->rowid;
4301  $result = $this->insertExtraFields();
4302  if ($result < 0) {
4303  $error++;
4304  }
4305  }
4306 
4307  if (!$error && !$notrigger) {
4308  // Call trigger
4309  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4310  if ($result < 0) {
4311  $this->db->rollback();
4312  return -1;
4313  }
4314  // End call triggers
4315  }
4316 
4317  $this->db->commit();
4318  return 1;
4319  } else {
4320  $this->error = $this->db->error()." sql=".$sql;
4321  $this->db->rollback();
4322  return -1;
4323  }
4324  }
4325 
4333  public function delete(User $user, $notrigger = 0)
4334  {
4335  global $conf;
4336 
4337  $error = 0;
4338  $this->db->begin();
4339 
4340  if (!$notrigger) {
4341  // Call trigger
4342  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4343  if ($result < 0) {
4344  $error++;
4345  }
4346  }
4347  // End call triggers
4348 
4349  if (!$error) {
4350  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4351  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4352  if ($this->db->query($sql)) {
4353  // Remove extrafields
4354  if (!$error) {
4355  $this->id = $this->rowid;
4356  $result = $this->deleteExtraFields();
4357  if ($result < 0) {
4358  $error++;
4359  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4360  }
4361  }
4362  } else {
4363  $this->error = $this->db->error() . " sql=" . $sql;
4364  $error++;
4365  }
4366  }
4367 
4368  if ($error) {
4369  $this->db->rollback();
4370  return -1;
4371  } else {
4372  $this->db->commit();
4373  return 1;
4374  }
4375  }
4376 
4383  public function update($notrigger = 0)
4384  {
4385  global $conf, $user;
4386 
4387  $error = 0;
4388 
4389  $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'.
4390 
4391  if (empty($this->id) && !empty($this->rowid)) {
4392  $this->id = $this->rowid;
4393  }
4394 
4395  // Clean parameters
4396  if (empty($this->tva_tx)) {
4397  $this->tva_tx = 0;
4398  }
4399  if (empty($this->localtax1_tx)) {
4400  $this->localtax1_tx = 0;
4401  }
4402  if (empty($this->localtax2_tx)) {
4403  $this->localtax2_tx = 0;
4404  }
4405  if (empty($this->total_localtax1)) {
4406  $this->total_localtax1 = 0;
4407  }
4408  if (empty($this->total_localtax2)) {
4409  $this->total_localtax2 = 0;
4410  }
4411  if (empty($this->localtax1_type)) {
4412  $this->localtax1_type = 0;
4413  }
4414  if (empty($this->localtax2_type)) {
4415  $this->localtax2_type = 0;
4416  }
4417  if (empty($this->marque_tx)) {
4418  $this->marque_tx = 0;
4419  }
4420  if (empty($this->marge_tx)) {
4421  $this->marge_tx = 0;
4422  }
4423  if (empty($this->price)) {
4424  $this->price = 0; // TODO A virer
4425  }
4426  if (empty($this->remise_percent)) {
4427  $this->remise_percent = 0;
4428  }
4429  if (empty($this->info_bits)) {
4430  $this->info_bits = 0;
4431  }
4432  if (empty($this->special_code)) {
4433  $this->special_code = 0;
4434  }
4435  if (empty($this->fk_parent_line)) {
4436  $this->fk_parent_line = 0;
4437  }
4438  if (empty($this->fk_fournprice)) {
4439  $this->fk_fournprice = 0;
4440  }
4441  if (empty($this->subprice)) {
4442  $this->subprice = 0;
4443  }
4444  if (empty($this->pa_ht)) {
4445  $this->pa_ht = 0;
4446  }
4447 
4448  // if buy price not defined, define buyprice as configured in margin admin
4449  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4450  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4451  return $result;
4452  } else {
4453  $this->pa_ht = $result;
4454  }
4455  }
4456 
4457  $this->db->begin();
4458 
4459  // Mise a jour ligne en base
4460  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4461  $sql .= " description='".$this->db->escape($this->desc)."'";
4462  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4463  $sql .= ", product_type=".$this->product_type;
4464  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4465  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4466  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4467  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4468  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4469  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4470  $sql .= ", qty='".price2num($this->qty)."'";
4471  $sql .= ", subprice=".price2num($this->subprice);
4472  $sql .= ", remise_percent=".price2num($this->remise_percent);
4473  $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4474  $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4475  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4476  if (empty($this->skip_update_total)) {
4477  $sql .= ", total_ht=".price2num($this->total_ht);
4478  $sql .= ", total_tva=".price2num($this->total_tva);
4479  $sql .= ", total_ttc=".price2num($this->total_ttc);
4480  $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4481  $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4482  }
4483  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4484  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4485  if (strlen($this->special_code)) {
4486  $sql .= ", special_code=".$this->special_code;
4487  }
4488  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4489  if (!empty($this->rang)) {
4490  $sql .= ", rang=".((int) $this->rang);
4491  }
4492  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4493  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4494  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4495 
4496  // Multicurrency
4497  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4498  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4499  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4500  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4501 
4502  $sql .= " WHERE rowid = ".((int) $this->id);
4503 
4504  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4505  $resql = $this->db->query($sql);
4506  if ($resql) {
4507  if (!$error) {
4508  $result = $this->insertExtraFields();
4509  if ($result < 0) {
4510  $error++;
4511  }
4512  }
4513 
4514  if (!$error && !$notrigger) {
4515  // Call trigger
4516  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4517  if ($result < 0) {
4518  $this->db->rollback();
4519  return -1;
4520  }
4521  // End call triggers
4522  }
4523 
4524  $this->db->commit();
4525  return 1;
4526  } else {
4527  $this->error = $this->db->error();
4528  $this->db->rollback();
4529  return -2;
4530  }
4531  }
4532 
4533  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4540  public function update_total()
4541  {
4542  // phpcs:enable
4543  $this->db->begin();
4544 
4545  // Mise a jour ligne en base
4546  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4547  $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4548  $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4549  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4550  $sql .= " WHERE rowid = ".((int) $this->rowid);
4551 
4552  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4553 
4554  $resql = $this->db->query($sql);
4555  if ($resql) {
4556  $this->db->commit();
4557  return 1;
4558  } else {
4559  $this->error = $this->db->error();
4560  $this->db->rollback();
4561  return -2;
4562  }
4563  }
4564 }
$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 commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
delete_linked_contact($source='', $code='')
Delete all links between an object $this and all its contacts.
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.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
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.
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.
deleteline($lineid, $id=0)
Delete detail line.
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.
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:45
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:1401
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:1250
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:1453
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