dolibarr  18.0.0
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 
276 
301  // BEGIN MODULEBUILDER PROPERTIES
305  public $fields = array(
306  'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
307  'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>15, 'index'=>1),
308  'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>20),
309  'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
310  'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'RefExt', 'enabled'=>1, 'visible'=>0, 'position'=>40),
311  'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>-1, 'position'=>23),
312  '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),
313  'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
314  'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
315  'datep' =>array('type'=>'date', 'label'=>'Date', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
316  'fin_validite' =>array('type'=>'datetime', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
317  'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
318  'date_cloture' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
319  'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
320  'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>85),
321  'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
322  'fk_user_cloture' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user cloture', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
323  'price' =>array('type'=>'double', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
324  //'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
325  //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
326  //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
327  'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
328  'total_tva' =>array('type'=>'double(24,8)', 'label'=>'VAT', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
329  'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LocalTax1', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
330  'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LocalTax2', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
331  'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>145, 'isameasure'=>1),
332  'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'isModEnabled("banque")', 'visible'=>-1, 'position'=>150),
333  'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'Currency', 'enabled'=>1, 'visible'=>-1, 'position'=>155),
334  'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
335  'deposit_percent' =>array('type'=>'varchar(63)', 'label'=>'DepositPercent', 'enabled'=>1, 'visible'=>-1, 'position'=>161),
336  'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
337  'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>170),
338  'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>175),
339  'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'PDFTemplate', 'enabled'=>1, 'visible'=>0, 'position'=>180),
340  'date_livraison' =>array('type'=>'date', 'label'=>'DateDeliveryPlanned', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
341  'fk_shipping_method' =>array('type'=>'integer', 'label'=>'ShippingMethod', 'enabled'=>1, 'visible'=>-1, 'position'=>190),
342  'fk_warehouse' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Fk warehouse', 'enabled'=>'isModEnabled("stock")', 'visible'=>-1, 'position'=>191),
343  'fk_availability' =>array('type'=>'integer', 'label'=>'Availability', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
344  'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>0, 'position'=>200), // deprecated
345  'fk_input_reason' =>array('type'=>'integer', 'label'=>'InputReason', 'enabled'=>1, 'visible'=>-1, 'position'=>205),
346  'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
347  'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>220),
348  'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>225),
349  'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>1, 'visible'=>-1, 'position'=>230),
350  'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
351  'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240, 'isameasure'=>1),
352  'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>245, 'isameasure'=>1),
353  'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>250, 'isameasure'=>1),
354  'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>255, 'isameasure'=>1),
355  'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>260),
356  'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
357  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
358  );
359  // END MODULEBUILDER PROPERTIES
360 
364  const STATUS_DRAFT = 0;
368  const STATUS_VALIDATED = 1;
372  const STATUS_SIGNED = 2;
376  const STATUS_NOTSIGNED = 3;
380  const STATUS_BILLED = 4; // Todo rename into STATUS_CLOSE ?
381 
382 
390  public function __construct($db, $socid = 0, $propalid = 0)
391  {
392  global $conf, $langs;
393 
394  $this->db = $db;
395 
396  $this->socid = $socid;
397  $this->id = $propalid;
398 
399  $this->duree_validite = getDolGlobalInt('PROPALE_VALIDITY_DURATION', 0);
400  }
401 
402 
403  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
415  public function add_product($idproduct, $qty, $remise_percent = 0)
416  {
417  // phpcs:enable
418  global $conf, $mysoc;
419 
420  if (!$qty) {
421  $qty = 1;
422  }
423 
424  dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
425  if ($idproduct > 0) {
426  $prod = new Product($this->db);
427  $prod->fetch($idproduct);
428 
429  $productdesc = $prod->description;
430 
431  $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
432  $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
433  if (empty($tva_tx)) {
434  $tva_npr = 0;
435  }
436  $vat_src_code = ''; // May be defined into tva_tx
437 
438  $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
439  $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
440 
441  // multiprices
442  if ($conf->global->PRODUIT_MULTIPRICES && $this->thirdparty->price_level) {
443  $price = $prod->multiprices[$this->thirdparty->price_level];
444  } else {
445  $price = $prod->price;
446  }
447 
448  $line = new PropaleLigne($this->db);
449 
450  $line->fk_product = $idproduct;
451  $line->desc = $productdesc;
452  $line->qty = $qty;
453  $line->subprice = $price;
454  $line->remise_percent = $remise_percent;
455  $line->vat_src_code = $vat_src_code;
456  $line->tva_tx = $tva_tx;
457  $line->fk_unit = $prod->fk_unit;
458  if ($tva_npr) {
459  $line->info_bits = 1;
460  }
461 
462  $this->lines[] = $line;
463  }
464 
465  return 1;
466  }
467 
468  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
475  public function insert_discount($idremise)
476  {
477  // phpcs:enable
478  global $langs;
479 
480  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
481  include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
482 
483  $this->db->begin();
484 
485  $remise = new DiscountAbsolute($this->db);
486  $result = $remise->fetch($idremise);
487 
488  if ($result > 0) {
489  if ($remise->fk_facture) { // Protection against multiple submission
490  $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
491  $this->db->rollback();
492  return -5;
493  }
494 
495  $line = new PropaleLigne($this->db);
496 
497  $this->line->context = $this->context;
498 
499  $line->fk_propal = $this->id;
500  $line->fk_remise_except = $remise->id;
501  $line->desc = $remise->description; // Description ligne
502  $line->vat_src_code = $remise->vat_src_code;
503  $line->tva_tx = $remise->tva_tx;
504  $line->subprice = -$remise->amount_ht;
505  $line->fk_product = 0; // Id produit predefined
506  $line->qty = 1;
507  $line->remise_percent = 0;
508  $line->rang = -1;
509  $line->info_bits = 2;
510 
511  // TODO deprecated
512  $line->price = -$remise->amount_ht;
513 
514  $line->total_ht = -$remise->amount_ht;
515  $line->total_tva = -$remise->amount_tva;
516  $line->total_ttc = -$remise->amount_ttc;
517 
518  $result = $line->insert();
519  if ($result > 0) {
520  $result = $this->update_price(1);
521  if ($result > 0) {
522  $this->db->commit();
523  return 1;
524  } else {
525  $this->db->rollback();
526  return -1;
527  }
528  } else {
529  $this->error = $line->error;
530  $this->errors = $line->errors;
531  $this->db->rollback();
532  return -2;
533  }
534  } else {
535  $this->db->rollback();
536  return -2;
537  }
538  }
539 
577  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)
578  {
579  global $mysoc, $conf, $langs;
580 
581  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);
582 
583  if ($this->statut == self::STATUS_DRAFT) {
584  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
585 
586  // Clean parameters
587  if (empty($remise_percent)) {
588  $remise_percent = 0;
589  }
590  if (empty($qty)) {
591  $qty = 0;
592  }
593  if (empty($info_bits)) {
594  $info_bits = 0;
595  }
596  if (empty($rang)) {
597  $rang = 0;
598  }
599  if (empty($fk_parent_line) || $fk_parent_line < 0) {
600  $fk_parent_line = 0;
601  }
602 
604  $qty = price2num($qty);
605  $pu_ht = price2num($pu_ht);
606  $pu_ht_devise = price2num($pu_ht_devise);
607  $pu_ttc = price2num($pu_ttc);
608  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
609  $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
610  }
611  $txlocaltax1 = price2num($txlocaltax1);
612  $txlocaltax2 = price2num($txlocaltax2);
613  $pa_ht = price2num($pa_ht);
614  if ($price_base_type == 'HT') {
615  $pu = $pu_ht;
616  } else {
617  $pu = $pu_ttc;
618  }
619 
620  // Check parameters
621  if ($type < 0) {
622  return -1;
623  }
624 
625  if ($date_start && $date_end && $date_start > $date_end) {
626  $langs->load("errors");
627  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
628  return -1;
629  }
630 
631  $this->db->begin();
632 
633  $product_type = $type;
634  if (!empty($fk_product) && $fk_product > 0) {
635  $product = new Product($this->db);
636  $result = $product->fetch($fk_product);
637  $product_type = $product->type;
638 
639  if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_PROPOSAL) && $product_type == 0 && $product->stock_reel < $qty) {
640  $langs->load("errors");
641  $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnProposal', $product->ref);
642  $this->db->rollback();
643  return -3;
644  }
645  }
646 
647  // Calcul du total TTC et de la TVA pour la ligne a partir de
648  // qty, pu, remise_percent et txtva
649  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
650  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
651 
652  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
653 
654  // Clean vat code
655  $reg = array();
656  $vat_src_code = '';
657  $reg = array();
658  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
659  $vat_src_code = $reg[1];
660  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
661  }
662 
663  $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);
664 
665  $total_ht = $tabprice[0];
666  $total_tva = $tabprice[1];
667  $total_ttc = $tabprice[2];
668  $total_localtax1 = $tabprice[9];
669  $total_localtax2 = $tabprice[10];
670  $pu_ht = $tabprice[3];
671  $pu_tva = $tabprice[4];
672  $pu_ttc = $tabprice[5];
673 
674  // MultiCurrency
675  $multicurrency_total_ht = $tabprice[16];
676  $multicurrency_total_tva = $tabprice[17];
677  $multicurrency_total_ttc = $tabprice[18];
678  $pu_ht_devise = $tabprice[19];
679 
680  // Rang to use
681  $ranktouse = $rang;
682  if ($ranktouse == -1) {
683  $rangmax = $this->line_max($fk_parent_line);
684  $ranktouse = $rangmax + 1;
685  }
686 
687  // TODO A virer
688  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
689  $price = $pu;
690  $remise = 0;
691  if ($remise_percent > 0) {
692  $remise = round(($pu * $remise_percent / 100), 2);
693  $price = $pu - $remise;
694  }
695 
696  // Insert line
697  $this->line = new PropaleLigne($this->db);
698 
699  $this->line->context = $this->context;
700 
701  $this->line->fk_propal = $this->id;
702  $this->line->label = $label;
703  $this->line->desc = $desc;
704  $this->line->qty = $qty;
705 
706  $this->line->vat_src_code = $vat_src_code;
707  $this->line->tva_tx = $txtva;
708  $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
709  $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
710  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
711  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
712  $this->line->fk_product = $fk_product;
713  $this->line->product_type = $type;
714  $this->line->fk_remise_except = $fk_remise_except;
715  $this->line->remise_percent = $remise_percent;
716  $this->line->subprice = $pu_ht;
717  $this->line->rang = $ranktouse;
718  $this->line->info_bits = $info_bits;
719  $this->line->total_ht = $total_ht;
720  $this->line->total_tva = $total_tva;
721  $this->line->total_localtax1 = $total_localtax1;
722  $this->line->total_localtax2 = $total_localtax2;
723  $this->line->total_ttc = $total_ttc;
724  $this->line->special_code = $special_code;
725  $this->line->fk_parent_line = $fk_parent_line;
726  $this->line->fk_unit = $fk_unit;
727 
728  $this->line->date_start = $date_start;
729  $this->line->date_end = $date_end;
730 
731  $this->line->fk_fournprice = $fk_fournprice;
732  $this->line->pa_ht = $pa_ht;
733 
734  $this->line->origin_id = $origin_id;
735  $this->line->origin = $origin;
736 
737  // Multicurrency
738  $this->line->fk_multicurrency = $this->fk_multicurrency;
739  $this->line->multicurrency_code = $this->multicurrency_code;
740  $this->line->multicurrency_subprice = $pu_ht_devise;
741  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
742  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
743  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
744 
745  // Mise en option de la ligne
746  if (empty($qty) && empty($special_code)) {
747  $this->line->special_code = 3;
748  }
749 
750  // TODO deprecated
751  $this->line->price = $price;
752 
753  if (is_array($array_options) && count($array_options) > 0) {
754  $this->line->array_options = $array_options;
755  }
756 
757  $result = $this->line->insert();
758  if ($result > 0) {
759  // Reorder if child line
760  if (!empty($fk_parent_line)) {
761  $this->line_order(true, 'DESC');
762  } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
763  $linecount = count($this->lines);
764  for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
765  $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
766  }
767  }
768 
769  // Mise a jour informations denormalisees au niveau de la propale meme
770  if (empty($noupdateafterinsertline)) {
771  $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.
772  }
773 
774  if ($result > 0) {
775  $this->db->commit();
776  return $this->line->id;
777  } else {
778  $this->error = $this->db->error();
779  $this->db->rollback();
780  return -1;
781  }
782  } else {
783  $this->error = $this->line->error;
784  $this->errors = $this->line->errors;
785  $this->db->rollback();
786  return -2;
787  }
788  } else {
789  dol_syslog(get_class($this)."::addline status of proposal must be Draft to allow use of ->addline()", LOG_ERR);
790  return -3;
791  }
792  }
793 
794 
824  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)
825  {
826  global $mysoc, $langs;
827 
828  dol_syslog(get_class($this)."::updateLine rowid=$rowid, pu=$pu, qty=$qty, remise_percent=$remise_percent,
829  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");
830  include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
831 
832  // Clean parameters
834  $qty = price2num($qty);
835  $pu = price2num($pu);
836  $pu_ht_devise = price2num($pu_ht_devise);
837  if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
838  $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
839  }
840  $txlocaltax1 = price2num($txlocaltax1);
841  $txlocaltax2 = price2num($txlocaltax2);
842  $pa_ht = price2num($pa_ht);
843  if (empty($qty) && empty($special_code)) {
844  $special_code = 3; // Set option tag
845  }
846  if (!empty($qty) && $special_code == 3) {
847  $special_code = 0; // Remove option tag
848  }
849  if (empty($type)) {
850  $type = 0;
851  }
852 
853  if ($date_start && $date_end && $date_start > $date_end) {
854  $langs->load("errors");
855  $this->error = $langs->trans('ErrorStartDateGreaterEnd');
856  return -1;
857  }
858 
859  if ($this->statut == self::STATUS_DRAFT) {
860  $this->db->begin();
861 
862  // Calcul du total TTC et de la TVA pour la ligne a partir de
863  // qty, pu, remise_percent et txtva
864  // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
865  // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
866 
867  $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
868 
869  // Clean vat code
870  $reg = array();
871  $vat_src_code = '';
872  if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
873  $vat_src_code = $reg[1];
874  $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
875  }
876 
877  // TODO Implement if (getDolGlobalInt('MAIN_UNIT_PRICE_WITH_TAX_IS_FOR_ALL_TAXES')) ?
878 
879  $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);
880  $total_ht = $tabprice[0];
881  $total_tva = $tabprice[1];
882  $total_ttc = $tabprice[2];
883  $total_localtax1 = $tabprice[9];
884  $total_localtax2 = $tabprice[10];
885  $pu_ht = $tabprice[3];
886  $pu_tva = $tabprice[4];
887  $pu_ttc = $tabprice[5];
888 
889  // MultiCurrency
890  $multicurrency_total_ht = $tabprice[16];
891  $multicurrency_total_tva = $tabprice[17];
892  $multicurrency_total_ttc = $tabprice[18];
893  $pu_ht_devise = $tabprice[19];
894 
895  // Anciens indicateurs: $price, $remise (a ne plus utiliser)
896  $price = $pu;
897  $remise = 0;
898  if ($remise_percent > 0) {
899  $remise = round(($pu * $remise_percent / 100), 2);
900  $price = $pu - $remise;
901  }
902 
903  //Fetch current line from the database and then clone the object and set it in $oldline property
904  $line = new PropaleLigne($this->db);
905  $line->fetch($rowid);
906 
907  $staticline = clone $line;
908 
909  $line->oldline = $staticline;
910  $this->line = $line;
911  $this->line->context = $this->context;
912  $this->line->rang = $rang;
913 
914  // Reorder if fk_parent_line change
915  if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
916  $rangmax = $this->line_max($fk_parent_line);
917  $this->line->rang = $rangmax + 1;
918  }
919 
920  $this->line->id = $rowid;
921  $this->line->label = $label;
922  $this->line->desc = $desc;
923  $this->line->qty = $qty;
924  $this->line->product_type = $type;
925  $this->line->vat_src_code = $vat_src_code;
926  $this->line->tva_tx = $txtva;
927  $this->line->localtax1_tx = $txlocaltax1;
928  $this->line->localtax2_tx = $txlocaltax2;
929  $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
930  $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
931  $this->line->remise_percent = $remise_percent;
932  $this->line->subprice = $pu_ht;
933  $this->line->info_bits = $info_bits;
934 
935  $this->line->total_ht = $total_ht;
936  $this->line->total_tva = $total_tva;
937  $this->line->total_localtax1 = $total_localtax1;
938  $this->line->total_localtax2 = $total_localtax2;
939  $this->line->total_ttc = $total_ttc;
940  $this->line->special_code = $special_code;
941  $this->line->fk_parent_line = $fk_parent_line;
942  $this->line->skip_update_total = $skip_update_total;
943  $this->line->fk_unit = $fk_unit;
944 
945  $this->line->fk_fournprice = $fk_fournprice;
946  $this->line->pa_ht = $pa_ht;
947 
948  $this->line->date_start = $date_start;
949  $this->line->date_end = $date_end;
950 
951  if (is_array($array_options) && count($array_options) > 0) {
952  // We replace values in this->line->array_options only for entries defined into $array_options
953  foreach ($array_options as $key => $value) {
954  $this->line->array_options[$key] = $array_options[$key];
955  }
956  }
957 
958  // Multicurrency
959  $this->line->multicurrency_subprice = $pu_ht_devise;
960  $this->line->multicurrency_total_ht = $multicurrency_total_ht;
961  $this->line->multicurrency_total_tva = $multicurrency_total_tva;
962  $this->line->multicurrency_total_ttc = $multicurrency_total_ttc;
963 
964  $result = $this->line->update($notrigger);
965  if ($result > 0) {
966  // Reorder if child line
967  if (!empty($fk_parent_line)) {
968  $this->line_order(true, 'DESC');
969  }
970 
971  $this->update_price(1, 'auto');
972 
973  $this->fk_propal = $this->id;
974  $this->rowid = $rowid;
975 
976  $this->db->commit();
977  return $result;
978  } else {
979  $this->error = $this->line->error;
980  $this->errors = $this->line->errors;
981  $this->db->rollback();
982  return -1;
983  }
984  } else {
985  dol_syslog(get_class($this)."::updateline Erreur -2 Propal en mode incompatible pour cette action");
986  return -2;
987  }
988  }
989 
990 
998  public function deleteline($lineid, $id = 0)
999  {
1000  global $user;
1001 
1002  if ($this->statut == self::STATUS_DRAFT) {
1003  $this->db->begin();
1004 
1005  $line = new PropaleLigne($this->db);
1006 
1007  $line->context = $this->context;
1008 
1009  // Load data
1010  $line->fetch($lineid);
1011 
1012  if ($id > 0 && $line->fk_propal != $id) {
1013  $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1014  return -1;
1015  }
1016 
1017  // Memorize previous line for triggers
1018  $staticline = clone $line;
1019  $line->oldline = $staticline;
1020 
1021  if ($line->delete($user) > 0) {
1022  $this->update_price(1);
1023 
1024  $this->db->commit();
1025  return 1;
1026  } else {
1027  $this->error = $line->error;
1028  $this->errors = $line->errors;
1029  $this->db->rollback();
1030  return -1;
1031  }
1032  } else {
1033  $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
1034  return -2;
1035  }
1036  }
1037 
1038 
1047  public function create($user, $notrigger = 0)
1048  {
1049  global $conf, $hookmanager, $mysoc;
1050  $error = 0;
1051 
1052  $now = dol_now();
1053 
1054  // Clean parameters
1055  if (empty($this->date)) {
1056  $this->date = $this->datep;
1057  }
1058  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1059  if (empty($this->availability_id)) {
1060  $this->availability_id = 0;
1061  }
1062  if (empty($this->demand_reason_id)) {
1063  $this->demand_reason_id = 0;
1064  }
1065 
1066  // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1067  if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1068  list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
1069  } else {
1070  $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1071  }
1072  if (empty($this->fk_multicurrency)) {
1073  $this->multicurrency_code = $conf->currency;
1074  $this->fk_multicurrency = 0;
1075  $this->multicurrency_tx = 1;
1076  }
1077 
1078  // Set tmp vars
1079  $delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1080 
1081  dol_syslog(get_class($this)."::create");
1082 
1083  // Check parameters
1084  $result = $this->fetch_thirdparty();
1085  if ($result < 0) {
1086  $this->error = "Failed to fetch company";
1087  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1088  return -3;
1089  }
1090 
1091  // Check parameters
1092  if (!empty($this->ref)) { // We check that ref is not already used
1093  $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1094  if ($result > 0) {
1095  $this->error = 'ErrorRefAlreadyExists';
1096  dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1097  $this->db->rollback();
1098  return -1;
1099  }
1100  }
1101 
1102  if (empty($this->date)) {
1103  $this->error = "Date of proposal is required";
1104  dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1105  return -4;
1106  }
1107 
1108 
1109  $this->db->begin();
1110 
1111  // Insert into database
1112  $sql = "INSERT INTO ".MAIN_DB_PREFIX."propal (";
1113  $sql .= "fk_soc";
1114  $sql .= ", price";
1115  $sql .= ", remise"; // deprecated
1116  $sql .= ", remise_percent"; // deprecated
1117  $sql .= ", remise_absolue"; // deprecated
1118  $sql .= ", total_tva";
1119  $sql .= ", total_ttc";
1120  $sql .= ", datep";
1121  $sql .= ", datec";
1122  $sql .= ", ref";
1123  $sql .= ", fk_user_author";
1124  $sql .= ", note_private";
1125  $sql .= ", note_public";
1126  $sql .= ", model_pdf";
1127  $sql .= ", fin_validite";
1128  $sql .= ", fk_cond_reglement";
1129  $sql .= ", deposit_percent";
1130  $sql .= ", fk_mode_reglement";
1131  $sql .= ", fk_account";
1132  $sql .= ", ref_client";
1133  $sql .= ", ref_ext";
1134  $sql .= ", date_livraison";
1135  $sql .= ", fk_shipping_method";
1136  $sql .= ", fk_warehouse";
1137  $sql .= ", fk_availability";
1138  $sql .= ", fk_input_reason";
1139  $sql .= ", fk_projet";
1140  $sql .= ", fk_incoterms";
1141  $sql .= ", location_incoterms";
1142  $sql .= ", entity";
1143  $sql .= ", fk_multicurrency";
1144  $sql .= ", multicurrency_code";
1145  $sql .= ", multicurrency_tx";
1146  $sql .= ") ";
1147  $sql .= " VALUES (";
1148  $sql .= $this->socid;
1149  $sql .= ", 0";
1150  $sql .= ", ".((float) $this->remise); // deprecated
1151  $sql .= ", ".($this->remise_percent ? ((float) $this->remise_percent) : 'NULL'); // deprecated
1152  $sql .= ", ".($this->remise_absolue ? ((float) $this->remise_absolue) : 'NULL'); // deprecated
1153  $sql .= ", 0";
1154  $sql .= ", 0";
1155  $sql .= ", '".$this->db->idate($this->date)."'";
1156  $sql .= ", '".$this->db->idate($now)."'";
1157  $sql .= ", '(PROV)'";
1158  $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "NULL");
1159  $sql .= ", '".$this->db->escape($this->note_private)."'";
1160  $sql .= ", '".$this->db->escape($this->note_public)."'";
1161  $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1162  $sql .= ", ".($this->fin_validite != '' ? "'".$this->db->idate($this->fin_validite)."'" : "NULL");
1163  $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1164  $sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : 'NULL');
1165  $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1166  $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1167  $sql .= ", '".$this->db->escape($this->ref_client)."'";
1168  $sql .= ", '".$this->db->escape($this->ref_ext)."'";
1169  $sql .= ", ".(empty($delivery_date) ? "NULL" : "'".$this->db->idate($delivery_date)."'");
1170  $sql .= ", ".($this->shipping_method_id > 0 ? $this->shipping_method_id : 'NULL');
1171  $sql .= ", ".($this->warehouse_id > 0 ? $this->warehouse_id : 'NULL');
1172  $sql .= ", ".$this->availability_id;
1173  $sql .= ", ".$this->demand_reason_id;
1174  $sql .= ", ".($this->fk_project ? $this->fk_project : "null");
1175  $sql .= ", ".(int) $this->fk_incoterms;
1176  $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1177  $sql .= ", ".setEntity($this);
1178  $sql .= ", ".(int) $this->fk_multicurrency;
1179  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1180  $sql .= ", ".(double) $this->multicurrency_tx;
1181  $sql .= ")";
1182 
1183  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1184  $resql = $this->db->query($sql);
1185  if ($resql) {
1186  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."propal");
1187 
1188  if ($this->id) {
1189  $this->ref = '(PROV'.$this->id.')';
1190  $sql = 'UPDATE '.MAIN_DB_PREFIX."propal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1191 
1192  dol_syslog(get_class($this)."::create", LOG_DEBUG);
1193  $resql = $this->db->query($sql);
1194  if (!$resql) {
1195  $error++;
1196  }
1197 
1198  if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1199  $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1200  }
1201 
1202  // Add object linked
1203  if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1204  foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1205  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, ...))
1206  foreach ($tmp_origin_id as $origin_id) {
1207  $ret = $this->add_object_linked($origin, $origin_id);
1208  if (!$ret) {
1209  $this->error = $this->db->lasterror();
1210  $error++;
1211  }
1212  }
1213  } else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1214  {
1215  $origin_id = $tmp_origin_id;
1216  $ret = $this->add_object_linked($origin, $origin_id);
1217  if (!$ret) {
1218  $this->error = $this->db->lasterror();
1219  $error++;
1220  }
1221  }
1222  }
1223  }
1224 
1225  /*
1226  * Insertion du detail des produits dans la base
1227  * Insert products detail in database
1228  */
1229  if (!$error) {
1230  $fk_parent_line = 0;
1231  $num = count($this->lines);
1232 
1233  for ($i = 0; $i < $num; $i++) {
1234  if (!is_object($this->lines[$i])) { // If this->lines is not array of objects, coming from REST API
1235  // Convert into object this->lines[$i].
1236  $line = (object) $this->lines[$i];
1237  } else {
1238  $line = $this->lines[$i];
1239  }
1240  // Reset fk_parent_line for line that are not child lines or special product
1241  if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
1242  $fk_parent_line = 0;
1243  }
1244  // Complete vat rate with code
1245  $vatrate = $line->tva_tx;
1246  if ($line->vat_src_code && !preg_match('/\‍(.*\‍)/', $vatrate)) {
1247  $vatrate .= ' ('.$line->vat_src_code.')';
1248  }
1249 
1250  if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
1251  $originid = $line->origin_id;
1252  $origintype = $line->origin;
1253  } else {
1254  $originid = $line->id;
1255  $origintype = $this->element;
1256  }
1257 
1258  $result = $this->addline(
1259  $line->desc,
1260  $line->subprice,
1261  $line->qty,
1262  $vatrate,
1263  $line->localtax1_tx,
1264  $line->localtax2_tx,
1265  $line->fk_product,
1266  $line->remise_percent,
1267  'HT',
1268  0,
1269  $line->info_bits,
1270  $line->product_type,
1271  $line->rang,
1272  $line->special_code,
1273  $fk_parent_line,
1274  $line->fk_fournprice,
1275  $line->pa_ht,
1276  $line->label,
1277  $line->date_start,
1278  $line->date_end,
1279  $line->array_options,
1280  $line->fk_unit,
1281  $origintype,
1282  $originid,
1283  0,
1284  0,
1285  1
1286  );
1287 
1288  if ($result < 0) {
1289  $error++;
1290  $this->error = $this->db->error;
1291  dol_print_error($this->db);
1292  break;
1293  }
1294 
1295  // Set the id on created row
1296  $line->id = $result;
1297 
1298  // Defined the new fk_parent_line
1299  if ($result > 0 && $line->product_type == 9) {
1300  $fk_parent_line = $result;
1301  }
1302  }
1303  }
1304 
1305  // Set delivery address
1306  /*if (! $error && $this->fk_delivery_address)
1307  {
1308  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
1309  $sql.= " SET fk_delivery_address = ".((int) $this->fk_delivery_address);
1310  $sql.= " WHERE ref = '".$this->db->escape($this->ref)."'";
1311  $sql.= " AND entity = ".setEntity($this);
1312 
1313  $result=$this->db->query($sql);
1314  }*/
1315 
1316  if (!$error) {
1317  // Mise a jour infos denormalisees
1318  $resql = $this->update_price(1, 'auto', 0, $mysoc);
1319  if ($resql) {
1320  $action = 'update';
1321 
1322  // Actions on extra fields
1323  if (!$error) {
1324  $result = $this->insertExtraFields();
1325  if ($result < 0) {
1326  $error++;
1327  }
1328  }
1329 
1330  if (!$error && !$notrigger) {
1331  // Call trigger
1332  $result = $this->call_trigger('PROPAL_CREATE', $user);
1333  if ($result < 0) {
1334  $error++;
1335  }
1336  // End call triggers
1337  }
1338  } else {
1339  $this->error = $this->db->lasterror();
1340  $error++;
1341  }
1342  }
1343  } else {
1344  $this->error = $this->db->lasterror();
1345  $error++;
1346  }
1347 
1348  if (!$error) {
1349  $this->db->commit();
1350  dol_syslog(get_class($this)."::create done id=".$this->id);
1351  return $this->id;
1352  } else {
1353  $this->db->rollback();
1354  return -2;
1355  }
1356  } else {
1357  $this->error = $this->db->lasterror();
1358  $this->db->rollback();
1359  return -1;
1360  }
1361  }
1362 
1373  public function createFromClone(User $user, $socid = 0, $forceentity = null, $update_prices = false, $update_desc = false)
1374  {
1375  global $conf, $hookmanager, $mysoc;
1376 
1377  dol_include_once('/projet/class/project.class.php');
1378 
1379  $error = 0;
1380  $now = dol_now();
1381 
1382  dol_syslog(__METHOD__, LOG_DEBUG);
1383 
1384  $object = new self($this->db);
1385 
1386  $this->db->begin();
1387 
1388  // Load source object
1389  $object->fetch($this->id);
1390 
1391  $objsoc = new Societe($this->db);
1392 
1393  // Change socid if needed
1394  if (!empty($socid) && $socid != $object->socid) {
1395  if ($objsoc->fetch($socid) > 0) {
1396  $object->socid = $objsoc->id;
1397  $object->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1398  $object->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : null);
1399  $object->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1400  $object->fk_delivery_address = '';
1401 
1402  /*if (isModEnabled('project'))
1403  {
1404  $project = new Project($db);
1405  if ($this->fk_project > 0 && $project->fetch($this->fk_project)) {
1406  if ($project->socid <= 0) $clonedObj->fk_project = $this->fk_project;
1407  else $clonedObj->fk_project = '';
1408  } else {
1409  $clonedObj->fk_project = '';
1410  }
1411  }*/
1412  $object->fk_project = ''; // A cloned proposal is set by default to no project.
1413  }
1414 
1415  // reset ref_client
1416  $object->ref_client = '';
1417 
1418  // TODO Change product price if multi-prices
1419  } else {
1420  $objsoc->fetch($object->socid);
1421  }
1422 
1423  // update prices
1424  if ($update_prices === true || $update_desc === true) {
1425  if ($objsoc->id > 0 && !empty($object->lines)) {
1426  if ($update_prices === true && !empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1427  // If price per customer
1428  require_once DOL_DOCUMENT_ROOT . '/product/class/productcustomerprice.class.php';
1429  }
1430 
1431  foreach ($object->lines as $line) {
1432  $line->id = 0;
1433 
1434  if ($line->fk_product > 0) {
1435  $prod = new Product($this->db);
1436  $res = $prod->fetch($line->fk_product);
1437  if ($res > 0) {
1438  if ($update_prices === true) {
1439  $pu_ht = $prod->price;
1440  $tva_tx = get_default_tva($mysoc, $objsoc, $prod->id);
1441  $remise_percent = $objsoc->remise_percent;
1442 
1443  if (!empty($conf->global->PRODUIT_MULTIPRICES) && $objsoc->price_level > 0) {
1444  $pu_ht = $prod->multiprices[$objsoc->price_level];
1445  if (!empty($conf->global->PRODUIT_MULTIPRICES_USE_VAT_PER_LEVEL)) { // using this option is a bug. kept for backward compatibility
1446  if (isset($prod->multiprices_tva_tx[$objsoc->price_level])) {
1447  $tva_tx = $prod->multiprices_tva_tx[$objsoc->price_level];
1448  }
1449  }
1450  } elseif (!empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
1451  $prodcustprice = new Productcustomerprice($this->db);
1452  $filter = array('t.fk_product' => $prod->id, 't.fk_soc' => $objsoc->id);
1453  $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
1454  if ($result) {
1455  // If there is some prices specific to the customer
1456  if (count($prodcustprice->lines) > 0) {
1457  $pu_ht = price($prodcustprice->lines[0]->price);
1458  $tva_tx = ($prodcustprice->lines[0]->default_vat_code ? $prodcustprice->lines[0]->tva_tx.' ('.$prodcustprice->lines[0]->default_vat_code.' )' : $prodcustprice->lines[0]->tva_tx);
1459  if ($prodcustprice->lines[0]->default_vat_code && !preg_match('/\‍(.*\‍)/', $tva_tx)) {
1460  $tva_tx .= ' ('.$prodcustprice->lines[0]->default_vat_code.')';
1461  }
1462  }
1463  }
1464  }
1465 
1466  $line->subprice = $pu_ht;
1467  $line->tva_tx = $tva_tx;
1468  $line->remise_percent = $remise_percent;
1469  }
1470  if ($update_desc === true) {
1471  $line->desc = $prod->description;
1472  }
1473  }
1474  }
1475  }
1476  }
1477  }
1478 
1479  $object->id = 0;
1480  $object->ref = '';
1481  $object->entity = (!empty($forceentity) ? $forceentity : $object->entity);
1482  $object->statut = self::STATUS_DRAFT;
1483 
1484  // Clear fields
1485  $object->user_author = $user->id;
1486  $object->user_valid = 0;
1487  $object->date = $now;
1488  $object->datep = $now; // deprecated
1489  $object->fin_validite = $object->date + ($object->duree_validite * 24 * 3600);
1490  if (empty($conf->global->MAIN_KEEP_REF_CUSTOMER_ON_CLONING)) {
1491  $object->ref_client = '';
1492  }
1493  if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1494  $object->note_private = '';
1495  $object->note_public = '';
1496  }
1497  // Create clone
1498  $object->context['createfromclone'] = 'createfromclone';
1499  $result = $object->create($user);
1500  if ($result < 0) {
1501  $this->error = $object->error;
1502  $this->errors = array_merge($this->errors, $object->errors);
1503  $error++;
1504  }
1505 
1506  if (!$error) {
1507  // copy internal contacts
1508  if ($object->copy_linked_contact($this, 'internal') < 0) {
1509  $error++;
1510  }
1511  }
1512 
1513  if (!$error) {
1514  // copy external contacts if same company
1515  if ($this->socid == $object->socid) {
1516  if ($object->copy_linked_contact($this, 'external') < 0) {
1517  $error++;
1518  }
1519  }
1520  }
1521 
1522  if (!$error) {
1523  // Hook of thirdparty module
1524  if (is_object($hookmanager)) {
1525  $parameters = array('objFrom'=>$this, 'clonedObj'=>$object);
1526  $action = '';
1527  $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1528  if ($reshook < 0) {
1529  $this->setErrorsFromObject($hookmanager);
1530  $error++;
1531  }
1532  }
1533  }
1534 
1535  unset($object->context['createfromclone']);
1536 
1537  // End
1538  if (!$error) {
1539  $this->db->commit();
1540  return $object->id;
1541  } else {
1542  $this->db->rollback();
1543  return -1;
1544  }
1545  }
1546 
1556  public function fetch($rowid, $ref = '', $ref_ext = '', $forceentity = 0)
1557  {
1558  $sql = "SELECT p.rowid, p.ref, p.entity, p.remise, p.remise_percent, p.remise_absolue, p.fk_soc";
1559  $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1560  $sql .= ", p.datec";
1561  $sql .= ", p.date_signature as dates";
1562  $sql .= ", p.date_valid as datev";
1563  $sql .= ", p.datep as dp";
1564  $sql .= ", p.fin_validite as dfv";
1565  $sql .= ", p.date_livraison as delivery_date";
1566  $sql .= ", p.model_pdf, p.last_main_doc, p.ref_client, ref_ext, p.extraparams";
1567  $sql .= ", p.note_private, p.note_public";
1568  $sql .= ", p.fk_projet as fk_project, p.fk_statut";
1569  $sql .= ", p.fk_user_author, p.fk_user_valid, p.fk_user_cloture";
1570  $sql .= ", p.fk_delivery_address";
1571  $sql .= ", p.fk_availability";
1572  $sql .= ", p.fk_input_reason";
1573  $sql .= ", p.fk_cond_reglement";
1574  $sql .= ", p.fk_mode_reglement";
1575  $sql .= ', p.fk_account';
1576  $sql .= ", p.fk_shipping_method";
1577  $sql .= ", p.fk_warehouse";
1578  $sql .= ", p.fk_incoterms, p.location_incoterms";
1579  $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1580  $sql .= ", p.tms as date_modification";
1581  $sql .= ", i.libelle as label_incoterms";
1582  $sql .= ", c.label as statut_label";
1583  $sql .= ", ca.code as availability_code, ca.label as availability";
1584  $sql .= ", dr.code as demand_reason_code, dr.label as demand_reason";
1585  $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1586  $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1587  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
1588  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_propalst as c ON p.fk_statut = c.id';
1589  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id AND cp.entity IN ('.getEntity('c_paiement').')';
1590  $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').')';
1591  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON p.fk_availability = ca.rowid';
1592  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON p.fk_input_reason = dr.rowid';
1593  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON p.fk_incoterms = i.rowid';
1594 
1595  if (!empty($ref)) {
1596  if (!empty($forceentity)) {
1597  $sql .= " WHERE p.entity = ".(int) $forceentity; // Check only the current entity because we may have the same reference in several entities
1598  } else {
1599  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
1600  }
1601  $sql .= " AND p.ref='".$this->db->escape($ref)."'";
1602  } else {
1603  // Dont't use entity if you use rowid
1604  $sql .= " WHERE p.rowid = ".((int) $rowid);
1605  }
1606 
1607  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1608  $resql = $this->db->query($sql);
1609  if ($resql) {
1610  if ($this->db->num_rows($resql)) {
1611  $obj = $this->db->fetch_object($resql);
1612 
1613  $this->id = $obj->rowid;
1614  $this->entity = $obj->entity;
1615 
1616  $this->ref = $obj->ref;
1617  $this->ref_client = $obj->ref_client;
1618  $this->ref_customer = $obj->ref_client;
1619  $this->ref_ext = $obj->ref_ext;
1620 
1621  $this->remise = $obj->remise; // TODO deprecated
1622  $this->remise_percent = $obj->remise_percent; // TODO deprecated
1623  $this->remise_absolue = $obj->remise_absolue; // TODO deprecated
1624  $this->total = $obj->total_ttc; // TODO deprecated
1625  $this->total_ttc = $obj->total_ttc;
1626  $this->total_ht = $obj->total_ht;
1627  $this->total_tva = $obj->total_tva;
1628  $this->total_localtax1 = $obj->localtax1;
1629  $this->total_localtax2 = $obj->localtax2;
1630 
1631  $this->socid = $obj->fk_soc;
1632  $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1633 
1634  $this->fk_project = $obj->fk_project;
1635  $this->project = null; // Clear if another value was already set by fetch_projet
1636 
1637  $this->model_pdf = $obj->model_pdf;
1638  $this->modelpdf = $obj->model_pdf; // deprecated
1639  $this->last_main_doc = $obj->last_main_doc;
1640  $this->note = $obj->note_private; // TODO deprecated
1641  $this->note_private = $obj->note_private;
1642  $this->note_public = $obj->note_public;
1643 
1644  $this->status = (int) $obj->fk_statut;
1645  $this->statut = $this->status; // deprecated
1646 
1647  $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1648  $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1649  $this->date_creation = $this->db->jdate($obj->datec); //Creation date
1650  $this->date_validation = $this->db->jdate($obj->datev); //Validation date
1651  $this->date_modification = $this->db->jdate($obj->date_modification); // tms
1652  $this->date_signature = $this->db->jdate($obj->dates); // Signature date
1653  $this->date = $this->db->jdate($obj->dp); // Proposal date
1654  $this->datep = $this->db->jdate($obj->dp); // deprecated
1655  $this->fin_validite = $this->db->jdate($obj->dfv);
1656  $this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
1657  $this->delivery_date = $this->db->jdate($obj->delivery_date);
1658  $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1659  $this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
1660  $this->availability_id = $obj->fk_availability;
1661  $this->availability_code = $obj->availability_code;
1662  $this->availability = $obj->availability;
1663  $this->demand_reason_id = $obj->fk_input_reason;
1664  $this->demand_reason_code = $obj->demand_reason_code;
1665  $this->demand_reason = $obj->demand_reason;
1666  $this->fk_address = $obj->fk_delivery_address;
1667 
1668  $this->mode_reglement_id = $obj->fk_mode_reglement;
1669  $this->mode_reglement_code = $obj->mode_reglement_code;
1670  $this->mode_reglement = $obj->mode_reglement;
1671  $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1672  $this->cond_reglement_id = $obj->fk_cond_reglement;
1673  $this->cond_reglement_code = $obj->cond_reglement_code;
1674  $this->cond_reglement = $obj->cond_reglement;
1675  $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1676  $this->deposit_percent = $obj->deposit_percent;
1677 
1678  $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1679 
1680  $this->user_author_id = $obj->fk_user_author;
1681  $this->user_valid_id = $obj->fk_user_valid;
1682  $this->user_close_id = $obj->fk_user_cloture;
1683 
1684  //Incoterms
1685  $this->fk_incoterms = $obj->fk_incoterms;
1686  $this->location_incoterms = $obj->location_incoterms;
1687  $this->label_incoterms = $obj->label_incoterms;
1688 
1689  // Multicurrency
1690  $this->fk_multicurrency = $obj->fk_multicurrency;
1691  $this->multicurrency_code = $obj->multicurrency_code;
1692  $this->multicurrency_tx = $obj->multicurrency_tx;
1693  $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1694  $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1695  $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1696 
1697  if ($obj->fk_statut == self::STATUS_DRAFT) {
1698  $this->brouillon = 1;
1699  }
1700 
1701  // Retrieve all extrafield
1702  // fetch optionals attributes and labels
1703  $this->fetch_optionals();
1704 
1705  $this->db->free($resql);
1706 
1707  $this->lines = array();
1708 
1709  // Lines
1710  $result = $this->fetch_lines();
1711  if ($result < 0) {
1712  return -3;
1713  }
1714 
1715  return 1;
1716  }
1717 
1718  $this->error = "Record Not Found";
1719  return 0;
1720  } else {
1721  $this->error = $this->db->lasterror();
1722  return -1;
1723  }
1724  }
1725 
1733  public function update(User $user, $notrigger = 0)
1734  {
1735  global $conf;
1736 
1737  $error = 0;
1738 
1739  // Clean parameters
1740  if (isset($this->ref)) {
1741  $this->ref = trim($this->ref);
1742  }
1743  if (isset($this->ref_client)) {
1744  $this->ref_client = trim($this->ref_client);
1745  }
1746  if (isset($this->note) || isset($this->note_private)) {
1747  $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1748  }
1749  if (isset($this->note_public)) {
1750  $this->note_public = trim($this->note_public);
1751  }
1752  if (isset($this->model_pdf)) {
1753  $this->model_pdf = trim($this->model_pdf);
1754  }
1755  if (isset($this->import_key)) {
1756  $this->import_key = trim($this->import_key);
1757  }
1758  if (!empty($this->duree_validite) && is_numeric($this->duree_validite)) {
1759  $this->fin_validite = $this->date + ($this->duree_validite * 24 * 3600);
1760  }
1761 
1762  // Check parameters
1763  // Put here code to add control on parameters values
1764 
1765  // Update request
1766  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET";
1767  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1768  $sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1769  $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1770  $sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1771  $sql .= " datep=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1772  if (!empty($this->fin_validite)) {
1773  $sql .= " fin_validite=".(strval($this->fin_validite) != '' ? "'".$this->db->idate($this->fin_validite)."'" : 'null').",";
1774  }
1775  $sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1776  $sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1777  $sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1778  $sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1779  $sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1780  $sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1781  $sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1782  $sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1783  $sql .= " fk_user_valid=".(isset($this->user_valid) ? $this->user_valid : "null").",";
1784  $sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1785  $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1786  $sql .= " deposit_percent=".(!empty($this->deposit_percent) ? "'".$this->db->escape($this->deposit_percent)."'" : "null").",";
1787  $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1788  $sql .= " fk_input_reason=".(isset($this->demand_reason_id) ? $this->demand_reason_id : "null").",";
1789  $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1790  $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1791  $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1792  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1793  $sql .= " WHERE rowid=".((int) $this->id);
1794 
1795  $this->db->begin();
1796 
1797  dol_syslog(get_class($this)."::update", LOG_DEBUG);
1798  $resql = $this->db->query($sql);
1799  if (!$resql) {
1800  $error++;
1801  $this->errors[] = "Error ".$this->db->lasterror();
1802  }
1803 
1804  if (!$error) {
1805  $result = $this->insertExtraFields();
1806  if ($result < 0) {
1807  $error++;
1808  }
1809  }
1810 
1811  if (!$error && !$notrigger) {
1812  // Call trigger
1813  $result = $this->call_trigger('PROPAL_MODIFY', $user);
1814  if ($result < 0) {
1815  $error++;
1816  }
1817  // End call triggers
1818  }
1819 
1820  // Commit or rollback
1821  if ($error) {
1822  foreach ($this->errors as $errmsg) {
1823  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1824  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1825  }
1826  $this->db->rollback();
1827  return -1 * $error;
1828  } else {
1829  $this->db->commit();
1830  return 1;
1831  }
1832  }
1833 
1834 
1835  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1845  public function fetch_lines($only_product = 0, $loadalsotranslation = 0, $filters = '')
1846  {
1847  // phpcs:enable
1848  global $langs, $conf;
1849 
1850  $this->lines = array();
1851 
1852  $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,';
1853  $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,';
1854  $sql .= ' d.fk_unit,';
1855  $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,';
1856  $sql .= ' p.weight, p.weight_units, p.volume, p.volume_units,';
1857  $sql .= ' d.date_start, d.date_end,';
1858  $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc';
1859  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as d';
1860  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (d.fk_product = p.rowid)';
1861  $sql .= ' WHERE d.fk_propal = '.((int) $this->id);
1862  if ($only_product) {
1863  $sql .= ' AND p.fk_product_type = 0';
1864  }
1865  if ($filters) {
1866  $sql .= $filters;
1867  }
1868  $sql .= ' ORDER by d.rang';
1869 
1870  dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1871  $result = $this->db->query($sql);
1872  if ($result) {
1873  require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
1874 
1875  $num = $this->db->num_rows($result);
1876 
1877  $i = 0;
1878  while ($i < $num) {
1879  $objp = $this->db->fetch_object($result);
1880 
1881  $line = new PropaleLigne($this->db);
1882 
1883  $line->rowid = $objp->rowid; //Deprecated
1884  $line->id = $objp->rowid;
1885  $line->fk_propal = $objp->fk_propal;
1886  $line->fk_parent_line = $objp->fk_parent_line;
1887  $line->product_type = $objp->product_type;
1888  $line->label = $objp->custom_label;
1889  $line->desc = $objp->description; // Description ligne
1890  $line->description = $objp->description; // Description ligne
1891  $line->qty = $objp->qty;
1892  $line->vat_src_code = $objp->vat_src_code;
1893  $line->tva_tx = $objp->tva_tx;
1894  $line->localtax1_tx = $objp->localtax1_tx;
1895  $line->localtax2_tx = $objp->localtax2_tx;
1896  $line->localtax1_type = $objp->localtax1_type;
1897  $line->localtax2_type = $objp->localtax2_type;
1898  $line->subprice = $objp->subprice;
1899  $line->fk_remise_except = $objp->fk_remise_except;
1900  $line->remise_percent = $objp->remise_percent;
1901  $line->price = $objp->price; // TODO deprecated
1902 
1903  $line->info_bits = $objp->info_bits;
1904  $line->total_ht = $objp->total_ht;
1905  $line->total_tva = $objp->total_tva;
1906  $line->total_localtax1 = $objp->total_localtax1;
1907  $line->total_localtax2 = $objp->total_localtax2;
1908  $line->total_ttc = $objp->total_ttc;
1909  $line->fk_fournprice = $objp->fk_fournprice;
1910  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1911  $line->pa_ht = $marginInfos[0];
1912  $line->marge_tx = $marginInfos[1];
1913  $line->marque_tx = $marginInfos[2];
1914  $line->special_code = $objp->special_code;
1915  $line->rang = $objp->rang;
1916 
1917  $line->fk_product = $objp->fk_product;
1918 
1919  $line->ref = $objp->product_ref; // deprecated
1920  $line->libelle = $objp->product_label; // deprecated
1921 
1922  $line->product_ref = $objp->product_ref;
1923  $line->product_label = $objp->product_label;
1924  $line->product_desc = $objp->product_desc; // Description produit
1925  $line->product_tobatch = $objp->product_tobatch;
1926  $line->product_barcode = $objp->product_barcode;
1927 
1928  $line->fk_product_type = $objp->fk_product_type; // deprecated
1929  $line->fk_unit = $objp->fk_unit;
1930  $line->weight = $objp->weight;
1931  $line->weight_units = $objp->weight_units;
1932  $line->volume = $objp->volume;
1933  $line->volume_units = $objp->volume_units;
1934 
1935  $line->date_start = $this->db->jdate($objp->date_start);
1936  $line->date_end = $this->db->jdate($objp->date_end);
1937 
1938  // Multicurrency
1939  $line->fk_multicurrency = $objp->fk_multicurrency;
1940  $line->multicurrency_code = $objp->multicurrency_code;
1941  $line->multicurrency_subprice = $objp->multicurrency_subprice;
1942  $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1943  $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1944  $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1945 
1946  $line->fetch_optionals();
1947 
1948  // multilangs
1949  if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1950  $tmpproduct = new Product($this->db);
1951  $tmpproduct->fetch($objp->fk_product);
1952  $tmpproduct->getMultiLangs();
1953 
1954  $line->multilangs = $tmpproduct->multilangs;
1955  }
1956 
1957  $this->lines[$i] = $line;
1958 
1959  $i++;
1960  }
1961 
1962  $this->db->free($result);
1963 
1964  return $num;
1965  } else {
1966  $this->error = $this->db->lasterror();
1967  return -3;
1968  }
1969  }
1970 
1978  public function valid($user, $notrigger = 0)
1979  {
1980  global $conf;
1981 
1982  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1983 
1984  $error = 0;
1985 
1986  // Protection
1987  if ($this->statut == self::STATUS_VALIDATED) {
1988  dol_syslog(get_class($this)."::valid action abandonned: already validated", LOG_WARNING);
1989  return 0;
1990  }
1991 
1992  if (!((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->creer))
1993  || (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->propal->propal_advance->validate)))) {
1994  $this->error = 'ErrorPermissionDenied';
1995  dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
1996  return -1;
1997  }
1998 
1999  $now = dol_now();
2000 
2001  $this->db->begin();
2002 
2003  // Numbering module definition
2004  $soc = new Societe($this->db);
2005  $soc->fetch($this->socid);
2006 
2007  // Define new ref
2008  if (!$error && (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
2009  $num = $this->getNextNumRef($soc);
2010  } else {
2011  $num = $this->ref;
2012  }
2013  $this->newref = dol_sanitizeFileName($num);
2014 
2015  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2016  $sql .= " SET ref = '".$this->db->escape($num)."',";
2017  $sql .= " fk_statut = ".self::STATUS_VALIDATED.", date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
2018  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2019 
2020  dol_syslog(get_class($this)."::valid", LOG_DEBUG);
2021  $resql = $this->db->query($sql);
2022  if (!$resql) {
2023  dol_print_error($this->db);
2024  $error++;
2025  }
2026 
2027  // Trigger calls
2028  if (!$error && !$notrigger) {
2029  // Call trigger
2030  $result = $this->call_trigger('PROPAL_VALIDATE', $user);
2031  if ($result < 0) {
2032  $error++;
2033  }
2034  // End call triggers
2035  }
2036 
2037  if (!$error) {
2038  $this->oldref = $this->ref;
2039 
2040  // Rename directory if dir was a temporary ref
2041  if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
2042  // Now we rename also files into index
2043  $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)."'";
2044  $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'propale/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
2045  $resql = $this->db->query($sql);
2046  if (!$resql) {
2047  $error++;
2048  $this->error = $this->db->lasterror();
2049  }
2050 
2051  // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2052  $oldref = dol_sanitizeFileName($this->ref);
2053  $newref = dol_sanitizeFileName($num);
2054  $dirsource = $conf->propal->multidir_output[$this->entity].'/'.$oldref;
2055  $dirdest = $conf->propal->multidir_output[$this->entity].'/'.$newref;
2056  if (!$error && file_exists($dirsource)) {
2057  dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2058  if (@rename($dirsource, $dirdest)) {
2059  dol_syslog("Rename ok");
2060  // Rename docs starting with $oldref with $newref
2061  $listoffiles = dol_dir_list($dirdest, 'files', 1, '^'.preg_quote($oldref, '/'));
2062  foreach ($listoffiles as $fileentry) {
2063  $dirsource = $fileentry['name'];
2064  $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2065  $dirsource = $fileentry['path'].'/'.$dirsource;
2066  $dirdest = $fileentry['path'].'/'.$dirdest;
2067  @rename($dirsource, $dirdest);
2068  }
2069  }
2070  }
2071  }
2072 
2073  $this->ref = $num;
2074  $this->brouillon = 0;
2075  $this->statut = self::STATUS_VALIDATED;
2076  $this->user_valid_id = $user->id;
2077  $this->datev = $now;
2078 
2079  $this->db->commit();
2080  return 1;
2081  } else {
2082  $this->db->rollback();
2083  return -1;
2084  }
2085  }
2086 
2087 
2088  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2097  public function set_date($user, $date, $notrigger = 0)
2098  {
2099  // phpcs:enable
2100  if (empty($date)) {
2101  $this->error = 'ErrorBadParameter';
2102  dol_syslog(get_class($this)."::set_date ".$this->error, LOG_ERR);
2103  return -1;
2104  }
2105 
2106  if (!empty($user->rights->propal->creer)) {
2107  $error = 0;
2108 
2109  $this->db->begin();
2110 
2111  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET datep = '".$this->db->idate($date)."'";
2112  $sql .= " WHERE rowid = ".((int) $this->id);
2113 
2114  dol_syslog(__METHOD__, LOG_DEBUG);
2115  $resql = $this->db->query($sql);
2116  if (!$resql) {
2117  $this->errors[] = $this->db->error();
2118  $error++;
2119  }
2120 
2121  if (!$error) {
2122  $this->oldcopy = clone $this;
2123  $this->date = $date;
2124  $this->datep = $date; // deprecated
2125  }
2126 
2127  if (!$notrigger && empty($error)) {
2128  // Call trigger
2129  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2130  if ($result < 0) {
2131  $error++;
2132  }
2133  // End call triggers
2134  }
2135 
2136  if (!$error) {
2137  $this->db->commit();
2138  return 1;
2139  } else {
2140  foreach ($this->errors as $errmsg) {
2141  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2142  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2143  }
2144  $this->db->rollback();
2145  return -1 * $error;
2146  }
2147  }
2148 
2149  return -1;
2150  }
2151 
2152  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2161  public function set_echeance($user, $date_end_validity, $notrigger = 0)
2162  {
2163  // phpcs:enable
2164  if (!empty($user->rights->propal->creer)) {
2165  $error = 0;
2166 
2167  $this->db->begin();
2168 
2169  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET fin_validite = ".($date_end_validity != '' ? "'".$this->db->idate($date_end_validity)."'" : 'null');
2170  $sql .= " WHERE rowid = ".((int) $this->id);
2171 
2172  dol_syslog(__METHOD__, LOG_DEBUG);
2173 
2174  $resql = $this->db->query($sql);
2175  if (!$resql) {
2176  $this->errors[] = $this->db->error();
2177  $error++;
2178  }
2179 
2180 
2181  if (!$error) {
2182  $this->oldcopy = clone $this;
2183  $this->fin_validite = $date_end_validity;
2184  }
2185 
2186  if (!$notrigger && empty($error)) {
2187  // Call trigger
2188  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2189  if ($result < 0) {
2190  $error++;
2191  }
2192  // End call triggers
2193  }
2194 
2195  if (!$error) {
2196  $this->db->commit();
2197  return 1;
2198  } else {
2199  foreach ($this->errors as $errmsg) {
2200  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2201  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2202  }
2203  $this->db->rollback();
2204  return -1 * $error;
2205  }
2206  }
2207 
2208  return -1;
2209  }
2210 
2211  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2221  public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2222  {
2223  // phpcs:enable
2224  return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2225  }
2226 
2235  public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2236  {
2237  if (!empty($user->rights->propal->creer)) {
2238  $error = 0;
2239 
2240  $this->db->begin();
2241 
2242  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2243  $sql .= " SET date_livraison = ".($delivery_date != '' ? "'".$this->db->idate($delivery_date)."'" : 'null');
2244  $sql .= " WHERE rowid = ".((int) $this->id);
2245 
2246  dol_syslog(__METHOD__, LOG_DEBUG);
2247  $resql = $this->db->query($sql);
2248  if (!$resql) {
2249  $this->errors[] = $this->db->error();
2250  $error++;
2251  }
2252 
2253  if (!$error) {
2254  $this->oldcopy = clone $this;
2255  $this->date_livraison = $delivery_date;
2256  $this->delivery_date = $delivery_date;
2257  }
2258 
2259  if (!$notrigger && empty($error)) {
2260  // Call trigger
2261  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2262  if ($result < 0) {
2263  $error++;
2264  }
2265  // End call triggers
2266  }
2267 
2268  if (!$error) {
2269  $this->db->commit();
2270  return 1;
2271  } else {
2272  foreach ($this->errors as $errmsg) {
2273  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2274  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2275  }
2276  $this->db->rollback();
2277  return -1 * $error;
2278  }
2279  }
2280 
2281  return -1;
2282  }
2283 
2284  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2293  public function set_availability($user, $id, $notrigger = 0)
2294  {
2295  // phpcs:enable
2296  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2297  $error = 0;
2298 
2299  $this->db->begin();
2300 
2301  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2302  $sql .= " SET fk_availability = ".((int) $id);
2303  $sql .= " WHERE rowid = ".((int) $this->id);
2304 
2305  dol_syslog(__METHOD__.' availability('.$id.')', LOG_DEBUG);
2306  $resql = $this->db->query($sql);
2307  if (!$resql) {
2308  $this->errors[] = $this->db->error();
2309  $error++;
2310  }
2311 
2312  if (!$error) {
2313  $this->oldcopy = clone $this;
2314  $this->fk_availability = $id;
2315  $this->availability_id = $id;
2316  }
2317 
2318  if (!$notrigger && empty($error)) {
2319  // Call trigger
2320  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2321  if ($result < 0) {
2322  $error++;
2323  }
2324  // End call triggers
2325  }
2326 
2327  if (!$error) {
2328  $this->db->commit();
2329  return 1;
2330  } else {
2331  foreach ($this->errors as $errmsg) {
2332  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2333  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2334  }
2335  $this->db->rollback();
2336  return -1 * $error;
2337  }
2338  } else {
2339  $error_str = 'Propal status do not meet requirement '.$this->statut;
2340  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2341  $this->error = $error_str;
2342  $this->errors[] = $this->error;
2343  return -2;
2344  }
2345  }
2346 
2347  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2356  public function set_demand_reason($user, $id, $notrigger = 0)
2357  {
2358  // phpcs:enable
2359  if (!empty($user->rights->propal->creer) && $this->statut >= self::STATUS_DRAFT) {
2360  $error = 0;
2361 
2362  $this->db->begin();
2363 
2364  $sql = "UPDATE ".MAIN_DB_PREFIX."propal ";
2365  $sql .= " SET fk_input_reason = ".((int) $id);
2366  $sql .= " WHERE rowid = ".((int) $this->id);
2367 
2368  dol_syslog(__METHOD__, LOG_DEBUG);
2369  $resql = $this->db->query($sql);
2370  if (!$resql) {
2371  $this->errors[] = $this->db->error();
2372  $error++;
2373  }
2374 
2375 
2376  if (!$error) {
2377  $this->oldcopy = clone $this;
2378  $this->fk_input_reason = $id;
2379  $this->demand_reason_id = $id;
2380  }
2381 
2382 
2383  if (!$notrigger && empty($error)) {
2384  // Call trigger
2385  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2386  if ($result < 0) {
2387  $error++;
2388  }
2389  // End call triggers
2390  }
2391 
2392  if (!$error) {
2393  $this->db->commit();
2394  return 1;
2395  } else {
2396  foreach ($this->errors as $errmsg) {
2397  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2398  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2399  }
2400  $this->db->rollback();
2401  return -1 * $error;
2402  }
2403  } else {
2404  $error_str = 'Propal status do not meet requirement '.$this->statut;
2405  dol_syslog(__METHOD__.$error_str, LOG_ERR);
2406  $this->error = $error_str;
2407  $this->errors[] = $this->error;
2408  return -2;
2409  }
2410  }
2411 
2412  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2421  public function set_ref_client($user, $ref_client, $notrigger = 0)
2422  {
2423  // phpcs:enable
2424  if (!empty($user->rights->propal->creer)) {
2425  $error = 0;
2426 
2427  $this->db->begin();
2428 
2429  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET ref_client = ".(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2430  $sql .= " WHERE rowid = ".((int) $this->id);
2431 
2432  dol_syslog(__METHOD__.' $this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2433  $resql = $this->db->query($sql);
2434  if (!$resql) {
2435  $this->errors[] = $this->db->error();
2436  $error++;
2437  }
2438 
2439  if (!$error) {
2440  $this->oldcopy = clone $this;
2441  $this->ref_client = $ref_client;
2442  }
2443 
2444  if (!$notrigger && empty($error)) {
2445  // Call trigger
2446  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2447  if ($result < 0) {
2448  $error++;
2449  }
2450  // End call triggers
2451  }
2452 
2453  if (!$error) {
2454  $this->db->commit();
2455  return 1;
2456  } else {
2457  foreach ($this->errors as $errmsg) {
2458  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2459  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2460  }
2461  $this->db->rollback();
2462  return -1 * $error;
2463  }
2464  }
2465 
2466  return -1;
2467  }
2468 
2469  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2479  public function set_remise_percent($user, $remise, $notrigger = 0)
2480  {
2481  // phpcs:enable
2482  $remise = trim($remise) ?trim($remise) : 0;
2483 
2484  if (!empty($user->rights->propal->creer)) {
2485  $remise = price2num($remise, 2);
2486 
2487  $error = 0;
2488 
2489  $this->db->begin();
2490 
2491  $sql = "UPDATE ".MAIN_DB_PREFIX."propal SET remise_percent = ".((float) $remise);
2492  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2493 
2494  dol_syslog(__METHOD__, LOG_DEBUG);
2495  $resql = $this->db->query($sql);
2496  if (!$resql) {
2497  $this->errors[] = $this->db->error();
2498  $error++;
2499  }
2500 
2501  if (!$error) {
2502  $this->oldcopy = clone $this;
2503  $this->remise_percent = $remise;
2504  $this->update_price(1);
2505  }
2506 
2507  if (!$notrigger && empty($error)) {
2508  // Call trigger
2509  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2510  if ($result < 0) {
2511  $error++;
2512  }
2513  // End call triggers
2514  }
2515 
2516  if (!$error) {
2517  $this->db->commit();
2518  return 1;
2519  } else {
2520  foreach ($this->errors as $errmsg) {
2521  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2522  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2523  }
2524  $this->db->rollback();
2525  return -1 * $error;
2526  }
2527  }
2528 
2529  return -1;
2530  }
2531 
2532 
2533  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2542  public function set_remise_absolue($user, $remise, $notrigger = 0)
2543  {
2544  // phpcs:enable
2545  if (empty($remise)) {
2546  $remise = 0;
2547  }
2549 
2550  if (!empty($user->rights->propal->creer)) {
2551  $error = 0;
2552 
2553  $this->db->begin();
2554 
2555  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2556  $sql .= " SET remise_absolue = ".((float) $remise);
2557  $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".self::STATUS_DRAFT;
2558 
2559  dol_syslog(__METHOD__, LOG_DEBUG);
2560  $resql = $this->db->query($sql);
2561  if (!$resql) {
2562  $this->errors[] = $this->db->error();
2563  $error++;
2564  }
2565 
2566  if (!$error) {
2567  $this->oldcopy = clone $this;
2568  $this->update_price(1);
2569  }
2570 
2571  if (!$notrigger && empty($error)) {
2572  // Call trigger
2573  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2574  if ($result < 0) {
2575  $error++;
2576  }
2577  // End call triggers
2578  }
2579 
2580  if (!$error) {
2581  $this->db->commit();
2582  return 1;
2583  } else {
2584  foreach ($this->errors as $errmsg) {
2585  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2586  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2587  }
2588  $this->db->rollback();
2589  return -1 * $error;
2590  }
2591  }
2592 
2593  return -1;
2594  }
2595 
2596 
2597 
2607  public function reopen($user, $status, $note = '', $notrigger = 0)
2608  {
2609  $error = 0;
2610 
2611  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2612  $sql .= " SET fk_statut = ".((int) $status).",";
2613  if (!empty($note)) {
2614  $sql .= " note_private = '".$this->db->escape($note)."',";
2615  }
2616  $sql .= " date_cloture=NULL, fk_user_cloture=NULL";
2617  $sql .= " WHERE rowid = ".((int) $this->id);
2618 
2619  $this->db->begin();
2620 
2621  dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
2622  $resql = $this->db->query($sql);
2623  if (!$resql) {
2624  $error++;
2625  $this->errors[] = "Error ".$this->db->lasterror();
2626  }
2627  if (!$error) {
2628  if (!$notrigger) {
2629  // Call trigger
2630  $result = $this->call_trigger('PROPAL_REOPEN', $user);
2631  if ($result < 0) {
2632  $error++;
2633  }
2634  // End call triggers
2635  }
2636  }
2637 
2638  // Commit or rollback
2639  if ($error) {
2640  if (!empty($this->errors)) {
2641  foreach ($this->errors as $errmsg) {
2642  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2643  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2644  }
2645  }
2646  $this->db->rollback();
2647  return -1 * $error;
2648  } else {
2649  $this->statut = $status;
2650  $this->status = $status;
2651 
2652  $this->db->commit();
2653  return 1;
2654  }
2655  }
2656 
2666  public function closeProposal($user, $status, $note = '', $notrigger = 0)
2667  {
2668  global $langs,$conf;
2669 
2670  $error = 0;
2671  $now = dol_now();
2672 
2673  $this->db->begin();
2674 
2675  $newprivatenote = dol_concatdesc($this->note_private, $note);
2676 
2677  if (empty($conf->global->PROPALE_KEEP_OLD_SIGNATURE_INFO)) {
2678  $date_signature = $now;
2679  $fk_user_signature = $user->id;
2680  } else {
2681  $this->info($this->id);
2682  if (!isset($this->date_signature) || $this->date_signature == '') {
2683  $date_signature = $now;
2684  $fk_user_signature = $user->id;
2685  } else {
2686  $date_signature = $this->date_signature;
2687  $fk_user_signature = $this->user_signature->id;
2688  }
2689  }
2690 
2691  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2692  $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;
2693  $sql .= " WHERE rowid = ".((int) $this->id);
2694 
2695  $resql = $this->db->query($sql);
2696  if ($resql) {
2697  // Status self::STATUS_REFUSED by default
2698  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_CLOSED) ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2699  $trigger_name = 'PROPAL_CLOSE_REFUSED'; // used later in call_trigger()
2700 
2701  if ($status == self::STATUS_SIGNED) { // Status self::STATUS_SIGNED
2702  $trigger_name = 'PROPAL_CLOSE_SIGNED'; // used later in call_trigger()
2703  $modelpdf = !empty($conf->global->PROPALE_ADDON_PDF_ODT_TOBILL) ? $conf->global->PROPALE_ADDON_PDF_ODT_TOBILL : $this->model_pdf;
2704 
2705  // The connected company is classified as a client
2706  $soc=new Societe($this->db);
2707  $soc->id = $this->socid;
2708  $result = $soc->set_as_client();
2709 
2710  if ($result < 0) {
2711  $this->error=$this->db->lasterror();
2712  $this->db->rollback();
2713  return -2;
2714  }
2715  }
2716 
2717  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2718  // Define output language
2719  $outputlangs = $langs;
2720  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2721  $outputlangs = new Translate("", $conf);
2722  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2723  $outputlangs->setDefaultLang($newlang);
2724  }
2725 
2726  // PDF
2727  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2728  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2729  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2730 
2731  //$ret=$object->fetch($id); // Reload to get new records
2732  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2733  }
2734 
2735  if (!$error) {
2736  $this->oldcopy= clone $this;
2737  $this->statut = $status;
2738  $this->status = $status;
2739  $this->date_signature = $date_signature;
2740  $this->note_private = $newprivatenote;
2741  }
2742 
2743  if (!$notrigger && empty($error)) {
2744  // Call trigger
2745  $result=$this->call_trigger($trigger_name, $user);
2746  if ($result < 0) {
2747  $error++;
2748  }
2749  // End call triggers
2750  }
2751 
2752  if (!$error ) {
2753  $this->db->commit();
2754  return 1;
2755  } else {
2756  $this->statut = $this->oldcopy->statut;
2757  $this->status = $this->oldcopy->statut;
2758  $this->date_signature = $this->oldcopy->date_signature;
2759  $this->note_private = $this->oldcopy->note_private;
2760 
2761  $this->db->rollback();
2762  return -1;
2763  }
2764  } else {
2765  $this->error = $this->db->lasterror();
2766  $this->db->rollback();
2767  return -1;
2768  }
2769  }
2770 
2779  public function classifyBilled(User $user, $notrigger = 0, $note = '')
2780  {
2781  global $conf, $langs;
2782 
2783  $error = 0;
2784 
2785  $now = dol_now();
2786  $num = 0;
2787 
2788  $triggerName = 'PROPAL_CLASSIFY_BILLED';
2789 
2790  $this->db->begin();
2791 
2792  $newprivatenote = dol_concatdesc($this->note_private, $note);
2793 
2794  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal SET fk_statut = '.self::STATUS_BILLED.", ";
2795  $sql .= " note_private = '".$this->db->escape($newprivatenote)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
2796  $sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_SIGNED);
2797 
2798  dol_syslog(__METHOD__, LOG_DEBUG);
2799  $resql = $this->db->query($sql);
2800  if (!$resql) {
2801  $this->errors[] = $this->db->error();
2802  $error++;
2803  } else {
2804  $num = $this->db->affected_rows($resql);
2805  }
2806 
2807  if (!$error) {
2808  $modelpdf = $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED ? $conf->global->PROPALE_ADDON_PDF_ODT_CLOSED : $this->model_pdf;
2809 
2810  if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
2811  // Define output language
2812  $outputlangs = $langs;
2813  if (getDolGlobalInt('MAIN_MULTILANGS')) {
2814  $outputlangs = new Translate("", $conf);
2815  $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
2816  $outputlangs->setDefaultLang($newlang);
2817  }
2818 
2819  // PDF
2820  $hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS) ? 1 : 0);
2821  $hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC) ? 1 : 0);
2822  $hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF) ? 1 : 0);
2823 
2824  //$ret=$object->fetch($id); // Reload to get new records
2825  $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
2826  }
2827 
2828  $this->oldcopy = clone $this;
2829  $this->statut = self::STATUS_BILLED;
2830  $this->date_cloture = $now;
2831  $this->note_private = $newprivatenote;
2832  }
2833 
2834  if (!$notrigger && empty($error)) {
2835  // Call trigger
2836  $result = $this->call_trigger($triggerName, $user);
2837  if ($result < 0) {
2838  $error++;
2839  }
2840  // End call triggers
2841  }
2842 
2843  if (!$error) {
2844  $this->db->commit();
2845  return $num;
2846  } else {
2847  foreach ($this->errors as $errmsg) {
2848  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2849  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2850  }
2851  $this->db->rollback();
2852  return -1 * $error;
2853  }
2854  }
2855 
2856  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2864  public function setDraft($user, $notrigger = 0)
2865  {
2866  // phpcs:enable
2867  $error = 0;
2868 
2869  // Protection
2870  if ($this->statut <= self::STATUS_DRAFT) {
2871  return 0;
2872  }
2873 
2874  dol_syslog(get_class($this)."::setDraft", LOG_DEBUG);
2875 
2876  $this->db->begin();
2877 
2878  $sql = "UPDATE ".MAIN_DB_PREFIX."propal";
2879  $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2880  $sql .= ", online_sign_ip = NULL , online_sign_name = NULL";
2881  $sql .= " WHERE rowid = ".((int) $this->id);
2882 
2883  $resql = $this->db->query($sql);
2884  if (!$resql) {
2885  $this->errors[] = $this->db->error();
2886  $error++;
2887  }
2888 
2889  if (!$error) {
2890  $this->oldcopy = clone $this;
2891  }
2892 
2893  if (!$notrigger && empty($error)) {
2894  // Call trigger
2895  $result = $this->call_trigger('PROPAL_MODIFY', $user);
2896  if ($result < 0) {
2897  $error++;
2898  }
2899  // End call triggers
2900  }
2901 
2902  if (!$error) {
2903  $this->statut = self::STATUS_DRAFT;
2904  $this->brouillon = 1;
2905 
2906  $this->db->commit();
2907  return 1;
2908  } else {
2909  foreach ($this->errors as $errmsg) {
2910  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2911  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2912  }
2913  $this->db->rollback();
2914  return -1 * $error;
2915  }
2916  }
2917 
2918 
2919  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2933  public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datep', $sortorder = 'DESC')
2934  {
2935  // phpcs:enable
2936  global $user;
2937 
2938  $ga = array();
2939 
2940  $sql = "SELECT s.rowid, s.nom as name, s.client,";
2941  $sql .= " p.rowid as propalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2942  $sql .= " p.datep as dp, p.fin_validite as datelimite";
2943  if (empty($user->rights->societe->client->voir) && !$socid) {
2944  $sql .= ", sc.fk_soc, sc.fk_user";
2945  }
2946  $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."propal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2947  if (empty($user->rights->societe->client->voir) && !$socid) {
2948  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
2949  }
2950  $sql .= " WHERE p.entity IN (".getEntity('propal').")";
2951  $sql .= " AND p.fk_soc = s.rowid";
2952  $sql .= " AND p.fk_statut = c.id";
2953  if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
2954  $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2955  }
2956  if ($socid) {
2957  $sql .= " AND s.rowid = ".((int) $socid);
2958  }
2959  if ($draft) {
2960  $sql .= " AND p.fk_statut = ".self::STATUS_DRAFT;
2961  }
2962  if ($notcurrentuser > 0) {
2963  $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2964  }
2965  $sql .= $this->db->order($sortfield, $sortorder);
2966  $sql .= $this->db->plimit($limit, $offset);
2967 
2968  $result = $this->db->query($sql);
2969  if ($result) {
2970  $num = $this->db->num_rows($result);
2971  if ($num) {
2972  $i = 0;
2973  while ($i < $num) {
2974  $obj = $this->db->fetch_object($result);
2975 
2976  if ($shortlist == 1) {
2977  $ga[$obj->propalid] = $obj->ref;
2978  } elseif ($shortlist == 2) {
2979  $ga[$obj->propalid] = $obj->ref.' ('.$obj->name.')';
2980  } else {
2981  $ga[$i]['id'] = $obj->propalid;
2982  $ga[$i]['ref'] = $obj->ref;
2983  $ga[$i]['name'] = $obj->name;
2984  }
2985 
2986  $i++;
2987  }
2988  }
2989  return $ga;
2990  } else {
2991  dol_print_error($this->db);
2992  return -1;
2993  }
2994  }
2995 
3001  public function getInvoiceArrayList()
3002  {
3003  return $this->InvoiceArrayList($this->id);
3004  }
3005 
3006  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3013  public function InvoiceArrayList($id)
3014  {
3015  // phpcs:enable
3016  $ga = array();
3017  $linkedInvoices = array();
3018 
3019  $this->fetchObjectLinked($id, $this->element);
3020  foreach ($this->linkedObjectsIds as $objecttype => $objectid) {
3021  // Nouveau système du comon object renvoi des rowid et non un id linéaire de 1 à n
3022  // On parcourt donc une liste d'objets en tant qu'objet unique
3023  foreach ($objectid as $key => $object) {
3024  // Cas des factures liees directement
3025  if ($objecttype == 'facture') {
3026  $linkedInvoices[] = $object;
3027  } else {
3028  // Cas des factures liees par un autre objet (ex: commande)
3029  $this->fetchObjectLinked($object, $objecttype);
3030  foreach ($this->linkedObjectsIds as $subobjecttype => $subobjectid) {
3031  foreach ($subobjectid as $subkey => $subobject) {
3032  if ($subobjecttype == 'facture') {
3033  $linkedInvoices[] = $subobject;
3034  }
3035  }
3036  }
3037  }
3038  }
3039  }
3040 
3041  if (count($linkedInvoices) > 0) {
3042  $sql = "SELECT rowid as facid, ref, total_ht as total, datef as df, fk_user_author, fk_statut, paye";
3043  $sql .= " FROM ".MAIN_DB_PREFIX."facture";
3044  $sql .= " WHERE rowid IN (".$this->db->sanitize(implode(',', $linkedInvoices)).")";
3045 
3046  dol_syslog(get_class($this)."::InvoiceArrayList", LOG_DEBUG);
3047  $resql = $this->db->query($sql);
3048 
3049  if ($resql) {
3050  $tab_sqlobj = array();
3051  $nump = $this->db->num_rows($resql);
3052  for ($i = 0; $i < $nump; $i++) {
3053  $sqlobj = $this->db->fetch_object($resql);
3054  $tab_sqlobj[] = $sqlobj;
3055  }
3056  $this->db->free($resql);
3057 
3058  $nump = count($tab_sqlobj);
3059 
3060  if ($nump) {
3061  $i = 0;
3062  while ($i < $nump) {
3063  $obj = array_shift($tab_sqlobj);
3064 
3065  $ga[$i] = $obj;
3066 
3067  $i++;
3068  }
3069  }
3070  return $ga;
3071  } else {
3072  return -1;
3073  }
3074  } else {
3075  return $ga;
3076  }
3077  }
3078 
3086  public function delete($user, $notrigger = 0)
3087  {
3088  global $conf;
3089  require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3090 
3091  $error = 0;
3092 
3093  $this->db->begin();
3094 
3095  if (!$notrigger) {
3096  // Call trigger
3097  $result = $this->call_trigger('PROPAL_DELETE', $user);
3098  if ($result < 0) {
3099  $error++;
3100  }
3101  // End call triggers
3102  }
3103 
3104  // Delete extrafields of lines and lines
3105  if (!$error && !empty($this->table_element_line)) {
3106  $tabletodelete = $this->table_element_line;
3107  $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).")";
3108  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
3109  if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
3110  $error++;
3111  $this->error = $this->db->lasterror();
3112  $this->errors[] = $this->error;
3113  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3114  }
3115  }
3116 
3117  if (!$error) {
3118  // Delete linked object
3119  $res = $this->deleteObjectLinked();
3120  if ($res < 0) {
3121  $error++;
3122  }
3123  }
3124 
3125  if (!$error) {
3126  // Delete linked contacts
3127  $res = $this->delete_linked_contact();
3128  if ($res < 0) {
3129  $error++;
3130  }
3131  }
3132 
3133  // Removed extrafields of object
3134  if (!$error) {
3135  $result = $this->deleteExtraFields();
3136  if ($result < 0) {
3137  $error++;
3138  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3139  }
3140  }
3141 
3142  // Delete main record
3143  if (!$error) {
3144  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
3145  $res = $this->db->query($sql);
3146  if (!$res) {
3147  $error++;
3148  $this->error = $this->db->lasterror();
3149  $this->errors[] = $this->error;
3150  dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
3151  }
3152  }
3153 
3154  // Delete record into ECM index and physically
3155  if (!$error) {
3156  $res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
3157  if (!$res) {
3158  $error++;
3159  }
3160  }
3161 
3162  if (!$error) {
3163  // We remove directory
3164  $ref = dol_sanitizeFileName($this->ref);
3165  if ($conf->propal->multidir_output[$this->entity] && !empty($this->ref)) {
3166  $dir = $conf->propal->multidir_output[$this->entity]."/".$ref;
3167  $file = $dir."/".$ref.".pdf";
3168  if (file_exists($file)) {
3169  dol_delete_preview($this);
3170 
3171  if (!dol_delete_file($file, 0, 0, 0, $this)) {
3172  $this->error = 'ErrorFailToDeleteFile';
3173  $this->errors[] = $this->error;
3174  $this->db->rollback();
3175  return 0;
3176  }
3177  }
3178  if (file_exists($dir)) {
3179  $res = @dol_delete_dir_recursive($dir);
3180  if (!$res) {
3181  $this->error = 'ErrorFailToDeleteDir';
3182  $this->errors[] = $this->error;
3183  $this->db->rollback();
3184  return 0;
3185  }
3186  }
3187  }
3188  }
3189 
3190  if (!$error) {
3191  dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
3192  $this->db->commit();
3193  return 1;
3194  } else {
3195  $this->db->rollback();
3196  return -1;
3197  }
3198  }
3199 
3208  public function availability($availability_id, $notrigger = 0)
3209  {
3210  global $user;
3211 
3212  if ($this->statut >= self::STATUS_DRAFT) {
3213  $error = 0;
3214 
3215  $this->db->begin();
3216 
3217  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3218  $sql .= ' SET fk_availability = '.((int) $availability_id);
3219  $sql .= ' WHERE rowid='.((int) $this->id);
3220 
3221  dol_syslog(__METHOD__.' availability('.$availability_id.')', LOG_DEBUG);
3222  $resql = $this->db->query($sql);
3223  if (!$resql) {
3224  $this->errors[] = $this->db->error();
3225  $error++;
3226  }
3227 
3228  if (!$error) {
3229  $this->oldcopy = clone $this;
3230  $this->availability_id = $availability_id;
3231  }
3232 
3233  if (!$notrigger && empty($error)) {
3234  // Call trigger
3235  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3236  if ($result < 0) {
3237  $error++;
3238  }
3239  // End call triggers
3240  }
3241 
3242  if (!$error) {
3243  $this->db->commit();
3244  return 1;
3245  } else {
3246  foreach ($this->errors as $errmsg) {
3247  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3248  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3249  }
3250  $this->db->rollback();
3251  return -1 * $error;
3252  }
3253  } else {
3254  $error_str = 'Propal status do not meet requirement '.$this->statut;
3255  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3256  $this->error = $error_str;
3257  $this->errors[] = $this->error;
3258  return -2;
3259  }
3260  }
3261 
3262  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3271  public function demand_reason($demand_reason_id, $notrigger = 0)
3272  {
3273  // phpcs:enable
3274  global $user;
3275 
3276  if ($this->statut >= self::STATUS_DRAFT) {
3277  $error = 0;
3278 
3279  $this->db->begin();
3280 
3281  $sql = 'UPDATE '.MAIN_DB_PREFIX.'propal';
3282  $sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
3283  $sql .= ' WHERE rowid='.((int) $this->id);
3284 
3285  dol_syslog(__METHOD__.' demand_reason('.$demand_reason_id.')', LOG_DEBUG);
3286  $resql = $this->db->query($sql);
3287  if (!$resql) {
3288  $this->errors[] = $this->db->error();
3289  $error++;
3290  }
3291 
3292  if (!$error) {
3293  $this->oldcopy = clone $this;
3294  $this->demand_reason_id = $demand_reason_id;
3295  }
3296 
3297  if (!$notrigger && empty($error)) {
3298  // Call trigger
3299  $result = $this->call_trigger('PROPAL_MODIFY', $user);
3300  if ($result < 0) {
3301  $error++;
3302  }
3303  // End call triggers
3304  }
3305 
3306  if (!$error) {
3307  $this->db->commit();
3308  return 1;
3309  } else {
3310  foreach ($this->errors as $errmsg) {
3311  dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3312  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3313  }
3314  $this->db->rollback();
3315  return -1 * $error;
3316  }
3317  } else {
3318  $error_str = 'Propal status do not meet requirement '.$this->statut;
3319  dol_syslog(__METHOD__.$error_str, LOG_ERR);
3320  $this->error = $error_str;
3321  $this->errors[] = $this->error;
3322  return -2;
3323  }
3324  }
3325 
3326 
3333  public function info($id)
3334  {
3335  $sql = "SELECT c.rowid, ";
3336  $sql .= " c.datec, c.date_valid as datev, c.date_signature, c.date_cloture,";
3337  $sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_signature, c.fk_user_cloture";
3338  $sql .= " FROM ".MAIN_DB_PREFIX."propal as c";
3339  $sql .= " WHERE c.rowid = ".((int) $id);
3340 
3341  $result = $this->db->query($sql);
3342 
3343  if ($result) {
3344  if ($this->db->num_rows($result)) {
3345  $obj = $this->db->fetch_object($result);
3346 
3347  $this->id = $obj->rowid;
3348 
3349  $this->date_creation = $this->db->jdate($obj->datec);
3350  $this->date_validation = $this->db->jdate($obj->datev);
3351  $this->date_signature = $this->db->jdate($obj->date_signature);
3352  $this->date_cloture = $this->db->jdate($obj->date_cloture);
3353 
3354  $cuser = new User($this->db);
3355  $cuser->fetch($obj->fk_user_author);
3356  $this->user_creation = $cuser;
3357 
3358  if ($obj->fk_user_valid) {
3359  $vuser = new User($this->db);
3360  $vuser->fetch($obj->fk_user_valid);
3361  $this->user_validation = $vuser;
3362  }
3363 
3364  if ($obj->fk_user_signature) {
3365  $user_signature = new User($this->db);
3366  $user_signature->fetch($obj->fk_user_signature);
3367  $this->user_signature = $user_signature;
3368  }
3369 
3370  if ($obj->fk_user_cloture) {
3371  $cluser = new User($this->db);
3372  $cluser->fetch($obj->fk_user_cloture);
3373  $this->user_cloture = $cluser;
3374  }
3375  }
3376  $this->db->free($result);
3377  } else {
3378  dol_print_error($this->db);
3379  }
3380  }
3381 
3382 
3389  public function getLibStatut($mode = 0)
3390  {
3391  return $this->LibStatut($this->statut, $mode);
3392  }
3393 
3394  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3402  public function LibStatut($status, $mode = 1)
3403  {
3404  // phpcs:enable
3405  global $conf, $hookmanager;
3406 
3407  // Init/load array of translation of status
3408  if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
3409  global $langs;
3410  $langs->load("propal");
3411  $this->labelStatus[0] = $langs->transnoentitiesnoconv("PropalStatusDraft");
3412  $this->labelStatus[1] = $langs->transnoentitiesnoconv("PropalStatusValidated");
3413  $this->labelStatus[2] = $langs->transnoentitiesnoconv("PropalStatusSigned");
3414  $this->labelStatus[3] = $langs->transnoentitiesnoconv("PropalStatusNotSigned");
3415  $this->labelStatus[4] = $langs->transnoentitiesnoconv("PropalStatusBilled");
3416  $this->labelStatusShort[0] = $langs->transnoentitiesnoconv("PropalStatusDraftShort");
3417  $this->labelStatusShort[1] = $langs->transnoentitiesnoconv("PropalStatusValidatedShort");
3418  $this->labelStatusShort[2] = $langs->transnoentitiesnoconv("PropalStatusSignedShort");
3419  $this->labelStatusShort[3] = $langs->transnoentitiesnoconv("PropalStatusNotSignedShort");
3420  $this->labelStatusShort[4] = $langs->transnoentitiesnoconv("PropalStatusBilledShort");
3421  }
3422 
3423  $statusType = '';
3424  if ($status == self::STATUS_DRAFT) {
3425  $statusType = 'status0';
3426  } elseif ($status == self::STATUS_VALIDATED) {
3427  $statusType = 'status1';
3428  } elseif ($status == self::STATUS_SIGNED) {
3429  $statusType = 'status4';
3430  } elseif ($status == self::STATUS_NOTSIGNED) {
3431  $statusType = 'status9';
3432  } elseif ($status == self::STATUS_BILLED) {
3433  $statusType = 'status6';
3434  }
3435 
3436 
3437  $parameters = array('status' => $status, 'mode' => $mode);
3438  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
3439 
3440  if ($reshook > 0) {
3441  return $hookmanager->resPrint;
3442  }
3443 
3444  return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
3445  }
3446 
3447 
3448  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3456  public function load_board($user, $mode)
3457  {
3458  // phpcs:enable
3459  global $conf, $langs;
3460 
3461  $clause = " WHERE";
3462 
3463  $sql = "SELECT p.rowid, p.ref, p.datec as datec, p.fin_validite as datefin, p.total_ht";
3464  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3465  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3466  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3467  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3468  $clause = " AND";
3469  }
3470  $sql .= $clause." p.entity IN (".getEntity('propal').")";
3471  if ($mode == 'opened') {
3472  $sql .= " AND p.fk_statut = ".self::STATUS_VALIDATED;
3473  }
3474  if ($mode == 'signed') {
3475  $sql .= " AND p.fk_statut = ".self::STATUS_SIGNED;
3476  }
3477  if ($user->socid) {
3478  $sql .= " AND p.fk_soc = ".((int) $user->socid);
3479  }
3480 
3481  $resql = $this->db->query($sql);
3482  if ($resql) {
3483  $langs->load("propal");
3484  $now = dol_now();
3485 
3486  $delay_warning = 0;
3487  $status = 0;
3488  $label = $labelShort = '';
3489  if ($mode == 'opened') {
3490  $delay_warning = $conf->propal->cloture->warning_delay;
3491  $status = self::STATUS_VALIDATED;
3492  $label = $langs->transnoentitiesnoconv("PropalsToClose");
3493  $labelShort = $langs->transnoentitiesnoconv("ToAcceptRefuse");
3494  }
3495  if ($mode == 'signed') {
3496  $delay_warning = $conf->propal->facturation->warning_delay;
3497  $status = self::STATUS_SIGNED;
3498  $label = $langs->trans("PropalsToBill"); // We set here bill but may be billed or ordered
3499  $labelShort = $langs->trans("ToBill");
3500  }
3501 
3502  $response = new WorkboardResponse();
3503  $response->warning_delay = $delay_warning / 60 / 60 / 24;
3504  $response->label = $label;
3505  $response->labelShort = $labelShort;
3506  $response->url = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals';
3507  $response->url_late = DOL_URL_ROOT.'/comm/propal/list.php?search_status='.$status.'&mainmenu=commercial&leftmenu=propals&sortfield=p.datep&sortorder=asc';
3508  $response->img = img_object('', "propal");
3509 
3510  // This assignment in condition is not a bug. It allows walking the results.
3511  while ($obj = $this->db->fetch_object($resql)) {
3512  $response->nbtodo++;
3513  $response->total += $obj->total_ht;
3514 
3515  if ($mode == 'opened') {
3516  $datelimit = $this->db->jdate($obj->datefin);
3517  if ($datelimit < ($now - $delay_warning)) {
3518  $response->nbtodolate++;
3519  }
3520  }
3521  // TODO Definir regle des propales a facturer en retard
3522  // if ($mode == 'signed' && ! count($this->FactureListeArray($obj->rowid))) $this->nbtodolate++;
3523  }
3524 
3525  return $response;
3526  } else {
3527  $this->error = $this->db->error();
3528  return -1;
3529  }
3530  }
3531 
3532 
3540  public function initAsSpecimen()
3541  {
3542  global $conf, $langs;
3543 
3544  // Load array of products prodids
3545  $num_prods = 0;
3546  $prodids = array();
3547  $sql = "SELECT rowid";
3548  $sql .= " FROM ".MAIN_DB_PREFIX."product";
3549  $sql .= " WHERE entity IN (".getEntity('product').")";
3550  $sql .= $this->db->plimit(100);
3551 
3552  $resql = $this->db->query($sql);
3553  if ($resql) {
3554  $num_prods = $this->db->num_rows($resql);
3555  $i = 0;
3556  while ($i < $num_prods) {
3557  $i++;
3558  $row = $this->db->fetch_row($resql);
3559  $prodids[$i] = $row[0];
3560  }
3561  }
3562 
3563  // Initialise parametres
3564  $this->id = 0;
3565  $this->ref = 'SPECIMEN';
3566  $this->ref_client = 'NEMICEPS';
3567  $this->specimen = 1;
3568  $this->socid = 1;
3569  $this->date = time();
3570  $this->fin_validite = $this->date + 3600 * 24 * 30;
3571  $this->cond_reglement_id = 1;
3572  $this->cond_reglement_code = 'RECEP';
3573  $this->mode_reglement_id = 7;
3574  $this->mode_reglement_code = 'CHQ';
3575  $this->availability_id = 1;
3576  $this->availability_code = 'AV_NOW';
3577  $this->demand_reason_id = 1;
3578  $this->demand_reason_code = 'SRC_00';
3579  $this->note_public = 'This is a comment (public)';
3580  $this->note_private = 'This is a comment (private)';
3581 
3582  $this->multicurrency_tx = 1;
3583  $this->multicurrency_code = $conf->currency;
3584 
3585  // Lines
3586  $nbp = 5;
3587  $xnbp = 0;
3588  while ($xnbp < $nbp) {
3589  $line = new PropaleLigne($this->db);
3590  $line->desc = $langs->trans("Description")." ".$xnbp;
3591  $line->qty = 1;
3592  $line->subprice = 100;
3593  $line->price = 100;
3594  $line->tva_tx = 20;
3595  $line->localtax1_tx = 0;
3596  $line->localtax2_tx = 0;
3597  if ($xnbp == 2) {
3598  $line->total_ht = 50;
3599  $line->total_ttc = 60;
3600  $line->total_tva = 10;
3601  $line->remise_percent = 50;
3602  } else {
3603  $line->total_ht = 100;
3604  $line->total_ttc = 120;
3605  $line->total_tva = 20;
3606  $line->remise_percent = 00;
3607  }
3608 
3609  if ($num_prods > 0) {
3610  $prodid = mt_rand(1, $num_prods);
3611  $line->fk_product = $prodids[$prodid];
3612  $line->product_ref = 'SPECIMEN';
3613  }
3614 
3615  $this->lines[$xnbp] = $line;
3616 
3617  $this->total_ht += $line->total_ht;
3618  $this->total_tva += $line->total_tva;
3619  $this->total_ttc += $line->total_ttc;
3620 
3621  $xnbp++;
3622  }
3623  }
3624 
3625  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3631  public function load_state_board()
3632  {
3633  // phpcs:enable
3634  global $user;
3635 
3636  $this->nb = array();
3637  $clause = "WHERE";
3638 
3639  $sql = "SELECT count(p.rowid) as nb";
3640  $sql .= " FROM ".MAIN_DB_PREFIX."propal as p";
3641  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
3642  if (empty($user->rights->societe->client->voir) && !$user->socid) {
3643  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3644  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3645  $clause = "AND";
3646  }
3647  $sql .= " ".$clause." p.entity IN (".getEntity('propal').")";
3648 
3649  $resql = $this->db->query($sql);
3650  if ($resql) {
3651  // This assignment in condition is not a bug. It allows walking the results.
3652  while ($obj = $this->db->fetch_object($resql)) {
3653  $this->nb["proposals"] = $obj->nb;
3654  }
3655  $this->db->free($resql);
3656  return 1;
3657  } else {
3658  dol_print_error($this->db);
3659  $this->error = $this->db->error();
3660  return -1;
3661  }
3662  }
3663 
3664 
3672  public function getNextNumRef($soc)
3673  {
3674  global $conf, $langs;
3675  $langs->load("propal");
3676 
3677  $classname = $conf->global->PROPALE_ADDON;
3678 
3679  if (!empty($classname)) {
3680  $mybool = false;
3681 
3682  $file = $classname.".php";
3683 
3684  // Include file with class
3685  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3686  foreach ($dirmodels as $reldir) {
3687  $dir = dol_buildpath($reldir."core/modules/propale/");
3688 
3689  // Load file with numbering class (if found)
3690  $mybool |= @include_once $dir.$file;
3691  }
3692 
3693  if (!$mybool) {
3694  dol_print_error('', "Failed to include file ".$file);
3695  return '';
3696  }
3697 
3698  $obj = new $classname();
3699  $numref = "";
3700  $numref = $obj->getNextValue($soc, $this);
3701 
3702  if ($numref != "") {
3703  return $numref;
3704  } else {
3705  $this->error = $obj->error;
3706  //dol_print_error($db,"Propale::getNextNumRef ".$obj->error);
3707  return "";
3708  }
3709  } else {
3710  $langs->load("errors");
3711  print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("Proposal"));
3712  return "";
3713  }
3714  }
3715 
3722  public function getTooltipContentArray($params)
3723  {
3724  global $conf, $langs, $user;
3725 
3726  $langs->load('propal');
3727  $datas = [];
3728  $nofetch = !empty($params['nofetch']);
3729 
3730  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3731  return ['optimize' => $langs->trans("Proposal")];
3732  }
3733  if ($user->hasRight('propal', 'lire')) {
3734  $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Proposal").'</u>';
3735  if (isset($this->statut)) {
3736  $datas['status'] = ' '.$this->getLibStatut(5);
3737  }
3738  if (!empty($this->ref)) {
3739  $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
3740  }
3741  if (!$nofetch) {
3742  $langs->load('companies');
3743  if (empty($this->thirdparty)) {
3744  $this->fetch_thirdparty();
3745  }
3746  $datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
3747  }
3748  if (!empty($this->ref_client)) {
3749  $datas['refcustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
3750  }
3751  if (!$nofetch) {
3752  $langs->load('project');
3753  if (empty($this->project)) {
3754  $res = $this->fetch_project();
3755  if ($res > 0) {
3756  $datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, 1);
3757  }
3758  }
3759  }
3760  if (!empty($this->total_ht)) {
3761  $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3762  }
3763  if (!empty($this->total_tva)) {
3764  $datas['vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3765  }
3766  if (!empty($this->total_ttc)) {
3767  $datas['amountttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3768  }
3769  if (!empty($this->date)) {
3770  $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
3771  }
3772  if (!empty($this->delivery_date)) {
3773  $datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
3774  }
3775  }
3776 
3777  return $datas;
3778  }
3779 
3791  public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = -1)
3792  {
3793  global $langs, $conf, $user, $hookmanager;
3794 
3795  if (!empty($conf->dol_no_mouse_hover)) {
3796  $notooltip = 1; // Force disable tooltips
3797  }
3798 
3799  $result = '';
3800  $params = [
3801  'id' => $this->id,
3802  'objecttype' => $this->element,
3803  'option' => $option,
3804  'nofetch' => 1,
3805  ];
3806  $classfortooltip = 'classfortooltip';
3807  $dataparams = '';
3808  if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3809  $classfortooltip = 'classforajaxtooltip';
3810  $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
3811  $label = '';
3812  } else {
3813  $label = implode($this->getTooltipContentArray($params));
3814  }
3815 
3816  $url = '';
3817  if ($user->hasRight('propal', 'lire')) {
3818  if ($option == '') {
3819  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3820  } elseif ($option == 'compta') { // deprecated
3821  $url = DOL_URL_ROOT.'/comm/propal/card.php?id='.$this->id.$get_params;
3822  } elseif ($option == 'expedition') {
3823  $url = DOL_URL_ROOT.'/expedition/propal.php?id='.$this->id.$get_params;
3824  } elseif ($option == 'document') {
3825  $url = DOL_URL_ROOT.'/comm/propal/document.php?id='.$this->id.$get_params;
3826  }
3827 
3828  if ($option != 'nolink') {
3829  // Add param to save lastsearch_values or not
3830  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3831  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
3832  $add_save_lastsearch_values = 1;
3833  }
3834  if ($add_save_lastsearch_values) {
3835  $url .= '&save_lastsearch_values=1';
3836  }
3837  }
3838  }
3839 
3840  $linkclose = '';
3841  if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
3842  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3843  $label = $langs->trans("Proposal");
3844  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
3845  }
3846  $linkclose .= ($label ? ' title="'.dol_escape_htmltag($label, 1).'"' : ' title="tocomplete"');
3847  $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
3848  }
3849 
3850  $linkstart = '<a href="'.$url.'"';
3851  $linkstart .= $linkclose.'>';
3852  $linkend = '</a>';
3853 
3854  $result .= $linkstart;
3855  if ($withpicto) {
3856  $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'paddingright ' : '').$classfortooltip.'"'), 0, 0, $notooltip ? 0 : 1);
3857  }
3858  if ($withpicto != 2) {
3859  $result .= $this->ref;
3860  }
3861  $result .= $linkend;
3862 
3863  if ($addlinktonotes >= 0) {
3864  $txttoshow = '';
3865 
3866  if ($addlinktonotes == 0) {
3867  if (!empty($this->note_private) || !empty($this->note_public)) {
3868  $txttoshow = $langs->trans('ViewPrivateNote');
3869  }
3870  } elseif ($addlinktonotes == 1) {
3871  if (!empty($this->note_private)) {
3872  $txttoshow .= ($user->socid > 0 ? '' : dol_string_nohtmltag($this->note_private, 1));
3873  }
3874  } elseif ($addlinktonotes == 2) {
3875  if (!empty($this->note_public)) {
3876  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3877  }
3878  } elseif ($addlinktonotes == 3) {
3879  if ($user->socid > 0) {
3880  if (!empty($this->note_public)) {
3881  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3882  }
3883  } else {
3884  if (!empty($this->note_public)) {
3885  $txttoshow .= dol_string_nohtmltag($this->note_public, 1);
3886  }
3887  if (!empty($this->note_private)) {
3888  if (!empty($txttoshow)) {
3889  $txttoshow .= '<br><br>';
3890  }
3891  $txttoshow .= dol_string_nohtmltag($this->note_private, 1);
3892  }
3893  }
3894  }
3895 
3896  if ($txttoshow) {
3897  $result .= ' <span class="note inline-block">';
3898  $result .= '<a href="'.DOL_URL_ROOT.'/comm/propal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($txttoshow).'">';
3899  $result .= img_picto('', 'note');
3900  $result .= '</a>';
3901  $result .= '</span>';
3902  }
3903  }
3904 
3905  global $action;
3906  $hookmanager->initHooks(array($this->element . 'dao'));
3907  $parameters = array('id'=>$this->id, 'getnomurl' => &$result);
3908  $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3909  if ($reshook > 0) {
3910  $result = $hookmanager->resPrint;
3911  } else {
3912  $result .= $hookmanager->resPrint;
3913  }
3914  return $result;
3915  }
3916 
3923  public function getLinesArray($filters = '')
3924  {
3925  return $this->fetch_lines(0, 0, $filters);
3926  }
3927 
3939  public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3940  {
3941  global $conf, $langs;
3942 
3943  $langs->load("propale");
3944  $outputlangs->load("products");
3945 
3946  if (!dol_strlen($modele)) {
3947  $modele = 'azur';
3948 
3949  if ($this->model_pdf) {
3950  $modele = $this->model_pdf;
3951  } elseif (!empty($conf->global->PROPALE_ADDON_PDF)) {
3952  $modele = $conf->global->PROPALE_ADDON_PDF;
3953  }
3954  }
3955 
3956  $modelpath = "core/modules/propale/doc/";
3957 
3958  return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3959  }
3960 
3969  public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3970  {
3971  $tables = array(
3972  'propal'
3973  );
3974 
3975  return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3976  }
3977 
3986  public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3987  {
3988  $tables = array(
3989  'propaldet'
3990  );
3991 
3992  return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3993  }
3994 
4002  public function getKanbanView($option = '', $arraydata = null)
4003  {
4004  global $langs;
4005 
4006  $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
4007 
4008  $return = '<div class="box-flex-item box-flex-grow-zero">';
4009  $return .= '<div class="info-box info-box-sm">';
4010  $return .= '<span class="info-box-icon bg-infobox-action">';
4011  $return .= img_picto('', $this->picto);
4012  $return .= '</span>';
4013  $return .= '<div class="info-box-content">';
4014  $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
4015  $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
4016  if (property_exists($this, 'fk_project')) {
4017  $return .= '<span class="info-box-ref"> | '.$this->fk_project.'</span>';
4018  }
4019  if (property_exists($this, 'author')) {
4020  $return .= '<br><span class="info-box-label">'.$this->author.'</span>';
4021  }
4022  if (property_exists($this, 'total_ht')) {
4023  $return .='<br><span class="" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ht).'</span>';
4024  }
4025  if (method_exists($this, 'getLibStatut')) {
4026  $return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(3).'</div>';
4027  }
4028  $return .= '</div>';
4029  $return .= '</div>';
4030  $return .= '</div>';
4031  return $return;
4032  }
4033 }
4034 
4039 {
4043  public $element = 'propaldet';
4044 
4048  public $table_element = 'propaldet';
4049 
4050  public $oldline;
4051 
4052  // From llx_propaldet
4053  public $fk_propal;
4054  public $fk_parent_line;
4055  public $desc; // Description ligne
4056  public $fk_product; // Id produit predefini
4067  public $product_type = Product::TYPE_PRODUCT;
4068 
4069  public $qty;
4070 
4071  public $tva_tx;
4072  public $vat_src_code;
4073 
4074  public $subprice;
4075  public $remise_percent;
4076  public $fk_remise_except;
4077 
4078  public $rang = 0;
4079 
4080  public $fk_fournprice;
4081  public $pa_ht;
4082  public $marge_tx;
4083  public $marque_tx;
4084 
4085  public $special_code; // Tag for special lines (exlusive tags)
4086  // 1: frais de port
4087  // 2: ecotaxe
4088  // 3: option line (when qty = 0)
4089 
4090  public $info_bits = 0; // Some other info:
4091  // Bit 0: 0 si TVA normal - 1 si TVA NPR
4092  // Bit 1: 0 ligne normale - 1 si ligne de remise fixe
4093 
4094  public $total_ht; // Total HT de la ligne toute quantite et incluant la remise ligne
4095  public $total_tva; // Total TVA de la ligne toute quantite et incluant la remise ligne
4096  public $total_ttc; // Total TTC de la ligne toute quantite et incluant la remise ligne
4097 
4102  public $remise;
4107  public $price;
4108 
4109  // From llx_product
4114  public $ref;
4119  public $product_ref;
4124  public $libelle;
4129  public $label;
4134  public $product_label;
4139  public $product_desc;
4140 
4145  public $product_tobatch;
4146 
4151  public $product_barcode;
4152 
4153  public $localtax1_tx; // Local tax 1
4154  public $localtax2_tx; // Local tax 2
4155  public $localtax1_type; // Local tax 1 type
4156  public $localtax2_type; // Local tax 2 type
4157  public $total_localtax1; // Line total local tax 1
4158  public $total_localtax2; // Line total local tax 2
4159 
4160  public $date_start;
4161  public $date_end;
4162 
4163  public $skip_update_total; // Skip update price total for special lines
4164 
4165  // Multicurrency
4166  public $fk_multicurrency;
4167  public $multicurrency_code;
4168  public $multicurrency_subprice;
4169  public $multicurrency_total_ht;
4170  public $multicurrency_total_tva;
4171  public $multicurrency_total_ttc;
4172 
4173 
4179  public function __construct($db)
4180  {
4181  $this->db = $db;
4182  }
4183 
4190  public function fetch($rowid)
4191  {
4192  $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,';
4193  $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice,';
4194  $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,';
4195  $sql .= ' pd.fk_unit,';
4196  $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
4197  $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,';
4198  $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
4199  $sql .= ' pd.date_start, pd.date_end, pd.product_type';
4200  $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd';
4201  $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
4202  $sql .= ' WHERE pd.rowid = '.((int) $rowid);
4203 
4204  $result = $this->db->query($sql);
4205  if ($result) {
4206  $objp = $this->db->fetch_object($result);
4207 
4208  if ($objp) {
4209  $this->id = $objp->rowid;
4210  $this->rowid = $objp->rowid; // deprecated
4211  $this->fk_propal = $objp->fk_propal;
4212  $this->fk_parent_line = $objp->fk_parent_line;
4213  $this->label = $objp->custom_label;
4214  $this->desc = $objp->description;
4215  $this->qty = $objp->qty;
4216  $this->price = $objp->price; // deprecated
4217  $this->subprice = $objp->subprice;
4218  $this->vat_src_code = $objp->vat_src_code;
4219  $this->tva_tx = $objp->tva_tx;
4220  $this->remise = $objp->remise; // deprecated
4221  $this->remise_percent = $objp->remise_percent;
4222  $this->fk_remise_except = $objp->fk_remise_except;
4223  $this->fk_product = $objp->fk_product;
4224  $this->info_bits = $objp->info_bits;
4225 
4226  $this->total_ht = $objp->total_ht;
4227  $this->total_tva = $objp->total_tva;
4228  $this->total_ttc = $objp->total_ttc;
4229 
4230  $this->fk_fournprice = $objp->fk_fournprice;
4231 
4232  $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
4233  $this->pa_ht = $marginInfos[0];
4234  $this->marge_tx = $marginInfos[1];
4235  $this->marque_tx = $marginInfos[2];
4236 
4237  $this->special_code = $objp->special_code;
4238  $this->product_type = $objp->product_type;
4239  $this->rang = $objp->rang;
4240 
4241  $this->ref = $objp->product_ref; // deprecated
4242  $this->product_ref = $objp->product_ref;
4243  $this->libelle = $objp->product_label; // deprecated
4244  $this->product_label = $objp->product_label;
4245  $this->product_desc = $objp->product_desc;
4246  $this->fk_unit = $objp->fk_unit;
4247 
4248  $this->date_start = $this->db->jdate($objp->date_start);
4249  $this->date_end = $this->db->jdate($objp->date_end);
4250 
4251  // Multicurrency
4252  $this->fk_multicurrency = $objp->fk_multicurrency;
4253  $this->multicurrency_code = $objp->multicurrency_code;
4254  $this->multicurrency_subprice = $objp->multicurrency_subprice;
4255  $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
4256  $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
4257  $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
4258 
4259  $this->fetch_optionals();
4260 
4261  $this->db->free($result);
4262 
4263  return 1;
4264  } else {
4265  return 0;
4266  }
4267  } else {
4268  return -1;
4269  }
4270  }
4271 
4278  public function insert($notrigger = 0)
4279  {
4280  global $conf, $user;
4281 
4282  $error = 0;
4283 
4284  dol_syslog(get_class($this)."::insert rang=".$this->rang);
4285 
4286  $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'.
4287 
4288  // Clean parameters
4289  if (empty($this->tva_tx)) {
4290  $this->tva_tx = 0;
4291  }
4292  if (empty($this->localtax1_tx)) {
4293  $this->localtax1_tx = 0;
4294  }
4295  if (empty($this->localtax2_tx)) {
4296  $this->localtax2_tx = 0;
4297  }
4298  if (empty($this->localtax1_type)) {
4299  $this->localtax1_type = 0;
4300  }
4301  if (empty($this->localtax2_type)) {
4302  $this->localtax2_type = 0;
4303  }
4304  if (empty($this->total_localtax1)) {
4305  $this->total_localtax1 = 0;
4306  }
4307  if (empty($this->total_localtax2)) {
4308  $this->total_localtax2 = 0;
4309  }
4310  if (empty($this->rang)) {
4311  $this->rang = 0;
4312  }
4313  if (empty($this->remise_percent) || !is_numeric($this->remise_percent)) {
4314  $this->remise_percent = 0;
4315  }
4316  if (empty($this->info_bits)) {
4317  $this->info_bits = 0;
4318  }
4319  if (empty($this->special_code)) {
4320  $this->special_code = 0;
4321  }
4322  if (empty($this->fk_parent_line)) {
4323  $this->fk_parent_line = 0;
4324  }
4325  if (empty($this->fk_fournprice)) {
4326  $this->fk_fournprice = 0;
4327  }
4328  if (!is_numeric($this->qty)) {
4329  $this->qty = 0;
4330  }
4331  if (empty($this->pa_ht)) {
4332  $this->pa_ht = 0;
4333  }
4334  if (empty($this->multicurrency_subprice)) {
4335  $this->multicurrency_subprice = 0;
4336  }
4337  if (empty($this->multicurrency_total_ht)) {
4338  $this->multicurrency_total_ht = 0;
4339  }
4340  if (empty($this->multicurrency_total_tva)) {
4341  $this->multicurrency_total_tva = 0;
4342  }
4343  if (empty($this->multicurrency_total_ttc)) {
4344  $this->multicurrency_total_ttc = 0;
4345  }
4346 
4347  // if buy price not defined, define buyprice as configured in margin admin
4348  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4349  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4350  return $result;
4351  } else {
4352  $this->pa_ht = $result;
4353  }
4354  }
4355 
4356  // Check parameters
4357  if ($this->product_type < 0) {
4358  return -1;
4359  }
4360 
4361  $this->db->begin();
4362 
4363  // Insert line into database
4364  $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'propaldet';
4365  $sql .= ' (fk_propal, fk_parent_line, label, description, fk_product, product_type,';
4366  $sql .= ' fk_remise_except, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
4367  $sql .= ' subprice, remise_percent, ';
4368  $sql .= ' info_bits, ';
4369  $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
4370  $sql .= ' fk_unit,';
4371  $sql .= ' date_start, date_end';
4372  $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc)';
4373  $sql .= " VALUES (".$this->fk_propal.",";
4374  $sql .= " ".($this->fk_parent_line > 0 ? "'".$this->db->escape($this->fk_parent_line)."'" : "null").",";
4375  $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
4376  $sql .= " '".$this->db->escape($this->desc)."',";
4377  $sql .= " ".($this->fk_product ? "'".$this->db->escape($this->fk_product)."'" : "null").",";
4378  $sql .= " '".$this->db->escape($this->product_type)."',";
4379  $sql .= " ".($this->fk_remise_except ? "'".$this->db->escape($this->fk_remise_except)."'" : "null").",";
4380  $sql .= " ".price2num($this->qty, 'MS').",";
4381  $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
4382  $sql .= " ".price2num($this->tva_tx).",";
4383  $sql .= " ".price2num($this->localtax1_tx).",";
4384  $sql .= " ".price2num($this->localtax2_tx).",";
4385  $sql .= " '".$this->db->escape($this->localtax1_type)."',";
4386  $sql .= " '".$this->db->escape($this->localtax2_type)."',";
4387  $sql .= " ".(price2num($this->subprice) !== '' ? price2num($this->subprice, 'MU') : "null").",";
4388  $sql .= " ".price2num($this->remise_percent, 3).",";
4389  $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
4390  $sql .= " ".price2num($this->total_ht, 'MT').",";
4391  $sql .= " ".price2num($this->total_tva, 'MT').",";
4392  $sql .= " ".price2num($this->total_localtax1, 'MT').",";
4393  $sql .= " ".price2num($this->total_localtax2, 'MT').",";
4394  $sql .= " ".price2num($this->total_ttc, 'MT').",";
4395  $sql .= " ".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null").",";
4396  $sql .= " ".(isset($this->pa_ht) ? "'".price2num($this->pa_ht)."'" : "null").",";
4397  $sql .= ' '.((int) $this->special_code).',';
4398  $sql .= ' '.((int) $this->rang).',';
4399  $sql .= ' '.(empty($this->fk_unit) ? 'NULL' : ((int) $this->fk_unit)).',';
4400  $sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").',';
4401  $sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4402  $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
4403  $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
4404  $sql .= ", ".price2num($this->multicurrency_subprice, 'CU');
4405  $sql .= ", ".price2num($this->multicurrency_total_ht, 'CT');
4406  $sql .= ", ".price2num($this->multicurrency_total_tva, 'CT');
4407  $sql .= ", ".price2num($this->multicurrency_total_ttc, 'CT');
4408  $sql .= ')';
4409 
4410  dol_syslog(get_class($this).'::insert', LOG_DEBUG);
4411  $resql = $this->db->query($sql);
4412  if ($resql) {
4413  $this->rowid = $this->db->last_insert_id(MAIN_DB_PREFIX.'propaldet');
4414 
4415  if (!$error) {
4416  $this->id = $this->rowid;
4417  $result = $this->insertExtraFields();
4418  if ($result < 0) {
4419  $error++;
4420  }
4421  }
4422 
4423  if (!$error && !$notrigger) {
4424  // Call trigger
4425  $result = $this->call_trigger('LINEPROPAL_INSERT', $user);
4426  if ($result < 0) {
4427  $this->db->rollback();
4428  return -1;
4429  }
4430  // End call triggers
4431  }
4432 
4433  $this->db->commit();
4434  return 1;
4435  } else {
4436  $this->error = $this->db->error()." sql=".$sql;
4437  $this->db->rollback();
4438  return -1;
4439  }
4440  }
4441 
4449  public function delete(User $user, $notrigger = 0)
4450  {
4451  global $conf;
4452 
4453  $error = 0;
4454  $this->db->begin();
4455 
4456  if (!$notrigger) {
4457  // Call trigger
4458  $result = $this->call_trigger('LINEPROPAL_DELETE', $user);
4459  if ($result < 0) {
4460  $error++;
4461  }
4462  }
4463  // End call triggers
4464 
4465  if (!$error) {
4466  $sql = "DELETE FROM " . MAIN_DB_PREFIX . "propaldet WHERE rowid = " . ((int) $this->rowid);
4467  dol_syslog("PropaleLigne::delete", LOG_DEBUG);
4468  if ($this->db->query($sql)) {
4469  // Remove extrafields
4470  if (!$error) {
4471  $this->id = $this->rowid;
4472  $result = $this->deleteExtraFields();
4473  if ($result < 0) {
4474  $error++;
4475  dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
4476  }
4477  }
4478  } else {
4479  $this->error = $this->db->error() . " sql=" . $sql;
4480  $error++;
4481  }
4482  }
4483 
4484  if ($error) {
4485  $this->db->rollback();
4486  return -1;
4487  } else {
4488  $this->db->commit();
4489  return 1;
4490  }
4491  }
4492 
4499  public function update($notrigger = 0)
4500  {
4501  global $conf, $user;
4502 
4503  $error = 0;
4504 
4505  $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'.
4506 
4507  if (empty($this->id) && !empty($this->rowid)) {
4508  $this->id = $this->rowid;
4509  }
4510 
4511  // Clean parameters
4512  if (empty($this->tva_tx)) {
4513  $this->tva_tx = 0;
4514  }
4515  if (empty($this->localtax1_tx)) {
4516  $this->localtax1_tx = 0;
4517  }
4518  if (empty($this->localtax2_tx)) {
4519  $this->localtax2_tx = 0;
4520  }
4521  if (empty($this->total_localtax1)) {
4522  $this->total_localtax1 = 0;
4523  }
4524  if (empty($this->total_localtax2)) {
4525  $this->total_localtax2 = 0;
4526  }
4527  if (empty($this->localtax1_type)) {
4528  $this->localtax1_type = 0;
4529  }
4530  if (empty($this->localtax2_type)) {
4531  $this->localtax2_type = 0;
4532  }
4533  if (empty($this->marque_tx)) {
4534  $this->marque_tx = 0;
4535  }
4536  if (empty($this->marge_tx)) {
4537  $this->marge_tx = 0;
4538  }
4539  if (empty($this->price)) {
4540  $this->price = 0; // TODO A virer
4541  }
4542  if (empty($this->remise_percent)) {
4543  $this->remise_percent = 0;
4544  }
4545  if (empty($this->info_bits)) {
4546  $this->info_bits = 0;
4547  }
4548  if (empty($this->special_code)) {
4549  $this->special_code = 0;
4550  }
4551  if (empty($this->fk_parent_line)) {
4552  $this->fk_parent_line = 0;
4553  }
4554  if (empty($this->fk_fournprice)) {
4555  $this->fk_fournprice = 0;
4556  }
4557  if (empty($this->subprice)) {
4558  $this->subprice = 0;
4559  }
4560  if (empty($this->pa_ht)) {
4561  $this->pa_ht = 0;
4562  }
4563 
4564  // if buy price not defined, define buyprice as configured in margin admin
4565  if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
4566  if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
4567  return $result;
4568  } else {
4569  $this->pa_ht = $result;
4570  }
4571  }
4572 
4573  $this->db->begin();
4574 
4575  // Mise a jour ligne en base
4576  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4577  $sql .= " description='".$this->db->escape($this->desc)."'";
4578  $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
4579  $sql .= ", product_type=".$this->product_type;
4580  $sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4581  $sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4582  $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
4583  $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
4584  $sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4585  $sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4586  $sql .= ", qty='".price2num($this->qty)."'";
4587  $sql .= ", subprice=".price2num($this->subprice);
4588  $sql .= ", remise_percent=".price2num($this->remise_percent);
4589  $sql .= ", price=".(float) price2num($this->price); // TODO A virer
4590  $sql .= ", remise=".(float) price2num($this->remise); // TODO A virer
4591  $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4592  if (empty($this->skip_update_total)) {
4593  $sql .= ", total_ht=".price2num($this->total_ht);
4594  $sql .= ", total_tva=".price2num($this->total_tva);
4595  $sql .= ", total_ttc=".price2num($this->total_ttc);
4596  $sql .= ", total_localtax1=".price2num($this->total_localtax1);
4597  $sql .= ", total_localtax2=".price2num($this->total_localtax2);
4598  }
4599  $sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
4600  $sql .= ", buy_price_ht=".price2num($this->pa_ht);
4601  if (strlen($this->special_code)) {
4602  $sql .= ", special_code=".$this->special_code;
4603  }
4604  $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
4605  if (!empty($this->rang)) {
4606  $sql .= ", rang=".((int) $this->rang);
4607  }
4608  $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4609  $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4610  $sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
4611 
4612  // Multicurrency
4613  $sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4614  $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4615  $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4616  $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4617 
4618  $sql .= " WHERE rowid = ".((int) $this->id);
4619 
4620  dol_syslog(get_class($this)."::update", LOG_DEBUG);
4621  $resql = $this->db->query($sql);
4622  if ($resql) {
4623  if (!$error) {
4624  $result = $this->insertExtraFields();
4625  if ($result < 0) {
4626  $error++;
4627  }
4628  }
4629 
4630  if (!$error && !$notrigger) {
4631  // Call trigger
4632  $result = $this->call_trigger('LINEPROPAL_MODIFY', $user);
4633  if ($result < 0) {
4634  $this->db->rollback();
4635  return -1;
4636  }
4637  // End call triggers
4638  }
4639 
4640  $this->db->commit();
4641  return 1;
4642  } else {
4643  $this->error = $this->db->error();
4644  $this->db->rollback();
4645  return -2;
4646  }
4647  }
4648 
4649  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4656  public function update_total()
4657  {
4658  // phpcs:enable
4659  $this->db->begin();
4660 
4661  // Mise a jour ligne en base
4662  $sql = "UPDATE ".MAIN_DB_PREFIX."propaldet SET";
4663  $sql .= " total_ht=".price2num($this->total_ht, 'MT');
4664  $sql .= ",total_tva=".price2num($this->total_tva, 'MT');
4665  $sql .= ",total_ttc=".price2num($this->total_ttc, 'MT');
4666  $sql .= " WHERE rowid = ".((int) $this->rowid);
4667 
4668  dol_syslog("PropaleLigne::update_total", LOG_DEBUG);
4669 
4670  $resql = $this->db->query($sql);
4671  if ($resql) {
4672  $this->db->commit();
4673  return 1;
4674  } else {
4675  $this->error = $this->db->error();
4676  $this->db->rollback();
4677  return -2;
4678  }
4679  }
4680 }
$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:1485
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:1334
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:1537
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:921
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