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