dolibarr  13.0.2
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-2017 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-2020 Frédéric France <frederic.france@netlogic.fr>
17  * Copyright (C) 2018 Ferran Marcet <fmarcet@2byte.es>
18  *
19  * This program is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <https://www.gnu.org/licenses/>.
31  */
32 
38 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
39 require_once DOL_DOCUMENT_ROOT."/core/class/commonobjectline.class.php";
40 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
41 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
42 require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
43 require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
44 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
45 
49 class Propal extends CommonObject
50 {
51  use CommonIncoterm;
52 
56  public $element = 'propal';
57 
61  public $table_element = 'propal';
62 
66  public $table_element_line = 'propaldet';
67 
71  public $fk_element = 'fk_propal';
72 
76  public $picto = 'propal';
77 
82  public $ismultientitymanaged = 1;
83 
88  public $restrictiononfksoc = 1;
89 
93  protected $table_ref_field = 'ref';
94 
99  public $socid;
100 
105  public $contactid;
106  public $author;
107 
112  public $ref_client;
113 
119  public $statut;
120 
125  public $datec;
126 
130  public $date_creation;
131 
136  public $datev;
137 
141  public $date_validation;
142 
146  public $date;
147 
152  public $datep;
153 
158  public $date_livraison; // deprecated; Use delivery_date instead.
159 
163  public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
164 
165 
166  public $fin_validite;
167 
168  public $user_author_id;
169  public $user_valid_id;
170  public $user_close_id;
171 
176  public $price;
181  public $tva;
186  public $total;
187 
188  public $cond_reglement_code;
189  public $mode_reglement_code;
190  public $remise = 0;
191  public $remise_percent = 0;
192  public $remise_absolue = 0;
193 
198  public $fk_address;
199 
200  public $address_type;
201  public $address;
202 
203  public $availability_id;
204  public $availability_code;
205 
206  public $duree_validite;
207 
208  public $demand_reason_id;
209  public $demand_reason_code;
210 
211  public $extraparams = array();
212 
216  public $lines = array();
217  public $line;
218 
219  public $labelStatus = array();
220  public $labelStatusShort = array();
221 
222  // Multicurrency
226  public $fk_multicurrency;
227 
228  public $multicurrency_code;
229  public $multicurrency_tx;
230  public $multicurrency_total_ht;
231  public $multicurrency_total_tva;
232  public $multicurrency_total_ttc;
233 
234  public $oldcopy;
235 
236 
261  // BEGIN MODULEBUILDER PROPERTIES
265  public $fields = array(
266  'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
267  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
268  'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
269  'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
270  'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
271  'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>1, 'visible'=>-1, 'position'=>23),
272  'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Fk projet', 'enabled'=>1, 'visible'=>-1, 'position'=>24),
273  'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
274  'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
275  'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
276  'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
277  'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
278  'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
279  'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
280  'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
281  'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
282  'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
283  'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
284  'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
285  'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
286  //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
287  'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
288  'tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
289  'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
290  'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
291  'total' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
292  'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>1, 'visible'=>-1, 'position'=>150),
293  'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
294  'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
295  'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
296  'note_private' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>170),
297  'note_public' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>175),
298  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
299  'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
300  'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
301  'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
302  'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
303  'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
304  'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
305  'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
306  'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
307  'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
308  'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>235),
309  'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
310  'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
311  'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
312  'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
313  'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
314  'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
315  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
316  );
317  // END MODULEBUILDER PROPERTIES
318 
322  const STATUS_DRAFT = 0;
326  const STATUS_VALIDATED = 1;
330  const STATUS_SIGNED = 2;
334  const STATUS_NOTSIGNED = 3;
338  const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
339 
340 
348  public function __construct($db, $socid = 0, $propalid = 0)
349  {
350  global $conf, $langs;
351 
352  $this->db = $db;
353 
354  $this->socid = $socid;
355  $this->id = $propalid;
356 
357  $this->duree_validite = ((int) $conf->global->PROPALE_VALIDITY_DURATION);
358  }
359 
360 
361  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
373  public function add_product($idproduct, $qty, $remise_percent = 0)
374  {
375  // phpcs:enable
376  global $conf, $mysoc;
377 
378  if (!$qty) $qty = 1;
379 
380  dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
381  if ($idproduct > 0)
382  {
383  $prod = new Product($this->db);
384  $prod->fetch($idproduct);
385 
386  $productdesc = $prod->description;
387 
388  $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
389  $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
390  if (empty($tva_tx)) $tva_npr = 0;
391  $vat_src_code = ''; // May be defined into tva_tx
392 
393  $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
394  $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
395 
396  // multiprices
397  if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level)
398  {
399  $price = $prod->multiprices[$this->thirdparty->price_level];
400  } else {
401  $price = $prod->price;
402  }
403 
404  $line = new PropaleLigne($this->db);
405 
406  $line->fk_product = $idproduct;
407  $line->desc = $productdesc;
408  $line->qty = $qty;
409  $line->subprice = $price;
410  $line->remise_percent = $remise_percent;
411  $line->vat_src_code = $vat_src_code;
412  $line->tva_tx = $tva_tx;
413  $line->fk_unit = $prod->fk_unit;
414  if ($tva_npr) $line->info_bits = 1;
415 
416  $this->lines[] = $line;
417  }
418  }
419 
420  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
427  public function insert_discount($idremise)
428  {
429  // phpcs:enable
430  global $langs;
431 
432  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
433  include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
434 
435  $this->db->begin();
436 
437  $remise = new DiscountAbsolute($this->db);
438  $result = $remise->fetch($idremise);
439 
440  if ($result > 0)
441  {
442  if ($remise->fk_facture) // Protection against multiple submission
443  {
444  $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
445  $this->db->rollback();
446  return -5;
447  }
448 
449  $line = new PropaleLigne($this->db);
450 
451  $this->line->context = $this->context;
452 
453  $line->fk_propal = $this->id;
454  $line->fk_remise_except = $remise->id;
455  $line->desc = $remise->description; // Description ligne
456  $line->vat_src_code = $remise->vat_src_code;
457  $line->tva_tx = $remise->tva_tx;
458  $line->subprice = -$remise->amount_ht;
459  $line->fk_product = 0; // Id produit predefined
460  $line->qty = 1;
461  $line->remise = 0;
462  $line->remise_percent = 0;
463  $line->rang = -1;
464  $line->info_bits = 2;
465 
466  // TODO deprecated
467  $line->price = -$remise->amount_ht;
468 
469  $line->total_ht = -$remise->amount_ht;
470  $line->total_tva = -$remise->amount_tva;
471  $line->total_ttc = -$remise->amount_ttc;
472 
473  $result = $line->insert();
474  if ($result > 0)
475  {
476  $result = $this->update_price(1);
477  if ($result > 0)
478  {
479  $this->db->commit();
480  return 1;
481  } else {
482  $this->db->rollback();
483  return -1;
484  }
485  } else {
486  $this->error = $line->error;
487  $this->errors = $line->errors;
488  $this->db->rollback();
489  return -2;
490  }
491  } else {
492  $this->db->rollback();
493  return -2;
494  }
495  }
496 
533  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)
534  {
535  global $mysoc, $conf, $langs;
536 
537  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);
538 
539  if ($this->statut == self::STATUS_DRAFT)
540  {
541  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
542 
543  // Clean parameters
544  if (empty($remise_percent)) $remise_percent = 0;
545  if (empty($qty)) $qty = 0;
546  if (empty($info_bits)) $info_bits = 0;
547  if (empty($rang)) $rang = 0;
548  if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line = 0;
549 
550  $remise_percent = price2num($remise_percent);
551  $qty = price2num($qty);
552  $pu_ht = price2num($pu_ht);
553  $pu_ht_devise = price2num($pu_ht_devise);
554  $pu_ttc = price2num($pu_ttc);
555  if (!preg_match('/\((.*)\)/', $txtva)) {
556  $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
557  }
558  $txlocaltax1 = price2num($txlocaltax1);
559  $txlocaltax2 = price2num($txlocaltax2);
560  $pa_ht = price2num($pa_ht);
561  if ($price_base_type == 'HT')
562  {
563  $pu = $pu_ht;
564  } else {
565  $pu = $pu_ttc;
566  }
567 
568  // Check parameters
569  if ($type < 0) return -1;
570 
571  if ($date_start && $date_end && $date_start > $date_end) {
572  $langs->load("errors");
573  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
574  return -1;
575  }
576 
577  $this->db->begin();
578 
579  $product_type = $type;
580  if (!empty($fk_product))
581  {
582  $product = new Product($this->db);
583  $result = $product->fetch($fk_product);
584  $product_type = $product->type;
585 
586  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
587  $langs->load("errors");
588  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
589  $this->db->rollback();
590  return -3;
591  }
592  }
593 
594  // Calcul du total TTC et de la TVA pour la ligne a partir de
595  // qty, pu, remise_percent et txtva
596  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
597  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
598 
599  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
600 
601  // Clean vat code
602  $reg = array();
603  $vat_src_code = '';
604  $reg = array();
605  if (preg_match('/\((.*)\)/', $txtva, $reg))
606  {
607  $vat_src_code = $reg[1];
608  $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
609  }
610 
611  $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);
612 
613  $total_ht = $tabprice[0];
614  $total_tva = $tabprice[1];
615  $total_ttc = $tabprice[2];
616  $total_localtax1 = $tabprice[9];
617  $total_localtax2 = $tabprice[10];
618  $pu_ht = $tabprice[3];
619  $pu_tva = $tabprice[4];
620  $pu_ttc = $tabprice[5];
621 
622  // MultiCurrency
623  $multicurrency_total_ht = $tabprice[16];
624  $multicurrency_total_tva = $tabprice[17];
625  $multicurrency_total_ttc = $tabprice[18];
626  $pu_ht_devise = $tabprice[19];
627 
628  // Rang to use
629  $ranktouse = $rang;
630  if ($ranktouse == -1)
631  {
632  $rangmax = $this->line_max($fk_parent_line);
633  $ranktouse = $rangmax + 1;
634  }
635 
636  // TODO A virer
637  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
638  $price = $pu;
639  $remise = 0;
640  if ($remise_percent > 0)
641  {
642  $remise = round(($pu * $remise_percent / 100), 2);
643  $price = $pu - $remise;
644  }
645 
646  // Insert line
647  $this->line = new PropaleLigne($this->db);
648 
649  $this->line->context = $this->context;
650 
651  $this->line->fk_propal = $this->id;
652  $this->line->label = $label;
653  $this->line->desc = $desc;
654  $this->line->qty = $qty;
655 
656  $this->line->vat_src_code = $vat_src_code;
657  $this->line->tva_tx = $txtva;
658  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
659  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
660  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
661  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
662  $this->line->fk_product = $fk_product;
663  $this->line->product_type = $type;
664  $this->line->fk_remise_except = $fk_remise_except;
665  $this->line->remise_percent = $remise_percent;
666  $this->line->subprice = $pu_ht;
667  $this->line->rang = $ranktouse;
668  $this->line->info_bits = $info_bits;
669  $this->line->total_ht = $total_ht;
670  $this->line->total_tva = $total_tva;
671  $this->line->total_localtax1 = $total_localtax1;
672  $this->line->total_localtax2 = $total_localtax2;
673  $this->line->total_ttc = $total_ttc;
674  $this->line->special_code = $special_code;
675  $this->line->fk_parent_line = $fk_parent_line;
676  $this->line->fk_unit = $fk_unit;
677 
678  $this->line->date_start = $date_start;
679  $this->line->date_end = $date_end;
680 
681  $this->line->fk_fournprice = $fk_fournprice;
682  $this->line->pa_ht = $pa_ht;
683 
684  $this->line->origin_id = $origin_id;
685  $this->line->origin = $origin;
686 
687  // Multicurrency
688  $this->line->fk_multicurrency = $this->fk_multicurrency;
689  $this->line->multicurrency_code = $this->multicurrency_code;
690  $this->line->multicurrency_subprice = $pu_ht_devise;
691  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
692  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
693  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
694 
695  // Mise en option de la ligne
696  if (empty($qty) && empty($special_code)) $this->line->special_code = 3;
697 
698  // TODO deprecated
699  $this->line->price = $price;
700  $this->line->remise = $remise;
701 
702  if (is_array($array_options) && count($array_options) > 0) {
703  $this->line->array_options = $array_options;
704  }
705 
706  $result = $this->line->insert();
707  if ($result > 0)
708  {
709  // Reorder if child line
710  if (!empty($fk_parent_line)) $this->line_order(true, 'DESC');
711 
712  // Mise a jour informations denormalisees au niveau de la propale meme
713  $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.
714 
715  if ($result > 0) {
716  $this->db->commit();
717  return $this->line->id;
718  } else {
719  $this->error = $this->db->error();
720  $this->db->rollback();
721  return -1;
722  }
723  } else {
724  $this->error = $this->line->error;
725  $this->errors = $this->line->errors;
726  $this->db->rollback();
727  return -2;
728  }
729  } else {
730  dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
731  return -3;
732  }
733  }
734 
735 
764  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)
765  {
766  global $mysoc, $langs;
767 
768  dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
769  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");
770  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
771 
772  // Clean parameters
773  $remise_percent = price2num($remise_percent);
774  $qty = price2num($qty);
775  $pu = price2num($pu);
776  $pu_ht_devise = price2num($pu_ht_devise);
777  if (!preg_match('/\((.*)\)/', $txtva)) {
778  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
779  }
780  $txlocaltax1 = price2num($txlocaltax1);
781  $txlocaltax2 = price2num($txlocaltax2);
782  $pa_ht = price2num($pa_ht);
783  if (empty($qty) && empty($special_code)) $special_code = 3; // Set option tag
784  if (!empty($qty) && $special_code == 3) $special_code = 0; // Remove option tag
785  if (empty($type)) $type = 0;
786 
787  if ($date_start && $date_end && $date_start > $date_end) {
788  $langs->load("errors");
789  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
790  return -1;
791  }
792 
793  if ($this->statut == self::STATUS_DRAFT)
794  {
795  $this->db->begin();
796 
797  // Calcul du total TTC et de la TVA pour la ligne a partir de
798  // qty, pu, remise_percent et txtva
799  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
800  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
801 
802  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
803 
804  // Clean vat code
805  $reg = array();
806  $vat_src_code = '';
807  if (preg_match('/\((.*)\)/', $txtva, $reg))
808  {
809  $vat_src_code = $reg[1];
810  $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
811  }
812 
813  $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);
814  $total_ht = $tabprice[0];
815  $total_tva = $tabprice[1];
816  $total_ttc = $tabprice[2];
817  $total_localtax1 = $tabprice[9];
818  $total_localtax2 = $tabprice[10];
819  $pu_ht = $tabprice[3];
820  $pu_tva = $tabprice[4];
821  $pu_ttc = $tabprice[5];
822 
823  // MultiCurrency
824  $multicurrency_total_ht = $tabprice[16];
825  $multicurrency_total_tva = $tabprice[17];
826  $multicurrency_total_ttc = $tabprice[18];
827  $pu_ht_devise = $tabprice[19];
828 
829  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
830  $price = $pu;
831  $remise = 0;
832  if ($remise_percent > 0)
833  {
834  $remise = round(($pu * $remise_percent / 100), 2);
835  $price = $pu - $remise;
836  }
837 
838  //Fetch current line from the database and then clone the object and set it in $oldline property
839  $line = new PropaleLigne($this->db);
840  $line->fetch($rowid);
841 
842  $staticline = clone $line;
843 
844  $line->oldline = $staticline;
845  $this->line = $line;
846  $this->line->context = $this->context;
847 
848  // Reorder if fk_parent_line change
849  if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
850  {
851  $rangmax = $this->line_max($fk_parent_line);
852  $this->line->rang = $rangmax + 1;
853  }
854 
855  $this->line->id = $rowid;
856  $this->line->label = $label;
857  $this->line->desc = $desc;
858  $this->line->qty = $qty;
859  $this->line->product_type = $type;
860  $this->line->vat_src_code = $vat_src_code;
861  $this->line->tva_tx = $txtva;
862  $this->line->localtax1_tx = $txlocaltax1;
863  $this->line->localtax2_tx = $txlocaltax2;
864  $this->line->localtax1_type = $localtaxes_type[0];
865  $this->line->localtax2_type = $localtaxes_type[2];
866  $this->line->remise_percent = $remise_percent;
867  $this->line->subprice = $pu_ht;
868  $this->line->info_bits = $info_bits;
869 
870  $this->line->total_ht = $total_ht;
871  $this->line->total_tva = $total_tva;
872  $this->line->total_localtax1 = $total_localtax1;
873  $this->line->total_localtax2 = $total_localtax2;
874  $this->line->total_ttc = $total_ttc;
875  $this->line->special_code = $special_code;
876  $this->line->fk_parent_line = $fk_parent_line;
877  $this->line->skip_update_total = $skip_update_total;
878  $this->line->fk_unit = $fk_unit;
879 
880  $this->line->fk_fournprice = $fk_fournprice;
881  $this->line->pa_ht = $pa_ht;
882 
883  $this->line->date_start = $date_start;
884  $this->line->date_end = $date_end;
885 
886  // TODO deprecated
887  $this->line->price = $price;
888  $this->line->remise = $remise;
889 
890  if (is_array($array_options) && count($array_options) > 0) {
891  // We replace values in this->line->array_options only for entries defined into $array_options
892  foreach ($array_options as $key => $value) {
893  $this->line->array_options[$key] = $array_options[$key];
894  }
895  }
896 
897  // Multicurrency
898  $this->line->multicurrency_subprice = $pu_ht_devise;
899  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
900  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
901  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
902 
903  $result = $this->line->update($notrigger);
904  if ($result > 0)
905  {
906  // Reorder if child line
907  if (!empty($fk_parent_line)) $this->line_order(true, 'DESC');
908 
909  $this->update_price(1);
910 
911  $this->fk_propal = $this->id;
912  $this->rowid = $rowid;
913 
914  $this->db->commit();
915  return $result;
916  } else {
917  $this->error = $this->line->error;
918  $this->errors = $this->line->errors;
919  $this->db->rollback();
920  return -1;
921  }
922  } else {
923  dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
924  return -2;
925  }
926  }
927 
928 
935  public function deleteline($lineid)
936  {
937  global $user;
938 
939  if ($this->statut == self::STATUS_DRAFT)
940  {
941  $this->db->begin();
942 
943  $line = new PropaleLigne($this->db);
944 
945  // For triggers
946  $line->fetch($lineid);
947 
948  if ($line->delete($user) > 0)
949  {
950  $this->update_price(1);
951 
952  $this->db->commit();
953  return 1;
954  } else {
955  $this->db->rollback();
956  return -1;
957  }
958  } else {
959  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
960  return -2;
961  }
962  }
963 
964 
973  public function create($user, $notrigger = 0)
974  {
975  global $conf, $hookmanager;
976  $error = 0;
977 
978  $now = dol_now();
979 
980  // Clean parameters
981  if (empty($this->date)) $this->date = $this->datep;
982  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
983  if (empty($this->availability_id)) $this->availability_id = 0;
984  if (empty($this->demand_reason_id)) $this->demand_reason_id = 0;
985 
986  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
987  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
988  else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
989  if (empty($this->fk_multicurrency))
990  {
991  $this->multicurrency_code = $conf->currency;
992  $this->fk_multicurrency = 0;
993  $this->multicurrency_tx = 1;
994  }
995 
996  // Set tmp vars
997  $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
998 
999  dol_syslog(get_class($this)."::create");
1000 
1001  // Check parameters
1002  $result = $this->fetch_thirdparty();
1003  if ($result < 0)
1004  {
1005  $this->error = "Failed to fetch company";
1006  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1007  return -3;
1008  }
1009 
1010  // Check parameters
1011  if (!empty($this->ref)) // We check that ref is not already used
1012  {
1013  $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1014  if ($result > 0)
1015  {
1016  $this->error = 'ErrorRefAlreadyExists';
1017  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1018  $this->db->rollback();
1019  return -1;
1020  }
1021  }
1022 
1023  if (empty($this->date))
1024  {
1025  $this->error = "Date of proposal is required";
1026  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1027  return -4;
1028  }
1029 
1030 
1031  $this->db->begin();
1032 
1033  // Insert into database
1034  $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1035  $sql .= "fk_soc";
1036  $sql .= ", price";
1037  $sql .= ", remise";
1038  $sql .= ", remise_percent";
1039  $sql .= ", remise_absolue";
1040  $sql .= ", tva";
1041  $sql .= ", total";
1042  $sql .= ", datep";
1043  $sql .= ", datec";
1044  $sql .= ", ref";
1045  $sql .= ", fk_user_author";
1046  $sql .= ", note_private";
1047  $sql .= ", note_public";
1048  $sql .= ", model_pdf";
1049  $sql .= ", fin_validite";
1050  $sql .= ", fk_cond_reglement";
1051  $sql .= ", fk_mode_reglement";
1052  $sql .= ", fk_account";
1053  $sql .= ", ref_client";
1054  $sql .= ", date_livraison";
1055  $sql .= ", fk_shipping_method";
1056  $sql .= ", fk_availability";
1057  $sql .= ", fk_input_reason";
1058  $sql .= ", fk_projet";
1059  $sql .= ", fk_incoterms";
1060  $sql .= ", location_incoterms";
1061  $sql .= ", entity";
1062  $sql .= ", fk_multicurrency";
1063  $sql .= ", multicurrency_code";
1064  $sql .= ", multicurrency_tx";
1065  $sql .= ") ";
1066  $sql .= " VALUES (";
1067  $sql .= $this->socid;
1068  $sql .= ", 0";
1069  $sql .= ", ".$this->remise;
1070  $sql .= ", ".($this->remise_percent ? $this->db->escape($this->remise_percent) : 'NULL');
1071  $sql .= ", ".($this->remise_absolue ? $this->db->escape($this->remise_absolue) : 'NULL');
1072  $sql .= ", 0";
1073  $sql .= ", 0";
1074  $sql .= ", '".$this->db->idate($this->date)."'";
1075  $sql .= ", '".$this->db->idate($now)."'";
1076  $sql .= ", '(PROV)'";
1077  $sql .= ", ".($user->id > 0 ? "'".$this->db->escape($user->id)."'" : "NULL");
1078  $sql .= ", '".$this->db->escape($this->note_private)."'";
1079  $sql .= ", '".$this->db->escape($this->note_public)."'";
1080  $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1081  $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1082  $sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'NULL');
1083  $sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'NULL');
1084  $sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
1085  $sql .= ", '".$this->db->escape($this->ref_client)."'";
1086  $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1087  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1088  $sql .= ", ".$this->availability_id;
1089  $sql .= ", ".$this->demand_reason_id;
1090  $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1091  $sql .= ", ".(int) $this->fk_incoterms;
1092  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1093  $sql .= ", ".setEntity($this);
1094  $sql .= ", ".(int) $this->fk_multicurrency;
1095  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1096  $sql .= ", ".(double) $this->multicurrency_tx;
1097  $sql .= ")";
1098 
1099  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1100  $resql = $this->db->query($sql);
1101  if ($resql)
1102  {
1103  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1104 
1105  if ($this->id)
1106  {
1107  $this->ref = '(PROV'.$this->id.')';
1108  $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1109 
1110  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1111  $resql = $this->db->query($sql);
1112  if (!$resql) $error++;
1113 
1114  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) // To use new linkedObjectsIds instead of old linked_objects
1115  {
1116  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1117  }
1118 
1119  // Add object linked
1120  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects))
1121  {
1122  foreach ($this->linked_objects as $origin => $tmp_origin_id)
1123  {
1124  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, ...))
1125  {
1126  foreach ($tmp_origin_id as $origin_id)
1127  {
1128  $ret = $this->add_object_linked($origin, $origin_id);
1129  if (!$ret)
1130  {
1131  $this->error = $this->db->lasterror();
1132  $error++;
1133  }
1134  }
1135  } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1136  {
1137  $origin_id = $tmp_origin_id;
1138  $ret = $this->add_object_linked($origin, $origin_id);
1139  if (!$ret)
1140  {
1141  $this->error = $this->db->lasterror();
1142  $error++;
1143  }
1144  }
1145  }
1146  }
1147 
1148  /*
1149  * Insertion du detail des produits dans la base
1150  * Insert products detail in database
1151  */
1152  if (!$error)
1153  {
1154  $fk_parent_line = 0;
1155  $num = count($this->lines);
1156 
1157  for ($i = 0; $i < $num; $i++)
1158  {
1159  if (!is_object($this->lines[$i])) // If this->lines is not array of objects, coming from REST API
1160  { // Convert into object this->lines[$i].
1161  $line = (object) $this->lines[$i];
1162  } else {
1163  $line = $this->lines[$i];
1164  }
1165  // Reset fk_parent_line for line that are not child lines or special product
1166  if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1167  $fk_parent_line = 0;
1168  }
1169  // Complete vat rate with code
1170  $vatrate = $line->tva_tx;
1171  if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) $vatrate .= ' ('.$line->vat_src_code.')';
1172 
1173  if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1174  $originid = $line->origin_id;
1175  $origintype = $line->origin;
1176  } else {
1177  $originid = $line->id;
1178  $origintype = $this->element;
1179  }
1180 
1181  $result = $this->addline(
1182  $line->desc,
1183  $line->subprice,
1184  $line->qty,
1185  $vatrate,
1186  $line->localtax1_tx,
1187  $line->localtax2_tx,
1188  $line->fk_product,
1189  $line->remise_percent,
1190  'HT',
1191  0,
1192  $line->info_bits,
1193  $line->product_type,
1194  $line->rang,
1195  $line->special_code,
1196  $fk_parent_line,
1197  $line->fk_fournprice,
1198  $line->pa_ht,
1199  $line->label,
1200  $line->date_start,
1201  $line->date_end,
1202  $line->array_options,
1203  $line->fk_unit,
1204  $origintype,
1205  $originid
1206  );
1207 
1208  if ($result < 0)
1209  {
1210  $error++;
1211  $this->error = $this->db->error;
1212  dol_print_error($this->db);
1213  break;
1214  }
1215  // Defined the new fk_parent_line
1216  if ($result > 0 && $line->product_type == 9) {
1217  $fk_parent_line = $result;
1218  }
1219  }
1220  }
1221 
1222  // Set delivery address
1223  /*if (! $error && $this->fk_delivery_address)
1224  {
1225  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1226  $sql.= " SET fk_delivery_address = ".$this->fk_delivery_address;
1227  $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1228  $sql.= " AND entity = ".setEntity($this);
1229 
1230  $result=$this->db->query($sql);
1231  }*/
1232 
1233  if (!$error)
1234  {
1235  // Mise a jour infos denormalisees
1236  $resql = $this->update_price(1);
1237  if ($resql)
1238  {
1239  $action = 'update';
1240 
1241  // Actions on extra fields
1242  if (!$error)
1243  {
1244  $result = $this->insertExtraFields();
1245  if ($result < 0)
1246  {
1247  $error++;
1248  }
1249  }
1250 
1251  if (!$error && !$notrigger)
1252  {
1253  // Call trigger
1254  $result = $this->call_trigger('PROPAL_CREATE', $user);
1255  if ($result < 0) { $error++; }
1256  // End call triggers
1257  }
1258  } else {
1259  $this->error = $this->db->lasterror();
1260  $error++;
1261  }
1262  }
1263  } else {
1264  $this->error = $this->db->lasterror();
1265  $error++;
1266  }
1267 
1268  if (!$error)
1269  {
1270  $this->db->commit();
1271  dol_syslog(get_class($this)."::create done id=".$this->id);
1272  return $this->id;
1273  } else {
1274  $this->db->rollback();
1275  return -2;
1276  }
1277  } else {
1278  $this->error = $this->db->lasterror();
1279  $this->db->rollback();
1280  return -1;
1281  }
1282  }
1283 
1292  public function createFromClone(User $user, $socid = 0, $forceentity = null)
1293  {
1294  global $conf, $hookmanager;
1295 
1296  dol_include_once('/projet/class/project.class.php');
1297 
1298  $error = 0;
1299  $now = dol_now();
1300 
1301  dol_syslog(__METHOD__, LOG_DEBUG);
1302 
1303  $object = new self($this->db);
1304 
1305  $this->db->begin();
1306 
1307  // Load source object
1308  $object->fetch($this->id);
1309 
1310  $objsoc = new Societe($this->db);
1311 
1312  // Change socid if needed
1313  if (!empty($socid) && $socid != $object->socid)
1314  {
1315  if ($objsoc->fetch($socid) > 0)
1316  {
1317  $object->socid = $objsoc->id;
1318  $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1319  $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1320  $object->fk_delivery_address = '';
1321 
1322  /*if (!empty($conf->projet->enabled))
1323  {
1324  $project = new Project($db);
1325  if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1326  if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1327  else $clonedObj->fk_project = '';
1328  } else {
1329  $clonedObj->fk_project = '';
1330  }
1331  }*/
1332  $object->fk_project = ''; // A cloned proposal is set by default to no project.
1333  }
1334 
1335  // reset ref_client
1336  $object->ref_client = '';
1337 
1338  // TODO Change product price if multi-prices
1339  } else {
1340  $objsoc->fetch($object->socid);
1341  }
1342 
1343  $object->id = 0;
1344  $object->ref = '';
1345  $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1346  $object->statut = self::STATUS_DRAFT;
1347 
1348  // Clear fields
1349  $object->user_author = $user->id;
1350  $object->user_valid = '';
1351  $object->date = $now;
1352  $object->datep = $now; // deprecated
1353  $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1354  if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) $object->ref_client = '';
1355  if ($conf->global->MAIN_DONT_KEEP_NOTE_ON_CLONING == 1)
1356  {
1357  $object->note_private = '';
1358  $object->note_public = '';
1359  }
1360  // Create clone
1361  $object->context['createfromclone'] = 'createfromclone';
1362  $result = $object->create($user);
1363  if ($result < 0)
1364  {
1365  $this->error = $object->error;
1366  $this->errors = array_merge($this->errors, $object->errors);
1367  $error++;
1368  }
1369 
1370  if (!$error)
1371  {
1372  // copy internal contacts
1373  if ($object->copy_linked_contact($this, 'internal') < 0)
1374  {
1375  $error++;
1376  }
1377  }
1378 
1379  if (!$error)
1380  {
1381  // copy external contacts if same company
1382  if ($this->socid == $object->socid)
1383  {
1384  if ($object->copy_linked_contact($this, 'external') < 0)
1385  $error++;
1386  }
1387  }
1388 
1389  if (!$error)
1390  {
1391  // Hook of thirdparty module
1392  if (is_object($hookmanager))
1393  {
1394  $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1395  $action = '';
1396  $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1397  if ($reshook < 0) $error++;
1398  }
1399  }
1400 
1401  unset($object->context['createfromclone']);
1402 
1403  // End
1404  if (!$error)
1405  {
1406  $this->db->commit();
1407  return $object->id;
1408  } else {
1409  $this->db->rollback();
1410  return -1;
1411  }
1412  }
1413 
1422  public function fetch($rowid, $ref = '', $ref_ext = '')
1423  {
1424  $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1425  $sql .= ", p.total, p.tva, p.localtax1, p.localtax2, p.total_ht";
1426  $sql .= ", p.datec";
1427  $sql .= ", p.date_valid as datev";
1428  $sql .= ", p.datep as dp";
1429  $sql .= ", p.fin_validite as dfv";
1430  $sql .= ", p.date_livraison as delivery_date";
1431  $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, p.extraparams";
1432  $sql .= ", p.note_private, p.note_public";
1433  $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1434  $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1435  $sql .= ", p.fk_delivery_address";
1436  $sql .= ", p.fk_availability";
1437  $sql .= ", p.fk_input_reason";
1438  $sql .= ", p.fk_cond_reglement";
1439  $sql .= ", p.fk_mode_reglement";
1440  $sql .= ', p.fk_account';
1441  $sql .= ", p.fk_shipping_method";
1442  $sql .= ", p.fk_incoterms, p.location_incoterms";
1443  $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1444  $sql .= ", p.tms as date_modification";
1445  $sql .= ", i.libelle as label_incoterms";
1446  $sql .= ", c.label as statut_label";
1447  $sql .= ", ca.code as availability_code, ca.label as availability";
1448  $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1449  $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc";
1450  $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1451  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1452  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1453  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1454  $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').')';
1455  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1456  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1457  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1458 
1459  if ($ref) {
1460  $sql .= " WHERE p.entity IN (".getEntity('propal').")"; // Dont't use entity if you use rowid
1461  $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1462  } else $sql .= " WHERE p.rowid=".$rowid;
1463 
1464  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1465  $resql = $this->db->query($sql);
1466  if ($resql)
1467  {
1468  if ($this->db->num_rows($resql))
1469  {
1470  $obj = $this->db->fetch_object($resql);
1471 
1472  $this->id = $obj->rowid;
1473  $this->entity = $obj->entity;
1474 
1475  $this->ref = $obj->ref;
1476  $this->ref_client = $obj->ref_client;
1477  $this->remise = $obj->remise;
1478  $this->remise_percent = $obj->remise_percent;
1479  $this->remise_absolue = $obj->remise_absolue;
1480  $this->total = $obj->total; // TODO deprecated
1481  $this->total_ht = $obj->total_ht;
1482  $this->total_tva = $obj->tva;
1483  $this->total_localtax1 = $obj->localtax1;
1484  $this->total_localtax2 = $obj->localtax2;
1485  $this->total_ttc = $obj->total;
1486 
1487  $this->socid = $obj->fk_soc;
1488  $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1489 
1490  $this->fk_project = $obj->fk_project;
1491  $this->project = null; // Clear if another value was already set by fetch_projet
1492 
1493  $this->model_pdf = $obj->model_pdf;
1494  $this->modelpdf = $obj->model_pdf; // deprecated
1495  $this->last_main_doc = $obj->last_main_doc;
1496  $this->note = $obj->note_private; // TODO deprecated
1497  $this->note_private = $obj->note_private;
1498  $this->note_public = $obj->note_public;
1499 
1500  $this->status = (int) $obj->fk_statut;
1501  $this->statut = $this->status; // deprecated
1502  $this->statut_libelle = $obj->statut_label;
1503 
1504  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1505  $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1506  $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1507  $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1508  $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1509  $this->date = $this->db->jdate($obj->dp); // Proposal date
1510  $this->datep = $this->db->jdate($obj->dp); // deprecated
1511  $this->fin_validite = $this->db->jdate($obj->dfv);
1512  $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
1513  $this->delivery_date = $this->db->jdate($obj->delivery_date);
1514  $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1515  $this->availability_id = $obj->fk_availability;
1516  $this->availability_code = $obj->availability_code;
1517  $this->availability = $obj->availability;
1518  $this->demand_reason_id = $obj->fk_input_reason;
1519  $this->demand_reason_code = $obj->demand_reason_code;
1520  $this->demand_reason = $obj->demand_reason;
1521  $this->fk_address = $obj->fk_delivery_address;
1522 
1523  $this->mode_reglement_id = $obj->fk_mode_reglement;
1524  $this->mode_reglement_code = $obj->mode_reglement_code;
1525  $this->mode_reglement = $obj->mode_reglement;
1526  $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1527  $this->cond_reglement_id = $obj->fk_cond_reglement;
1528  $this->cond_reglement_code = $obj->cond_reglement_code;
1529  $this->cond_reglement = $obj->cond_reglement;
1530  $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1531 
1532  $this->extraparams = (array) json_decode($obj->extraparams, true);
1533 
1534  $this->user_author_id = $obj->fk_user_author;
1535  $this->user_valid_id = $obj->fk_user_valid;
1536  $this->user_close_id = $obj->fk_user_cloture;
1537 
1538  //Incoterms
1539  $this->fk_incoterms = $obj->fk_incoterms;
1540  $this->location_incoterms = $obj->location_incoterms;
1541  $this->label_incoterms = $obj->label_incoterms;
1542 
1543  // Multicurrency
1544  $this->fk_multicurrency = $obj->fk_multicurrency;
1545  $this->multicurrency_code = $obj->multicurrency_code;
1546  $this->multicurrency_tx = $obj->multicurrency_tx;
1547  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1548  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1549  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1550 
1551  if ($obj->fk_statut == self::STATUS_DRAFT)
1552  {
1553  $this->brouillon = 1;
1554  }
1555 
1556  // Retrieve all extrafield
1557  // fetch optionals attributes and labels
1558  $this->fetch_optionals();
1559 
1560  $this->db->free($resql);
1561 
1562  $this->lines = array();
1563 
1564  // Lines
1565  $result = $this->fetch_lines();
1566  if ($result < 0)
1567  {
1568  return -3;
1569  }
1570 
1571  return 1;
1572  }
1573 
1574  $this->error = "Record Not Found";
1575  return 0;
1576  } else {
1577  $this->error = $this->db->lasterror();
1578  return -1;
1579  }
1580  }
1581 
1589  public function update(User $user, $notrigger = 0)
1590  {
1591  global $conf;
1592 
1593  $error = 0;
1594 
1595  // Clean parameters
1596  if (isset($this->ref)) $this->ref = trim($this->ref);
1597  if (isset($this->ref_client)) $this->ref_client = trim($this->ref_client);
1598  if (isset($this->note) || isset($this->note_private)) $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1599  if (isset($this->note_public)) $this->note_public = trim($this->note_public);
1600  if (isset($this->model_pdf)) $this->model_pdf = trim($this->model_pdf);
1601  if (isset($this->import_key)) $this->import_key = trim($this->import_key);
1602  if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1603 
1604  // Check parameters
1605  // Put here code to add control on parameters values
1606 
1607  // Update request
1608  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1609  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1610  $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1611  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1612  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1613  $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1614  if (!empty($this->fin_validite)) $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1615  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1616  $sql .= " tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1617  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1618  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1619  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1620  $sql .= " total=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1621  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1622  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1623  $sql .= " fk_user_valid=".(isset($this->user_valid) ? $this->user_valid : "null").",";
1624  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1625  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1626  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1627  $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1628  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1629  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1630  $sql .= " model_pdf=".(isset($this->modelpdf) ? "'".$this->db->escape($this->modelpdf)."'" : "null").",";
1631  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null")."";
1632  $sql .= " WHERE rowid=".$this->id;
1633 
1634  $this->db->begin();
1635 
1636  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1637  $resql = $this->db->query($sql);
1638  if (!$resql) {
1639  $error++; $this->errors[] = "Error ".$this->db->lasterror();
1640  }
1641 
1642  if (!$error)
1643  {
1644  $result = $this->insertExtraFields();
1645  if ($result < 0)
1646  {
1647  $error++;
1648  }
1649  }
1650 
1651  if (!$error && !$notrigger)
1652  {
1653  // Call trigger
1654  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1655  if ($result < 0) $error++;
1656  // End call triggers
1657  }
1658 
1659  // Commit or rollback
1660  if ($error)
1661  {
1662  foreach ($this->errors as $errmsg)
1663  {
1664  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1665  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1666  }
1667  $this->db->rollback();
1668  return -1 * $error;
1669  } else {
1670  $this->db->commit();
1671  return 1;
1672  }
1673  }
1674 
1675 
1676  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1685  public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
1686  {
1687  global $langs, $conf;
1688  // phpcs:enable
1689  $this->lines = array();
1690 
1691  $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,';
1692  $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,';
1693  $sql .= ' d.fk_unit,';
1694  $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,';
1695  $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1696  $sql .= ' d.date_start, d.date_end,';
1697  $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1698  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1699  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1700  $sql .= ' WHERE d.fk_propal = '.$this->id;
1701  if ($only_product) $sql .= ' AND p.fk_product_type = 0';
1702  $sql .= ' ORDER by d.rang';
1703 
1704  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1705  $result = $this->db->query($sql);
1706  if ($result)
1707  {
1708  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1709 
1710  $num = $this->db->num_rows($result);
1711 
1712  $i = 0;
1713  while ($i < $num)
1714  {
1715  $objp = $this->db->fetch_object($result);
1716 
1717  $line = new PropaleLigne($this->db);
1718 
1719  $line->rowid = $objp->rowid; //Deprecated
1720  $line->id = $objp->rowid;
1721  $line->fk_propal = $objp->fk_propal;
1722  $line->fk_parent_line = $objp->fk_parent_line;
1723  $line->product_type = $objp->product_type;
1724  $line->label = $objp->custom_label;
1725  $line->desc = $objp->description; // Description ligne
1726  $line->description = $objp->description; // Description ligne
1727  $line->qty = $objp->qty;
1728  $line->vat_src_code = $objp->vat_src_code;
1729  $line->tva_tx = $objp->tva_tx;
1730  $line->localtax1_tx = $objp->localtax1_tx;
1731  $line->localtax2_tx = $objp->localtax2_tx;
1732  $line->localtax1_type = $objp->localtax1_type;
1733  $line->localtax2_type = $objp->localtax2_type;
1734  $line->subprice = $objp->subprice;
1735  $line->fk_remise_except = $objp->fk_remise_except;
1736  $line->remise_percent = $objp->remise_percent;
1737  $line->price = $objp->price; // TODO deprecated
1738 
1739  $line->info_bits = $objp->info_bits;
1740  $line->total_ht = $objp->total_ht;
1741  $line->total_tva = $objp->total_tva;
1742  $line->total_localtax1 = $objp->total_localtax1;
1743  $line->total_localtax2 = $objp->total_localtax2;
1744  $line->total_ttc = $objp->total_ttc;
1745  $line->fk_fournprice = $objp->fk_fournprice;
1746  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1747  $line->pa_ht = $marginInfos[0];
1748  $line->marge_tx = $marginInfos[1];
1749  $line->marque_tx = $marginInfos[2];
1750  $line->special_code = $objp->special_code;
1751  $line->rang = $objp->rang;
1752 
1753  $line->fk_product = $objp->fk_product;
1754 
1755  $line->ref = $objp->product_ref; // deprecated
1756  $line->libelle = $objp->product_label; // deprecated
1757 
1758  $line->product_ref = $objp->product_ref;
1759  $line->product_label = $objp->product_label;
1760  $line->product_desc = $objp->product_desc; // Description produit
1761  $line->product_tobatch = $objp->product_tobatch;
1762  $line->product_barcode = $objp->product_barcode;
1763 
1764  $line->fk_product_type = $objp->fk_product_type; // deprecated
1765  $line->fk_unit = $objp->fk_unit;
1766  $line->weight = $objp->weight;
1767  $line->weight_units = $objp->weight_units;
1768  $line->volume = $objp->volume;
1769  $line->volume_units = $objp->volume_units;
1770 
1771  $line->date_start = $this->db->jdate($objp->date_start);
1772  $line->date_end = $this->db->jdate($objp->date_end);
1773 
1774  // Multicurrency
1775  $line->fk_multicurrency = $objp->fk_multicurrency;
1776  $line->multicurrency_code = $objp->multicurrency_code;
1777  $line->multicurrency_subprice = $objp->multicurrency_subprice;
1778  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1779  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1780  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1781 
1782  $line->fetch_optionals();
1783 
1784  // multilangs
1785  if (!empty($conf->global->MAIN_MULTILANGS) && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1786  $line = new Product($this->db);
1787  $line->fetch($objp->fk_product);
1788  $line->getMultiLangs();
1789  }
1790 
1791  $this->lines[$i] = $line;
1792  //dol_syslog("1 ".$line->fk_product);
1793  //print "xx $i ".$this->lines[$i]->fk_product;
1794  $i++;
1795  }
1796 
1797  $this->db->free($result);
1798 
1799  return $num;
1800  } else {
1801  $this->error = $this->db->lasterror();
1802  return -3;
1803  }
1804  }
1805 
1813  public function valid($user, $notrigger = 0)
1814  {
1815  global $conf;
1816 
1817  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1818 
1819  $error = 0;
1820 
1821  // Protection
1822  if ($this->statut == self::STATUS_VALIDATED)
1823  {
1824  dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1825  return 0;
1826  }
1827 
1828  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
1829  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate))))
1830  {
1831  $this->error = 'ErrorPermissionDenied';
1832  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1833  return -1;
1834  }
1835 
1836  $now = dol_now();
1837 
1838  $this->db->begin();
1839 
1840  // Numbering module definition
1841  $soc = new Societe($this->db);
1842  $soc->fetch($this->socid);
1843 
1844  // Define new ref
1845  if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
1846  {
1847  $num = $this->getNextNumRef($soc);
1848  } else {
1849  $num = $this->ref;
1850  }
1851  $this->newref = dol_sanitizeFileName($num);
1852 
1853  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1854  $sql .= " SET ref = '".$this->db->escape($num)."',";
1855  $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".$user->id;
1856  $sql .= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
1857 
1858  dol_syslog(get_class($this)."::valid", LOG_DEBUG);
1859  $resql = $this->db->query($sql);
1860  if (!$resql)
1861  {
1862  dol_print_error($this->db);
1863  $error++;
1864  }
1865 
1866  // Trigger calls
1867  if (!$error && !$notrigger)
1868  {
1869  // Call trigger
1870  $result = $this->call_trigger('PROPAL_VALIDATE', $user);
1871  if ($result < 0) { $error++; }
1872  // End call triggers
1873  }
1874 
1875  if (!$error)
1876  {
1877  $this->oldref = $this->ref;
1878 
1879  // Rename directory if dir was a temporary ref
1880  if (preg_match('/^[\(]?PROV/i', $this->ref))
1881  {
1882  // Now we rename also files into index
1883  $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)."'";
1884  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1885  $resql = $this->db->query($sql);
1886  if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
1887 
1888  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1889  $oldref = dol_sanitizeFileName($this->ref);
1890  $newref = dol_sanitizeFileName($num);
1891  $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
1892  $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
1893  if (!$error && file_exists($dirsource))
1894  {
1895  dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
1896  if (@rename($dirsource, $dirdest))
1897  {
1898  dol_syslog("Rename ok");
1899  // Rename docs starting with $oldref with $newref
1900  $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
1901  foreach ($listoffiles as $fileentry)
1902  {
1903  $dirsource = $fileentry['name'];
1904  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1905  $dirsource = $fileentry['path'].'/'.$dirsource;
1906  $dirdest = $fileentry['path'].'/'.$dirdest;
1907  @rename($dirsource, $dirdest);
1908  }
1909  }
1910  }
1911  }
1912 
1913  $this->ref = $num;
1914  $this->brouillon = 0;
1915  $this->statut = self::STATUS_VALIDATED;
1916  $this->user_valid_id = $user->id;
1917  $this->datev = $now;
1918 
1919  $this->db->commit();
1920  return 1;
1921  } else {
1922  $this->db->rollback();
1923  return -1;
1924  }
1925  }
1926 
1927 
1928  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1937  public function set_date($user, $date, $notrigger = 0)
1938  {
1939  // phpcs:enable
1940  if (empty($date))
1941  {
1942  $this->error = 'ErrorBadParameter';
1943  dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
1944  return -1;
1945  }
1946 
1947  if (!empty($user->rights->propal->creer))
1948  {
1949  $error = 0;
1950 
1951  $this->db->begin();
1952 
1953  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
1954  $sql .= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
1955 
1956  dol_syslog(__METHOD__, LOG_DEBUG);
1957  $resql = $this->db->query($sql);
1958  if (!$resql)
1959  {
1960  $this->errors[] = $this->db->error();
1961  $error++;
1962  }
1963 
1964  if (!$error)
1965  {
1966  $this->oldcopy = clone $this;
1967  $this->date = $date;
1968  $this->datep = $date; // deprecated
1969  }
1970 
1971  if (!$notrigger && empty($error))
1972  {
1973  // Call trigger
1974  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1975  if ($result < 0) $error++;
1976  // End call triggers
1977  }
1978 
1979  if (!$error)
1980  {
1981  $this->db->commit();
1982  return 1;
1983  } else {
1984  foreach ($this->errors as $errmsg)
1985  {
1986  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
1987  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1988  }
1989  $this->db->rollback();
1990  return -1 * $error;
1991  }
1992  }
1993  }
1994 
1995  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2004  public function set_echeance($user, $date_fin_validite, $notrigger = 0)
2005  {
2006  // phpcs:enable
2007  if (!empty($user->rights->propal->creer))
2008  {
2009  $error = 0;
2010 
2011  $this->db->begin();
2012 
2013  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_fin_validite != '' ? "'".$this->db->idate($date_fin_validite)."'" : 'null');
2014  $sql .= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
2015 
2016  dol_syslog(__METHOD__, LOG_DEBUG);
2017  $resql = $this->db->query($sql);
2018  if (!$resql)
2019  {
2020  $this->errors[] = $this->db->error();
2021  $error++;
2022  }
2023 
2024 
2025  if (!$error)
2026  {
2027  $this->oldcopy = clone $this;
2028  $this->fin_validite = $date_fin_validite;
2029  }
2030 
2031  if (!$notrigger && empty($error))
2032  {
2033  // Call trigger
2034  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2035  if ($result < 0) $error++;
2036  // End call triggers
2037  }
2038 
2039  if (!$error)
2040  {
2041  $this->db->commit();
2042  return 1;
2043  } else {
2044  foreach ($this->errors as $errmsg)
2045  {
2046  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2047  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2048  }
2049  $this->db->rollback();
2050  return -1 * $error;
2051  }
2052  }
2053  }
2054 
2055  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2065  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2066  {
2067  // phpcs:enable
2068  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2069  }
2070 
2079  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2080  {
2081  if (!empty($user->rights->propal->creer))
2082  {
2083  $error = 0;
2084 
2085  $this->db->begin();
2086 
2087  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2088  $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2089  $sql .= " WHERE rowid = ".$this->id;
2090 
2091  dol_syslog(__METHOD__, LOG_DEBUG);
2092  $resql = $this->db->query($sql);
2093  if (!$resql)
2094  {
2095  $this->errors[] = $this->db->error();
2096  $error++;
2097  }
2098 
2099  if (!$error)
2100  {
2101  $this->oldcopy = clone $this;
2102  $this->date_livraison = $delivery_date;
2103  $this->delivery_date = $delivery_date;
2104  }
2105 
2106  if (!$notrigger && empty($error))
2107  {
2108  // Call trigger
2109  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2110  if ($result < 0) $error++;
2111  // End call triggers
2112  }
2113 
2114  if (!$error)
2115  {
2116  $this->db->commit();
2117  return 1;
2118  } else {
2119  foreach ($this->errors as $errmsg)
2120  {
2121  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2122  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2123  }
2124  $this->db->rollback();
2125  return -1 * $error;
2126  }
2127  }
2128  }
2129 
2130  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2139  public function set_availability($user, $id, $notrigger = 0)
2140  {
2141  // phpcs:enable
2142  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT)
2143  {
2144  $error = 0;
2145 
2146  $this->db->begin();
2147 
2148  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2149  $sql .= " SET fk_availability = '".$id."'";
2150  $sql .= " WHERE rowid = ".$this->id;
2151 
2152  dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2153  $resql = $this->db->query($sql);
2154  if (!$resql)
2155  {
2156  $this->errors[] = $this->db->error();
2157  $error++;
2158  }
2159 
2160  if (!$error)
2161  {
2162  $this->oldcopy = clone $this;
2163  $this->fk_availability = $id;
2164  $this->availability_id = $id;
2165  }
2166 
2167  if (!$notrigger && empty($error))
2168  {
2169  // Call trigger
2170  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2171  if ($result < 0) $error++;
2172  // End call triggers
2173  }
2174 
2175  if (!$error)
2176  {
2177  $this->db->commit();
2178  return 1;
2179  } else {
2180  foreach ($this->errors as $errmsg)
2181  {
2182  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2183  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2184  }
2185  $this->db->rollback();
2186  return -1 * $error;
2187  }
2188  } else {
2189  $error_str = 'Propal status do not meet requirement '.$this->statut;
2190  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2191  $this->error = $error_str;
2192  $this->errors[] = $this->error;
2193  return -2;
2194  }
2195  }
2196 
2197  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2206  public function set_demand_reason($user, $id, $notrigger = 0)
2207  {
2208  // phpcs:enable
2209  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT)
2210  {
2211  $error = 0;
2212 
2213  $this->db->begin();
2214 
2215  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2216  $sql .= " SET fk_input_reason = ".$id;
2217  $sql .= " WHERE rowid = ".$this->id;
2218 
2219  dol_syslog(__METHOD__, LOG_DEBUG);
2220  $resql = $this->db->query($sql);
2221  if (!$resql)
2222  {
2223  $this->errors[] = $this->db->error();
2224  $error++;
2225  }
2226 
2227 
2228  if (!$error)
2229  {
2230  $this->oldcopy = clone $this;
2231  $this->fk_input_reason = $id;
2232  $this->demand_reason_id = $id;
2233  }
2234 
2235 
2236  if (!$notrigger && empty($error))
2237  {
2238  // Call trigger
2239  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2240  if ($result < 0) $error++;
2241  // End call triggers
2242  }
2243 
2244  if (!$error)
2245  {
2246  $this->db->commit();
2247  return 1;
2248  } else {
2249  foreach ($this->errors as $errmsg)
2250  {
2251  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2252  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2253  }
2254  $this->db->rollback();
2255  return -1 * $error;
2256  }
2257  } else {
2258  $error_str = 'Propal status do not meet requirement '.$this->statut;
2259  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2260  $this->error = $error_str;
2261  $this->errors[] = $this->error;
2262  return -2;
2263  }
2264  }
2265 
2266  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2275  public function set_ref_client($user, $ref_client, $notrigger = 0)
2276  {
2277  // phpcs:enable
2278  if (!empty($user->rights->propal->creer))
2279  {
2280  $error = 0;
2281 
2282  $this->db->begin();
2283 
2284  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET ref_client = '.(empty($ref_client) ? 'NULL' : '\''.$this->db->escape($ref_client).'\'');
2285  $sql .= ' WHERE rowid = '.$this->id;
2286 
2287  dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2288  $resql = $this->db->query($sql);
2289  if (!$resql)
2290  {
2291  $this->errors[] = $this->db->error();
2292  $error++;
2293  }
2294 
2295  if (!$error)
2296  {
2297  $this->oldcopy = clone $this;
2298  $this->ref_client = $ref_client;
2299  }
2300 
2301  if (!$notrigger && empty($error))
2302  {
2303  // Call trigger
2304  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2305  if ($result < 0) $error++;
2306  // End call triggers
2307  }
2308 
2309  if (!$error)
2310  {
2311  $this->db->commit();
2312  return 1;
2313  } else {
2314  foreach ($this->errors as $errmsg)
2315  {
2316  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2317  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2318  }
2319  $this->db->rollback();
2320  return -1 * $error;
2321  }
2322  } else {
2323  return -1;
2324  }
2325  }
2326 
2327  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2336  public function set_remise_percent($user, $remise, $notrigger = 0)
2337  {
2338  // phpcs:enable
2339  $remise = trim($remise) ?trim($remise) : 0;
2340 
2341  if (!empty($user->rights->propal->creer))
2342  {
2343  $remise = price2num($remise);
2344 
2345  $error = 0;
2346 
2347  $this->db->begin();
2348 
2349  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".$remise;
2350  $sql .= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
2351 
2352  dol_syslog(__METHOD__, LOG_DEBUG);
2353  $resql = $this->db->query($sql);
2354  if (!$resql)
2355  {
2356  $this->errors[] = $this->db->error();
2357  $error++;
2358  }
2359 
2360  if (!$error)
2361  {
2362  $this->oldcopy = clone $this;
2363  $this->remise_percent = $remise;
2364  $this->update_price(1);
2365  }
2366 
2367  if (!$notrigger && empty($error))
2368  {
2369  // Call trigger
2370  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2371  if ($result < 0) $error++;
2372  // End call triggers
2373  }
2374 
2375  if (!$error)
2376  {
2377  $this->db->commit();
2378  return 1;
2379  } else {
2380  foreach ($this->errors as $errmsg)
2381  {
2382  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2383  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2384  }
2385  $this->db->rollback();
2386  return -1 * $error;
2387  }
2388  }
2389  }
2390 
2391 
2392  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2401  public function set_remise_absolue($user, $remise, $notrigger = 0)
2402  {
2403  // phpcs:enable
2404  $remise = trim($remise) ?trim($remise) : 0;
2405 
2406  if (!empty($user->rights->propal->creer))
2407  {
2408  $remise = price2num($remise);
2409 
2410  $error = 0;
2411 
2412  $this->db->begin();
2413 
2414  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2415  $sql .= " SET remise_absolue = ".$remise;
2416  $sql .= " WHERE rowid = ".$this->id." AND fk_statut = ".self::STATUS_DRAFT;
2417 
2418  dol_syslog(__METHOD__, LOG_DEBUG);
2419  $resql = $this->db->query($sql);
2420  if (!$resql)
2421  {
2422  $this->errors[] = $this->db->error();
2423  $error++;
2424  }
2425 
2426  if (!$error)
2427  {
2428  $this->oldcopy = clone $this;
2429  $this->remise_absolue = $remise;
2430  $this->update_price(1);
2431  }
2432 
2433  if (!$notrigger && empty($error))
2434  {
2435  // Call trigger
2436  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2437  if ($result < 0) $error++;
2438  // End call triggers
2439  }
2440 
2441  if (!$error)
2442  {
2443  $this->db->commit();
2444  return 1;
2445  } else {
2446  foreach ($this->errors as $errmsg)
2447  {
2448  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2449  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2450  }
2451  $this->db->rollback();
2452  return -1 * $error;
2453  }
2454  }
2455  }
2456 
2457 
2458 
2468  public function reopen($user, $statut, $note = '', $notrigger = 0)
2469  {
2470 
2471  $this->statut = $statut;
2472  $error = 0;
2473 
2474  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2475  $sql .= " SET fk_statut = ".$this->statut.",";
2476  if (!empty($note)) $sql .= " note_private = '".$this->db->escape($note)."',";
2477  $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2478  $sql .= " WHERE rowid = ".$this->id;
2479 
2480  $this->db->begin();
2481 
2482  dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2483  $resql = $this->db->query($sql);
2484  if (!$resql) {
2485  $error++; $this->errors[] = "Error ".$this->db->lasterror();
2486  }
2487  if (!$error)
2488  {
2489  if (!$notrigger)
2490  {
2491  // Call trigger
2492  $result = $this->call_trigger('PROPAL_REOPEN', $user);
2493  if ($result < 0) { $error++; }
2494  // End call triggers
2495  }
2496  }
2497 
2498  // Commit or rollback
2499  if ($error)
2500  {
2501  if (!empty($this->errors))
2502  {
2503  foreach ($this->errors as $errmsg)
2504  {
2505  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2506  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2507  }
2508  }
2509  $this->db->rollback();
2510  return -1 * $error;
2511  } else {
2512  $this->db->commit();
2513  return 1;
2514  }
2515  }
2516 
2517 
2527  public function cloture($user, $status, $note = "", $notrigger = 0)
2528  {
2529  global $langs, $conf;
2530 
2531  $error = 0;
2532  $now = dol_now();
2533 
2534  $this->db->begin();
2535 
2536  $newprivatenote = dol_concatdesc($this->note_private, $note);
2537 
2538  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2539  $sql .= " SET fk_statut = ".$status.", note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".$user->id;
2540  $sql .= " WHERE rowid = ".$this->id;
2541 
2542  $resql = $this->db->query($sql);
2543  if ($resql)
2544  {
2545  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2546  $triggerName = 'PROPAL_CLOSE_REFUSED';
2547 
2548  if ($status == self::STATUS_SIGNED)
2549  {
2550  $triggerName = 'PROPAL_CLOSE_SIGNED';
2551  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2552 
2553  // The connected company is classified as a client
2554  $soc = new Societe($this->db);
2555  $soc->id = $this->socid;
2556  $result = $soc->set_as_client();
2557 
2558  if ($result < 0)
2559  {
2560  $this->error = $this->db->lasterror();
2561  $this->db->rollback();
2562  return -2;
2563  }
2564  }
2565  if ($status == self::STATUS_BILLED) // ->cloture() can also be called when we set it to billed, after setting it to signed
2566  {
2567  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2568  }
2569 
2570  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE))
2571  {
2572  // Define output language
2573  $outputlangs = $langs;
2574  if (!empty($conf->global->MAIN_MULTILANGS))
2575  {
2576  $outputlangs = new Translate("", $conf);
2577  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2578  $outputlangs->setDefaultLang($newlang);
2579  }
2580  //$ret=$object->fetch($id); // Reload to get new records
2581  $this->generateDocument($modelpdf, $outputlangs);
2582  }
2583 
2584  if (!$error)
2585  {
2586  $this->oldcopy = clone $this;
2587  $this->statut = $status;
2588  $this->date_cloture = $now;
2589  $this->note_private = $newprivatenote;
2590  }
2591 
2592  if (!$notrigger && empty($error))
2593  {
2594  // Call trigger
2595  $result = $this->call_trigger($triggerName, $user);
2596  if ($result < 0) { $error++; }
2597  // End call triggers
2598  }
2599 
2600  if (!$error)
2601  {
2602  $this->db->commit();
2603  return 1;
2604  } else {
2605  $this->statut = $this->oldcopy->statut;
2606  $this->date_cloture = $this->oldcopy->date_cloture;
2607  $this->note_private = $this->oldcopy->note_private;
2608 
2609  $this->db->rollback();
2610  return -1;
2611  }
2612  } else {
2613  $this->error = $this->db->lasterror();
2614  $this->db->rollback();
2615  return -1;
2616  }
2617  }
2618 
2626  public function classifyBilled(User $user, $notrigger = 0)
2627  {
2628  $error = 0;
2629 
2630  $this->db->begin();
2631 
2632  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED;
2633  $sql .= ' WHERE rowid = '.$this->id.' AND fk_statut > '.self::STATUS_DRAFT;
2634 
2635  dol_syslog(__METHOD__, LOG_DEBUG);
2636  $resql = $this->db->query($sql);
2637  if (!$resql)
2638  {
2639  $this->errors[] = $this->db->error();
2640  $error++;
2641  }
2642 
2643  if (!$error)
2644  {
2645  $this->oldcopy = clone $this;
2646  $this->statut = self::STATUS_BILLED;
2647  }
2648 
2649  if (!$notrigger && empty($error))
2650  {
2651  // Call trigger
2652  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2653  if ($result < 0) $error++;
2654  // End call triggers
2655  }
2656 
2657  if (!$error)
2658  {
2659  $this->db->commit();
2660  return 1;
2661  } else {
2662  foreach ($this->errors as $errmsg)
2663  {
2664  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2665  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2666  }
2667  $this->db->rollback();
2668  return -1 * $error;
2669  }
2670  }
2671 
2672  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2680  public function setDraft($user, $notrigger = 0)
2681  {
2682  // phpcs:enable
2683  $error = 0;
2684 
2685  // Protection
2686  if ($this->statut <= self::STATUS_DRAFT)
2687  {
2688  return 0;
2689  }
2690 
2691  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2692 
2693  $this->db->begin();
2694 
2695  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2696  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2697  $sql .= " WHERE rowid = ".$this->id;
2698 
2699  $resql = $this->db->query($sql);
2700  if (!$resql)
2701  {
2702  $this->errors[] = $this->db->error();
2703  $error++;
2704  }
2705 
2706  if (!$error)
2707  {
2708  $this->oldcopy = clone $this;
2709  }
2710 
2711  if (!$notrigger && empty($error))
2712  {
2713  // Call trigger
2714  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2715  if ($result < 0) $error++;
2716  // End call triggers
2717  }
2718 
2719  if (!$error)
2720  {
2721  $this->statut = self::STATUS_DRAFT;
2722  $this->brouillon = 1;
2723 
2724  $this->db->commit();
2725  return 1;
2726  } else {
2727  foreach ($this->errors as $errmsg)
2728  {
2729  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2730  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2731  }
2732  $this->db->rollback();
2733  return -1 * $error;
2734  }
2735  }
2736 
2737 
2738  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2752  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2753  {
2754  // phpcs:enable
2755  global $user;
2756 
2757  $ga = array();
2758 
2759  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2760  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2761  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2762  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", sc.fk_soc, sc.fk_user";
2763  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2764  if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2765  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2766  $sql .= " AND p.fk_soc = s.rowid";
2767  $sql .= " AND p.fk_statut = c.id";
2768  if (!$user->rights->societe->client->voir && !$socid) //restriction
2769  {
2770  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".$user->id;
2771  }
2772  if ($socid) $sql .= " AND s.rowid = ".$socid;
2773  if ($draft) $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2774  if ($notcurrentuser > 0) $sql .= " AND p.fk_user_author <> ".$user->id;
2775  $sql .= $this->db->order($sortfield, $sortorder);
2776  $sql .= $this->db->plimit($limit, $offset);
2777 
2778  $result = $this->db->query($sql);
2779  if ($result)
2780  {
2781  $num = $this->db->num_rows($result);
2782  if ($num)
2783  {
2784  $i = 0;
2785  while ($i < $num)
2786  {
2787  $obj = $this->db->fetch_object($result);
2788 
2789  if ($shortlist == 1)
2790  {
2791  $ga[$obj->propalid] = $obj->ref;
2792  } elseif ($shortlist == 2)
2793  {
2794  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2795  } else {
2796  $ga[$i]['id'] = $obj->propalid;
2797  $ga[$i]['ref'] = $obj->ref;
2798  $ga[$i]['name'] = $obj->name;
2799  }
2800 
2801  $i++;
2802  }
2803  }
2804  return $ga;
2805  } else {
2806  dol_print_error($this->db);
2807  return -1;
2808  }
2809  }
2810 
2816  public function getInvoiceArrayList()
2817  {
2818  return $this->InvoiceArrayList($this->id);
2819  }
2820 
2821  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2828  public function InvoiceArrayList($id)
2829  {
2830  // phpcs:enable
2831  $ga = array();
2832  $linkedInvoices = array();
2833 
2834  $this->fetchObjectLinked($id, $this->element);
2835  foreach ($this->linkedObjectsIds as $objecttype => $objectid)
2836  {
2837  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
2838  // On parcourt donc une liste d'objets en tant qu'objet unique
2839  foreach ($objectid as $key => $object)
2840  {
2841  // Cas des factures liees directement
2842  if ($objecttype == 'facture')
2843  {
2844  $linkedInvoices[] = $object;
2845  } // Cas des factures liees par un autre objet (ex: commande)
2846  else {
2847  $this->fetchObjectLinked($object, $objecttype);
2848  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid)
2849  {
2850  foreach ($subobjectid as $subkey => $subobject)
2851  {
2852  if ($subobjecttype == 'facture')
2853  {
2854  $linkedInvoices[] = $subobject;
2855  }
2856  }
2857  }
2858  }
2859  }
2860  }
2861 
2862  if (count($linkedInvoices) > 0)
2863  {
2864  $sql = "SELECT rowid as facid, ref, total, datef as df, fk_user_author, fk_statut, paye";
2865  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
2866  $sql .= " WHERE rowid IN (".implode(',', $linkedInvoices).")";
2867 
2868  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
2869  $resql = $this->db->query($sql);
2870 
2871  if ($resql)
2872  {
2873  $tab_sqlobj = array();
2874  $nump = $this->db->num_rows($resql);
2875  for ($i = 0; $i < $nump; $i++)
2876  {
2877  $sqlobj = $this->db->fetch_object($resql);
2878  $tab_sqlobj[] = $sqlobj;
2879  }
2880  $this->db->free($resql);
2881 
2882  $nump = count($tab_sqlobj);
2883 
2884  if ($nump)
2885  {
2886  $i = 0;
2887  while ($i < $nump)
2888  {
2889  $obj = array_shift($tab_sqlobj);
2890 
2891  $ga[$i] = $obj;
2892 
2893  $i++;
2894  }
2895  }
2896  return $ga;
2897  } else {
2898  return -1;
2899  }
2900  } else return $ga;
2901  }
2902 
2910  public function delete($user, $notrigger = 0)
2911  {
2912  global $conf;
2913  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2914 
2915  $error = 0;
2916 
2917  $this->db->begin();
2918 
2919  if (!$notrigger) {
2920  // Call trigger
2921  $result = $this->call_trigger('PROPAL_DELETE', $user);
2922  if ($result < 0) { $error++; }
2923  // End call triggers
2924  }
2925 
2926  // Delete extrafields of lines and lines
2927  if (!$error && !empty($this->table_element_line)) {
2928  $tabletodelete = $this->table_element_line;
2929  $sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".$this->id.")";
2930  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".$this->id;
2931  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
2932  $error++;
2933  $this->error = $this->db->lasterror();
2934  $this->errors[] = $this->error;
2935  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2936  }
2937  }
2938 
2939  if (!$error) {
2940  // Delete linked object
2941  $res = $this->deleteObjectLinked();
2942  if ($res < 0) $error++;
2943  }
2944 
2945  if (!$error) {
2946  // Delete linked contacts
2947  $res = $this->delete_linked_contact();
2948  if ($res < 0) $error++;
2949  }
2950 
2951  // Removed extrafields of object
2952  if (!$error) {
2953  $result = $this->deleteExtraFields();
2954  if ($result < 0) {
2955  $error++;
2956  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2957  }
2958  }
2959 
2960  // Delete main record
2961  if (!$error) {
2962  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".$this->id;
2963  $res = $this->db->query($sql);
2964  if (!$res) {
2965  $error++;
2966  $this->error = $this->db->lasterror();
2967  $this->errors[] = $this->error;
2968  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2969  }
2970  }
2971 
2972  // Delete record into ECM index and physically
2973  if (!$error) {
2974  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2975  if (!$res) {
2976  $error++;
2977  }
2978  }
2979 
2980  if (!$error) {
2981  // We remove directory
2982  $ref = dol_sanitizeFileName($this->ref);
2983  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
2984  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
2985  $file = $dir."/".$ref.".pdf";
2986  if (file_exists($file)) {
2987  dol_delete_preview($this);
2988 
2989  if (!dol_delete_file($file, 0, 0, 0, $this)) {
2990  $this->error = 'ErrorFailToDeleteFile';
2991  $this->errors[] = $this->error;
2992  $this->db->rollback();
2993  return 0;
2994  }
2995  }
2996  if (file_exists($dir)) {
2997  $res = @dol_delete_dir_recursive($dir);
2998  if (!$res) {
2999  $this->error = 'ErrorFailToDeleteDir';
3000  $this->errors[] = $this->error;
3001  $this->db->rollback();
3002  return 0;
3003  }
3004  }
3005  }
3006  }
3007 
3008  if (!$error) {
3009  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3010  $this->db->commit();
3011  return 1;
3012  } else {
3013  $this->db->rollback();
3014  return -1;
3015  }
3016  }
3017 
3026  public function availability($availability_id, $notrigger = 0)
3027  {
3028  global $user;
3029 
3030  if ($this->statut >= self::STATUS_DRAFT)
3031  {
3032  $error = 0;
3033 
3034  $this->db->begin();
3035 
3036  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3037  $sql .= ' SET fk_availability = '.$availability_id;
3038  $sql .= ' WHERE rowid='.$this->id;
3039 
3040  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3041  $resql = $this->db->query($sql);
3042  if (!$resql)
3043  {
3044  $this->errors[] = $this->db->error();
3045  $error++;
3046  }
3047 
3048  if (!$error)
3049  {
3050  $this->oldcopy = clone $this;
3051  $this->availability_id = $availability_id;
3052  }
3053 
3054  if (!$notrigger && empty($error))
3055  {
3056  // Call trigger
3057  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3058  if ($result < 0) $error++;
3059  // End call triggers
3060  }
3061 
3062  if (!$error)
3063  {
3064  $this->db->commit();
3065  return 1;
3066  } else {
3067  foreach ($this->errors as $errmsg)
3068  {
3069  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3070  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3071  }
3072  $this->db->rollback();
3073  return -1 * $error;
3074  }
3075  } else {
3076  $error_str = 'Propal status do not meet requirement '.$this->statut;
3077  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3078  $this->error = $error_str;
3079  $this->errors[] = $this->error;
3080  return -2;
3081  }
3082  }
3083 
3084  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3093  public function demand_reason($demand_reason_id, $notrigger = 0)
3094  {
3095  // phpcs:enable
3096  global $user;
3097 
3098  if ($this->statut >= self::STATUS_DRAFT)
3099  {
3100  $error = 0;
3101 
3102  $this->db->begin();
3103 
3104  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3105  $sql .= ' SET fk_input_reason = '.$demand_reason_id;
3106  $sql .= ' WHERE rowid='.$this->id;
3107 
3108  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3109  $resql = $this->db->query($sql);
3110  if (!$resql)
3111  {
3112  $this->errors[] = $this->db->error();
3113  $error++;
3114  }
3115 
3116  if (!$error)
3117  {
3118  $this->oldcopy = clone $this;
3119  $this->demand_reason_id = $demand_reason_id;
3120  }
3121 
3122  if (!$notrigger && empty($error))
3123  {
3124  // Call trigger
3125  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3126  if ($result < 0) $error++;
3127  // End call triggers
3128  }
3129 
3130  if (!$error)
3131  {
3132  $this->db->commit();
3133  return 1;
3134  } else {
3135  foreach ($this->errors as $errmsg)
3136  {
3137  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3138  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3139  }
3140  $this->db->rollback();
3141  return -1 * $error;
3142  }
3143  } else {
3144  $error_str = 'Propal status do not meet requirement '.$this->statut;
3145  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3146  $this->error = $error_str;
3147  $this->errors[] = $this->error;
3148  return -2;
3149  }
3150  }
3151 
3152 
3159  public function info($id)
3160  {
3161  $sql = "SELECT c.rowid, ";
3162  $sql .= " c.datec, c.date_valid as datev, c.date_cloture as dateo,";
3163  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_cloture";
3164  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3165  $sql .= " WHERE c.rowid = ".((int) $id);
3166 
3167  $result = $this->db->query($sql);
3168 
3169  if ($result)
3170  {
3171  if ($this->db->num_rows($result))
3172  {
3173  $obj = $this->db->fetch_object($result);
3174 
3175  $this->id = $obj->rowid;
3176 
3177  $this->date_creation = $this->db->jdate($obj->datec);
3178  $this->date_validation = $this->db->jdate($obj->datev);
3179  $this->date_cloture = $this->db->jdate($obj->dateo);
3180 
3181  $cuser = new User($this->db);
3182  $cuser->fetch($obj->fk_user_author);
3183  $this->user_creation = $cuser;
3184 
3185  if ($obj->fk_user_valid)
3186  {
3187  $vuser = new User($this->db);
3188  $vuser->fetch($obj->fk_user_valid);
3189  $this->user_validation = $vuser;
3190  }
3191 
3192  if ($obj->fk_user_cloture)
3193  {
3194  $cluser = new User($this->db);
3195  $cluser->fetch($obj->fk_user_cloture);
3196  $this->user_cloture = $cluser;
3197  }
3198  }
3199  $this->db->free($result);
3200  } else {
3201  dol_print_error($this->db);
3202  }
3203  }
3204 
3205 
3212  public function getLibStatut($mode = 0)
3213  {
3214  return $this->LibStatut($this->statut, $mode);
3215  }
3216 
3217  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3225  public function LibStatut($status, $mode = 1)
3226  {
3227  // phpcs:enable
3228  global $conf;
3229 
3230  // Init/load array of translation of status
3231  if (empty($this->labelStatus) || empty($this->labelStatusShort))
3232  {
3233  global $langs;
3234  $langs->load("propal");
3235  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3236  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3237  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3238  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3239  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3240  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3241  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3242  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3243  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3244  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3245  }
3246 
3247  $statusType = '';
3248  if ($status == self::STATUS_DRAFT) $statusType = 'status0';
3249  elseif ($status == self::STATUS_VALIDATED) $statusType = 'status1';
3250  elseif ($status == self::STATUS_SIGNED) $statusType = 'status4';
3251  elseif ($status == self::STATUS_NOTSIGNED) $statusType = 'status9';
3252  elseif ($status == self::STATUS_BILLED) $statusType = 'status6';
3253 
3254  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3255  }
3256 
3257 
3258  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3266  public function load_board($user, $mode)
3267  {
3268  // phpcs:enable
3269  global $conf, $langs;
3270 
3271  $clause = " WHERE";
3272 
3273  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3274  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3275  if (!$user->rights->societe->client->voir && !$user->socid)
3276  {
3277  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3278  $sql .= " WHERE sc.fk_user = ".$user->id;
3279  $clause = " AND";
3280  }
3281  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3282  if ($mode == 'opened') $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3283  if ($mode == 'signed') $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3284  if ($user->socid) $sql .= " AND p.fk_soc = ".$user->socid;
3285 
3286  $resql = $this->db->query($sql);
3287  if ($resql)
3288  {
3289  $langs->load("propal");
3290  $now = dol_now();
3291 
3292  $delay_warning = 0;
3293  $status = 0;
3294  $label = $labelShort = '';
3295  if ($mode == 'opened') {
3296  $delay_warning = $conf->propal->cloture->warning_delay;
3297  $status = self::STATUS_VALIDATED;
3298  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3299  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3300  }
3301  if ($mode == 'signed') {
3302  $delay_warning = $conf->propal->facturation->warning_delay;
3303  $status = self::STATUS_SIGNED;
3304  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3305  $labelShort = $langs->trans("ToBill");
3306  }
3307 
3308  $response = new WorkboardResponse();
3309  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3310  $response->label = $label;
3311  $response->labelShort = $labelShort;
3312  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3313  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3314  $response->img = img_object('', "propal");
3315 
3316  // This assignment in condition is not a bug. It allows walking the results.
3317  while ($obj = $this->db->fetch_object($resql))
3318  {
3319  $response->nbtodo++;
3320  $response->total += $obj->total_ht;
3321 
3322  if ($mode == 'opened')
3323  {
3324  $datelimit = $this->db->jdate($obj->datefin);
3325  if ($datelimit < ($now - $delay_warning))
3326  {
3327  $response->nbtodolate++;
3328  }
3329  }
3330  // TODO Definir regle des propales a facturer en retard
3331  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3332  }
3333 
3334  return $response;
3335  } else {
3336  $this->error = $this->db->error();
3337  return -1;
3338  }
3339  }
3340 
3341 
3349  public function initAsSpecimen()
3350  {
3351  global $conf, $langs;
3352 
3353  // Load array of products prodids
3354  $num_prods = 0;
3355  $prodids = array();
3356  $sql = "SELECT rowid";
3357  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3358  $sql .= " WHERE entity IN (".getEntity('product').")";
3359  $sql .= $this->db->plimit(100);
3360 
3361  $resql = $this->db->query($sql);
3362  if ($resql)
3363  {
3364  $num_prods = $this->db->num_rows($resql);
3365  $i = 0;
3366  while ($i < $num_prods)
3367  {
3368  $i++;
3369  $row = $this->db->fetch_row($resql);
3370  $prodids[$i] = $row[0];
3371  }
3372  }
3373 
3374  // Initialise parametres
3375  $this->id = 0;
3376  $this->ref = 'SPECIMEN';
3377  $this->ref_client = 'NEMICEPS';
3378  $this->specimen = 1;
3379  $this->socid = 1;
3380  $this->date = time();
3381  $this->fin_validite = $this->date + 3600 * 24 * 30;
3382  $this->cond_reglement_id = 1;
3383  $this->cond_reglement_code = 'RECEP';
3384  $this->mode_reglement_id = 7;
3385  $this->mode_reglement_code = 'CHQ';
3386  $this->availability_id = 1;
3387  $this->availability_code = 'AV_NOW';
3388  $this->demand_reason_id = 1;
3389  $this->demand_reason_code = 'SRC_00';
3390  $this->note_public = 'This is a comment (public)';
3391  $this->note_private = 'This is a comment (private)';
3392 
3393  $this->multicurrency_tx = 1;
3394  $this->multicurrency_code = $conf->currency;
3395 
3396  // Lines
3397  $nbp = 5;
3398  $xnbp = 0;
3399  while ($xnbp < $nbp)
3400  {
3401  $line = new PropaleLigne($this->db);
3402  $line->desc = $langs->trans("Description")." ".$xnbp;
3403  $line->qty = 1;
3404  $line->subprice = 100;
3405  $line->price = 100;
3406  $line->tva_tx = 20;
3407  $line->localtax1_tx = 0;
3408  $line->localtax2_tx = 0;
3409  if ($xnbp == 2)
3410  {
3411  $line->total_ht = 50;
3412  $line->total_ttc = 60;
3413  $line->total_tva = 10;
3414  $line->remise_percent = 50;
3415  } else {
3416  $line->total_ht = 100;
3417  $line->total_ttc = 120;
3418  $line->total_tva = 20;
3419  $line->remise_percent = 00;
3420  }
3421 
3422  if ($num_prods > 0)
3423  {
3424  $prodid = mt_rand(1, $num_prods);
3425  $line->fk_product = $prodids[$prodid];
3426  $line->product_ref = 'SPECIMEN';
3427  }
3428 
3429  $this->lines[$xnbp] = $line;
3430 
3431  $this->total_ht += $line->total_ht;
3432  $this->total_tva += $line->total_tva;
3433  $this->total_ttc += $line->total_ttc;
3434 
3435  $xnbp++;
3436  }
3437  }
3438 
3439  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3445  public function load_state_board()
3446  {
3447  // phpcs:enable
3448  global $user;
3449 
3450  $this->nb = array();
3451  $clause = "WHERE";
3452 
3453  $sql = "SELECT count(p.rowid) as nb";
3454  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3455  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3456  if (!$user->rights->societe->client->voir && !$user->socid)
3457  {
3458  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3459  $sql .= " WHERE sc.fk_user = ".$user->id;
3460  $clause = "AND";
3461  }
3462  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3463 
3464  $resql = $this->db->query($sql);
3465  if ($resql)
3466  {
3467  // This assignment in condition is not a bug. It allows walking the results.
3468  while ($obj = $this->db->fetch_object($resql))
3469  {
3470  $this->nb["proposals"] = $obj->nb;
3471  }
3472  $this->db->free($resql);
3473  return 1;
3474  } else {
3475  dol_print_error($this->db);
3476  $this->error = $this->db->error();
3477  return -1;
3478  }
3479  }
3480 
3481 
3489  public function getNextNumRef($soc)
3490  {
3491  global $conf, $langs;
3492  $langs->load("propal");
3493 
3494  $classname = $conf->global->PROPALE_ADDON;
3495 
3496  if (!empty($classname))
3497  {
3498  $mybool = false;
3499 
3500  $file = $classname.".php";
3501 
3502  // Include file with class
3503  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3504  foreach ($dirmodels as $reldir) {
3505  $dir = dol_buildpath($reldir."core/modules/propale/");
3506 
3507  // Load file with numbering class (if found)
3508  $mybool |= @include_once $dir.$file;
3509  }
3510 
3511  if (!$mybool)
3512  {
3513  dol_print_error('', "Failed to include file ".$file);
3514  return '';
3515  }
3516 
3517  $obj = new $classname();
3518  $numref = "";
3519  $numref = $obj->getNextValue($soc, $this);
3520 
3521  if ($numref != "")
3522  {
3523  return $numref;
3524  } else {
3525  $this->error = $obj->error;
3526  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3527  return "";
3528  }
3529  } else {
3530  $langs->load("errors");
3531  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3532  return "";
3533  }
3534  }
3535 
3547  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3548  {
3549  global $langs, $conf, $user;
3550 
3551  if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
3552 
3553  $result = '';
3554  $label = '';
3555  $url = '';
3556 
3557  if ($user->rights->propal->lire)
3558  {
3559  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3560  if (isset($this->statut)) {
3561  $label .= ' '.$this->getLibStatut(5);
3562  }
3563  if (!empty($this->ref)) {
3564  $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3565  }
3566  if (!empty($this->ref_client)) {
3567  $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3568  }
3569  if (!empty($this->total_ht)) {
3570  $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3571  }
3572  if (!empty($this->total_tva)) {
3573  $label .= '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3574  }
3575  if (!empty($this->total_ttc)) {
3576  $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3577  }
3578  if (!empty($this->delivery_date)) {
3579  $label .= '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3580  }
3581 
3582  if ($option == '') {
3583  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3584  } elseif ($option == 'compta') { // deprecated
3585  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3586  } elseif ($option == 'expedition') {
3587  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3588  } elseif ($option == 'document') {
3589  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3590  }
3591 
3592  if ($option != 'nolink')
3593  {
3594  // Add param to save lastsearch_values or not
3595  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3596  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
3597  if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
3598  }
3599  }
3600 
3601  $linkclose = '';
3602  if (empty($notooltip) && $user->rights->propal->lire)
3603  {
3604  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
3605  {
3606  $label = $langs->trans("Proposal");
3607  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3608  }
3609  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
3610  $linkclose .= ' class="classfortooltip"';
3611  }
3612 
3613  $linkstart = '<a href="'.$url.'"';
3614  $linkstart .= $linkclose.'>';
3615  $linkend = '</a>';
3616 
3617  $result .= $linkstart;
3618  if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
3619  if ($withpicto != 2) $result .= $this->ref;
3620  $result .= $linkend;
3621 
3622  if ($addlinktonotes >= 0) {
3623  $txttoshow = '';
3624 
3625  if ($addlinktonotes == 0) {
3626  if (!empty($this->note_private) || !empty($this->note_public)) {
3627  $txttoshow = $langs->trans('ViewPrivateNote');
3628  }
3629  } elseif ($addlinktonotes == 1) {
3630  if (!empty($this->note_private)) {
3631  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3632  }
3633  } elseif ($addlinktonotes == 2) {
3634  if (!empty($this->note_public)) {
3635  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3636  }
3637  } elseif ($addlinktonotes == 3) {
3638  if ($user->socid > 0) {
3639  if (!empty($this->note_public)) {
3640  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3641  }
3642  } else {
3643  if (!empty($this->note_public)) {
3644  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3645  }
3646  if (!empty($this->note_private)) {
3647  if (!empty($txttoshow)) $txttoshow .= '<br><br>';
3648  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3649  }
3650  }
3651  }
3652 
3653  if ($txttoshow) {
3654  $result .= ' <span class="note inline-block">';
3655  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3656  $result .= img_picto('', 'note');
3657  $result .= '</a>';
3658  $result .= '</span>';
3659  }
3660  }
3661 
3662  return $result;
3663  }
3664 
3670  public function getLinesArray()
3671  {
3672  return $this->fetch_lines();
3673  }
3674 
3686  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3687  {
3688  global $conf, $langs;
3689 
3690  $langs->load("propale");
3691  $outputlangs->load("products");
3692 
3693  if (!dol_strlen($modele)) {
3694  $modele = 'azur';
3695 
3696  if ($this->model_pdf) {
3697  $modele = $this->model_pdf;
3698  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3699  $modele = $conf->global->PROPALE_ADDON_PDF;
3700  }
3701  }
3702 
3703  $modelpath = "core/modules/propale/doc/";
3704 
3705  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3706  }
3707 
3716  public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
3717  {
3718  $tables = array(
3719  'propal'
3720  );
3721 
3722  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3723  }
3724 }
3725 
3726 
3731 {
3735  public $element = 'propaldet';
3736 
3740  public $table_element = 'propaldet';
3741 
3742  public $oldline;
3743 
3744  // From llx_propaldet
3745  public $fk_propal;
3746  public $fk_parent_line;
3747  public $desc; // Description ligne
3748  public $fk_product; // Id produit predefini
3759  public $product_type = Product::TYPE_PRODUCT;
3760 
3761  public $qty;
3762 
3763  public $tva_tx;
3764  public $vat_src_code;
3765 
3766  public $subprice;
3767  public $remise_percent;
3768  public $fk_remise_except;
3769 
3770  public $rang = 0;
3771 
3772  public $fk_fournprice;
3773  public $pa_ht;
3774  public $marge_tx;
3775  public $marque_tx;
3776 
3777  public $special_code; // Tag for special lines (exlusive tags)
3778  // 1: frais de port
3779  // 2: ecotaxe
3780  // 3: option line (when qty = 0)
3781 
3782  public $info_bits = 0; // Some other info:
3783  // Bit 0: 0 si TVA normal - 1 si TVA NPR
3784  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
3785 
3786  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
3787  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
3788  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
3789 
3794  public $remise;
3799  public $price;
3800 
3801  // From llx_product
3806  public $ref;
3811  public $product_ref;
3816  public $libelle;
3821  public $label;
3826  public $product_label;
3831  public $product_desc;
3832 
3837  public $product_tobatch;
3838 
3843  public $product_barcode;
3844 
3845  public $localtax1_tx; // Local tax 1
3846  public $localtax2_tx; // Local tax 2
3847  public $localtax1_type; // Local tax 1 type
3848  public $localtax2_type; // Local tax 2 type
3849  public $total_localtax1; // Line total local tax 1
3850  public $total_localtax2; // Line total local tax 2
3851 
3852  public $date_start;
3853  public $date_end;
3854 
3855  public $skip_update_total; // Skip update price total for special lines
3856 
3857  // Multicurrency
3858  public $fk_multicurrency;
3859  public $multicurrency_code;
3860  public $multicurrency_subprice;
3861  public $multicurrency_total_ht;
3862  public $multicurrency_total_tva;
3863  public $multicurrency_total_ttc;
3864 
3870  public function __construct($db)
3871  {
3872  $this->db = $db;
3873  }
3874 
3881  public function fetch($rowid)
3882  {
3883  $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,';
3884  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
3885  $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,';
3886  $sql .= ' pd.fk_unit,';
3887  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
3888  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
3889  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3890  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
3891  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
3892  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
3893  $sql .= ' WHERE pd.rowid = '.$rowid;
3894 
3895  $result = $this->db->query($sql);
3896  if ($result)
3897  {
3898  $objp = $this->db->fetch_object($result);
3899 
3900  if ($objp)
3901  {
3902  $this->id = $objp->rowid;
3903  $this->rowid = $objp->rowid; // deprecated
3904  $this->fk_propal = $objp->fk_propal;
3905  $this->fk_parent_line = $objp->fk_parent_line;
3906  $this->label = $objp->custom_label;
3907  $this->desc = $objp->description;
3908  $this->qty = $objp->qty;
3909  $this->price = $objp->price; // deprecated
3910  $this->subprice = $objp->subprice;
3911  $this->vat_src_code = $objp->vat_src_code;
3912  $this->tva_tx = $objp->tva_tx;
3913  $this->remise = $objp->remise; // deprecated
3914  $this->remise_percent = $objp->remise_percent;
3915  $this->fk_remise_except = $objp->fk_remise_except;
3916  $this->fk_product = $objp->fk_product;
3917  $this->info_bits = $objp->info_bits;
3918 
3919  $this->total_ht = $objp->total_ht;
3920  $this->total_tva = $objp->total_tva;
3921  $this->total_ttc = $objp->total_ttc;
3922 
3923  $this->fk_fournprice = $objp->fk_fournprice;
3924 
3925  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
3926  $this->pa_ht = $marginInfos[0];
3927  $this->marge_tx = $marginInfos[1];
3928  $this->marque_tx = $marginInfos[2];
3929 
3930  $this->special_code = $objp->special_code;
3931  $this->product_type = $objp->product_type;
3932  $this->rang = $objp->rang;
3933 
3934  $this->ref = $objp->product_ref; // deprecated
3935  $this->product_ref = $objp->product_ref;
3936  $this->libelle = $objp->product_label; // deprecated
3937  $this->product_label = $objp->product_label;
3938  $this->product_desc = $objp->product_desc;
3939  $this->fk_unit = $objp->fk_unit;
3940 
3941  $this->date_start = $this->db->jdate($objp->date_start);
3942  $this->date_end = $this->db->jdate($objp->date_end);
3943 
3944  // Multicurrency
3945  $this->fk_multicurrency = $objp->fk_multicurrency;
3946  $this->multicurrency_code = $objp->multicurrency_code;
3947  $this->multicurrency_subprice = $objp->multicurrency_subprice;
3948  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
3949  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
3950  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
3951 
3952  $this->fetch_optionals();
3953 
3954  $this->db->free($result);
3955 
3956  return 1;
3957  } else {
3958  return 0;
3959  }
3960  } else {
3961  return -1;
3962  }
3963  }
3964 
3971  public function insert($notrigger = 0)
3972  {
3973  global $conf, $user;
3974 
3975  $error = 0;
3976 
3977  dol_syslog(get_class($this)."::insert rang=".$this->rang);
3978 
3979  $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'.
3980 
3981  // Clean parameters
3982  if (empty($this->tva_tx)) $this->tva_tx = 0;
3983  if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
3984  if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
3985  if (empty($this->localtax1_type)) $this->localtax1_type = 0;
3986  if (empty($this->localtax2_type)) $this->localtax2_type = 0;
3987  if (empty($this->total_localtax1)) $this->total_localtax1 = 0;
3988  if (empty($this->total_localtax2)) $this->total_localtax2 = 0;
3989  if (empty($this->rang)) $this->rang = 0;
3990  if (empty($this->remise)) $this->remise = 0;
3991  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) $this->remise_percent = 0;
3992  if (empty($this->info_bits)) $this->info_bits = 0;
3993  if (empty($this->special_code)) $this->special_code = 0;
3994  if (empty($this->fk_parent_line)) $this->fk_parent_line = 0;
3995  if (empty($this->fk_fournprice)) $this->fk_fournprice = 0;
3996  if (!is_numeric($this->qty)) $this->qty = 0;
3997  if (empty($this->pa_ht)) $this->pa_ht = 0;
3998  if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice = 0;
3999  if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht = 0;
4000  if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva = 0;
4001  if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc = 0;
4002 
4003  // if buy price not defined, define buyprice as configured in margin admin
4004  if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4005  {
4006  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4007  {
4008  return $result;
4009  } else {
4010  $this->pa_ht = $result;
4011  }
4012  }
4013 
4014  // Check parameters
4015  if ($this->product_type < 0) return -1;
4016 
4017  $this->db->begin();
4018 
4019  // Insert line into database
4020  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4021  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4022  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4023  $sql .= ' subprice, remise_percent, ';
4024  $sql .= ' info_bits, ';
4025  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4026  $sql .= ' fk_unit,';
4027  $sql .= ' date_start, date_end';
4028  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4029  $sql .= " VALUES (".$this->fk_propal.",";
4030  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4031  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4032  $sql .= " '".$this->db->escape($this->desc)."',";
4033  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4034  $sql .= " '".$this->db->escape($this->product_type)."',";
4035  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4036  $sql .= " ".price2num($this->qty).",";
4037  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4038  $sql .= " ".price2num($this->tva_tx).",";
4039  $sql .= " ".price2num($this->localtax1_tx).",";
4040  $sql .= " ".price2num($this->localtax2_tx).",";
4041  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4042  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4043  $sql .= " ".(price2num($this->subprice) !== '' ?price2num($this->subprice) : "null").",";
4044  $sql .= " ".price2num($this->remise_percent).",";
4045  $sql .= " ".(isset($this->info_bits) ? "'".$this->db->escape($this->info_bits)."'" : "null").",";
4046  $sql .= " ".price2num($this->total_ht).",";
4047  $sql .= " ".price2num($this->total_tva).",";
4048  $sql .= " ".price2num($this->total_localtax1).",";
4049  $sql .= " ".price2num($this->total_localtax2).",";
4050  $sql .= " ".price2num($this->total_ttc).",";
4051  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4052  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4053  $sql .= ' '.$this->special_code.',';
4054  $sql .= ' '.$this->rang.',';
4055  $sql .= ' '.(!$this->fk_unit ? 'NULL' : $this->fk_unit).',';
4056  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4057  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4058  $sql .= ", ".($this->fk_multicurrency > 0 ? $this->fk_multicurrency : 'null');
4059  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4060  $sql .= ", ".$this->multicurrency_subprice;
4061  $sql .= ", ".$this->multicurrency_total_ht;
4062  $sql .= ", ".$this->multicurrency_total_tva;
4063  $sql .= ", ".$this->multicurrency_total_ttc;
4064  $sql .= ')';
4065 
4066  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4067  $resql = $this->db->query($sql);
4068  if ($resql)
4069  {
4070  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4071 
4072  if (!$error)
4073  {
4074  $this->id = $this->rowid;
4075  $result = $this->insertExtraFields();
4076  if ($result < 0)
4077  {
4078  $error++;
4079  }
4080  }
4081 
4082  if (!$error && !$notrigger)
4083  {
4084  // Call trigger
4085  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4086  if ($result < 0)
4087  {
4088  $this->db->rollback();
4089  return -1;
4090  }
4091  // End call triggers
4092  }
4093 
4094  $this->db->commit();
4095  return 1;
4096  } else {
4097  $this->error = $this->db->error()." sql=".$sql;
4098  $this->db->rollback();
4099  return -1;
4100  }
4101  }
4102 
4110  public function delete(User $user, $notrigger = 0)
4111  {
4112  global $conf;
4113 
4114  $error = 0;
4115  $this->db->begin();
4116 
4117  $sql = "DELETE FROM ".MAIN_DB_PREFIX."propaldet WHERE rowid = ".$this->rowid;
4118  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4119  if ($this->db->query($sql))
4120  {
4121  // Remove extrafields
4122  if (!$error)
4123  {
4124  $this->id = $this->rowid;
4125  $result = $this->deleteExtraFields();
4126  if ($result < 0)
4127  {
4128  $error++;
4129  dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
4130  }
4131  }
4132 
4133  if (!$error && !$notrigger)
4134  {
4135  // Call trigger
4136  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4137  if ($result < 0)
4138  {
4139  $this->db->rollback();
4140  return -1;
4141  }
4142  }
4143  // End call triggers
4144 
4145  $this->db->commit();
4146 
4147  return 1;
4148  } else {
4149  $this->error = $this->db->error()." sql=".$sql;
4150  $this->db->rollback();
4151  return -1;
4152  }
4153  }
4154 
4161  public function update($notrigger = 0)
4162  {
4163  global $conf, $user;
4164 
4165  $error = 0;
4166 
4167  $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'.
4168 
4169  if (empty($this->id) && !empty($this->rowid)) $this->id = $this->rowid;
4170 
4171  // Clean parameters
4172  if (empty($this->tva_tx)) $this->tva_tx = 0;
4173  if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
4174  if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
4175  if (empty($this->total_localtax1)) $this->total_localtax1 = 0;
4176  if (empty($this->total_localtax2)) $this->total_localtax2 = 0;
4177  if (empty($this->localtax1_type)) $this->localtax1_type = 0;
4178  if (empty($this->localtax2_type)) $this->localtax2_type = 0;
4179  if (empty($this->marque_tx)) $this->marque_tx = 0;
4180  if (empty($this->marge_tx)) $this->marge_tx = 0;
4181  if (empty($this->price)) $this->price = 0; // TODO A virer
4182  if (empty($this->remise)) $this->remise = 0; // TODO A virer
4183  if (empty($this->remise_percent)) $this->remise_percent = 0;
4184  if (empty($this->info_bits)) $this->info_bits = 0;
4185  if (empty($this->special_code)) $this->special_code = 0;
4186  if (empty($this->fk_parent_line)) $this->fk_parent_line = 0;
4187  if (empty($this->fk_fournprice)) $this->fk_fournprice = 0;
4188  if (empty($this->subprice)) $this->subprice = 0;
4189  if (empty($this->pa_ht)) $this->pa_ht = 0;
4190 
4191  // if buy price not defined, define buyprice as configured in margin admin
4192  if ($this->pa_ht == 0 && $pa_ht_isemptystring)
4193  {
4194  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
4195  {
4196  return $result;
4197  } else {
4198  $this->pa_ht = $result;
4199  }
4200  }
4201 
4202  $this->db->begin();
4203 
4204  // Mise a jour ligne en base
4205  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4206  $sql .= " description='".$this->db->escape($this->desc)."'";
4207  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4208  $sql .= ", product_type=".$this->product_type;
4209  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4210  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4211  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4212  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4213  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4214  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4215  $sql .= ", qty='".price2num($this->qty)."'";
4216  $sql .= ", subprice=".price2num($this->subprice)."";
4217  $sql .= ", remise_percent=".price2num($this->remise_percent)."";
4218  $sql .= ", price=".price2num($this->price).""; // TODO A virer
4219  $sql .= ", remise=".price2num($this->remise).""; // TODO A virer
4220  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4221  if (empty($this->skip_update_total))
4222  {
4223  $sql .= ", total_ht=".price2num($this->total_ht)."";
4224  $sql .= ", total_tva=".price2num($this->total_tva)."";
4225  $sql .= ", total_ttc=".price2num($this->total_ttc)."";
4226  $sql .= ", total_localtax1=".price2num($this->total_localtax1)."";
4227  $sql .= ", total_localtax2=".price2num($this->total_localtax2)."";
4228  }
4229  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4230  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4231  if (strlen($this->special_code)) $sql .= ", special_code=".$this->special_code;
4232  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4233  if (!empty($this->rang)) $sql .= ", rang=".$this->rang;
4234  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4235  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4236  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4237 
4238  // Multicurrency
4239  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
4240  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
4241  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
4242  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
4243 
4244  $sql .= " WHERE rowid = ".$this->id;
4245 
4246  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4247  $resql = $this->db->query($sql);
4248  if ($resql)
4249  {
4250  if (!$error)
4251  {
4252  $result = $this->insertExtraFields();
4253  if ($result < 0)
4254  {
4255  $error++;
4256  }
4257  }
4258 
4259  if (!$error && !$notrigger)
4260  {
4261  // Call trigger
4262  $result = $this->call_trigger('LINEPROPAL_UPDATE', $user);
4263  if ($result < 0)
4264  {
4265  $this->db->rollback();
4266  return -1;
4267  }
4268  // End call triggers
4269  }
4270 
4271  $this->db->commit();
4272  return 1;
4273  } else {
4274  $this->error = $this->db->error();
4275  $this->db->rollback();
4276  return -2;
4277  }
4278  }
4279 
4280  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4287  public function update_total()
4288  {
4289  // phpcs:enable
4290  $this->db->begin();
4291 
4292  // Mise a jour ligne en base
4293  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4294  $sql .= " total_ht=".price2num($this->total_ht, 'MT')."";
4295  $sql .= ",total_tva=".price2num($this->total_tva, 'MT')."";
4296  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT')."";
4297  $sql .= " WHERE rowid = ".$this->rowid;
4298 
4299  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4300 
4301  $resql = $this->db->query($sql);
4302  if ($resql)
4303  {
4304  $this->db->commit();
4305  return 1;
4306  } else {
4307  $this->error = $this->db->error();
4308  $this->db->rollback();
4309  return -2;
4310  }
4311  }
4312 }
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname= '')
Make an include_once using default root and alternate root if it fails.
deleteline($lineid)
Delete detail line.
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)
Update a proposal line.
GETPOST($paramname, $check= 'alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
reopen($user, $statut, $note= '', $notrigger=0)
Reopen the commercial proposal.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto= 'UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
set_date_livraison($user, $delivery_date, $notrigger=0)
Set delivery date.
getInvoiceArrayList()
Returns an array with the numbers of related invoices.
set_ref_client($user, $ref_client, $notrigger=0)
Set customer reference number.
fetch($rowid)
Retrieve the propal line object.
if(!empty($arrayfields['u.datec']['checked'])) print_liste_field_titre("DateCreationShort"u if(!empty($arrayfields['u.tms']['checked'])) print_liste_field_titre("DateModificationShort"u if(!empty($arrayfields['u.statut']['checked'])) print_liste_field_titre("Status"u statut
Definition: list.php:632
getMarginInfos($pvht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $paht)
Return an array with margins information of a line.
update($notrigger=0)
Update propal line object into DB.
setDraft($user, $notrigger=0)
Set draft status.
static getIdAndTxFromCode($db, $code, $date_document= '')
Get id and rate of currency from code.
InvoiceArrayList($id)
Returns an array with id and ref of related invoices.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
</td >< tdcolspan="3">< spanclass="opacitymedium"></span ></td ></tr >< trclass="liste_total"> CREANCES DETTES< tdcolspan="3"class="right"></td >< tdcolspan="3"class="right"></td ></tr > CREANCES DETTES RECETTES DEPENSES trips CREANCES DETTES Y m expensereport p date_valid Y m expensereport pe datep $db idate($date_start)."' AND $column < p rowid
Class to manage products or services.
dol_now($mode= 'auto')
Return date for now.
line_order($renum=false, $rowidorder= 'ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
dol_delete_preview($object)
Delete all preview files linked to object instance.
Definition: files.lib.php:1335
delete_linked_contact($source= '', $code= '')
Delete all links between an object $this and all its contacts.
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
Class to manage Dolibarr users.
Definition: user.class.php:44
Class to manage Dolibarr database access.
set_echeance($user, $date_fin_validite, $notrigger=0)
Define end validity date.
set_availability($user, $id, $notrigger=0)
Set delivery.
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.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
getLinesArray()
Retrieve an array of proposal lines.
const TYPE_PRODUCT
Regular product.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Fonction qui renvoie si tva doit etre tva percue recuperable.
insert($notrigger=0)
Insert object line propal in database.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this-&gt;socid or $this-&gt;fk_soc, into this-&gt;thirdparty.
update(User $user, $notrigger=0)
Update database.
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...
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, from a $thirdparty_buyer to a $thirdparty_seller Note: This function applies same rules than get_default_tva.
valid($user, $notrigger=0)
Set status to validated.
$conf db
API class for accounts.
Definition: inc.php:54
add_product($idproduct, $qty, $remise_percent=0)
Add line into array -&gt;lines $this-&gt;thirdparty should be loaded.
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...
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
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)
Add a proposal line into database (linked to product/service or not) The parameters are already suppo...
insertExtraFields($trigger= '', $userused=null)
Add/Update all extra fields values for the current object.
create($user, $notrigger=0)
Create commercial proposal into database this-&gt;ref can be set or empty.
set_demand_reason($user, $id, $notrigger=0)
Set source of demand.
const STATUS_NOTSIGNED
Not signed quote.
$table_ref_field
{}
Class to manage third parties objects (customers, suppliers, prospects...)
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
createFromClone(User $user, $socid=0, $forceentity=null)
Load an object from its id and create a new one in database.
dol_strlen($string, $stringencoding= 'UTF-8')
Make a strlen call.
const STATUS_BILLED
Billed or processed quote.
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
const STATUS_SIGNED
Signed quote.
deleteEcmFiles($mode=0)
Delete related files of object in database.
static commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static getIdFromCode($db, $code)
Get id of currency from code.
LibStatut($status, $mode=1)
Return label of a status (draft, validated, ...)
img_picto($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt= '', $morecss= '', $marginleftonlyshort=2)
Show picto whatever it&#39;s its name (generic function)
set_remise_percent($user, $remise, $notrigger=0)
Set an overall discount on the proposal.
load_state_board()
Charge indicateurs this-&gt;nb de tableau de bord.
demand_reason($demand_reason_id, $notrigger=0)
Change source demand.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
static replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories) ...
Definition: files.lib.php:1286
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). ...
Class to manage commercial proposal lines.
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1)
Remove a file or several files with a mask.
Definition: files.lib.php:1144
img_object($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
deleteExtraFields()
Delete all extra fields values for the current object.
set_remise_absolue($user, $remise, $notrigger=0)
Set an absolute overall discount on the proposal.
Class to manage translations.
dol_sanitizeFileName($str, $newstr= '_', $unaccent=1)
Clean a string to use it as a file name.
dol_dir_list($path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0)
Scan a directory and return a list of files/directories.
Definition: files.lib.php:60
set_date($user, $date, $notrigger=0)
Define proposal date.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this-&gt;array_options This method is in most cases call...
deleteObjectLinked($sourceid=null, $sourcetype= '', $targetid=null, $targettype= '', $rowid= '')
Delete all links between an object $this.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0)
Get buy price to use for margin calculation.
fetch_lines($only_product=0, $loadalsotranslation=0)
Load array lines.
print $_SERVER["PHP_SELF"]
Edit parameters.
const STATUS_DRAFT
Draft status.
__construct($db, $socid=0, $propalid=0)
Constructor.
setDeliveryDate($user, $delivery_date, $notrigger=0)
Set delivery date.
trait CommonIncoterm
Superclass for incoterm classes.
print
Draft customers invoices.
Definition: index.php:89
dol_print_date($time, $format= '', $tzoutput= 'auto', $outputlangs= '', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
call_trigger($triggerName, $user)
Call trigger based on this instance.
fetch($rowid, $ref= '', $ref_ext= '')
Load a proposal from database.
__construct($db)
Class line Contructor.
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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) Si ...
Class to manage absolute discounts.
dolGetStatus($statusLabel= '', $statusLabelShort= '', $html= '', $statusType= 'status0', $displayMode=0, $url= '', $params=array())
Output the badge of a status.
const STATUS_VALIDATED
Validated status.
getNextNumRef($soc)
Returns the reference to the following non used Proposal used depending on the active numbering modul...
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
initAsSpecimen()
Initialise an instance with random values.
insert_discount($idremise)
Adding line of fixed discount in the proposal in DB.
add_object_linked($origin=null, $origin_id=null)
Add objects linked in llx_element_element.
getLibStatut($mode=0)
Return label of status of proposal (draft, validated, ...)
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)...
update_total()
Update DB line fields total_xxx Used by migration.
availability($availability_id, $notrigger=0)
Change the delivery time.
if(!defined('CSRFCHECK_WITH_TOKEN')) define('CSRFCHECK_WITH_TOKEN'
Draft customers invoices.
classifyBilled(User $user, $notrigger=0)
Class invoiced the Propal.
info($id)
Object Proposal Information.
Parent class of all other business classes (invoices, contracts, proposals, orders, ...)
if(!empty($search_group)) natural_search(array("g.nom"g note
Definition: list.php:122
load_board($user, $mode)
Load indicators for dashboard (this-&gt;nbtodo and this-&gt;nbtodolate)
Class to manage proposals.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $keepmoretags= '', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields...
getNomUrl($withpicto=0, $option= '', $get_params= '', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=-1)
Return clicable link of object (with eventually picto)
cloture($user, $status, $note="", $notrigger=0)
Close the commercial proposal.