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)."', date_signature='".$this->db->idate($date_signature)."', fk_user_signature=".$fk_user_signature;
2701  $sql .= " WHERE rowid = ".((int) $this->id);
2702 
2703  $resql = $this->db->query($sql);
2704  if ($resql) {
2705  // Status self::STATUS_REFUSED by default
2706  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2707  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2708 
2709  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2710  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2711  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2712 
2713  // The connected company is classified as a client
2714  $soc=new Societe($this->db);
2715  $soc->id = $this->socid;
2716  $result = $soc->set_as_client();
2717 
2718  if ($result < 0) {
2719  $this->error=$this->db->lasterror();
2720  $this->db->rollback();
2721  return -2;
2722  }
2723  }
2724 
2725  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2726  // Define output language
2727  $outputlangs = $langs;
2728  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2729  $outputlangs = new Translate("", $conf);
2730  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2731  $outputlangs->setDefaultLang($newlang);
2732  }
2733 
2734  // PDF
2735  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2736  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2737  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2738 
2739  //$ret=$object->fetch($id); // Reload to get new records
2740  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2741  }
2742 
2743  if (!$error) {
2744  $this->oldcopy= clone $this;
2745  $this->statut = $status;
2746  $this->status = $status;
2747  $this->date_signature = $date_signature;
2748  $this->note_private = $newprivatenote;
2749  }
2750 
2751  if (!$notrigger && empty($error)) {
2752  // Call trigger
2753  $result=$this->call_trigger($trigger_name, $user);
2754  if ($result < 0) {
2755  $error++;
2756  }
2757  // End call triggers
2758  }
2759 
2760  if (!$error ) {
2761  $this->db->commit();
2762  return 1;
2763  } else {
2764  $this->statut = $this->oldcopy->statut;
2765  $this->status = $this->oldcopy->statut;
2766  $this->date_signature = $this->oldcopy->date_signature;
2767  $this->note_private = $this->oldcopy->note_private;
2768 
2769  $this->db->rollback();
2770  return -1;
2771  }
2772  } else {
2773  $this->error = $this->db->lasterror();
2774  $this->db->rollback();
2775  return -1;
2776  }
2777  }
2778 
2787  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2788  {
2789  global $conf, $langs;
2790 
2791  $error = 0;
2792 
2793  $now = dol_now();
2794  $num = 0;
2795 
2796  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2797 
2798  $this->db->begin();
2799 
2800  $newprivatenote = dol_concatdesc($this->note_private, $note);
2801 
2802  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2803  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2804  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2805 
2806  dol_syslog(__METHOD__, LOG_DEBUG);
2807  $resql = $this->db->query($sql);
2808  if (!$resql) {
2809  $this->errors[] = $this->db->error();
2810  $error++;
2811  } else {
2812  $num = $this->db->affected_rows($resql);
2813  }
2814 
2815  if (!$error) {
2816  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2817 
2818  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2819  // Define output language
2820  $outputlangs = $langs;
2821  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2822  $outputlangs = new Translate("", $conf);
2823  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2824  $outputlangs->setDefaultLang($newlang);
2825  }
2826 
2827  // PDF
2828  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2829  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2830  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2831 
2832  //$ret=$object->fetch($id); // Reload to get new records
2833  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2834  }
2835 
2836  $this->oldcopy = clone $this;
2837  $this->statut = self::STATUS_BILLED;
2838  $this->date_cloture = $now;
2839  $this->note_private = $newprivatenote;
2840  }
2841 
2842  if (!$notrigger && empty($error)) {
2843  // Call trigger
2844  $result = $this->call_trigger($triggerName, $user);
2845  if ($result < 0) {
2846  $error++;
2847  }
2848  // End call triggers
2849  }
2850 
2851  if (!$error) {
2852  $this->db->commit();
2853  return $num;
2854  } else {
2855  foreach ($this->errors as $errmsg) {
2856  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2857  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2858  }
2859  $this->db->rollback();
2860  return -1 * $error;
2861  }
2862  }
2863 
2864  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2872  public function setDraft($user, $notrigger = 0)
2873  {
2874  // phpcs:enable
2875  $error = 0;
2876 
2877  // Protection
2878  if ($this->statut <= self::STATUS_DRAFT) {
2879  return 0;
2880  }
2881 
2882  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2883 
2884  $this->db->begin();
2885 
2886  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2887  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2888  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2889  $sql .= " WHERE rowid = ".((int) $this->id);
2890 
2891  $resql = $this->db->query($sql);
2892  if (!$resql) {
2893  $this->errors[] = $this->db->error();
2894  $error++;
2895  }
2896 
2897  if (!$error) {
2898  $this->oldcopy = clone $this;
2899  }
2900 
2901  if (!$notrigger && empty($error)) {
2902  // Call trigger
2903  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2904  if ($result < 0) {
2905  $error++;
2906  }
2907  // End call triggers
2908  }
2909 
2910  if (!$error) {
2911  $this->statut = self::STATUS_DRAFT;
2912  $this->brouillon = 1;
2913 
2914  $this->db->commit();
2915  return 1;
2916  } else {
2917  foreach ($this->errors as $errmsg) {
2918  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2919  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2920  }
2921  $this->db->rollback();
2922  return -1 * $error;
2923  }
2924  }
2925 
2926 
2927  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2941  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2942  {
2943  // phpcs:enable
2944  global $user;
2945 
2946  $ga = array();
2947 
2948  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2949  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2950  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2951  if (empty($user->rights->societe->client->voir) && !$socid) {
2952  $sql .= ", sc.fk_soc, sc.fk_user";
2953  }
2954  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2955  if (empty($user->rights->societe->client->voir) && !$socid) {
2956  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2957  }
2958  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2959  $sql .= " AND p.fk_soc = s.rowid";
2960  $sql .= " AND p.fk_statut = c.id";
2961  if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2962  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2963  }
2964  if ($socid) {
2965  $sql .= " AND s.rowid = ".((int) $socid);
2966  }
2967  if ($draft) {
2968  $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2969  }
2970  if ($notcurrentuser > 0) {
2971  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2972  }
2973  $sql .= $this->db->order($sortfield, $sortorder);
2974  $sql .= $this->db->plimit($limit, $offset);
2975 
2976  $result = $this->db->query($sql);
2977  if ($result) {
2978  $num = $this->db->num_rows($result);
2979  if ($num) {
2980  $i = 0;
2981  while ($i < $num) {
2982  $obj = $this->db->fetch_object($result);
2983 
2984  if ($shortlist == 1) {
2985  $ga[$obj->propalid] = $obj->ref;
2986  } elseif ($shortlist == 2) {
2987  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2988  } else {
2989  $ga[$i]['id'] = $obj->propalid;
2990  $ga[$i]['ref'] = $obj->ref;
2991  $ga[$i]['name'] = $obj->name;
2992  }
2993 
2994  $i++;
2995  }
2996  }
2997  return $ga;
2998  } else {
2999  dol_print_error($this->db);
3000  return -1;
3001  }
3002  }
3003 
3009  public function getInvoiceArrayList()
3010  {
3011  return $this->InvoiceArrayList($this->id);
3012  }
3013 
3014  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3021  public function InvoiceArrayList($id)
3022  {
3023  // phpcs:enable
3024  $ga = array();
3025  $linkedInvoices = array();
3026 
3027  $this->fetchObjectLinked($id, $this->element);
3028  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3029  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
3030  // On parcourt donc une liste d'objets en tant qu'objet unique
3031  foreach ($objectid as $key => $object) {
3032  // Cas des factures liees directement
3033  if ($objecttype == 'facture') {
3034  $linkedInvoices[] = $object;
3035  } else {
3036  // Cas des factures liees par un autre objet (ex: commande)
3037  $this->fetchObjectLinked($object, $objecttype);
3038  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3039  foreach ($subobjectid as $subkey => $subobject) {
3040  if ($subobjecttype == 'facture') {
3041  $linkedInvoices[] = $subobject;
3042  }
3043  }
3044  }
3045  }
3046  }
3047  }
3048 
3049  if (count($linkedInvoices) > 0) {
3050  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3051  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3052  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3053 
3054  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3055  $resql = $this->db->query($sql);
3056 
3057  if ($resql) {
3058  $tab_sqlobj = array();
3059  $nump = $this->db->num_rows($resql);
3060  for ($i = 0; $i < $nump; $i++) {
3061  $sqlobj = $this->db->fetch_object($resql);
3062  $tab_sqlobj[] = $sqlobj;
3063  }
3064  $this->db->free($resql);
3065 
3066  $nump = count($tab_sqlobj);
3067 
3068  if ($nump) {
3069  $i = 0;
3070  while ($i < $nump) {
3071  $obj = array_shift($tab_sqlobj);
3072 
3073  $ga[$i] = $obj;
3074 
3075  $i++;
3076  }
3077  }
3078  return $ga;
3079  } else {
3080  return -1;
3081  }
3082  } else {
3083  return $ga;
3084  }
3085  }
3086 
3094  public function delete($user, $notrigger = 0)
3095  {
3096  global $conf;
3097  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3098 
3099  $error = 0;
3100 
3101  $this->db->begin();
3102 
3103  if (!$notrigger) {
3104  // Call trigger
3105  $result = $this->call_trigger('PROPAL_DELETE', $user);
3106  if ($result < 0) {
3107  $error++;
3108  }
3109  // End call triggers
3110  }
3111 
3112  // Delete extrafields of lines and lines
3113  if (!$error && !empty($this->table_element_line)) {
3114  $tabletodelete = $this->table_element_line;
3115  $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).")";
3116  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3117  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3118  $error++;
3119  $this->error = $this->db->lasterror();
3120  $this->errors[] = $this->error;
3121  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3122  }
3123  }
3124 
3125  if (!$error) {
3126  // Delete linked object
3127  $res = $this->deleteObjectLinked();
3128  if ($res < 0) {
3129  $error++;
3130  }
3131  }
3132 
3133  if (!$error) {
3134  // Delete linked contacts
3135  $res = $this->delete_linked_contact();
3136  if ($res < 0) {
3137  $error++;
3138  }
3139  }
3140 
3141  // Removed extrafields of object
3142  if (!$error) {
3143  $result = $this->deleteExtraFields();
3144  if ($result < 0) {
3145  $error++;
3146  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3147  }
3148  }
3149 
3150  // Delete main record
3151  if (!$error) {
3152  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3153  $res = $this->db->query($sql);
3154  if (!$res) {
3155  $error++;
3156  $this->error = $this->db->lasterror();
3157  $this->errors[] = $this->error;
3158  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3159  }
3160  }
3161 
3162  // Delete record into ECM index and physically
3163  if (!$error) {
3164  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3165  if (!$res) {
3166  $error++;
3167  }
3168  }
3169 
3170  if (!$error) {
3171  // We remove directory
3172  $ref = dol_sanitizeFileName($this->ref);
3173  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3174  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3175  $file = $dir."/".$ref.".pdf";
3176  if (file_exists($file)) {
3177  dol_delete_preview($this);
3178 
3179  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3180  $this->error = 'ErrorFailToDeleteFile';
3181  $this->errors[] = $this->error;
3182  $this->db->rollback();
3183  return 0;
3184  }
3185  }
3186  if (file_exists($dir)) {
3187  $res = @dol_delete_dir_recursive($dir);
3188  if (!$res) {
3189  $this->error = 'ErrorFailToDeleteDir';
3190  $this->errors[] = $this->error;
3191  $this->db->rollback();
3192  return 0;
3193  }
3194  }
3195  }
3196  }
3197 
3198  if (!$error) {
3199  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3200  $this->db->commit();
3201  return 1;
3202  } else {
3203  $this->db->rollback();
3204  return -1;
3205  }
3206  }
3207 
3216  public function availability($availability_id, $notrigger = 0)
3217  {
3218  global $user;
3219 
3220  if ($this->statut >= self::STATUS_DRAFT) {
3221  $error = 0;
3222 
3223  $this->db->begin();
3224 
3225  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3226  $sql .= ' SET fk_availability = '.((int) $availability_id);
3227  $sql .= ' WHERE rowid='.((int) $this->id);
3228 
3229  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3230  $resql = $this->db->query($sql);
3231  if (!$resql) {
3232  $this->errors[] = $this->db->error();
3233  $error++;
3234  }
3235 
3236  if (!$error) {
3237  $this->oldcopy = clone $this;
3238  $this->availability_id = $availability_id;
3239  }
3240 
3241  if (!$notrigger && empty($error)) {
3242  // Call trigger
3243  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3244  if ($result < 0) {
3245  $error++;
3246  }
3247  // End call triggers
3248  }
3249 
3250  if (!$error) {
3251  $this->db->commit();
3252  return 1;
3253  } else {
3254  foreach ($this->errors as $errmsg) {
3255  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3256  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3257  }
3258  $this->db->rollback();
3259  return -1 * $error;
3260  }
3261  } else {
3262  $error_str = 'Propal status do not meet requirement '.$this->statut;
3263  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3264  $this->error = $error_str;
3265  $this->errors[] = $this->error;
3266  return -2;
3267  }
3268  }
3269 
3270  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3279  public function demand_reason($demand_reason_id, $notrigger = 0)
3280  {
3281  // phpcs:enable
3282  global $user;
3283 
3284  if ($this->statut >= self::STATUS_DRAFT) {
3285  $error = 0;
3286 
3287  $this->db->begin();
3288 
3289  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3290  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3291  $sql .= ' WHERE rowid='.((int) $this->id);
3292 
3293  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3294  $resql = $this->db->query($sql);
3295  if (!$resql) {
3296  $this->errors[] = $this->db->error();
3297  $error++;
3298  }
3299 
3300  if (!$error) {
3301  $this->oldcopy = clone $this;
3302  $this->demand_reason_id = $demand_reason_id;
3303  }
3304 
3305  if (!$notrigger && empty($error)) {
3306  // Call trigger
3307  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3308  if ($result < 0) {
3309  $error++;
3310  }
3311  // End call triggers
3312  }
3313 
3314  if (!$error) {
3315  $this->db->commit();
3316  return 1;
3317  } else {
3318  foreach ($this->errors as $errmsg) {
3319  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3320  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3321  }
3322  $this->db->rollback();
3323  return -1 * $error;
3324  }
3325  } else {
3326  $error_str = 'Propal status do not meet requirement '.$this->statut;
3327  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3328  $this->error = $error_str;
3329  $this->errors[] = $this->error;
3330  return -2;
3331  }
3332  }
3333 
3334 
3341  public function info($id)
3342  {
3343  $sql = "SELECT c.rowid, ";
3344  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3345  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3346  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3347  $sql .= " WHERE c.rowid = ".((int) $id);
3348 
3349  $result = $this->db->query($sql);
3350 
3351  if ($result) {
3352  if ($this->db->num_rows($result)) {
3353  $obj = $this->db->fetch_object($result);
3354 
3355  $this->id = $obj->rowid;
3356 
3357  $this->date_creation = $this->db->jdate($obj->datec);
3358  $this->date_validation = $this->db->jdate($obj->datev);
3359  $this->date_signature = $this->db->jdate($obj->date_signature);
3360  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3361 
3362  $cuser = new User($this->db);
3363  $cuser->fetch($obj->fk_user_author);
3364  $this->user_creation = $cuser;
3365 
3366  if ($obj->fk_user_valid) {
3367  $vuser = new User($this->db);
3368  $vuser->fetch($obj->fk_user_valid);
3369  $this->user_validation = $vuser;
3370  }
3371 
3372  if ($obj->fk_user_signature) {
3373  $user_signature = new User($this->db);
3374  $user_signature->fetch($obj->fk_user_signature);
3375  $this->user_signature = $user_signature;
3376  }
3377 
3378  if ($obj->fk_user_cloture) {
3379  $cluser = new User($this->db);
3380  $cluser->fetch($obj->fk_user_cloture);
3381  $this->user_cloture = $cluser;
3382  }
3383  }
3384  $this->db->free($result);
3385  } else {
3386  dol_print_error($this->db);
3387  }
3388  }
3389 
3390 
3397  public function getLibStatut($mode = 0)
3398  {
3399  return $this->LibStatut($this->statut, $mode);
3400  }
3401 
3402  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3410  public function LibStatut($status, $mode = 1)
3411  {
3412  // phpcs:enable
3413  global $conf, $hookmanager;
3414 
3415  // Init/load array of translation of status
3416  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3417  global $langs;
3418  $langs->load("propal");
3419  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3420  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3421  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3422  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3423  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3424  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3425  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3426  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3427  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3428  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3429  }
3430 
3431  $statusType = '';
3432  if ($status == self::STATUS_DRAFT) {
3433  $statusType = 'status0';
3434  } elseif ($status == self::STATUS_VALIDATED) {
3435  $statusType = 'status1';
3436  } elseif ($status == self::STATUS_SIGNED) {
3437  $statusType = 'status4';
3438  } elseif ($status == self::STATUS_NOTSIGNED) {
3439  $statusType = 'status9';
3440  } elseif ($status == self::STATUS_BILLED) {
3441  $statusType = 'status6';
3442  }
3443 
3444 
3445  $parameters = array('status' => $status, 'mode' => $mode);
3446  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3447 
3448  if ($reshook > 0) {
3449  return $hookmanager->resPrint;
3450  }
3451 
3452  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3453  }
3454 
3455 
3456  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3464  public function load_board($user, $mode)
3465  {
3466  // phpcs:enable
3467  global $conf, $langs;
3468 
3469  $clause = " WHERE";
3470 
3471  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3472  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3473  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3474  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3475  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3476  $clause = " AND";
3477  }
3478  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3479  if ($mode == 'opened') {
3480  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3481  }
3482  if ($mode == 'signed') {
3483  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3484  }
3485  if ($user->socid) {
3486  $sql .= " AND p.fk_soc = ".((int) $user->socid);
3487  }
3488 
3489  $resql = $this->db->query($sql);
3490  if ($resql) {
3491  $langs->load("propal");
3492  $now = dol_now();
3493 
3494  $delay_warning = 0;
3495  $status = 0;
3496  $label = $labelShort = '';
3497  if ($mode == 'opened') {
3498  $delay_warning = $conf->propal->cloture->warning_delay;
3499  $status = self::STATUS_VALIDATED;
3500  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3501  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3502  }
3503  if ($mode == 'signed') {
3504  $delay_warning = $conf->propal->facturation->warning_delay;
3505  $status = self::STATUS_SIGNED;
3506  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3507  $labelShort = $langs->trans("ToBill");
3508  }
3509 
3510  $response = new WorkboardResponse();
3511  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3512  $response->label = $label;
3513  $response->labelShort = $labelShort;
3514  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3515  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3516  $response->img = img_object('', "propal");
3517 
3518  // This assignment in condition is not a bug. It allows walking the results.
3519  while ($obj = $this->db->fetch_object($resql)) {
3520  $response->nbtodo++;
3521  $response->total += $obj->total_ht;
3522 
3523  if ($mode == 'opened') {
3524  $datelimit = $this->db->jdate($obj->datefin);
3525  if ($datelimit < ($now - $delay_warning)) {
3526  $response->nbtodolate++;
3527  }
3528  }
3529  // TODO Definir regle des propales a facturer en retard
3530  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3531  }
3532 
3533  return $response;
3534  } else {
3535  $this->error = $this->db->error();
3536  return -1;
3537  }
3538  }
3539 
3540 
3548  public function initAsSpecimen()
3549  {
3550  global $conf, $langs;
3551 
3552  // Load array of products prodids
3553  $num_prods = 0;
3554  $prodids = array();
3555  $sql = "SELECT rowid";
3556  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3557  $sql .= " WHERE entity IN (".getEntity('product').")";
3558  $sql .= $this->db->plimit(100);
3559 
3560  $resql = $this->db->query($sql);
3561  if ($resql) {
3562  $num_prods = $this->db->num_rows($resql);
3563  $i = 0;
3564  while ($i < $num_prods) {
3565  $i++;
3566  $row = $this->db->fetch_row($resql);
3567  $prodids[$i] = $row[0];
3568  }
3569  }
3570 
3571  // Initialise parametres
3572  $this->id = 0;
3573  $this->ref = 'SPECIMEN';
3574  $this->ref_client = 'NEMICEPS';
3575  $this->specimen = 1;
3576  $this->socid = 1;
3577  $this->date = time();
3578  $this->fin_validite = $this->date + 3600 * 24 * 30;
3579  $this->cond_reglement_id = 1;
3580  $this->cond_reglement_code = 'RECEP';
3581  $this->mode_reglement_id = 7;
3582  $this->mode_reglement_code = 'CHQ';
3583  $this->availability_id = 1;
3584  $this->availability_code = 'AV_NOW';
3585  $this->demand_reason_id = 1;
3586  $this->demand_reason_code = 'SRC_00';
3587  $this->note_public = 'This is a comment (public)';
3588  $this->note_private = 'This is a comment (private)';
3589 
3590  $this->multicurrency_tx = 1;
3591  $this->multicurrency_code = $conf->currency;
3592 
3593  // Lines
3594  $nbp = 5;
3595  $xnbp = 0;
3596  while ($xnbp < $nbp) {
3597  $line = new PropaleLigne($this->db);
3598  $line->desc = $langs->trans("Description")." ".$xnbp;
3599  $line->qty = 1;
3600  $line->subprice = 100;
3601  $line->price = 100;
3602  $line->tva_tx = 20;
3603  $line->localtax1_tx = 0;
3604  $line->localtax2_tx = 0;
3605  if ($xnbp == 2) {
3606  $line->total_ht = 50;
3607  $line->total_ttc = 60;
3608  $line->total_tva = 10;
3609  $line->remise_percent = 50;
3610  } else {
3611  $line->total_ht = 100;
3612  $line->total_ttc = 120;
3613  $line->total_tva = 20;
3614  $line->remise_percent = 00;
3615  }
3616 
3617  if ($num_prods > 0) {
3618  $prodid = mt_rand(1, $num_prods);
3619  $line->fk_product = $prodids[$prodid];
3620  $line->product_ref = 'SPECIMEN';
3621  }
3622 
3623  $this->lines[$xnbp] = $line;
3624 
3625  $this->total_ht += $line->total_ht;
3626  $this->total_tva += $line->total_tva;
3627  $this->total_ttc += $line->total_ttc;
3628 
3629  $xnbp++;
3630  }
3631  }
3632 
3633  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3639  public function load_state_board()
3640  {
3641  // phpcs:enable
3642  global $user;
3643 
3644  $this->nb = array();
3645  $clause = "WHERE";
3646 
3647  $sql = "SELECT count(p.rowid) as nb";
3648  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3649  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3650  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3651  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3652  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3653  $clause = "AND";
3654  }
3655  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3656 
3657  $resql = $this->db->query($sql);
3658  if ($resql) {
3659  // This assignment in condition is not a bug. It allows walking the results.
3660  while ($obj = $this->db->fetch_object($resql)) {
3661  $this->nb["proposals"] = $obj->nb;
3662  }
3663  $this->db->free($resql);
3664  return 1;
3665  } else {
3666  dol_print_error($this->db);
3667  $this->error = $this->db->error();
3668  return -1;
3669  }
3670  }
3671 
3672 
3680  public function getNextNumRef($soc)
3681  {
3682  global $conf, $langs;
3683  $langs->load("propal");
3684 
3685  $classname = $conf->global->PROPALE_ADDON;
3686 
3687  if (!empty($classname)) {
3688  $mybool = false;
3689 
3690  $file = $classname.".php";
3691 
3692  // Include file with class
3693  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3694  foreach ($dirmodels as $reldir) {
3695  $dir = dol_buildpath($reldir."core/modules/propale/");
3696 
3697  // Load file with numbering class (if found)
3698  $mybool |= @include_once $dir.$file;
3699  }
3700 
3701  if (!$mybool) {
3702  dol_print_error('', "Failed to include file ".$file);
3703  return '';
3704  }
3705 
3706  $obj = new $classname();
3707  $numref = "";
3708  $numref = $obj->getNextValue($soc, $this);
3709 
3710  if ($numref != "") {
3711  return $numref;
3712  } else {
3713  $this->error = $obj->error;
3714  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3715  return "";
3716  }
3717  } else {
3718  $langs->load("errors");
3719  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3720  return "";
3721  }
3722  }
3723 
3730  public function getTooltipContentArray($params)
3731  {
3732  global $conf, $langs, $user;
3733 
3734  $langs->load('propal');
3735  $datas = [];
3736  $nofetch = !empty($params['nofetch']);
3737 
3738  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3739  return ['optimize' => $langs->trans("Proposal")];
3740  }
3741  if ($user->hasRight('propal', 'lire')) {
3742  $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3743  if (isset($this->statut)) {
3744  $datas['status'] = ' '.$this->getLibStatut(5);
3745  }
3746  if (!empty($this->ref)) {
3747  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3748  }
3749  if (!$nofetch) {
3750  $langs->load('companies');
3751  if (empty($this->thirdparty)) {
3752  $this->fetch_thirdparty();
3753  }
3754  $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3755  }
3756  if (!empty($this->ref_client)) {
3757  $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3758  }
3759  if (!$nofetch) {
3760  $langs->load('project');
3761  if (empty($this->project)) {
3762  $res = $this->fetch_project();
3763  if ($res > 0) {
3764  $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3765  }
3766  }
3767  }
3768  if (!empty($this->total_ht)) {
3769  $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3770  }
3771  if (!empty($this->total_tva)) {
3772  $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3773  }
3774  if (!empty($this->total_ttc)) {
3775  $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3776  }
3777  if (!empty($this->date)) {
3778  $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3779  }
3780  if (!empty($this->delivery_date)) {
3781  $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3782  }
3783  }
3784 
3785  return $datas;
3786  }
3787 
3799  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3800  {
3801  global $langs, $conf, $user, $hookmanager;
3802 
3803  if (!empty($conf->dol_no_mouse_hover)) {
3804  $notooltip = 1; // Force disable tooltips
3805  }
3806 
3807  $result = '';
3808  $params = [
3809  'id' => $this->id,
3810  'objecttype' => $this->element,
3811  'option' => $option,
3812  'nofetch' => 1,
3813  ];
3814  $classfortooltip = 'classfortooltip';
3815  $dataparams = '';
3816  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3817  $classfortooltip = 'classforajaxtooltip';
3818  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3819  $label = '';
3820  } else {
3821  $label = implode($this->getTooltipContentArray($params));
3822  }
3823 
3824  $url = '';
3825  if ($user->hasRight('propal', 'lire')) {
3826  if ($option == '') {
3827  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3828  } elseif ($option == 'compta') { // deprecated
3829  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3830  } elseif ($option == 'expedition') {
3831  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3832  } elseif ($option == 'document') {
3833  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3834  }
3835 
3836  if ($option != 'nolink') {
3837  // Add param to save lastsearch_values or not
3838  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3839  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3840  $add_save_lastsearch_values = 1;
3841  }
3842  if ($add_save_lastsearch_values) {
3843  $url .= '&save_lastsearch_values=1';
3844  }
3845  }
3846  }
3847 
3848  $linkclose = '';
3849  if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3850  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3851  $label = $langs->trans("Proposal");
3852  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3853  }
3854  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
3855  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3856  }
3857 
3858  $linkstart = '<a href="'.$url.'"';
3859  $linkstart .= $linkclose.'>';
3860  $linkend = '</a>';
3861 
3862  $result .= $linkstart;
3863  if ($withpicto) {
3864  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
3865  }
3866  if ($withpicto != 2) {
3867  $result .= $this->ref;
3868  }
3869  $result .= $linkend;
3870 
3871  if ($addlinktonotes >= 0) {
3872  $txttoshow = '';
3873 
3874  if ($addlinktonotes == 0) {
3875  if (!empty($this->note_private) || !empty($this->note_public)) {
3876  $txttoshow = $langs->trans('ViewPrivateNote');
3877  }
3878  } elseif ($addlinktonotes == 1) {
3879  if (!empty($this->note_private)) {
3880  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3881  }
3882  } elseif ($addlinktonotes == 2) {
3883  if (!empty($this->note_public)) {
3884  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3885  }
3886  } elseif ($addlinktonotes == 3) {
3887  if ($user->socid > 0) {
3888  if (!empty($this->note_public)) {
3889  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3890  }
3891  } else {
3892  if (!empty($this->note_public)) {
3893  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3894  }
3895  if (!empty($this->note_private)) {
3896  if (!empty($txttoshow)) {
3897  $txttoshow .= '<br><br>';
3898  }
3899  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3900  }
3901  }
3902  }
3903 
3904  if ($txttoshow) {
3905  $result .= ' <span class="note inline-block">';
3906  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3907  $result .= img_picto('', 'note');
3908  $result .= '</a>';
3909  $result .= '</span>';
3910  }
3911  }
3912 
3913  global $action;
3914  $hookmanager->initHooks(array($this->element . 'dao'));
3915  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3916  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3917  if ($reshook > 0) {
3918  $result = $hookmanager->resPrint;
3919  } else {
3920  $result .= $hookmanager->resPrint;
3921  }
3922  return $result;
3923  }
3924 
3931  public function getLinesArray($filters = '')
3932  {
3933  return $this->fetch_lines(0, 0, $filters);
3934  }
3935 
3947  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3948  {
3949  global $conf, $langs;
3950 
3951  $langs->load("propale");
3952  $outputlangs->load("products");
3953 
3954  if (!dol_strlen($modele)) {
3955  $modele = 'azur';
3956 
3957  if ($this->model_pdf) {
3958  $modele = $this->model_pdf;
3959  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3960  $modele = $conf->global->PROPALE_ADDON_PDF;
3961  }
3962  }
3963 
3964  $modelpath = "core/modules/propale/doc/";
3965 
3966  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3967  }
3968 
3977  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3978  {
3979  $tables = array(
3980  'propal'
3981  );
3982 
3983  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3984  }
3985 
3994  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3995  {
3996  $tables = array(
3997  'propaldet'
3998  );
3999 
4000  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
4001  }
4002 
4010  public function getKanbanView($option = '', $arraydata = null)
4011  {
4012  global $langs;
4013 
4014  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4015 
4016  $return = '<div class="box-flex-item box-flex-grow-zero">';
4017  $return .= '<div class="info-box info-box-sm">';
4018  $return .= '<span class="info-box-icon bg-infobox-action">';
4019  $return .= img_picto('', $this->picto);
4020  $return .= '</span>';
4021  $return .= '<div class="info-box-content">';
4022  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4023  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4024  if (property_exists($this, 'fk_project')) {
4025  $return .= '<span class="info-box-ref"> | '.$this->fk_project.'</span>';
4026  }
4027  if (property_exists($this, 'author')) {
4028  $return .= '<br><span class="info-box-label">'.$this->author.'</span>';
4029  }
4030  if (property_exists($this, 'total_ht')) {
4031  $return .='<br><span class="" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ht).'</span>';
4032  }
4033  if (method_exists($this, 'getLibStatut')) {
4034  $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
4035  }
4036  $return .= '</div>';
4037  $return .= '</div>';
4038  $return .= '</div>';
4039  return $return;
4040  }
4041 }
4042 
4047 {
4051  public $element = 'propaldet';
4052 
4056  public $table_element = 'propaldet';
4057 
4058  public $oldline;
4059 
4060  // From llx_propaldet
4061  public $fk_propal;
4062  public $fk_parent_line;
4063  public $desc; // Description ligne
4064  public $fk_product; // Id produit predefini
4075  public $product_type = Product::TYPE_PRODUCT;
4076 
4077  public $qty;
4078 
4079  public $tva_tx;
4080  public $vat_src_code;
4081 
4082  public $subprice;
4083  public $remise_percent;
4084  public $fk_remise_except;
4085 
4086  public $rang = 0;
4087 
4088  public $fk_fournprice;
4089  public $pa_ht;
4090  public $marge_tx;
4091  public $marque_tx;
4092 
4093  public $special_code; // Tag for special lines (exlusive tags)
4094  // 1: frais de port
4095  // 2: ecotaxe
4096  // 3: option line (when qty = 0)
4097 
4098  public $info_bits = 0; // Some other info:
4099  // Bit 0: 0 si TVA normal - 1 si TVA NPR
4100  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
4101 
4102  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
4103  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
4104  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4105 
4110  public $remise;
4115  public $price;
4116 
4117  // From llx_product
4122  public $ref;
4127  public $product_ref;
4132  public $libelle;
4137  public $label;
4142  public $product_label;
4147  public $product_desc;
4148 
4153  public $product_tobatch;
4154 
4159  public $product_barcode;
4160 
4161  public $localtax1_tx; // Local tax 1
4162  public $localtax2_tx; // Local tax 2
4163  public $localtax1_type; // Local tax 1 type
4164  public $localtax2_type; // Local tax 2 type
4165  public $total_localtax1; // Line total local tax 1
4166  public $total_localtax2; // Line total local tax 2
4167 
4168  public $date_start;
4169  public $date_end;
4170 
4171  public $skip_update_total; // Skip update price total for special lines
4172 
4173  // Multicurrency
4174  public $fk_multicurrency;
4175  public $multicurrency_code;
4176  public $multicurrency_subprice;
4177  public $multicurrency_total_ht;
4178  public $multicurrency_total_tva;
4179  public $multicurrency_total_ttc;
4180 
4181 
4187  public function __construct($db)
4188  {
4189  $this->db = $db;
4190  }
4191 
4198  public function fetch($rowid)
4199  {
4200  $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,';
4201  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4202  $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,';
4203  $sql .= ' pd.fk_unit,';
4204  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4205  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4206  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4207  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4208  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4209  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4210  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4211 
4212  $result = $this->db->query($sql);
4213  if ($result) {
4214  $objp = $this->db->fetch_object($result);
4215 
4216  if ($objp) {
4217  $this->id = $objp->rowid;
4218  $this->rowid = $objp->rowid; // deprecated
4219  $this->fk_propal = $objp->fk_propal;
4220  $this->fk_parent_line = $objp->fk_parent_line;
4221  $this->label = $objp->custom_label;
4222  $this->desc = $objp->description;
4223  $this->qty = $objp->qty;
4224  $this->price = $objp->price; // deprecated
4225  $this->subprice = $objp->subprice;
4226  $this->vat_src_code = $objp->vat_src_code;
4227  $this->tva_tx = $objp->tva_tx;
4228  $this->remise = $objp->remise; // deprecated
4229  $this->remise_percent = $objp->remise_percent;
4230  $this->fk_remise_except = $objp->fk_remise_except;
4231  $this->fk_product = $objp->fk_product;
4232  $this->info_bits = $objp->info_bits;
4233 
4234  $this->total_ht = $objp->total_ht;
4235  $this->total_tva = $objp->total_tva;
4236  $this->total_ttc = $objp->total_ttc;
4237 
4238  $this->fk_fournprice = $objp->fk_fournprice;
4239 
4240  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4241  $this->pa_ht = $marginInfos[0];
4242  $this->marge_tx = $marginInfos[1];
4243  $this->marque_tx = $marginInfos[2];
4244 
4245  $this->special_code = $objp->special_code;
4246  $this->product_type = $objp->product_type;
4247  $this->rang = $objp->rang;
4248 
4249  $this->ref = $objp->product_ref; // deprecated
4250  $this->product_ref = $objp->product_ref;
4251  $this->libelle = $objp->product_label; // deprecated
4252  $this->product_label = $objp->product_label;
4253  $this->product_desc = $objp->product_desc;
4254  $this->fk_unit = $objp->fk_unit;
4255 
4256  $this->date_start = $this->db->jdate($objp->date_start);
4257  $this->date_end = $this->db->jdate($objp->date_end);
4258 
4259  // Multicurrency
4260  $this->fk_multicurrency = $objp->fk_multicurrency;
4261  $this->multicurrency_code = $objp->multicurrency_code;
4262  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4263  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4264  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4265  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4266 
4267  $this->fetch_optionals();
4268 
4269  $this->db->free($result);
4270 
4271  return 1;
4272  } else {
4273  return 0;
4274  }
4275  } else {
4276  return -1;
4277  }
4278  }
4279 
4286  public function insert($notrigger = 0)
4287  {
4288  global $conf, $user;
4289 
4290  $error = 0;
4291 
4292  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4293 
4294  $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'.
4295 
4296  // Clean parameters
4297  if (empty($this->tva_tx)) {
4298  $this->tva_tx = 0;
4299  }
4300  if (empty($this->localtax1_tx)) {
4301  $this->localtax1_tx = 0;
4302  }
4303  if (empty($this->localtax2_tx)) {
4304  $this->localtax2_tx = 0;
4305  }
4306  if (empty($this->localtax1_type)) {
4307  $this->localtax1_type = 0;
4308  }
4309  if (empty($this->localtax2_type)) {
4310  $this->localtax2_type = 0;
4311  }
4312  if (empty($this->total_localtax1)) {
4313  $this->total_localtax1 = 0;
4314  }
4315  if (empty($this->total_localtax2)) {
4316  $this->total_localtax2 = 0;
4317  }
4318  if (empty($this->rang)) {
4319  $this->rang = 0;
4320  }
4321  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4322  $this->remise_percent = 0;
4323  }
4324  if (empty($this->info_bits)) {
4325  $this->info_bits = 0;
4326  }
4327  if (empty($this->special_code)) {
4328  $this->special_code = 0;
4329  }
4330  if (empty($this->fk_parent_line)) {
4331  $this->fk_parent_line = 0;
4332  }
4333  if (empty($this->fk_fournprice)) {
4334  $this->fk_fournprice = 0;
4335  }
4336  if (!is_numeric($this->qty)) {
4337  $this->qty = 0;
4338  }
4339  if (empty($this->pa_ht)) {
4340  $this->pa_ht = 0;
4341  }
4342  if (empty($this->multicurrency_subprice)) {
4343  $this->multicurrency_subprice = 0;
4344  }
4345  if (empty($this->multicurrency_total_ht)) {
4346  $this->multicurrency_total_ht = 0;
4347  }
4348  if (empty($this->multicurrency_total_tva)) {
4349  $this->multicurrency_total_tva = 0;
4350  }
4351  if (empty($this->multicurrency_total_ttc)) {
4352  $this->multicurrency_total_ttc = 0;
4353  }
4354 
4355  // if buy price not defined, define buyprice as configured in margin admin
4356  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4357  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4358  return $result;
4359  } else {
4360  $this->pa_ht = $result;
4361  }
4362  }
4363 
4364  // Check parameters
4365  if ($this->product_type < 0) {
4366  return -1;
4367  }
4368 
4369  $this->db->begin();
4370 
4371  // Insert line into database
4372  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4373  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4374  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4375  $sql .= ' subprice, remise_percent, ';
4376  $sql .= ' info_bits, ';
4377  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4378  $sql .= ' fk_unit,';
4379  $sql .= ' date_start, date_end';
4380  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4381  $sql .= " VALUES (".$this->fk_propal.",";
4382  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4383  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4384  $sql .= " '".$this->db->escape($this->desc)."',";
4385  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4386  $sql .= " '".$this->db->escape($this->product_type)."',";
4387  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4388  $sql .= " ".price2num($this->qty, 'MS').",";
4389  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4390  $sql .= " ".price2num($this->tva_tx).",";
4391  $sql .= " ".price2num($this->localtax1_tx).",";
4392  $sql .= " ".price2num($this->localtax2_tx).",";
4393  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4394  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4395  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4396  $sql .= " ".price2num($this->remise_percent, 3).",";
4397  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4398  $sql .= " ".price2num($this->total_ht, 'MT').",";
4399  $sql .= " ".price2num($this->total_tva, 'MT').",";
4400  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4401  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4402  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4403  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4404  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4405  $sql .= ' '.((int) $this->special_code).',';
4406  $sql .= ' '.((int) $this->rang).',';
4407  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4408  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4409  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4410  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4411  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4412  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4413  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4414  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4415  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4416  $sql .= ')';
4417 
4418  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4419  $resql = $this->db->query($sql);
4420  if ($resql) {
4421  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4422 
4423  if (!$error) {
4424  $this->id = $this->rowid;
4425  $result = $this->insertExtraFields();
4426  if ($result < 0) {
4427  $error++;
4428  }
4429  }
4430 
4431  if (!$error && !$notrigger) {
4432  // Call trigger
4433  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4434  if ($result < 0) {
4435  $this->db->rollback();
4436  return -1;
4437  }
4438  // End call triggers
4439  }
4440 
4441  $this->db->commit();
4442  return 1;
4443  } else {
4444  $this->error = $this->db->error()." sql=".$sql;
4445  $this->db->rollback();
4446  return -1;
4447  }
4448  }
4449 
4457  public function delete(User $user, $notrigger = 0)
4458  {
4459  global $conf;
4460 
4461  $error = 0;
4462  $this->db->begin();
4463 
4464  if (!$notrigger) {
4465  // Call trigger
4466  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4467  if ($result < 0) {
4468  $error++;
4469  }
4470  }
4471  // End call triggers
4472 
4473  if (!$error) {
4474  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4475  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4476  if ($this->db->query($sql)) {
4477  // Remove extrafields
4478  if (!$error) {
4479  $this->id = $this->rowid;
4480  $result = $this->deleteExtraFields();
4481  if ($result < 0) {
4482  $error++;
4483  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4484  }
4485  }
4486  } else {
4487  $this->error = $this->db->error() . " sql=" . $sql;
4488  $error++;
4489  }
4490  }
4491 
4492  if ($error) {
4493  $this->db->rollback();
4494  return -1;
4495  } else {
4496  $this->db->commit();
4497  return 1;
4498  }
4499  }
4500 
4507  public function update($notrigger = 0)
4508  {
4509  global $conf, $user;
4510 
4511  $error = 0;
4512 
4513  $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'.
4514 
4515  if (empty($this->id) && !empty($this->rowid)) {
4516  $this->id = $this->rowid;
4517  }
4518 
4519  // Clean parameters
4520  if (empty($this->tva_tx)) {
4521  $this->tva_tx = 0;
4522  }
4523  if (empty($this->localtax1_tx)) {
4524  $this->localtax1_tx = 0;
4525  }
4526  if (empty($this->localtax2_tx)) {
4527  $this->localtax2_tx = 0;
4528  }
4529  if (empty($this->total_localtax1)) {
4530  $this->total_localtax1 = 0;
4531  }
4532  if (empty($this->total_localtax2)) {
4533  $this->total_localtax2 = 0;
4534  }
4535  if (empty($this->localtax1_type)) {
4536  $this->localtax1_type = 0;
4537  }
4538  if (empty($this->localtax2_type)) {
4539  $this->localtax2_type = 0;
4540  }
4541  if (empty($this->marque_tx)) {
4542  $this->marque_tx = 0;
4543  }
4544  if (empty($this->marge_tx)) {
4545  $this->marge_tx = 0;
4546  }
4547  if (empty($this->price)) {
4548  $this->price = 0; // TODO A virer
4549  }
4550  if (empty($this->remise_percent)) {
4551  $this->remise_percent = 0;
4552  }
4553  if (empty($this->info_bits)) {
4554  $this->info_bits = 0;
4555  }
4556  if (empty($this->special_code)) {
4557  $this->special_code = 0;
4558  }
4559  if (empty($this->fk_parent_line)) {
4560  $this->fk_parent_line = 0;
4561  }
4562  if (empty($this->fk_fournprice)) {
4563  $this->fk_fournprice = 0;
4564  }
4565  if (empty($this->subprice)) {
4566  $this->subprice = 0;
4567  }
4568  if (empty($this->pa_ht)) {
4569  $this->pa_ht = 0;
4570  }
4571 
4572  // if buy price not defined, define buyprice as configured in margin admin
4573  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4574  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4575  return $result;
4576  } else {
4577  $this->pa_ht = $result;
4578  }
4579  }
4580 
4581  $this->db->begin();
4582 
4583  // Mise a jour ligne en base
4584  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4585  $sql .= " description='".$this->db->escape($this->desc)."'";
4586  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4587  $sql .= ", product_type=".$this->product_type;
4588  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4589  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4590  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4591  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4592  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4593  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4594  $sql .= ", qty='".price2num($this->qty)."'";
4595  $sql .= ", subprice=".price2num($this->subprice);
4596  $sql .= ", remise_percent=".price2num($this->remise_percent);
4597  $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4598  $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4599  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4600  if (empty($this->skip_update_total)) {
4601  $sql .= ", total_ht=".price2num($this->total_ht);
4602  $sql .= ", total_tva=".price2num($this->total_tva);
4603  $sql .= ", total_ttc=".price2num($this->total_ttc);
4604  $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4605  $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4606  }
4607  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4608  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4609  if (strlen($this->special_code)) {
4610  $sql .= ", special_code=".$this->special_code;
4611  }
4612  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4613  if (!empty($this->rang)) {
4614  $sql .= ", rang=".((int) $this->rang);
4615  }
4616  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4617  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4618  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4619 
4620  // Multicurrency
4621  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4622  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4623  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4624  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4625 
4626  $sql .= " WHERE rowid = ".((int) $this->id);
4627 
4628  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4629  $resql = $this->db->query($sql);
4630  if ($resql) {
4631  if (!$error) {
4632  $result = $this->insertExtraFields();
4633  if ($result < 0) {
4634  $error++;
4635  }
4636  }
4637 
4638  if (!$error && !$notrigger) {
4639  // Call trigger
4640  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4641  if ($result < 0) {
4642  $this->db->rollback();
4643  return -1;
4644  }
4645  // End call triggers
4646  }
4647 
4648  $this->db->commit();
4649  return 1;
4650  } else {
4651  $this->error = $this->db->error();
4652  $this->db->rollback();
4653  return -2;
4654  }
4655  }
4656 
4657  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4664  public function update_total()
4665  {
4666  // phpcs:enable
4667  $this->db->begin();
4668 
4669  // Mise a jour ligne en base
4670  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4671  $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4672  $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4673  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4674  $sql .= " WHERE rowid = ".((int) $this->rowid);
4675 
4676  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4677 
4678  $resql = $this->db->query($sql);
4679  if ($resql) {
4680  $this->db->commit();
4681  return 1;
4682  } else {
4683  $this->error = $this->db->error();
4684  $this->db->rollback();
4685  return -2;
4686  }
4687  }
4688 }
$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